图片的解压缩
最近在看 SDWebimage 的源码,顺便补习下图片的解压缩原理
我们一班看到的图片,都是如 png jpeg 之类的图片,都是经过编码的,为什么要编码,如果不编码将图片的原始信息传输,那么他的大小会非常非常大,不利用传输,所以经过压缩编码之后,效率更加
位图其实就是一个像素点阵图像,包含的一个一个像素点,将这些像素点组装起来放到屏幕上就可以显示了。
平时我们的使用
UIImageView *imageView = ...;
UIImage *image = [UIImage imageWithContentsOfFile:@"/.../.../path.JPG"];
imageView.image = image;
我们这样取用,UIImage *image = [UIImage imageWithContentsOfFile:@"/.../.../path.JPG"];
这样其实并没有对图片进行解压缩,当我们赋值给imageview 的image时候,图片马上要渲染到屏幕上的时候,系统会为我们解压缩,并且是在主线程中进行的,是非常耗cpu性能的,所以,你想,我们的tableview中,有很多图片,每次都这样去设置,那能不卡吗?所以现在用到的第三方框架,都是提前给你在子线程解码好了,显示的时候拿来显示就可以了
解码 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);
这个接口是创建一个 CGContextRef 上下文,来分析下每个参数的含义
bitsPerComponent:表示每一个成分有多少位组成,Component 就是指颜色分量,例如 RGB 中,指定 R/G/B 这些颜色分量由多少位来表示
bytesPerPixel:表示一个像素点有多少个字节组成,上面的方法注释中提到了一个公式 (bitsPerComponent * number of components + 7)/8,即一个像素点的字节数量与表示当前图像的颜色的颜色分量数量和每个分量的位数有关
bytesPerRow:图像一行有多少字节,上面注释中也提到了,它一般是width * bytes per pixel,很好理解,也就是图像像素宽度与每个像素字节大小的乘积。所以我们可以想到最开始我们说的那个计算位图大小的公式,只要 bytesPerRow 再乘上图像的像素高度 height 即可。
space:即颜色空间,我们平常一直说的 RGB 就是一种颜色空间,另外还有 CMYK 也都是颜色空间,每个像素的信息必须要在一个颜色空间中才有意义。也就是说,我们通过颜色空间告诉系统一个像素上面的颜色信息都是什么意思。显然同样的一个位图每一个像素的信息,在 RGB 颜色中间中表示的意思与在 CMYK 中是不同的,最后渲染出来的图像一定是不一样的。
我们的手机一般支持 RGB 颜色空间,其 bitsPerComponent = 8。那 bytesPerPixel 是多少呢,根据上面的公式,我们首先要知道 RGB 中有多少颜色分量呢?我们可能认为是 3,因为红、绿、蓝就是 3 个分量,实际上一般是4?因为 RGB 颜色空间中,其实还要包含一个 alpha 通道,也就是透明度。所以实际上应该是 ARGB 或者 RGBA,两者的区别就是 alpha 通道在哪里表示,这个下面还会再说。当然也可能是3,那就说明没有 alpha 通道。现在就是知道了 RGB 颜色空间中,实际上是有 4 个分量,所以我们可以算出 bytesPerPixel = (8 * 4 + 7)/8 = 4B。所以我们现在知道为什么最开始的时候我们计算位图的大小的时候,每个像素的大小我们使用的值是 4B 了。事实上不同的颜色空间下,上面这些值都是不同的,但是一般在手机上,我们使用 RGB 颜色空间,所以差不多就是上面的值。
现在我们回到上面那个方法的参数上面:
第一个参数data,根据注释,我们可以赋值为 NULL,这样系统会自动帮我们分配和释放相应大小的空间。
第二个参数和第三个参数即上下文的宽和高,因为我们根据原始的图片来创建上下文进行渲染,所以这两个值就是原始图片的宽和高。
第四个参数bitsPerComponent我们知道了就是每个成分由多少位组成,因为我们默认实在 RGB 颜色空间中,所以一般都是传入 8。
第五个参数bytesPerRow,这个我之前也专门介绍过了,就是像素宽度与每个像素大小的乘积,因为这些值都是知道的,我们可以这么传入。当然这里我们还可以直接传入0,这样系统会帮我们进行计算,而且这样做系统还会帮我们做一些优化,这样更好。
第六个参数space即颜色空间,我们可以直接使用RGB,直接使用系统提供的 API 获取: CGColorSpaceCreateDeviceRGB()
第七个参数bitmapInfo表示位图的布局信息。这个参数实际上系统提供了一个枚举值
颜色空间
颜色空间是对色彩的一种描述方式,主要有6种:RGB、CMY/CMYK、HSV/HSB、HSI/HSL、Lab、YUV/YCbCr。
比如RGB是通过红绿蓝三原色来描述颜色的颜色空间,R=Red、G=Green、B=Blue。RGB颜色空间下,一个像素由R、G、B三个颜色分量表示,每个分量使用的bit 数就是bpc。若每个分量用8位,那么一个像素共用24位表示,24就是像素的深度。
最常用的就是RGB和CMYK。同一个色值在不同的颜色空间下表现出来是不同的颜色。
比如我们拿一个RGB格式的图片去打印,会发现打印出来的颜色和我们在电脑上面看到的有色差,这就是因为颜色空间不同导致的,因为打印机的颜色空间是CMYK。
PBC
然后这个的PBC就是一个像素中每个独立的颜色分量使用的 bit 数。
颜色分量是什么?比如RGB是通过红绿蓝三原色来描述颜色的颜色空间,R=Red、G=Green、B=Blue,也就是红绿蓝。RGB颜色空间下,一个像素就由R、G、B三个颜色分量表示,这个就是颜色分量。每个分量使用的bit 数就是bpc。
如果每个分量用8位,那么一个像素共用24位表示,24就是像素的深度。再加上如果有透明度信息,那就是8888,一共有32位也就是4个字节,就是我们前面说的iOS中每个像素所占的字节数。
BitmapInfo
然后还有BitmapInfo。BitmapInfo就是用来说明每个像素中的bits包含了哪些信息。有以下三个方面:
是否包含Alpha通道,如果包含 alpha ,那么 alpha 信息所处的位置,在像素的最低有效位,比如 RGBA ,还是最高有效位,比如 ARGB ;
如果包含 alpha ,那么每个颜色分量是否已经乘以 alpha 的值,这种做法可以加速图片的渲染时间,因为它避免了渲染时的额外乘法运算。比如,对于 RGB 颜色空间,用已经乘以 alpha 的数据来渲染图片,每个像素都可以避免 3 次乘法运算,红色乘以 alpha ,绿色乘以 alpha 和蓝色乘以 alpha
颜色分量是否为浮点数
iOS中,alpha通道的布局信息是一个枚举值 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 */
};
kCGImageAlphaNone : 无alpha通道
kCGImageAlphaOnly:无颜色数据,只有alpha通道
kCGImageAlphaNoneSkipLast、kCGImageAlphaNoneSkipFirst :有alpha通道,但是忽略了alpha值,即透明度不起作用。两者的区别是alpha通道所在的位置
kCGImageAlphaLast、kCGImageAlphaFirst:有alpha通道,且alpha通道起作用,两者的区别是alpha通道所在的位置不同
kCGImageAlphaPremultipliedLast、kCGImageAlphaPremultipliedFirst :有alpha通道,且alpha通道起作用。这两个值的区别是alpha通道坐在的位置不同。和kCGImageAlphaLast、kCGImageAlphaFirst的区别是:带有Premultiplied,在解压缩的时候就将透明度乘到每个颜色分量上,这样渲染的时候就不用再处理alpha通道,提高了渲染的效率。
根据苹果官方文档的介绍,如果图片无alpha通道,则应该使用kCGImageAlphaNoneSkipFirst,如果图片含alpha通道,则应该使用kCGImageAlphaPremultipliedFirst。
然后就是bitmapInfo。这个参数除了要指定alpha的信息外,就是前面提到的ARGB还是RGBA,另外还需要指定字节顺序。
字节顺序分为两种:小端模式和大端模式。它是由枚举值 CGImageByteOrderInfo 来表示的:
typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) {
kCGImageByteOrderMask = 0x7000,
kCGImageByteOrderDefault = (0 << 12),
kCGImageByteOrder16Little = (1 << 12),
kCGImageByteOrder32Little = (2 << 12),
kCGImageByteOrder16Big = (3 << 12),
kCGImageByteOrder32Big = (4 << 12)
} CG_AVAILABLE_STARTING(10.0, 2.0);
在iOS中使用的是小端模式,在macOS中使用的是大端模式,为了兼容,使用kCGBitmapByteOrder32Host
,32位字节顺序,该宏在不同的平台上面会自动组装换成不同的模式。32是指数据以32bit为单位(字节顺序)。字节顺序也以32bit为单位排序。
#ifdef __BIG_ENDIAN__
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big
#else /* Little endian. */
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little
#endif