/**
* 解压图片:将图片通过位图重新绘制进行解压
*
* @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大量生产工作。
像素对齐
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下载图片后,用贝塞尔曲线重绘图片设置边框和圆角,并进行缓存。
- Instruments 中Color Hits Green and Misses Red选项,绿色代表无论何时一个屏幕外缓冲区被复用,而红色代表当缓冲区被重新创建。