转[谈谈 iOS 中图片的解压缩]

原文

图片加载的工作流

  • 1.假设我们使用 +imageWithContentsOfFile: 方法从磁盘中加载一张图片,这个时候的图片并没有解压缩;
  • 2.然后将生成的 UIImage 赋值给 UIImageView ;
  • 3.接着一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化;
  • 4.在主线程的下一个 run loop 到来时,Core Animation 提交了这个隐式的 transaction ,这个过程可能会对图片进行 copy 操作,而受图片是否字节对齐等因素的影响,这个 copy 操作可能会涉及以下部分或全部步骤:
    a.分配内存缓冲区用于管理文件 IO 和解压缩操作;
    b.将文件数据从磁盘读到内存中;
    c.将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作;
    d.最后 Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层

在上面的步骤中,我们提到了图片的解压缩是一个非常耗时的 CPU 操作,并且它默认是在主线程中执行的。那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出。

为什么需要解压缩

位图:位图就是一个像素数组,数组中的每个像素就代表着图片中的一个点。我们应用中经常用的png和jpeg就是位图,都是一种压缩的位图图形格式。

解压缩后的图片大小 = 图片的像素宽 30 * 图片的像素高 30 * 每个像素所占的字节数 4

在磁盘中的图片渲染到屏幕之前,必须得到原始的图片像素数据,这就是为什么要对图片进行解压缩操作。

强制解压缩的原理

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

看看 CGBitmapContextCreate
函数中每个参数所代表的具体含义:

  • data :如果不为 NULL,那么它应该指向一块大小至少为 bytesPerRow * height 字节的内存;如果 为 NULL
    ,那么系统就会为我们自动分配和释放所需的内存,所以一般指定 NULL 即可;
  • width 和 height:位图的宽度和高度,分别赋值为图片的像素宽度和像素高度即可;
  • bitsPerComponent :像素的每个颜色分量使用的 bit 数,在 RGB 颜色空间下指定 8 即可;
  • bytesPerRow:位图的每一行使用的字节数,大小至少为 width * bytes per pixel 字节。有意思的是,当我们指定 0 时,系统不仅会为我们自动计算,而且还会进行 cache line alignment 的优化,更多信息可以查看 what is byte alignment (cache line alignment) for Core Animation? Why it matters? 和 Why is my image’s Bytes per Row more than its Bytes per Pixel times its Width? ,亲测可用;
  • space :就是我们前面提到的颜色空间,一般使用 RGB 即可;
  • bitmapInfo :就是我们前面提到的位图的布局信息。

开源库的实现

首先,我们来看看 YYKit 中的相关代码,用于解压缩图片的函数 YYCGImageCreateDecodedCopy
存在于 YYImageCoder 类中,核心代码如下:

CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
    ...

    if (decodeForDisplay) { // decode with redraw (may lose some precision)
        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;
//使用 CGBitmapContextCreate 函数创建一个位图上下文
        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
        if (!context) return NULL;
//使用 CGContextDrawImage 函数将原始位图绘制到上下文中;
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
//使用 CGBitmapContextCreateImage 函数创建一张新的解压缩后的位图
        CGImageRef newImage = CGBitmapContextCreateImage(context);
        CFRelease(context);

        return newImage;
    } else {
        ...
    }
}

你可能感兴趣的:(转[谈谈 iOS 中图片的解压缩])