iOS 图片解压缩的过程

来源:互联网 发布:请州长夫人演唱 知乎 编辑:程序博客网 时间:2024/05/02 09:18

在移动app开发过程中,图片往往是不可或缺的资源。从磁盘上加载一张图片,到显示到屏幕上,中间经过了一些复杂的过程,其中非常重要的一步就是对图片的解压缩。那么,什么是图片解压缩?为什么要对图片解压缩?如何对图片解压缩?下面从这几个问题进行分析。
在解决上面的问题前,首先了解一些简单的概念。

和解压缩相关的概念

jpeg 和 png

  1. jpeg: jpeg 是一种有损压缩的图片格式,不支持透明通道。有损压缩是不可逆的,也就是说,不能从一张有损压缩的图片得到原图片数据。
  2. png: png 是一种无损压缩的图片格式。相对于jpeg来说,png 支持透明通道。因为png是无损压缩的,所以可以从png图片得到原图片数据。
    在iOS开发中,苹果提供了专门的api来 生成jpeg格式的图片和png格式的图片。
NSData * imageData = UIImageJPEGRepresentation(image, 0.9); 第二个参数就是压缩质量,官方推荐的系数是0.9。NSData *imageData = UIImagePNGRepresentation(image); 

从api 也可以看出 jpeg 是有损压缩,png 是无损压缩。

bmp 格式的图片

BMP,全称是bitmap,也是一种图片格式。bitmap 和 jpeg以及png最大的区别就是,bitmap是无损压缩的。由于是无损压缩的,相同的图片,bitmap格式的图片体积,要比jpeg和png格式的图片大很多。看看苹果开发者文档中对bitmap的描述:

A bitmap image (or sampled image) is an array of pixels (or samples). Each pixel represents a single point in the image. JPEG, TIFF, and PNG graphics files are examples of bitmap images.

实际上,bitmap图片就是一个像素数组,每一个像素代表图片上的一个点。
显示在屏幕上的图片,实际上就是由一个个的像素点组成的。因此,如果一张jpeg或者png格式的图片想要显示到屏幕上,需要得到该图片的像素数组,这个过程实际上就是解压缩。
这里可能会有个疑惑,既然jpeg、png格式的图片需要解压缩才能显示到屏幕上,为什么不直接使用bitmap格式的图片呢?原因上面也说了,bitmap格式的图片体积要远远大于jpeg和png格式的图片。从一个简单的例子来看下。
图标
这张图片是项目中使用到的一张png格式的图片,大小是2KB,尺寸为48px * 44px。如果一张尺寸为48px*44px,bitmap格式的图片体积是多大呢?
48 * 44 * bytesPerPixel (4) ,大约8KB左右。
在介绍 bytesPerPixel 之前,首先了解一下颜色空间的概念。

颜色空间

在iOS 开发中,一个颜色通常由一组数字来表示。比如1 0 0 ,1 1 1。而颜色空间的作用就是告诉系统如何来解析该颜色。比如说在RGB颜色空间下,1 0 0 表示的红色,而在 BGR 颜色空间下,1 0 0 表示的就是蓝色。由此可知,脱离了颜色空间,那么用来表示颜色的数字将变得毫无意义。iOS 设备中,通常使用的是RGB颜色空间。在程序中需要使用RGB颜色空间时,苹果已经提供了相应的api来创建颜色空间,代码如下:

static CGColorSpaceRef space;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{    space = CGColorSpaceCreateDeviceRGB();});return space;

核心api 是 CGColorSpaceCreateDeviceRGB:

/* Create a DeviceRGB color space. */CG_EXTERN CGColorSpaceRef cg_nullable CGColorSpaceCreateDeviceRGB(void)  CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

通过注释可以很容易明白该方法的作用。
因为绝大多数情况下使用的都是RGB颜色空间,所以创建RGB颜色空间的代码可以使用单例实现,提高效率。

bytesPerPixel

bytesPerPixel : 每个像素所占的字节数。
在RGB颜色空间下,每个颜色分量由8位组成(bitsPerComponent),也就是一个字节。
在RGB颜色空间下,一个颜色由多少个颜色分量组成呢?从字面上看,应该是3个,分别是R、G、B三个分量。实际上,通常情况下一个颜色由4个颜色分量组成,因为还包含alpha通道,也就是透明度。每个颜色分量占一个字节,因此每个像素所占的字节数为4。
回到上面的问题,一张尺寸为48px * 44px的图片,总共有像素点个数为 48 * 44,每个像素点所占的字节数为4,所以该图片的总字节数为 48 * 44 * 4,大小约8KB左右。
一张2KB左右的png格式图片,如果使用bmp格式来存储,大小为8KB,体积的变化是非常惊人的。在移动app开发中,一个app通常需要很多的图片资源,如果图片资源使用bmp格式,那么app包体积会变得非常大,这点是不能容忍的。因此,在app开发中,基本不用bmp格式的图片。

alpha通道

上面已经提到了alpha通道,有些图片是含有alpha通道的,有些图片不含有alpha通道。通过图片信息可以看到该图片是否含有alpha通道。如含有alpha通道的图片信息:
含alpha通道
不含alpha通道的图片信息:
不含alpha通道
在RGB颜色空间中,alpha通道所在的位置有两种,即RGBA和ARGB,即第一位或者最后一位。
iOS开发中,alpha通道的布局信息是一个枚举值,有以下几种情况:

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通道?实际上,已经有了相关的api。

CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) ;BOOL hasAlpha = NO;if (alphaInfo == kCGImageAlphaPremultipliedLast ||    alphaInfo == kCGImageAlphaPremultipliedFirst ||    alphaInfo == kCGImageAlphaLast ||    alphaInfo == kCGImageAlphaFirst) {        hasAlpha = YES;}

通过CGImageGetAlphaInfo 可以获取到alpha通道的布局信息,如果布局信息是kCGImageAlphaPremultipliedLast、kCGImageAlphaPremultipliedFirst、kCGImageAlphaLast、kCGImageAlphaFirst中的一个,则说明有alpha通道,否则无alpha通道。
对上面的基础的知识了解后,我们来看一下如何对图片解压缩。

如何对图片解压缩

因为项目中使用的第三方库是YYWebImage,因此,以YYWebImage为例来说明解压缩的过程(实际上,解压缩的过程是类似的)。
图片的解压缩实际上是对图片的一个拷贝,只不过是将一张压缩格式的图片(jpeg、png)拷贝成一张bitmap格式的图片。其中使用到的核心方法是:

/* 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);

该方法的作用是创建一个bitmap的上下文,参数比较多,了解下这些参数的含义。
data : 如果data不为null,则data的大小至少为 bytesPerRow * height 字节;如果data为null,系统会自动的分配所需要的空间。因此,该参数传 null 即可。
width、height:上下文的宽、高,根据注释,传图片的宽、高即可。
bitsPerComponent : 根据上面的介绍,该参数写8即可。
bytesPerRow : 该参数数必须是bytesPerPixel 的整数倍,bytesPerRow 即每行所含有的字节数,大小至少为 width * bytesPerPixel。如果该参数传的是0,系统会自动的帮我们计算,因此该参数传0即可。
space:使用RGB颜色空间即可。
bitmapInfo : 位图的布局信息。主要是指定了alpha通道的信息、颜色分量是否为浮点数、像素格式的字节顺序这三种信息。
alpha通道的布局信息上面已经介绍过了,那么在解压缩时,该使用哪个值呢?根据苹果官方文档的介绍,如果图片无alpha通道,则应该使用kCGImageAlphaNoneSkipFirst,如果图片含alpha通道,则应该使用 kCGImageAlphaPremultipliedFirst。
颜色分量是否为浮点数不用关注,通常是用不到的。
像素格式的字节顺序包含两种信息:大端存储还是小端存储,以及数据是以16位为单位还是以32位为单位。像素格式字节顺序实际上也是一个枚举值:

typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) {    kCGImageByteOrderMask     ,    kCGImageByteOrder16Little ,    kCGImageByteOrder32Little ,    kCGImageByteOrder16Big    ,    kCGImageByteOrder32Big   } CG_AVAILABLE_STARTING(__MAC_10_12, __IPHONE_10_0);

iPhone设备使用的是小端存储方式,根据前面的介绍,4个字节表示一个像素点,所以是以32位为单位。因此像素格式字节顺序的取值应为 kCGImageByteOrder32Little。
实际上,为了防止硬编码,该值通常使用系统提供的一个宏:kCGBitmapByteOrder32Host

#ifdef __BIG_ENDIAN__# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big#else    /* Little endian. */# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little#endif

无论是大端存储,还是小端存储,都可以使用kCGBitmapByteOrder32Host。
上面的方法是创建一个bitmap上下文,创建上下文成功后,使用CGContextDrawImage方法将位图绘制在上下文环境中,这个过程CPU会对图片解压缩,最终从bitmap上下文中获取解压缩后的图片,这张图片是可以渲染到屏幕上的。
一个图片解压缩的示例代码:

CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;BOOL hasAlpha = NO;if (alphaInfo == kCGImageAlphaPremultipliedLast ||    alphaInfo == kCGImageAlphaPremultipliedFirst ||    alphaInfo == kCGImageAlphaLast ||    alphaInfo == kCGImageAlphaFirst) {    hasAlpha = YES;}// BGRA8888 (premultiplied) or BGRX8888// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);if (!context) return NULL;CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decodeCGImageRef newImage = CGBitmapContextCreateImage(context);CFRelease(context);return newImage;

需要注意的是,解压缩的操作是非常消耗CPU的,如果解压缩的操作是在主线程中,在列表中有多张图片需要解码时,滑动会有卡顿。因此,现在流行的图片库通常都是在子线程对图片进行解码,然后在主线程对图片进行渲染。

总结

至此,关于图片解压缩相关的内容就介绍完毕了,参考了网上的一些文章,加上自己的理解。如果有不完善、不正确的地方,欢迎大家留言一起交流~

参考文章

  1. http://blog.leichunfeng.com/blog/2017/02/20/talking-about-the-decompression-of-the-image-in-ios/
  2. https://blog.ibireme.com/2015/11/02/mobile_image_benchmark/
  3. https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_images/dq_images.html#//apple_ref/doc/uid/TP30001066-CH212-SW3