iOS 关于图片缩放性能探究

3,148 阅读2分钟

前言

  • 最近做挺多的图片处理,透视、缩放、拼接、裁剪、效果等等,那么今天就先来详细对比一下系统API处理缩放的性能,这样也好方便选择那种更优的方式来处理
PNGJPG

大致分为以下五种API:

  1. UIKit,画布 drawInRect:UIGraphicsGetImageFromCurrentImageContext
  2. CoreGraphics / Quartz 2D,位图上下文CGContextScaleCTMCGContextDrawImage
  3. ImageIO,创建省略图 CGImageSourceCreateWithDataCGImageSourceCreateThumbnailAtIndex
  4. CoreImage,滤镜 CILanczosScaleTransform
  5. Accelerate,vImage CGBitmapContextCreateCGContextDrawImage

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

综合耗时比较

类型UIKitCoreGraphicsImageIOCoreImageAccelerate
PNG0.0093620.0090980.0530860.1280860.008618
JPG0.0011810.0010970.0106630.0201290.001000
  • 大小比较
类型原图UIKitCoreGraphicsImageIOCoreImageAccelerate
PNG4662903690236901385764024335748
JPG1893641977819783170992107819062

总结

  1. Accelerate 压缩出来质量最小
  2. ImageIO 肉眼感觉清晰度最高
  3. ImageIO 和 CoreImage 只能做等比缩放
  • 感兴趣的朋友可以换不同尺寸的图片多次测试,这样就可得到每种方式在不同区域的性能对比

备注:本文用到的部分函数方法和Demo,均来自三方库KJCategories

图片缩放性能对比介绍就到此完毕,后面有相关再补充