<><>g src="http://image65.360doc.com/DownloadImg/2013/10/1217/35830276_5.png" alt="">< font=""><>t="">g src="http://image65.360doc.com/DownloadImg/2013/10/1217/35830276_4.png" al< font=""><>t="">
总结:本篇博客中讨论了IOS中常见的图像编辑操作的原理和实现方法,所有操作都是基于quartz2D框架。quartz2D框架在完成2D图像的编辑和绘制方面功能还是很强大的,还包括了pattern,shadow,gradients以及pdf的加载和展示等等,文中所用到只是quartz2D中很少的一部分知识,学会了quartz2D你就可以写一个完整的图片编辑软件。
小结: 第四部分主要讨论了一下UIImage和实际像素数据之间的相互转换,整个流程中最关键的函数就是CGBitmapContextCreateImage,如果传入参数错误,可能会得到错误的结果。
// the data should be RGBA format
+ (UIImage*)createImageWithData:(Byte*)data width:(NSInteger)width height:(NSInteger)height alphaInfo:(CGImageAlphaInfo)alphaInfo
{
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
if (!colorSpaceRef) {
NSLog(@"Create ColorSpace Error!");
}
CGContextRef bitmapContext = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpaceRef, kCGImageAlphaPremultipliedLast);
if (!bitmapContext) {
NSLog(@"Create Bitmap context Error!");
CGColorSpaceRelease(colorSpaceRef);
return nil;
}
CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef);
CGColorSpaceRelease(colorSpaceRef);
CGContextRelease(bitmapContext);
return image;
}
下面是从像素创建UIImage的源码:
上面讲到了从UIImage获取像素,在我们编辑完像素以后,大部分情况会需要重新生成UIImage并显示出来。这一部分的逻辑跟上一部分差不多,通过传进来的像素创建画布,然后通过CGBitmapContextCreateImage方法从画布中获取到CGImage,最后再创建出UIImage。注意如果指定的alpha信息需要和实际的像素格式对应,否则会得到错误的效果。
2、从像素创建UIImage
// return bmpData is rgba
- (BOOL)getImageData:(void**)data width:(NSInteger*)width height:(NSInteger*)height alphaInfo:(CGImageAlphaInfo*)alphaInfo
{
int imgWidth = self.size.width * self.scale;
int imgHegiht = self.size.height * self.scale;
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
if (colorspace == NULL) {
NSLog(@"Create Colorspace Error!");
return NO;
}
void *imgData = NULL;
imgData = malloc(imgWidth * imgHegiht * 4);
if (imgData == NULL) {
NSLog(@"Memory Error!");
return NO;
}
CGContextRef bmpContext = CGBitmapContextCreate(imgData, imgWidth, imgHegiht, 8, imgWidth * 4, colorspace, kCGImageAlphaPremultipliedLast);
CGContextDrawImage(bmpContext, CGRectMake(0, 0, imgWidth, imgHegiht), self.CGImage);
*data = CGBitmapContextGetData(bmpContext);
*width = imgWidth;
*height = imgHegiht;
*alphaInfo = kCGImageAlphaLast;
CGColorSpaceRelease(colorspace);
CGContextRelease(bmpContext);
return YES;
}
代码如下:
d、使用CGBitmapContextGetData方法获取画布对应的像素数据。
c、使用UIImage的draw方法绘制图像到画布中。
b、使用CGBitmapContextCreate方法创建画布。
a、申请图像大小的内存。
完成获取像素需要以下四步:
要获取到UIImage所表示的图像的像素,我们需要借助quartz2D中的CGBitmapContext,前面我们创建BitmapContext的时候都是使用UIKit中的一个便利方法UIGraphicsBeginImageContext,这个方法的好处是方便易用,但易用的同时也就导致了很多细节我们不能控制。为了得到图片中的像素我们需要使用更低级别的CGBitmapContextCreate方法,该方法需要指定位深(RGB中每一位所占的字节),颜色空间(前面的博客中有提到)以及alpha信息等。
1、从UIImage获取像素
UIImage是UIKit中一个存储和绘制图像的工具类,可以打开常见的jpg,png,tif等格式的图片。IOS中通常情况下使用该类就可以满足日常使用了,但有些时候我们也需要获取到图像的像素,进行更细粒度的编辑操作,例如灰度化,二值话等等。
四、获取UIImage中图像的像素和使用像素创建UIImage
小结: 第三部分讲述了两种裁剪: 矩形裁剪,任意形状裁剪,主要用到的知识是quartz2D中的path和clipping area。
UIImage+SvImageEdit.m
下面是任意形状裁剪的源码:
f、从画布中获取目标图像。
e、使用UIImage的drawInRect方法进行绘制的时候,指定rect为(-cropRect.origin.x,-cropRect.origin.x,cropRect.size.width,cropRect.size.height)。
d、通过该path设置裁剪区域。
这块需要对path进行一个移动操作,因为传入的点集是相对于原图的原点位置的,因此我们需要对该path做一个(-cropRect.origin.x,-cropRect.origin.y)的平移操作。
c、在目标画布中开启一个path,然后添加所有点到path中。
b、创建目标大小的画布。
通常有两种方法可以完成这个需求: 第一种创建一个空的画布,然后开始一个Path,添加所有的点到path中,CGContextGetPathBoundingBox获取到裁剪区域的边框。或者直接创建一个mutablePath,然后添加所有点到该path中,通过通过CGPathGetBoundingBox获取裁剪区域的边框。当然也可以通过自己遍历点集重的每一个点,找到最小点的坐标和最大点的坐标计算出裁剪区域的边框。
a、通过给定的点集确定出整个裁剪区域的尺寸和位置cropRect,即目标画布的大小和裁剪区域的左上角的位置。
上图中黑色的框代表原图大小,虚线代表实际裁剪的形状,蓝色的框代表着时间裁剪路径的边框,完成任意形状抠图,通常需要以下六步:
任意形状裁剪一个比较典型的例子就是photo中通过磁性套索进行抠图,通过指定一系列的关键点来控制要扣出的图片区域。这种裁剪的实现比矩形裁剪要稍微复杂一点,主要用到quartz2D中的两个知识: Path,Clipping Area。任意形状裁剪的示意图如下:
2、任意形状裁剪
// UIImage+SvImageEdit.m
// SvImageEdit
//
// Created by maple on 5/8/13.
// Copyright (c) 2013 maple. All rights reservedt>.
//
#import "UIImage+Crop.h"
@implementation UIImage (SvImageEdit)
/*
* @brief crop image
*/
- (UIImage*)cropImageWithRect:(CGRect)cropRect
{
CGRect drawRect = CGRectMake(-cropRect.origin.x , -cropRect.origin.y, self.size.width * self.scale, self.size.height * self.scale);
UIGraphicsBeginImageContext(cropRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, CGRectMake(0, 0, cropRect.size.width, cropRect.size.height));
[self drawInRect:drawRect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
下面是裁剪部分的源码:
关键是在第二步,指定原图像的绘制区域,因为我们需要得到从x,y位置开始的图像,所做一个简单的坐标转换,只需要从-x,-y位置开始绘制即可。
c、从画布中得到裁剪后的图像。
b、使用UIImage的drawInRect方法进行绘制的时候,指定rect为(-x,-y,width,height)。
a、创建目标大小(cropWidth,cropHeight)的画布。
上图中黑色的框代表原图大小,蓝色的虚线框代表要裁剪出来的大小。很显然裁剪出来的图片不会比原图更大,如果你裁剪出来的图片比原图更大的话通常情况下就错了,当然除非你刻意为之。我们设裁剪区域的左上角坐标为(x,y),裁剪的宽高分别为cropWidth,cropHeight,原图像宽高分别为width,height。要完成裁剪功能,我们只需要三步:
矩形裁剪是最常见的裁剪操作,操作方法比较简单。下面我们看一下矩形裁剪示意图:
1、矩形裁剪
图像裁剪即去除不必要的图像区域,抠出我们希望保留的信息。按照裁剪形状可以分为以下两种:
三、图像裁剪
小结: 第二部分讲述使用quartz2D进行图像缩放的知识,我们可以看出quartz2D帮我们完成了图像缩放过程中插值的处理,十分方便。
c、保持比例填充。即缩放后的图像依旧保持原图比例的基础上进行填充,部分图片可能会被裁剪。这个类似于UIImageView中contentMode中的UIViewContentModeScaleAspectFill模式。
b、保持比例显示。即缩放后尽量使原图最大,同事维持原图本身的比例,剩余区域将会做全透明的填充。这个类似于UIImageView中contentMode中的UIViewContentModeScaleAspectFit模式。
a、拉伸填充。即不管目标尺寸中宽高的比例如何,我们都将对原图进行拉伸,使之充满整个目标图像。
这个工具类里面,实现了三种缩放模式(与缩放质量无关),分别是: enSvResizeScale,enSvResizeAspectFit,enSvResizeAspectFill。
UIImage+Zoom.m
UIImage+Zoom.h
下面看代码:
今天我们主要讨论IOS图像处理,使用quartz2D帮助我们完成图像缩放,只需要通过CGContextSetInterpolationQuality函数即可完成插值质量的设置。之于底层具体使用哪种插值算法,我们无从得知,也不需要去关心。使用quartz2D解决图像缩放的时候,所有我们需要做的事情只有生成一个目标大小的画布,然后设置插值质量,再使用UIImage的draw方法将图片绘制到画布中即可。
上图中,我们假设黑色代表原图尺寸,蓝色代表缩放后的尺寸。我们将图片放大两倍,那么原图中的每一个像素将会对应缩放后图片中的四个像素。如何从一个像素生成四个像素,这个就是插值算法要解决的问题。
下面我们看看图像缩放的原理图示:
图像缩放顾名思义即对图片的尺寸进行缩放,由于尺寸不同所以在生成新图的过程中像素不可能是一一对应,因此会有插值操作。所谓插值即根据原图和目标图大小比例,结合原图像素信息生成的新的像素的过程。常见的插值算法有线性插值,双线性插值,立方卷积插值等。网上有很多现成的算法,感兴趣的话可以去看看。
二、图像缩放
小结:第一部分讲述了两种图片旋转的方法,第一种方法处理速度块,但是只能处理特殊角度旋转。第二种方法处理速度比第一种要慢,因为牵扯到了实际的绘制和重新采样生成图片的过程。在实际操作中如果第一种方法满足需求,应该尽量使用第一种方法完成图片旋转。
clip模式下,旋转后的图片和原图一样大,部分图片区域会被裁剪掉;expand模式下,旋转后的图片可能会比原图大,所有的图片信息都会保留,剩下的区域会是全透明的。
enum {
enSvCropClip, // the image size will be equal to orignal image, some part of image may be cliped
enSvCropExpand, // the image size will expand to contain the whole image, remain area will be transparent
};
typedef NSInteger SvCropMode;
其中的CropMode定义如下:
//
// UIImage+Rotate_Flip.m
// SvImageEdit
//
// Created by maple on 5/14/13.
// Copyright (c) 2013 smileEvday. All rights ageContext();
UIGraphicsEndImageContext();
return image;
}tImCurrenFromagetImgSize.width / 2, -imgSize.height / 2);
[self drawInRect:CGRectMake(0, 0, imgSize.width, imgSize.height)];
UIImage *image = UIGraphicsGet, -import "UIImage+Rotate_Flip.h"
/*
* @brief rotate image with radian
*/
- (UIImage*)rotateImageWithRadian:(CGFloat)radian cropMode:(SvCropMode)cropMode
{
CGSize imgSize = CGSizeMake(self.size.width * self.scale, self.size.height * self.scale);
CGSize outputSize = imgSize;
if (cropMode == enSvCropExpand) {
CGRect rect = CGRectMake(0, 0, imgSize.width, imgSize.height);
rect = CGRectApplyAffineTransform(rect, CGAffineTransformMakeRotation(radian));
outputSize = CGSizeMake(CGRectGetWidth(rect), CGRectGetHeight(rect));
}
UIGraphicsBeginImageContext(outputSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, outputSize.width / 2, outputSize.height / 2);
CGContextRotateCTM(context, radian);
CGContextTranslateCTM(context>.
//
#imreserved
图片旋转的代码如下:
进过这三步我们就可以很方便的实现图片的任意角度旋转了。你可能会发现步骤a中向下移动了半个图片宽高,步骤c中又向相反方向移动了半个图片宽高。这两个操作不会抵消吗?答案是NO,步骤a中我们的移动是基于原坐标系统进行移动的,到了步骤c时我们的移动是基于这个时候的坐标系移动的,两个坐标系是不一样的,所以才能通过一来一回完成对图片的旋转。
c、将旋转后的图像的中心点重新移回原图的中心点,即x,y方向的平移距离分别是-width / 2,-height / 2。
b、对context进行旋转操作。
a、将context进行平移,将原点移动到原图的中心位置,x,y方向的平移距离分别为width / 2,height / 2。
我们设图片的宽度为width,高度为height,旋转的三个步骤依次如上图所示: