iOS 大尺寸图片的旋转和缩放

由于iPhone的硬件性能限制,直到iPhone 6s开始,才将最大内存拓展到2G
可即使是如此,也不代表一个应用可使用的空间是2G
一张10000 x 10000的图片,如果通过UIImageJPEGRepresentation方法将图片转成内存数据,会有一个峰值波动。
这里的峰值其实是图片在解压时产生的位图数据所占空间,然后才转换成我们可以操作的NSData
其计算公式是 W x H x 4 / 1024 / 1024 也就是 10000 x 10000 x4 /1024 / 1024 = 381.4(M)
这里会产生381M的消耗,及时会被回收,但是想一下,如果图片尺寸很大,数量很多的时候,很容易就会发生异常了。

接下来说下具体的操作

旋转

我们知道如果对一个UIImage对象进行旋转操作,可以有如下的方式

通过 CGContextDrawImage 进行图片绘制

+ (UIImage *)image:(UIImage *)image rotation:(UIImageOrientation)orientation {

    long double rotate = 0.0;
    CGRect rect;
    float translateX = 0;
    float translateY = 0;
    float scaleX = 1.0;
    float scaleY = 1.0;

    switch (orientation) {
        case UIImageOrientationLeft:
            rotate = M_PI_2;
            rect = CGRectMake(0, 0, image.size.height, image.size.width);
            translateX = 0;
            translateY = -rect.size.width;
            scaleY = rect.size.width/rect.size.height;
            scaleX = rect.size.height/rect.size.width;
        break;
        default:
            rotate = 0.0;
            rect = CGRectMake(0, 0, image.size.width, image.size.height);
            translateX = 0;
            translateY = 0;
        break;
    }

    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    //做CTM变换
    CGContextTranslateCTM(context, 0.0, rect.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextRotateCTM(context, rotate);
    CGContextTranslateCTM(context, translateX, translateY);

    CGContextScaleCTM(context, scaleX, scaleY);
    //绘制图片
    CGContextDrawImage(context, CGRectMake(0, 0, rect.size.width, rect.size.height), image.CGImage);
    UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();
    return newPic;
} 

这里有一个问题是,这里会创建一个新的图片大小空间的,然后进行重新绘制。可能会存在一个隐患,就是当图片尺寸过大的时候,就会出现内存占用过高的情况

接下来介绍一种另辟蹊径的解决方法--通过给图片添加滤镜的方式。

既然操作的对象是图片,那么它就会各种滤镜展示。系统给我们提供了多大一百多种滤镜,这里的滤镜不单只颜色等状态发生变化。
这其中就有我们需要的滤镜Key inputTransform

+ (UIImage *)getRotationImage:(UIImage *)image rotation:(CGFloat)rotation {

    CIImage *ciImage = [[CIImage alloc] initWithImage:image];
    CIFilter *filter = [CIFilter filterWithName:@"CIAffineTransform" keysAndValues:kCIInputImageKey, ciImage, nil];

    [filter setDefaults];
    CGAffineTransform transform = CATransform3DGetAffineTransform([self rotateTransform:CATransform3DIdentity clockwise:NO angle:rotation]);
    [filter setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"];

    //根据滤镜设置图片
    CIContext *context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(NO)}];
    CIImage *outputImage = [filter outputImage];
    CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]];

    UIImage *result = [UIImage imageWithCGImage:cgImage];

    CGImageRelease(cgImage);

    return result;
}
+ (CATransform3D)rotateTransform:(CATransform3D)initialTransform clockwise:(BOOL)clockwise angle:(CGFloat)angle {
   
    CGFloat arg = angle*M_PI / 180.0f;
    if(!clockwise){
        arg *= -1;
    }
    //进行形变
    CATransform3D transform = initialTransform;
    transform = CATransform3DRotate(transform, arg, 0, 0, 1);
    CGFloat _flipState1 = 0;
    CGFloat _flipState2 = 0;
    transform = CATransform3DRotate(transform, _flipState1*M_PI, 0, 1, 0);
    transform = CATransform3DRotate(transform, _flipState2*M_PI, 1, 0, 0);

    return transform;
} 

通过这种操作,可以利用GPU来进行图片操作,可以一定程度的降低消耗,节约资源。

缩放

既然图片很大,那么我们可以通过缩放的方式,来减小图片的尺寸,减少内存消耗,进而降低异常风险。
我们通常采用UIImage提供的系统方法drawInRect 及其一系列的方法,来进行图片缩放。
可是这种操作的缺陷和最开始介绍的旋转一样,其实质都是进行图片的重新绘制。

通过绘制图片的方式进行图片缩放

