SDWebImage探究(十九) —— 图像的解码 (一)
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.02.25 |
前言
我们做APP,文字和图片是绝对不可缺少的元素,特别是图片一般存储在图床里面,一般公司可以委托第三方保存,NB的公司也可以自己存储图片,ios有很多图片加载的第三方框架,其中最优秀的莫过于SDWebImage,它几乎可以满足你所有的需求,用了好几年这个框架,今天想总结一下。感兴趣的可以看其他几篇。
1. SDWebImage探究(一)
2. SDWebImage探究(二)
3. SDWebImage探究(三)
4. SDWebImage探究(四)
5. SDWebImage探究(五)
6. SDWebImage探究(六) —— 图片类型判断深入研究
7. SDWebImage探究(七) —— 深入研究图片下载流程(一)之有关option的位移枚举的说明
8. SDWebImage探究(八) —— 深入研究图片下载流程(二)之开始下载并返回下载结果的总的方法
9. SDWebImage探究(九) —— 深入研究图片下载流程(三)之下载之前的缓存查询操作
10. SDWebImage探究(十) —— 深入研究图片下载流程(四)之查询缓存后的block回调处理
11. SDWebImage探究(十一) —— 深入研究图片下载流程(五)之SDWebImageDownloadToken和操作对象的生成和返回
12. SDWebImage探究(十二) —— 深入研究图片下载流程(六)之下载器到具体下载操作的代理分发实现
13. SDWebImage探究(十三) —— 深入研究图片下载流程(七)之NSURLSession中几个代理的基本用法和关系
14. SDWebImage探究(十四) —— 深入研究图片下载流程(八)之下载完成代理方法的调用
15. SDWebImage探究(十五) —— 深入研究图片下载流程(九)之身份验证质询代理方法调用
16. SDWebImage探究(十六) —— 深入研究图片下载流程(十)之缓存相关代理方法调用
17. SDWebImage探究(十七) —— 深入研究图片下载流程(十一)之收到响应代理方法调用
18. SDWebImage探究(十八) —— 深入研究图片下载流程(十二)之收到图像数据代理方法调用
图像为什么需要解码
一般下载或者从磁盘获取的图片是PNG或者JPG,这是经过编码压缩后的图片数据,不是位图,要把它们渲染到屏幕前就需要进行解码转成位图数据,而这个解码操作比较耗时。你也可以这么理解,图片在远端存储一定都是编码后存储的,这样体积小,一个图像可以看做是一个图像文件,里面包含了文件头,文件体和文件尾,图像的数据就包含在文件体中,而我们的解码就是运用算法将文件体中的图像数据转化为位图数据,方便渲染和展示。
iOS默认是在主线程解码,所以SDWebImage将这个过程放到子线程了。
同时因为位图体积很大,所以磁盘缓存不会直接缓存位图数据,而是编码压缩后的PNG或JPG数据。
图像是否可以解码的判断
这里有几种情况是不进行解码的。
- 动图,判断
image.images != nil
,满足这个条件的就是动图,就不解码。 - 有alpha信息的图片不解码。
1. 动图
这里首先要纠正一个概念,动态图片也不仅仅限于gif,png格式也有动态的,只是常见的是GIF格式的比较多而已。
这里动图为什么不解码呢?
这个我还没找到正确的答案,猜测可能是动图解码后的数据占用内存太大,所以就不对动图进行解码了。关于GIF动图的支持在FLAnimatedImageView
分类中。
2. 有alpha信息的图片
(a) 有alpha信息的图片的判断
首先我们需要判断的就是什么图片包含alpha信息,苹果提供了支持。
/* Return the alpha info of `image'. */
CG_EXTERN CGImageAlphaInfo CGImageGetAlphaInfo(CGImageRef cg_nullable image)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
这里需要说明的就是CGImageAlphaInfo
,这个是什么鬼?
typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {
kCGImageAlphaNone, /* For example, RGB. */
kCGImageAlphaPremultipliedLast, /* For example, premultiplied RGBA */
kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
kCGImageAlphaLast, /* For example, non-premultiplied RGBA */
kCGImageAlphaFirst, /* For example, non-premultiplied ARGB */
kCGImageAlphaNoneSkipLast, /* For example, RBGX. */
kCGImageAlphaNoneSkipFirst, /* For example, XRGB. */
kCGImageAlphaOnly /* No color data, alpha data only */
};
这是CoreGraphic
框架中CGImage.h
中的一个枚举。
图像的解码实现
SDWebImage中有关于图像解码的部分,我们先看一下实现方式,然后进行详细的解析。
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
if (![UIImage shouldDecodeImage:image]) {
return image;
}
// 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{
CGImageRef imageRef = image.CGImage;
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bytesPerRow = kBytesPerPixel * width;
// 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.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (context == NULL) {
return image;
}
// 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];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// do not decode animated images
if (image.images != nil) {
return NO;
}
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
// do not decode images with alpha
if (anyAlpha) {
return NO;
}
return YES;
}
+ (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
// current
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
colorspaceRef = CGColorSpaceCreateDeviceRGB();
CFAutorelease(colorspaceRef);
}
return colorspaceRef;
}
这里做的就是首先判断什么样的图像可以解码,是一个类方法,如果不能就直接返回传入的image
参数,如果可以解码就对图像进行解码后返回。
这里有几个点需要说明:
(a) CGBitmapContextCreate上下文的创建
kCGImageAlphaNone
在CGBitmapContextCreate
中不受支持。由于此处的原始图像没有alpha信息,因此请使用kCGImageAlphaNoneSkipLast
创建不带alpha信息的位图图形上下文。
这里我们看一下,这个上下文创建的API
/* Create a bitmap context. The context draws into a bitmap which is `width'
pixels wide and `height' pixels high. The number of components for each
pixel is specified by `space', which may also specify a destination color
profile. The number of bits for each component of a pixel is specified by
`bitsPerComponent'. The number of bytes per pixel is equal to
`(bitsPerComponent * number of components + 7)/8'. Each row of the bitmap
consists of `bytesPerRow' bytes, which must be at least `width * bytes
per pixel' bytes; in addition, `bytesPerRow' must be an integer multiple
of the number of bytes per pixel. `data', if non-NULL, points to a block
of memory at least `bytesPerRow * height' bytes. If `data' is NULL, the
data for context is allocated automatically and freed when the context is
deallocated. `bitmapInfo' specifies whether the bitmap should contain an
alpha channel and how it's to be generated, along with whether the
components are floating-point or integer. */
CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(void * __nullable data,
size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow,
CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
创建一个位图上下文。 上下文绘制成宽度为width
像素和height
像素为高的位图。 每个像素的组件数量由space
指定,它也可以指定目标颜色配置文件。 像素的每个分量的位数由bitsPerComponent
指定。 每个像素的字节数等于(bitsPerComponent *components+7)/ 8
。 位图的每一行都由bytesPerRow
字节组成,它们必须至少为每个像素的字节数 * 宽度width
。另外,bytesPerRow
必须是每个像素字节数的整数倍。 data
,如果非NULL,则至少指向bytesPerRow * height
字节的内存块。 如果data
为NULL,上下文数据将自动分配,并在释放上下文时释放。 bitmapInfo
指定位图是否应该包含一个alpha通道以及如何生成,以及组件是浮点还是整数。
(b) 位图信息CGBitmapInfo
最后一个参数,关于CGBitmapInfo
的一个枚举。
typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
kCGBitmapAlphaInfoMask = 0x1F,
kCGBitmapFloatInfoMask = 0xF00,
kCGBitmapFloatComponents = (1 << 8),
kCGBitmapByteOrderMask = kCGImageByteOrderMask,
kCGBitmapByteOrderDefault = (0 << 12),
kCGBitmapByteOrder16Little = kCGImageByteOrder16Little,
kCGBitmapByteOrder32Little = kCGImageByteOrder32Little,
kCGBitmapByteOrder16Big = kCGImageByteOrder16Big,
kCGBitmapByteOrder32Big = kCGImageByteOrder32Big
} CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
这里存放的就是位图组成部分的信息。
-
kCGBitmapAlphaInfoMask
- Aplha通道信息遮罩。用这个值来提取alpha信息。这个值明确了位图是否包含了alpha通道和alpha通道是如何生成的。
-
kCGBitmapFloatComponents
- 位图数据都是浮点值。
-
kCGBitmapByteOrderMask
- 像素格式的字节顺序
-
kCGBitmapByteOrderDefault
- 默认的字节顺序
-
kCGBitmapByteOrder16Little
- 16位小端格式
-
kCGBitmapByteOrder32Little
- 32位小端格式
-
kCGBitmapByteOrder16Big
- 16位大端格式
-
kCGBitmapByteOrder32Big
- 32位大端格式
(c) 生成图像颜色空间CGColorSpaceRef
// current
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
colorspaceRef = CGColorSpaceCreateDeviceRGB();
CFAutorelease(colorspaceRef);
}
return colorspaceRef;
这里,CGColorSpaceRef
表示需要使用的色彩标准(为创建CGColor做准备)例如RBG:CGColorSpaceCreateDeviceRGB
。
Quartz支持由颜色管理系统使用的与设备无关的颜色空间的标准颜色空间,并且还支持通用,索引和模式颜色空间。 设备无关的颜色空间以在设备之间可移植的方式表示颜色。 它们用于将颜色数据从一个设备的本地颜色空间到另一个设备的本地颜色空间的交换。 在不同设备上显示时,设备无关颜色空间中的颜色显示相同,只要设备的功能允许。 因此,与设备无关的颜色空间是表示颜色的最佳选择。
具有精确颜色要求的应用程序应始终使用与设备无关的颜色空间。 常见的设备无关颜色空间是通用颜色空间。 通用颜色空间使操作系统为您的应用程序提供最佳的颜色空间。 绘图到显示看起来像打印相同的内容到打印机。
重点:iOS不支持设备无关或通用颜色空间。 iOS应用程序必须使用设备颜色空间。
下面看一下CGColorSpaceModel
,颜色空间模型。
/* The model of a color space. */
typedef CF_ENUM (int32_t, CGColorSpaceModel) {
kCGColorSpaceModelUnknown = -1,
kCGColorSpaceModelMonochrome,
kCGColorSpaceModelRGB,
kCGColorSpaceModelCMYK,
kCGColorSpaceModelLab,
kCGColorSpaceModelDeviceN,
kCGColorSpaceModelIndexed,
kCGColorSpaceModelPattern
};
参考文章
1. SDWebImageDecoder引发的思考
2. CGBitmapInfo
3. 【CoreGraphics】CGColorSpace - 色彩空间
4. Color and Color Spaces
后记
本篇文章主要解析了图像解码相关,包括为什么要进行图像解码、是否可以解码的判断以及解码的代码实现等几个问题。