// 1. CGImageRef inputCGImage = [image CGImage]; NSUInteger width = CGImageGetWidth(inputCGImage); NSUInteger height = CGImageGetHeight(inputCGImage); // 2. NSUInteger bytesPerPixel = 4; NSUInteger bytesPerRow = bytesPerPixel * width; NSUInteger bitsPerComponent = 8; UInt32 * pixels; pixels = (UInt32 *) calloc(height * width, sizeof(UInt32)); // 3. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPRemultipliedLast | kCGBitmapByteOrder32Big); // 4. CGContextDrawImage(context, CGRectMake(0, 0, width, height), inputCGImage); // 5. Cleanup CGColorSpaceRelease(colorSpace); CGContextRelease(context); 现在,让我们分段的来看一下: 1:第一部分:把UIImage对象转换为需要被核心图形库调用的CGImage对象。同时,得到图形的宽度和高度。 2:第二部分:由于你使用的是32位RGB颜色空间模式,你需要定义一些参数bytesPerPixel(每像素大小)和bitsPerComponent(每个颜色通道大小),然后计算图像bytesPerRow(每行有大)。最后,使用一个数组来存储像素的值。 3:第三部分:创建一个RGB模式的颜色空间CGColorSpace和一个容器CGBitmapContext,将像素指针参数传递到容器中缓存进行存储。在后面的章节中将会进一步研究核图形库。 4:第四部分:把缓存中的图形绘制到显示器上。像素的填充格式是由你在创建context的时候进行指定的。 5:第五部分:清除colorSpace和context. NOTE:当你绘制图像的时候,设备的GPU会进行解码并将它显示在屏幕。为了访问本地数据,你需要一份像素的复制,就像刚才做的那样。 此时此刻,pixels存储着图像的所有像素信息。下面的几行代码会对pixels进行遍历,并打印:// 1. #define Mask8(x) ( (x) & 0xFF ) #define R(x) ( Mask8(x) ) #define G(x) ( Mask8(x >> 8 ) ) #define B(x) ( Mask8(x >> 16) ) NSLog(@"Brightness of image:"); // 2. UInt32 * currentPixel = pixels; for (NSUInteger j = 0; j < height; j++) { for (NSUInteger i = 0; i < width; i++) { // 3. UInt32 color = *currentPixel; printf("%3.0f ", (R(color)+G(color)+B(color))/3.0); // 4. currentPixel++; } printf("/n"); } 代码解释: 1:定义了一些简单处理32位像素的宏。为了得到红色通道的值,你需要得到前8位。为了得到其它的颜色通道值,你需要进行位移并取截取。 2:定义一个指向第一个像素的指针,并使用2个for循环来遍历像素。其实也可以使用一个for循环从0遍历到width*height,但是这样写更容易理解图形是二维的。 3:得到当前像素的值赋值给currentPixel并把它的亮度值打印出来。 4:增加currentPixel的值,使它指向下一个像素。如果你对指针的运算比较生疏,记住这个:currentPixel是一个指向UInt32的变量,当你把它加1后,它就会向前移动4字节(32位),然后指向了下一个像素的值。 提示:还有一种非正统的方法就是把currentPiexl声明为一个指向8字节的类型的指针,比如char。这种方法,你每增加1,你将会移动图形的下一个颜色通道。与它进行位移运算,你会得到颜色通道的8位数值。 此时此刻,这个程序只是打印出了原图的像素信息,但并没有进行任何修改!下面将会教你如何进行修改。 SpookCame-原图修改四种研究方法都会在本小节进行,你将会花费更多的时间在本节,因为它包括了图形图像处理的第一原则。掌握了这个方法你会明白其它库所做的。 在本方法中,你会遍历每一个像素,就像之前做的那个,但这次,将会对每个像素进行新的赋值。 这种方法的优点是容易实现和理解;缺点就是扫描大的图形和效果的时候会更复杂,不精简。 正如你在程序开始看到的,ImageProcessor类已经存在。将它应用到ViewController中,替换-setupWithImage,代码如下:- (void)setupWithImage:(UIImage*)image { UIImage * fixedImage = [image imageWithFixedOrientation]; self.workingImage = fixedImage; // Commence with processing! [ImageProcessor sharedProcessor].delegate = self; [[ImageProcessor sharedProcessor] processImage:fixedImage]; } 注释掉 -viewDidLoad 中下面的代码:// [self setupWithImage:[UIImage imageNamed:@"Ghost_tiny.png"]]; 现在,打开 ImageProcessor.m。如你所见,ImageProcessor 是单例模式,调用 -processUsingPixels 来加载图像,然后通过 ImageProcessorDelegate 返回输出。 -processsUsingPixels:是之前你所看到获得图形像素代码的一种复制品,如同inputImage。注意两个额外的宏A(x)和RGBAMake(r,g,b,a)的定义,用来方便处理。 编译,并运行。从相册(拍照)选择一张图片,它将会出现在屏幕上:照片中的人看上去在放松,是时候把幽灵放进去了! 在processUsingPixels的返回语句前,添加如下代码,创建一个幽灵的CGImageRef对象。UIImage * ghostImage = [UIImage imageNamed:@"ghost"];CGImageRef ghostCGImage = [ghostImage CGImage]; 现在,做一些数学运算来确定幽灵图像放在原图的什么位置。
CGFloat ghostImageaspectRatio = ghostImage.size.width / ghostImage.size.height; NSInteger targetGhostWidth = inputWidth * 0.25; CGSize ghostSize = CGSizeMake(targetGhostWidth, targetGhostWidth / ghostImageAspectRatio); CGPoint ghostOrigin = CGPointMake(inputWidth * 0.5, inputHeight * 0.2); 以上代码会把幽灵的图像宽度缩小25%,并把它的原点设定在点ghostOrigin。 下一步是创建一张幽灵图像的缓存图,NSUInteger ghostBytesPerRow = bytesPerPixel * ghostSize.width; UInt32 * ghostPixels = (UInt32 *)calloc(ghostSize.width * ghostSize.height, sizeof(UInt32)); CGContextRef ghostContext = CGBitmapContextCreate(ghostPixels, ghostSize.width, ghostSize.height, bit sPerComponent, ghostBytesPerRow, colorSpace, kCG ImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGContextDrawImage(ghostContext, CGRectMake(0, 0, ghostSize.width, ghostSize.height),ghostCGImage); 上面的代码和你从inputImage中获得像素信息一样。不同的地方是,图像会被缩小尺寸,变得更小了。 现在已经到了把幽灵图像合并到你的照片中的最佳时间了。 合并:像前面提到的,每一个颜色都有一个透明通道来标识透明度。并且,你每创建一张图像,每一个像素都会有一个颜色值。 所以,如果遇到有透明度和半透明的颜色值该如何处理呢? 答案是,对透明度进行混合。在最顶层的颜色会使用一个公式与它后面的颜色进行混合。公式如下:NewColor = TopColor * TopColor.Alpha + BottomColor * (1 - TopColor.Alpha) 这是一个标准的线性差值方程。 ·当顶层透明度为1时,新的颜色值等于顶层颜色值。·当顶层透明度为0时,新的颜色值于底层颜色值。·最后,当顶层的透明度值是0到1之前的时候,新的颜色值会混合借于顶层和底层颜色值之间。 还可以用 premultiplied alpha的方法。 当处理成千上万像素的时候,他的性能会得以发挥。 好,回到幽灵图。 如同其它位图运算一样,你需要一些循环来遍历每一个像素。但是,你只需要遍历那些你需要修改的像素。 把下面的代码添加到processUsingPixels的下面,还是放在返回语句的前面:NSUInteger offsetPixelCountForInput = ghostOrigin.y * inputWidth + ghostOrigin.x; for (NSUInteger j = 0; j < ghostSize.height; j++) { for (NSUInteger i = 0; i < ghostSize.width; i++) { UInt32 * inputPixel = inputPixels + j * inputWidth + i + offsetPixelCountForInput; UInt32 inputColor = *inputPixel; UInt32 * ghostPixel = ghostPixels + j * (int)ghostSize.width + i; UInt32 ghostColor = *ghostPixel; // Do some processing here } } 通过对幽灵图像像素数的循环和offsetPixelCountForInput获得输入的图像。记住,虽然你使用的是2维数据存储图像,但在内存他它实际上是一维的。 下一步,添加下面的代码到注释语句 Do some processing here的下面来进行混合:// Blend the ghost with 50% alpha CGFloat ghostAlpha = 0.5f * (A(ghostColor) / 255.0); UInt32 newR = R(inputColor) * (1 - ghostAlpha) + R(ghostColor) * ghostAlpha; UInt32 newG = G(inputColor) * (1 - ghostAlpha) + G(ghostColor) * ghostAlpha; UInt32 newB = B(inputColor) * (1 - ghostAlpha) + B(ghostColor) * ghostAlpha; // Clamp, not really useful here :p newR = MAX(0,MIN(255, newR)); newG = MAX(0,MIN(255, newG)); newB = MAX(0,MIN(255, newB)); *inputPixel = RGBAMake(newR, newG, newB, A(inputColor)); 这部分有2点需要说明: 1:你将幽灵图像的每一个像素的透明通道都乘以了0.5,使它成为半透明状态。然后将它混合到图像中像之前讨论的那样。 2:clamping部分将每个颜色的值范围进行限定到0到255之间,虽然一般情况下值不会越界。但是,大多数情况下需要进行这种限定防止发生意外的错误输出。 最后一步,添加下面的代码到 processUsingPixels 的下面,替换之前的返回语句:// Create a new UIImage CGImageRef newCGImage = CGBitmapContextCreateImage(context); UIImage * processedImage = [UIImage imageWithCGImage:newCGImage]; return processedImage; 上面的代码创建了一张新的UIImage并返回它。暂时忽视掉内存泄露问题。编译并运行,你将会看到漂浮的幽灵图像:好了,完成了,这个程序简直就像个病毒! 黑白颜色最后一种效果。尝试自己实现黑白颜色效果。为了做到这点,你需要把每一个像素的红色,绿色,蓝色通道的值设定成三个通道原始颜色值的平均值,就像开始的时候输出幽灵图像所有像素亮度值那样。 在注释语句// create a new UIImage前添加上一步的代码 。 找到了吗?
// Convert the image to black and white for (NSUInteger j = 0; j < inputHeight; j++) { for (NSUInteger i = 0; i < inputWidth; i++) { UInt32 * currentPixel = inputPixels + (j * inputWidth) + i; UInt32 color = *currentPixel; // Average of RGB = greyscale UInt32 averageColor = (R(color) + G(color) + B(color)) / 3.0; *currentPixel = RGBAMake(averageColor, averageColor, averageColor, A(color)); } } 最后的一步就是清除内存。ARC不能代替你对CGImageRefs和CGContexts进行管理。添加如下代码到返回语句之前。CGColorSpaceRelease(colorSpace); CGContextRelease(context); CGContextRelease(ghostContext); free(inputPixels); free(ghostPixels); 编译并运行,不要被结果吓到:
新闻热点
疑难解答