+ (UIImage *)image:(UIImage *)image transformtoSize:(CGSize)Newsize {
    // 创建一个bitmap的context
    UIGraphicsBeginImageContext(Newsize);
    // 绘制改变大小的图片
    [image drawInRect:CGRectMake(0, 0, Newsize.width, Newsize.height)];
    // 从当前context中创建一个改变大小后的图片
    UIImage *TransformedImg=UIGraphicsGetImageFromCurrentImageContext();
    // 使当前的context出堆栈
    UIGraphicsEndImageContext();
    // 返回新的改变大小后的图片
    return TransformedImg;
}

这里是内存消耗。通过看图可以发现,针对大图,在进行缩放的时候,内存消耗的峰值能达到426M,耗时在1.5s左右
由于我们使用的手机是iPhone X,在更低端的设备上,这是多么大的损耗,很容易发生异常

缩放1

既然上面的方法损耗很大,我们来看下另外的一种方式。

先看下内存消耗


缩放2

通过图上可以看出,在进行图片缩放的时候,内存有小幅增加,产生的消耗在18M,耗时也在1.5s左右。
这样的效果是非常显著的。下面来看代码

+(UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)size {

    CIImage *ciImage = [[CIImage alloc] initWithImage:image];
    //创建一个input image类型的滤镜
    CIFilter *filter = [CIFilter filterWithName:@"CIAffineTransform" keysAndValues:kCIInputImageKey, ciImage, nil];
    //设置默认的滤镜效果
    [filter setDefaults];

    //设置缩放比例
    CGFloat scale = 1;
    if (size.width != CGFLOAT_MAX) {
        scale = (CGFloat) size.width / image.size.width;
    } else if (size.height != CGFLOAT_MAX) {
        scale = (CGFloat) size.height / image.size.height;
    }

    //进行赋值
    CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
    [filter setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"];

    //通过GPU的方式来进行处理
    CIContext *context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(NO)}];
    //根据滤镜输出图片
    CIImage *outputImage = [filter outputImage];
    CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]];
    //创建UIImage 对象,并释放资源
    UIImage *result = [UIImage imageWithCGImage:cgImage];

    CGImageRelease(cgImage);

    return result;
}

可以发现我们这里使用的和旋转是同样的方式。通过给图片添加滤镜能够很安全的实现我们的需求。

总结

1.针对巨幅图片操作,可以采用这种思路:先生成一个尺寸小的缩略图,然后在进行各种操作,可以降低资源消耗;
2.通过CoreImage.framework来进行图片处理。
3.之前一直对CoreImage.framework的理解,只是其能够对图片和视频添加那种可见的滤镜,未曾想过这种滤镜也支持缩放和旋转。

? 为什么CoreImage.framework的方式能够很安全呢?
该框架从iOS 5开始投入使用,通过对CoreGraphics.framework、CoreVideo.framework、Image I/O.framework进行数据处理,
可以自由在CPUGPU之间切换运算方式,
可以最大限度的利用GPU来进行计算,降低内存消耗,
甚至可以对视频进行实时滤镜处理。
针对不能通过原生对UIView进行transform操作的时候,CoreImage.framework会是你的朋友。

最直接的来自文档

Core Image is an image processing and analysis technology designed to provide near real-time processing for still and video images. It operates on image data types from the Core Graphics, Core Video, and Image I/O frameworks, using either a GPU or CPU rendering path. Core Image hides the details of low-level graphics processing by providing an easy-to-use application programming interface (API). You don’t need to know the details of OpenGL, OpenGL ES, or Metal to leverage the power of the GPU, nor do you need to know anything about Grand Central Dispatch (GCD) to get the benefit of multicore processing. Core Image handles the details for you.

它已经帮你把所有东西都处理好了,大胆的用吧

代码地址
原文地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_x阅读 15,967评论 3 119
  • 宛茹阅读 410评论 4 8
  • 关了厨房,只喝牛奶。 晚上12点的时候外面还是亮着的,因为院里的路灯离我的窗户很近,坐着咬指甲,放着广播,桌上的书...
    二辫儿阅读 389评论 0 0
  • 你抛一束蓝色绣球 将我引诱 懵懵懂懂的喝了一杯 妖艳的玫瑰酿制的香槟酒 一场粉多丁的传说 在万袋兰的世纪里摇曳 清...
    冷冬年阅读 212评论 0 3
  • 创玩第一次真正意义上的培训课程,耗时六周,终于在周日的时候完成了。在最后一节课里,在创业公司营销系统资源配置的章节...
    创玩MakEnjoy阅读 552评论 2 2