SDWebImage学习笔记(五):  SDWebImageDecoder

/**
 *  解压图片:将图片通过位图重新绘制进行解压
 *
 *  @param image UIImage
 *
 *  @return UIImage
 */
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
    // while downloading huge amount of images
    // autorelease the bitmap context
    // and all vars to help system to free memory
    // when there are memory warning.
    // on iOS7, do not forget to call
    // [[SDImageCache sharedImageCache] clearMemory];
    // 批量下载图片时,@autoreleasepool会释放bitmaps上下文、用到的变量来释放内存。iOS7下,内存报警告时,不要忘记调用[[SDImageCache sharedImageCache] clearMemory];
    
    if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
        return nil;
    }
    
    @autoreleasepool{
        // do not decode animated images
        if (image.images != nil) {
            return image;
        }
        
        CGImageRef imageRef = image.CGImage;
        
        CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
        // 是否有透明通道
        BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
                         alpha == kCGImageAlphaLast ||
                         alpha == kCGImageAlphaPremultipliedFirst ||
                         alpha == kCGImageAlphaPremultipliedLast);
        if (anyAlpha) {
            // 有alpha通道,肯定就是PNG图片,不需要解压直接返回
            return image;
        }
        
        // current
        CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
        CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
        
        // 图片的ColorSpaceModel为kCGColorSpaceModelUnknown,kCGColorSpaceModelMonochrome,和kCGColorSpaceModelIndexed时,说明该ColorSpace不受支持
        BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
                                      imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
                                      imageColorSpaceModel == kCGColorSpaceModelCMYK ||
                                      imageColorSpaceModel == kCGColorSpaceModelIndexed);
        if (unsupportedColorSpace) {
            // 不支持ColorSpace,ColorSpace使用RGB模式
            colorspaceRef = CGColorSpaceCreateDeviceRGB();
        }
        
        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);
        NSUInteger bytesPerPixel = 4;
        NSUInteger bytesPerRow = bytesPerPixel * width;
        NSUInteger bitsPerComponent = 8;

        // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
        // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
        // to create bitmap graphics contexts without alpha info.
        // 由于alpha通道 = kCGImageAlphaNone时,不支持通过位图上下文来创建,所以bitmapInfo = kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     bitsPerComponent,
                                                     bytesPerRow,
                                                     colorspaceRef,
                                                     kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        
        // 在上下文中绘制图片,重新绘制没有alpha通道的位图图片:Draw the image into the context and retrieve the new bitmap image without alpha
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
        UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                         scale:image.scale
                                                   orientation:image.imageOrientation];
        
        // 释放资源
        if (unsupportedColorSpace) {
            CGColorSpaceRelease(colorspaceRef);
        }
        
        CGContextRelease(context);
        CGImageRelease(imageRefWithoutAlpha);
        
        return imageWithoutAlpha;
    }
}

问题:
1、用SDWebImageDownloaderOperation下载回来的图片为什么要进行decoder处理,即调用方法 decodedImageWithImage?

下载一张图片所需消耗的“空间”(内存)包括3个方面:
磁盘空间或者通过internet传输所消耗的空间
解压缩空间,通常是长X宽X高X4字节(RGBA)
当显示在一个view中时,view本身也需要空间来存储layer

将一张图片显示到屏幕上所消耗的“时间”包括3个方面:
从磁盘上alloc/init UIImage的时间
解压缩的时间(这个部分所用时间占比非常大)
将解压缩后的比特转换成CGContext的时间,通常需要改变尺寸,混合,抗锯齿工作

由于UIImage的imageWithData函数是每次画图的时候才将Data解压成ARGB的图像,所以在每次画图的时候,会有一个解压操作,这样效率很低,但是只有瞬时的内存需求。为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了。这种做法是典型的空间换时间。

2、图片的读写操作、解压缩都比较消耗系统资源,项目中在- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;方法中根据key从缓存中查询获取图片时,通过异步操作如果内存的缓存中没有该图片,则对该图片进行内存缓存的写操作,使用了自动释放池。其他地方为啥没有采用?比如解压图片方法decodedImageWithImage。

原来在 decodedImageWithImage方法中已使用自动释放池。使用该方法批量下载图片时,@autoreleasepool会释放bitmaps上下文、用到的变量来释放内存。iOS7下,内存报警告时,不要忘记调用[[SDImageCache sharedImageCache] clearMemory];

图片在UIImageView上面显示的时候需要解压,而这个解压操作是在主线程里面进行的,比较耗时,这样就会产生延时效果,在后台解压能够解决这一问题。当你用 UIImage 或 CGImageSource 的几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。如果想要绕开这个机制,常见的做法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。

关于图片的延迟解压,《iOS Core Animation Advanced Techniques》一书在14章的14.1讲得非常清楚,参阅:
https://zsisme.gitbooks.io/ios-/content/chapter14/loading-and-latency.html
异步图片加载优化分析:https://segmentfault.com/a/1190000002776279
绘制像素到屏幕上:http://objccn.io/issue-3-1/

合成

