前言
- 最近做挺多的图片处理,
透视、缩放、拼接、裁剪、效果
等等,那么今天就先来详细对比一下系统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