在项目中因为性能原因,最好不要叠加太多UIView,CALayer层级的显示;但是很多情况又必须使用到图片的合成或是像素、滤镜处理。这里针这些常用的图片处理使用不同图形处理框架进行相关编码实践。原文链接 — iOS图片处理实践 、项目代码 。
一、图片手动解码 写在前面:图片编码解码理论见上上篇
场景:适用于需要快速显示图片的地方,例如tableCell,先把图片进行bitmap解码操作加入缓存。同时如果是超大图可以和下面第三节的图片压缩方法搭配使用。
解决方案:通过CGBitmapContextCreate 重绘图片,这种压缩的图片等于手动进行了一次解码,可以加快图片的展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 - (UIImage *)compressWithBitmap:(CGFloat )scale { CGImageRef imageRef = self .CGImage; NSUInteger width = CGImageGetWidth (imageRef)*scale; NSUInteger height = CGImageGetHeight (imageRef)*scale; CGColorSpaceRef colorSpace = CGImageGetColorSpace (imageRef); CGContextRef contextRef = CGBitmapContextCreate (nil , width, height, 8 , 4 *width, colorSpace, kCGImageAlphaNoneSkipLast); CGContextDrawImage (contextRef, CGRectMake (0 , 0 , width, height), imageRef); imageRef = CGBitmapContextCreateImage (contextRef); CGContextRelease (contextRef); return [UIImage imageWithCGImage:imageRef scale:self .scale orientation:UIImageOrientationUp ]; }
二、大图在本地的高效显示 项目场景:1、下载大图后需要显示在屏幕上;2、本地读取大图显示在屏幕上。特别是对性能和图片要求较高的时候。
最佳解决方案:WWDC2018 苹果给的方案,见上上篇最后一节。
三、图片压缩 写在前面:首先介绍两种最简单最常见的压缩方式,下面复杂的压缩方式也是在此之上的扩展,可以根据实际情况进行调整;
关于质量的压缩,苹果提供了一个方法:
1 UIImageJPEGRepresentation (image, compression);
关于这个方法,理论上值越小表示图片质量越低,图片文件自然越小。但是并不是 compression 取 0,就是0b大小,取 1 就是原图。而且如果你是一张很大的图,即使compression = 0.0001等或更小,图片压缩到一定大小后,都无法再被压缩下去。
3.1 按照指定压缩比例压缩图片 1 2 3 4 5 6 7 - (UIImage *)compressWithQuality:(CGFloat )rate { NSData *data = UIImageJPEGRepresentation (self , rate); UIImage *resultImage = [UIImage imageWithData:data]; return resultImage; }
3.2 按照指定尺寸压缩图片 1 2 3 4 5 6 7 8 9 - (UIImage *)compressWithSize:(CGSize )size { UIGraphicsBeginImageContext (size); [self drawInRect:CGRectMake (0 , 0 , size.width, size.height)]; UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); return resultImage; }
3.3 具体的应用场景分析 1. 上传或存储有大小要求的图片 循环逐渐减小图片尺寸,直到图片稍小于指定大小,这样做的好处是可以在我们限定图片大小后,图片尺寸也是此时最大的。问题是循环次数多,效率低,耗时长。可以用二分法来提高效率:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 - (UIImage *)compressWithCycleSize:(NSInteger )maxLength { UIImage *resultImage = self ; NSData *data = UIImageJPEGRepresentation (resultImage, 1 ); NSUInteger lastDataLength = 0 ; while (data.length > maxLength && data.length != lastDataLength) { lastDataLength = data.length; CGFloat ratio = (CGFloat )maxLength / data.length; CGSize size = CGSizeMake ((NSUInteger )(resultImage.size.width * sqrtf(ratio)), (NSUInteger )(resultImage.size.height * sqrtf(ratio))); UIGraphicsBeginImageContext (size); [resultImage drawInRect:CGRectMake (0 , 0 , size.width, size.height)]; resultImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); data = UIImageJPEGRepresentation (resultImage, 1 ); } return resultImage; }
2. 上传或存储有质量要求的图片 循环压缩图片质量直到图片稍小于指定大小,默认循环6次,循环太多次后面也再也压不下去,当然这个次数可以自行配置。好处就是最大限度的保证了图片质量。同样用二分法来提高效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - (UIImage *)compressWithCycleQulity:(NSInteger )maxLength { CGFloat compression = 1 ; NSData *data = UIImageJPEGRepresentation (self , compression); if (data.length < maxLength) return self ; CGFloat max = 1 ; CGFloat min = 0 ; for (int i = 0 ; i < 6 ; ++i) { compression = (max + min) / 2 ; data = UIImageJPEGRepresentation (self , compression); if (data.length < maxLength * 0.9 ) { min = compression; } else if (data.length > maxLength) { max = compression; } else { break ; } } UIImage *resultImage = [UIImage imageWithData:data]; return resultImage; }
3. 在大小有上限的情况下尽量保证质量 两种图片压缩方法结合 尽量兼顾质量和大小。以确保大小合适为标准。好处就是在大小限定的情况下最大保证了质量和尺寸。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 - (UIImage *)compressWithQulitySize:(NSInteger )maxLength { CGFloat compression = 1 ; NSData *data = UIImageJPEGRepresentation (self , compression); if (data.length < maxLength) return self ; CGFloat max = 1 ; CGFloat min = 0 ; for (int i = 0 ; i < 6 ; ++i) { compression = (max + min) / 2 ; data = UIImageJPEGRepresentation (self , compression); if (data.length < maxLength * 0.9 ) { min = compression; } else if (data.length > maxLength) { max = compression; } else { break ; } } UIImage *resultImage = [UIImage imageWithData:data]; if (data.length < maxLength) return resultImage; NSUInteger lastDataLength = 0 ; while (data.length > maxLength && data.length != lastDataLength) { lastDataLength = data.length; CGFloat ratio = (CGFloat )maxLength / data.length; CGSize size = CGSizeMake ((NSUInteger )(resultImage.size.width * sqrtf(ratio)), (NSUInteger )(resultImage.size.height * sqrtf(ratio))); UIGraphicsBeginImageContext (size); [resultImage drawInRect:CGRectMake (0 , 0 , size.width, size.height)]; resultImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); data = UIImageJPEGRepresentation (resultImage, compression); } return resultImage; }
四、图片像素修改操作 写在前面:这部分的理论都是通过图片重绘来修改修该图片位图中的像素值,从而达到图片的修改。
4.1 图片灰度图(黑白图) 灰度图的三种颜色转换算法:
浮点算法:R = G = B = 0.3R + 0.59 G + 0.11*B
平均值法:R = G = B = (R+G+B)/3
任取一个分量色:R = G = B = R或G或B
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 - (UIImage *)imageToGray:(NSInteger )type { CGImageRef imageRef = self .CGImage; NSUInteger width = CGImageGetWidth (imageRef); NSUInteger height = CGImageGetHeight (imageRef); CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB (); UInt32 *imagePiexl = (UInt32 *)calloc(width*height, sizeof (UInt32 )); CGContextRef contextRef = CGBitmapContextCreate (imagePiexl, width, height, 8 , 4 *width, colorSpaceRef, kCGImageAlphaNoneSkipLast); CGContextDrawImage (contextRef, CGRectMake (0 , 0 , width, height), self .CGImage); for (int y=0 ; y<height; y++) { for (int x=0 ; x<width; x++) { uint8_t *rgbPiexl = (uint8_t *)&imagePiexl[y*width+x]; uint32_t gray = rgbPiexl[0 ]*0.3 +rgbPiexl[1 ]*0.59 +rgbPiexl[2 ]*0.11 ; if (type == 0 ) { gray = rgbPiexl[1 ]; }else if (type == 1 ) { gray = (rgbPiexl[0 ]+rgbPiexl[1 ]+rgbPiexl[2 ])/3 ; }else if (type == 2 ) { gray = rgbPiexl[0 ]*0.3 +rgbPiexl[1 ]*0.59 +rgbPiexl[2 ]*0.11 ; } rgbPiexl[0 ] = gray; rgbPiexl[1 ] = gray; rgbPiexl[2 ] = gray; } } CGImageRef finalRef = CGBitmapContextCreateImage (contextRef); CGContextRelease (contextRef); CGColorSpaceRelease (colorSpaceRef); free(imagePiexl); return [UIImage imageWithCGImage:finalRef scale:self .scale orientation:UIImageOrientationUp ]; }
4.2 修改图片的RGB值 通过修改图片的RGB值来控制图片的颜色显示。或者替换某种颜色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 - (UIImage *)imageToRGB:(CGFloat )rk g:(CGFloat )gk b:(CGFloat )bk { CGImageRef imageRef = self .CGImage; NSUInteger width = CGImageGetWidth (imageRef); NSUInteger height = CGImageGetHeight (imageRef); CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB (); UInt32 *imagePiexl = (UInt32 *)calloc(width*height, sizeof (UInt32 )); CGContextRef contextRef = CGBitmapContextCreate (imagePiexl, width, height, 8 , 4 *width, colorSpaceRef, kCGImageAlphaNoneSkipLast); CGContextDrawImage (contextRef, CGRectMake (0 , 0 , width, height), imageRef); for (int y=0 ; y<height; y++) { for (int x=0 ; x<width; x++) { uint8_t *rgbPiexl = (uint8_t *)&imagePiexl[y*width+x]; if (rgbPiexl[0 ]>245 &&rgbPiexl[1 ]>245 &&rgbPiexl[2 ]>245 ) { NSLog (@"该色值下不做处理" ); }else { rgbPiexl[0 ] = rgbPiexl[0 ]*rk; rgbPiexl[1 ] = rgbPiexl[1 ]*gk; rgbPiexl[2 ] = rgbPiexl[2 ]*bk; } } } CGImageRef finalRef = CGBitmapContextCreateImage (contextRef); CGContextRelease (contextRef); CGColorSpaceRelease (colorSpaceRef); free(imagePiexl); return [UIImage imageWithCGImage:finalRef scale:self .scale orientation:UIImageOrientationUp ]; }
4.3 图片打码 马赛克就是让图片看上去模糊不清。将特定区域的像素点设置为同一种颜色,整体就会变得模糊,区域块越大越模糊,越小越接近于原始像素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 - (UIImage *)imageToMosaic:(NSInteger )size; { CGImageRef imageRef = self .CGImage; NSUInteger width = CGImageGetWidth (imageRef); NSUInteger height = CGImageGetHeight (imageRef); CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB (); UInt32 *imagePiexl = (UInt32 *)calloc(width*height, sizeof (UInt32 )); CGContextRef contextRef = CGBitmapContextCreate (imagePiexl, width, height, 8 , 4 *width, colorSpaceRef, kCGImageAlphaNoneSkipLast); CGContextDrawImage (contextRef, CGRectMake (0 , 0 , width, height), imageRef); UInt8 *bitmapPixels = CGBitmapContextGetData (contextRef); UInt8 *pixels[4 ] = {0 }; NSUInteger currentPixels = 0 ; NSUInteger preCurrentPiexls = 0 ; NSUInteger mosaicSize = size; if (size == 0 ) return self ; for (NSUInteger i = 0 ; i < height - 1 ; i++) { for (NSUInteger j = 0 ; j < width - 1 ; j++) { currentPixels = i * width + j; if (i % mosaicSize == 0 ) { if (j % mosaicSize == 0 ) { memcpy(pixels, bitmapPixels + 4 * currentPixels, 4 ); }else { memcpy(bitmapPixels + 4 * currentPixels, pixels, 4 ); } }else { preCurrentPiexls = (i - 1 ) * width + j; memcpy(bitmapPixels + 4 * currentPixels, bitmapPixels + 4 * preCurrentPiexls, 4 ); } } } CGImageRef finalRef = CGBitmapContextCreateImage (contextRef); CGContextRelease (contextRef); CGColorSpaceRelease (colorSpaceRef); free(imagePiexl); return [UIImage imageWithCGImage:finalRef scale:self .scale orientation:UIImageOrientationUp ]; }
五、图形框架合成图片 使用不同图形框架合成图片,添加滤镜水印等。
写在前面:理论和上面像素修改一样,通过操作像素达到修改图片的目的,但是这里使用了系统提供的不同框架和第三方GPUImage。不同框架效率也有所不一样。这里每段代码都加入了对应像素(黑白处理),只是为了学习,后面可以根据需求在对应代码块添加或替换对应对像素的操作,亦可后面加入参数进行封装。
5.1 直接绘图合成 此方案原理上就是通过绘图,将多张图片的像素按照自己的设计绘制在一张图片上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 - (UIImage *)processUsingPixels:(UIImage *)backImage frontImage:(UIImage *)frontImage; { UInt32 * backPixels; CGImageRef backCGImage = [backImage CGImage ]; NSUInteger backWidth = CGImageGetWidth (backCGImage); NSUInteger backHeight = CGImageGetHeight (backCGImage); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB (); NSUInteger bytesPerPixel = 4 ; NSUInteger bitsPerComponent = 8 ; NSUInteger backBytesPerRow = bytesPerPixel * backWidth; backPixels = (UInt32 *)calloc(backHeight * backWidth, sizeof (UInt32 )); CGContextRef context = CGBitmapContextCreate (backPixels, backWidth, backHeight, bitsPerComponent, backBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGContextDrawImage (context, CGRectMake (0 , 0 , backWidth, backHeight), backCGImage); CGImageRef frontCGImage = [frontImage CGImage ]; CGFloat frontImageAspectRatio = frontImage.size.width / frontImage.size.height; NSInteger targetFrontWidth = backWidth * 0.25 ; CGSize frontSize = CGSizeMake (targetFrontWidth, targetFrontWidth / frontImageAspectRatio); CGPoint frontOrigin = CGPointMake (0 , 0 ); NSUInteger frontBytesPerRow = bytesPerPixel * frontSize.width; UInt32 *frontPixels = (UInt32 *)calloc(frontSize.width * frontSize.height, sizeof (UInt32 )); CGContextRef frontContext = CGBitmapContextCreate (frontPixels, frontSize.width, frontSize.height, bitsPerComponent, frontBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGContextDrawImage (frontContext, CGRectMake (0 , 0 , frontSize.width, frontSize.height),frontCGImage); NSUInteger offsetPixelCountForInput = frontOrigin.y * backWidth + frontOrigin.x; for (NSUInteger j = 0 ; j < frontSize.height; j++) { for (NSUInteger i = 0 ; i < frontSize.width; i++) { UInt32 *backPixel = backPixels + j * backWidth + i + offsetPixelCountForInput; UInt32 backColor = *backPixel; UInt32 * frontPixel = frontPixels + j * (int )frontSize.width + i; UInt32 frontColor = *frontPixel; CGFloat frontAlpha = 1.0 f * (A(frontColor) / 255.0 ); UInt32 newR = R(backColor) * (1 - frontAlpha) + R(frontColor) * frontAlpha; UInt32 newG = G(backColor) * (1 - frontAlpha) + G(frontColor) * frontAlpha; UInt32 newB = B(backColor) * (1 - frontAlpha) + B(frontColor) * frontAlpha; newR = MAX(0 ,MIN(255 , newR)); newG = MAX(0 ,MIN(255 , newG)); newB = MAX(0 ,MIN(255 , newB)); *backPixel = RGBAMake(newR, newG, newB, A(backColor)); } } for (NSUInteger j = 0 ; j < backHeight; j++) { for (NSUInteger i = 0 ; i < backWidth; i++) { UInt32 * currentPixel = backPixels + (j * backWidth) + i; UInt32 color = *currentPixel; UInt32 averageColor = (R(color) + G(color) + B(color)) / 3.0 ; *currentPixel = RGBAMake(averageColor, averageColor, averageColor, A(color)); } } CGImageRef newCGImage = CGBitmapContextCreateImage (context); UIImage * processedImage = [UIImage imageWithCGImage:newCGImage]; CGColorSpaceRelease (colorSpace); CGContextRelease (context); CGContextRelease (frontContext); free(backPixels); free(frontPixels); return processedImage; }
5.2 CoreGraphics 框架合成图片 使用CoreGraphics框架合成图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 - (UIImage *)processUsingCoreGraphics:(UIImage *)backImage frontImage:(UIImage *)frontImage; { CGRect imageRect = {CGPointZero ,backImage.size}; NSInteger backWidth = CGRectGetWidth (imageRect); NSInteger backHeight = CGRectGetHeight (imageRect); CGFloat frontImageAspectRatio = frontImage.size.width / frontImage.size.height; NSInteger targetFrontWidth = backWidth * 0.25 ; CGSize frontSize = CGSizeMake (targetFrontWidth, targetFrontWidth / frontImageAspectRatio); CGPoint frontOrigin = CGPointMake (0 , 0 ); CGRect frontRect = {frontOrigin, frontSize}; UIGraphicsBeginImageContext (backImage.size); CGContextRef context = UIGraphicsGetCurrentContext (); CGAffineTransform flip = CGAffineTransformMakeScale (1.0 , -1.0 ); CGAffineTransform flipThenShift = CGAffineTransformTranslate (flip,0 ,-backHeight); CGContextConcatCTM (context, flipThenShift); CGContextDrawImage (context, imageRect, [backImage CGImage ]); CGContextSetBlendMode (context, kCGBlendModeSourceAtop); CGContextSetAlpha (context,0.5 ); CGRect transformedpatternRect = CGRectApplyAffineTransform (frontRect, flipThenShift); CGContextDrawImage (context, transformedpatternRect, [frontImage CGImage ]); UIImage * imageWithFront = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray (); context = CGBitmapContextCreate (nil , backWidth, backHeight, 8 , 0 , colorSpace, (CGBitmapInfo )kCGImageAlphaNone); CGContextDrawImage (context, imageRect, [imageWithFront CGImage ]); CGImageRef imageRef = CGBitmapContextCreateImage (context); UIImage * finalImage = [UIImage imageWithCGImage:imageRef]; CGColorSpaceRelease (colorSpace); CGContextRelease (context); CFRelease (imageRef); return finalImage; }
5.3 CoreImage 框架合成图片 使用CoreImage 框架以添加滤镜形式合成图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 - (UIImage *)processUsingCoreImage:(UIImage *)backImage frontImage:(UIImage *)frontImage { CIImage * backCIImage = [[CIImage alloc] initWithImage:backImage]; // 1. Create a grayscale filter CIFilter * grayFilter = [CIFilter filterWithName:@"CIColorControls"]; [grayFilter setValue:@(0) forKeyPath:@"inputSaturation"]; // 2. Create our pattern filter // Cheat: create a larger pattern image UIImage * patternFrontImage = [self createPaddedPatternImageWithSize:backImage.size pattern:frontImage]; CIImage * frontCIImage = [[CIImage alloc] initWithImage:patternFrontImage]; CIFilter * alphaFilter = [CIFilter filterWithName:@"CIColorMatrix"]; // CIVector * alphaVector = [CIVector vectorWithX:0 Y:0 Z:0.5 W:0]; CIVector * alphaVector = [CIVector vectorWithX:0 Y:0 Z:1.0 W:0]; [alphaFilter setValue:alphaVector forKeyPath:@"inputAVector"]; CIFilter * blendFilter = [CIFilter filterWithName:@"CISourceAtopCompositing"]; // 3. Apply our filters [alphaFilter setValue:frontCIImage forKeyPath:@"inputImage"]; frontCIImage = [alphaFilter outputImage]; [blendFilter setValue:frontCIImage forKeyPath:@"inputImage"]; [blendFilter setValue:backCIImage forKeyPath:@"inputBackgroundImage"]; CIImage * blendOutput = [blendFilter outputImage]; [grayFilter setValue:blendOutput forKeyPath:@"inputImage"]; CIImage * outputCIImage = [grayFilter outputImage]; // 4. Render our output image CIContext * context = [CIContext contextWithOptions:nil]; CGImageRef outputCGImage = [context createCGImage:outputCIImage fromRect:[outputCIImage extent]]; UIImage * outputImage = [UIImage imageWithCGImage:outputCGImage]; CGImageRelease(outputCGImage); return outputImage; }
createPaddedPatternImageWithSize 这是个生成滤镜图案的代码块具体请看DEMO
5.4 GPUImage 框架合成图片 使用GPUImage 框架以添加滤镜形式合成图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 - (UIImage *)processUsingGPUImage:(UIImage *)backImage frontImage:(UIImage *)frontImage { GPUImagePicture * backGPUImage = [[GPUImagePicture alloc] initWithImage:backImage]; UIImage *fliterImage = [self createPaddedPatternImageWithSize:backImage.size pattern:frontImage]; GPUImagePicture * frontGPUImage = [[GPUImagePicture alloc] initWithImage:fliterImage]; GPUImageAlphaBlendFilter * alphaBlendFilter = [[GPUImageAlphaBlendFilter alloc] init]; alphaBlendFilter.mix = 0.5 ; [backGPUImage addTarget:alphaBlendFilter atTextureLocation:0 ]; [frontGPUImage addTarget:alphaBlendFilter atTextureLocation:1 ]; GPUImageGrayscaleFilter * grayscaleFilter = [[GPUImageGrayscaleFilter alloc] init]; [alphaBlendFilter addTarget:grayscaleFilter]; [backGPUImage processImage]; [frontGPUImage processImage]; [grayscaleFilter useNextFrameForImageCapture]; UIImage * output = [grayscaleFilter imageFromCurrentFramebuffer]; return output; }
5.5 对比总结
从代码量来看:明显1直接绘图合成的代码量明显高出许多。CoreImage,和GPUImage的方案要自己加入pattern图,其实代码量也不算少。因此仅从合成图这个功能来看。代码量上 CoreGraphic方案最优。
从性能来看:本地测试,CoreGraphic,直接绘图合成,速度最快。GPUImage也差不多,CoreImage添加滤镜方案最慢。
从可控多样性需求来说:GPUImage本来就提供很多滤镜,同时开源。无疑当前最佳,但是其他的都可以自己进行对应功能封装。
总的来说还是要看项目需求,个人觉得一般性添加水印,合成图片什么如果要直接用CoreGraphic是个不错的选择,以后有时间可以基于CoreGraphic封装功能。
六、参考文档