前言
最近做挺多的图片处理,透视、缩放、拼接、裁剪、效果
等等,那么今天就先来详细对比一下系统API处理缩放的性能,这样也好方便选择那种更优的方式来处理
大致分为以下五种API:
UIKit,画布 drawInRect:
和 UIGraphicsGetImageFromCurrentImageContext
CoreGraphics / Quartz 2D,位图上下文CGContextScaleCTM
和 CGContextDrawImage
ImageIO,创建省略图 CGImageSourceCreateWithData
和 CGImageSourceCreateThumbnailAtIndex
CoreImage,滤镜 CILanczosScaleTransform
Accelerate,vImage CGBitmapContextCreate
和 CGContextDrawImage
API
/// UIKit方式 - (UIImage*)kj_UIKitChangeImageSize:(CGSize)size; /// Quartz 2D - (UIImage*)kj_QuartzChangeImageSize:(CGSize)size; /// ImageIO - (UIImage*)kj_ImageIOChangeImageSize:(CGSize)size; /// CoreImage - (UIImage*)kj_CoreImageChangeImageSize:(CGSize)size; /// Accelerate - (UIImage*)kj_AccelerateChangeImageSize:(CGSize)size;
UIKit
画布的形式,使用临时图形上下文来渲染缩放
- (UIImage*)kj_UIKitChangeImageSize:(CGSize)size{ UIGraphicsBeginImageContext(size); [self drawInRect:CGRectMake(0, 0, size.width, size.height)]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; }
CoreGraphics / Quartz 2D
绘图引擎,提供低级别、轻量级、高保真度的2D渲染
- (UIImage*)kj_QuartzChangeImageSize:(CGSize)size{ UIGraphicsBeginImageContext(size); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM(context, 0.0, size.height); CGContextScaleCTM(context, 1.0, -1.0); CGContextSetBlendMode(context, kCGBlendModeCopy); CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), self.CGImage); UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; }
ImageIO
使用导入 #import <ImageIO/ImageIO.h>
CGImageSource的键值说明
kCGImageSourceCreateThumbnailWithTransform - 设置缩略图是否进行Transfrom变换
kCGImageSourceCreateThumbnailFromImageAlways - 设置是否创建缩略图,无论原图像有没有包含缩略图,默认kCFBooleanFalse
kCGImageSourceCreateThumbnailFromImageIfAbsent - 设置是否创建缩略图,如果原图像有没有包含缩略图,则创建缩略图,默认kCFBooleanFalse
kCGImageSourceThumbnailMaxPixelSize - 设置缩略图的最大宽/高尺寸 需要设置为CFNumber值,设置后图片会根据最大宽/高 来等比例缩放图片
kCGImageSourceShouldCache - 设置是否以解码的方式读取图片数据 默认为kCFBooleanTrue,如果设置为true,在读取数据时就进行解码 如果为false 则在渲染时才进行解码
- (UIImage*)kj_ImageIOChangeImageSize:(CGSize)size{ NSData *date = UIImagePNGRepresentation(self); CGFloat max = size.width; if (max < size.height) max = size.height; CFDictionaryRef dicOptionsRef = (__bridge CFDictionaryRef) @{(id)kCGImageSourceCreateThumbnailFromImageIfAbsent : @(YES), (id)kCGImageSourceThumbnailMaxPixelSize : @(max), (id)kCGImageSourceShouldCache : @(YES),}; CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)date, nil); CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(src, 0, dicOptionsRef); UIImage *newImage = [UIImage imageWithCGImage:imageRef]; if (imageRef != nil) CFRelease(imageRef); CFRelease(src); return newImage; }
CoreImage
滤镜的处理方式,综合对比下来,这种是最差
使用导入 #import <CoreImage/CoreImage.h>
- (UIImage*)kj_CoreImageChangeImageSize:(CGSize)size{ CIImage *ciImage = [CIImage imageWithCGImage:self.CGImage]; CGFloat scale = fminf(size.height/self.size.height, size.width/self.size.width); NSDictionary *dict = @{kCIInputScaleKey:@(scale),kCIInputAspectRatioKey:@(1.),kCIInputImageKey:ciImage}; CIFilter *filter = [CIFilter filterWithName:@"CILanczosScaleTransform" withInputParameters:dict]; CIContext *ciContext = [[CIContext alloc] initWithOptions:@{kCIContextUseSoftwareRenderer : @(NO)}]; CGImageRef ciImageRef = [ciContext createCGImage:filter.outputImage fromRect:CGRectMake(0, 0, size.width, size.height)]; UIImage *newImage = [UIImage imageWithCGImage:ciImageRef]; return newImage; }
Accelerate
vImage,使用CPU的矢量处理器处理大图像
使用导入 #import <Accelerate/Accelerate.h>
- (UIImage*)kj_AccelerateChangeImageSize:(CGSize)size{ const size_t width = size.width, height = size.height; const size_t bytesPerRow = width * 4; CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 int bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast; #else int bitmapInfo = kCGImageAlphaPremultipliedLast; #endif CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, space, bitmapInfo); CGColorSpaceRelease(space); if (!bmContext) return nil; CGContextDrawImage(bmContext, CGRectMake(0, 0, width, height), self.CGImage); UInt8 * data = (UInt8*)CGBitmapContextGetData(bmContext); if (!data){ CGContextRelease(bmContext); return nil; } CGImageRef imageRef = CGBitmapContextCreateImage(bmContext); UIImage *newImage = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); CGContextRelease(bmContext); return newImage; }
测试示例
- (void)viewDidLoad { [super viewDidLoad]; CGFloat x,y; CGFloat sp = kAutoW(10); CGFloat w = (kScreenW-sp*2)/2.; CGFloat h = (kScreenH-4*sp-kSTATUSBAR_NAVIGATION_HEIGHT)/3; NSArray *names = @[@"原图",@"UIKit",@"Quartz 2D",@"ImageIO",@"CoreImage",@"Accelerate"]; UIImage *image = kGetImage(@"xxsf"); CGSize size = CGSizeMake(image.size.width/4.3, image.size.height/4.3); for (int k=0; k<names.count; k++) { x = k%2*(w+sp)+sp/2; y = k/2*(h+sp)+sp+kSTATUSBAR_NAVIGATION_HEIGHT; UILabel *label = [UILabel kj_createLabelWithText:names[k] FontSize:16 TextColor:UIColor.orangeColor]; label.frame = CGRectMake(x, y, w, 20); [self.view addSubview:label]; UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(x, y+25, w, h-25)]; imageView.contentMode = UIViewContentModeScaleAspectFit; imageView.backgroundColor = [UIColor.orangeColor colorWithAlphaComponent:0.1]; [self.view addSubview:imageView]; if (k==0) { imageView.image = image; NSData *date = UIImagePNGRepresentation(image); NSLog(@"OriginalData:%lu", (unsigned long)date.length); }else if (k==1) { CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); UIImage *img = [image kj_UIKitChangeImageSize:size]; NSData *date = UIImagePNGRepresentation(img); NSLog(@"UIKitTime:%f,Data:%lu", CFAbsoluteTimeGetCurrent() - start,(unsigned long)date.length); imageView.image = img; }else if (k==2) { CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); UIImage *img = [image kj_QuartzChangeImageSize:size]; NSData *date = UIImagePNGRepresentation(img); NSLog(@"QuartzTime:%f,Data:%lu", CFAbsoluteTimeGetCurrent() - start,(unsigned long)date.length); imageView.image = img; }else if (k==3) { CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); UIImage *img = [image kj_ImageIOChangeImageSize:size]; NSData *date = UIImagePNGRepresentation(img); NSLog(@"ImageIOTime:%f,Data:%lu", CFAbsoluteTimeGetCurrent() - start,(unsigned long)date.length); imageView.image = img; }else if (k==4) { CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); UIImage *img = [image kj_CoreImageChangeImageSize:size]; NSData *date = UIImagePNGRepresentation(img); NSLog(@"CoreImageTime:%f,Data:%lu", CFAbsoluteTimeGetCurrent() - start,(unsigned long)date.length); imageView.image = img; }else if (k==5) { CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); UIImage *img = [image kj_AccelerateChangeImageSize:size]; NSData *date = UIImagePNGRepresentation(img); NSLog(@"AccelerateTime:%f,Data:%lu", CFAbsoluteTimeGetCurrent() - start,(unsigned long)date.length); imageView.image = img; } } }
PNG图片耗时对比,等比缩放
------- 🎈 给我点赞 🎈 ------- 编译时间:14:11:38 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:38 打印信息:OriginalData:466290 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:11:38 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:43 打印信息:UIKitTime:0.009362,Data:36902 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:11:38 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:49 打印信息:QuartzTime:0.009098,Data:36901 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:11:38 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:55 打印信息:ImageIOTime:0.053086,Data:38576 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:11:38 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:61 打印信息:CoreImageTime:0.128086,Data:40243 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:11:38 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:67 打印信息:AccelerateTime:0.008618,Data:35748
JPG耗时比较
------- 🎈 给我点赞 🎈 ------- 编译时间:14:08:03 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:38 打印信息:OriginalData:189364 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:08:03 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:43 打印信息:UIKitTime:0.001181,Data:19778 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:08:03 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:49 打印信息:QuartzTime:0.001097,Data:19783 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:08:03 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:55 打印信息:ImageIOTime:0.010663,Data:17099 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:08:03 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:61 打印信息:CoreImageTime:0.020129,Data:21078 ------- 🎈 给我点赞 🎈 ------- 编译时间:14:08:03 文件名:KJImageCompressVC.m 方法名:-[KJImageCompressVC viewDidLoad] 行号:67 打印信息:AccelerateTime:0.001000,Data:19062
综合耗时比较
类型 | UIKit | CoreGraphics | ImageIO | CoreImage | Accelerate |
PNG | 0.009362 | 0.009098 | 0.053086 | 0.128086 | 0.008618 |
JPG | 0.001181 | 0.001097 | 0.010663 | 0.020129 | 0.001000 |
大小比较
类型 | 原图 | UIKit | CoreGraphics | ImageIO | CoreImage | Accelerate |
PNG | 466290 | 36902 | 36901 | 38576 | 40243 | 35748 |
JPG | 189364 | 19778 | 19783 | 17099 | 21078 | 19062 |
总结
Accelerate 压缩出来质量最小
ImageIO 肉眼感觉清晰度最高
ImageIO 和 CoreImage 只能做等比缩放
感兴趣的朋友可以换不同尺寸的图片多次测试,这样就可得到每种方式在不同区域的性能对比
备注:本文用到的部分函数方法和Demo,均来自三方库KJCategories