iOS 关于图片缩放性能探究

简介: iOS 关于图片缩放性能探究

前言


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

1.png

1.png


大致分为以下五种API:


UIKit,画布 drawInRect:UIGraphicsGetImageFromCurrentImageContext

CoreGraphics / Quartz 2D,位图上下文CGContextScaleCTMCGContextDrawImage

ImageIO,创建省略图 CGImageSourceCreateWithDataCGImageSourceCreateThumbnailAtIndex

CoreImage,滤镜 CILanczosScaleTransform

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


综合耗时比较


类型 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


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

相关文章
|
3天前
|
前端开发 Android开发 iOS开发
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
【4月更文挑战第30天】Flutter 框架实现跨平台移动应用,通过一致的 UI 渲染(Skia 引擎)、热重载功能和响应式框架提高开发效率和用户体验。然而,Android 和 iOS 的系统差异、渲染机制及编译过程影响性能。性能对比显示,iOS 可能因硬件优化提供更流畅体验,而 Android 更具灵活性和广泛硬件支持。开发者可采用代码、资源优化和特定平台优化策略,利用性能分析工具提升应用性能。
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
|
存储 UED 算法
|
3天前
|
监控 测试技术 iOS开发
查看ios 应用程序性能
查看ios 应用程序性能
42 0
|
3天前
|
监控 API iOS开发
克魔助手 - iOS性能检测平台
众所周知,如今的用户变得越来越关心app的体验,开发者必须关注应用性能所带来的用户流失问题。目前危害较大的性能问题主要有:闪退、卡顿、发热、耗电快、网络劫持等,但是做过iOS开发的人都知道,在开发过程中我们没有一个很直观的工具可以实时的知道开发者写出来的代码会不会造成性能问题,虽然Xcode里提供了耗电量检测、内存泄漏检测等工具,但是这些工具使用效果并不理想(如Leak无法发现循环引用造成的内存泄漏)。所以这篇文章主要是介绍一款实时监控app各项性能指标的工具,包括CPU占用率、内存使用量、内存泄漏、FPS、卡顿检测,并且会分析造成这些性能问题的原因。
|
3天前
|
监控 Linux iOS开发
如何使用克魔开发助手优化iOS应用性能
如何使用克魔开发助手优化iOS应用性能
34 1
|
存储 iOS开发 UED
iOS 性能检测新方式​——AnimationHitches
iOS 性能检测新方式​——AnimationHitches
iOS 性能检测新方式​——AnimationHitches
|
iOS开发
《移动 App 性能监测实践(iOS篇)》电子版地址
移动 App 性能监测实践(iOS篇)
130 0
《移动 App 性能监测实践(iOS篇)》电子版地址
|
机器学习/深度学习 安全 测试技术
阿里云EMAS-专家测试服务iOS和Android上百种机型性能、兼容及UI等测试
阿里云EMAS测试专家有着集团内部多个日活过亿规模APP经验,提供EMAS专家测试,客户只需提交测试需求,从用例设计、脚本录制、海量机型测试、整理测试结果、48小时输出专家测试报告均由阿里云EMAS测试专家一站式服务完成。覆盖功能测试、深度兼容测试、性能测试、UI适配测试以及隐私合规检测等,帮助用户以更低成本获得高质量的全面测试能力,可用于APP正式发版前验收,规避手机APP上线前或发版过程中各类隐患。
413 0
阿里云EMAS-专家测试服务iOS和Android上百种机型性能、兼容及UI等测试
|
安全 iOS开发 开发者
iOS系统关于URL Schemes的漏洞探究
iOS系统关于URL Schemes的漏洞探究
225 0
iOS系统关于URL Schemes的漏洞探究
|
存储 缓存 API
Metal新特性:大幅度提升iOS端性能
作为较早在客户端侧选择Flutter方案的技术团队,性能和用户体验一直是闲鱼技术团队在开发中比较关注的点。而Metal这样的直接操作GPU的底层接口无疑会给闲鱼技术团队突破性能瓶颈提供一些新的思路。 本文将会详细阐述一下这次大会Metal相关的新特性,以及对于闲鱼技术和整个淘系技术来说,这些新特性带来了哪些技术启发与思考。
Metal新特性:大幅度提升iOS端性能