1.忽略一些难懂的事例并且假定屏幕上一切事物皆纹理。一个纹理就是一个包含 RGBA 值的长方形,比如,每一个像素里面都包含红、绿、蓝和透明度的值。在 Core Animation 世界中这就相当于一个 CALayer。
2.每一个 layer 是一个纹理,所有的纹理都以某种方式堆叠在彼此的顶部。对于屏幕上的每一个像素,GPU 需要算出怎么混合这些纹理来得到像素 RGB 的值。
3.屏幕上只有一个纹理(和屏幕大小一致且像素对齐),屏幕上的每一个像素就是纹理中的一个像素;有第二个纹理在第一个纹理上是,GPU会把第二个纹理合成到第一个纹理上......以此类推。所以,一个视图上有很多层,所有的纹理需要合成到一起,是的GPU非常忙碌。
R = S + D * ( 1 – Sa )
结果的颜色=源色彩(顶端纹理)+目标颜色(低一层的纹理)*(1-源颜色的透明度)
4.对应 Instruments 中 color blended layers 选项:红色标注的都是混合层,颜色越深,混合得越厉害。

不透明

1.当源纹理是完全不透明的时候,目标像素就等于源纹理。这可以省下 GPU 很大的工作量,这样只需简单的拷贝源纹理而不需要合成所有的像素值,所以控件的opaque属性默认值为YES,使得GPU不做任何事情,节省工作量。
2.家在一个没有alpha通道的图片,也会节省GUP大量生产工作。

B9E71D87-8130-4A85-9303-AC86DF335210.png

像素对齐

1.对应 Instruments 中color misaligned images选项:
2.当所有的像素是对齐的时候我们得到相对简单的计算公式。每当 GPU 需要计算出屏幕上一个像素是什么颜色的时候,它只需要考虑在这个像素之上的所有 layer 中对应的单个像素,并把这些像素合并到一起。或者,如果最顶层的纹理是不透明的(即图层树的最底层),这时候 GPU 就可以简单的拷贝它的像素到屏幕上。
3.两个原因可能会造成不对齐:滚动(当一个纹理上下滚动的时候,纹理的像素便不会和屏幕的像素排列对);当纹理的起点不在一个像素的边界上。在这两种情况下,GPU 需要再做额外的计算。
4.如果图片边界没有与目标像素完美对齐,该功能可为图片叠加上一层品红色;如果图片使用确定的比例大小绘制,那么该功能会为图片添加一层黄色叠加。

离屏渲染

1.离屏渲染:屏幕外的渲染会合并/渲染图层树的一部分到一个新的缓冲区,然后该缓冲区被渲染到屏幕上。
2.直接将图层合成到帧的缓冲区中(在屏幕上)比先创建屏幕外缓冲区,然后渲染到纹理中,最后将结果渲染到帧的缓冲区中要廉价很多。其中涉及两次昂贵的环境转换(转换环境到屏幕外缓冲区,然后转换环境到帧缓冲区)。
3.好的解决方法:缓存合成的纹理/图层。
4.如果你的程序混合了很多图层,并且想要他们一起做动画,GPU 通常会为每一帧(1/60s)重复合成所有的图层。当使用离屏渲染时,GPU 第一次会混合所有图层到一个基于新的纹理的位图缓存上,然后使用这个纹理来绘制到屏幕上。现在,当这些图层一起移动的时候,GPU 便可以复用这个位图缓存,并且只需要做很少的工作。需要注意的是,只有当那些图层不改变时,这才可以用。如果那些图层改变了,GPU 需要重新创建位图缓存。你可以通过设置 shouldRasterize 为 YES 来触发这个行为。

shouldRasteriza(栅格化): 栅格即像素,栅格化就是将矢量图转化为位图操作,较耗时。

5.Instrument 中对应 Color Offscreen-Rendered Yellow 选项,将已经被渲染到屏幕外缓冲区的区域标注为黄色。

打开 Color Offscreen-Rendered Yellow 后看到黄色,这便是一个警告,但这不一定是不好的。

rasterized layer 的空间是有限的。苹果暗示大概有屏幕大小两倍的空间来存储 rasterized layer/屏幕外缓冲区。

layer 使用蒙板或者设置圆角半径会造成离屏渲染,产生阴影也会如此。

mask,圆角半径(特殊的mask)和 clipsToBounds/masksToBounds,你可以简单的为一个已经拥有 mask 的 layer 创建内容,比如,已经应用了 mask 的 layer 使用一张图片。如果你想根据 layer 的内容为其应用一个长方形 mask,你可以使用 contentsRect 来代替蒙板。(还没太明白?)

如果你最后设置了 shouldRasterize 为 YES,那也要记住设置 rasterizationScale 为 contentsScale。
6.圆角头像优化:通过SDWebImage下载图片后,用贝塞尔曲线重绘图片设置边框和圆角,并进行缓存。

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

推荐阅读更多精彩内容

  • 绘制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一个像素是如何绘制到屏幕上去的?有很多...
    阿狸旅途T恤阅读 1,626评论 0 7
  • 卷首语 欢迎来到 objc.io 的第三期! 这一期都是关于视图层的。当然视图层有很多方面,我们需要把它们缩小到几...
    评评分分阅读 1,757评论 0 18
  • 绘制像素到屏幕上 软件组成 从简单的角度来看, 软件堆栈看起来有点像这样: Display的上一层便是图形处理单元...
    VanChan阅读 832评论 0 1
  • 像素是如何显示在屏幕上的呢? 当然这里有很多种方式将某些东西显示到显示器上面,并且它们可能涉及到许多不同的fram...
    樗同学阅读 1,767评论 1 3
  • 时光荏苒 一晃就是好些年 年龄大了 记忆中的事情 越来越多的呈现于脑海之中 那年的青春 在微风吹过的巷口 遇见了迎...
    楚晨阳阅读 471评论 3 1