美文网首页
转[谈谈 iOS 中图片的解压缩]

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

作者: 大美象 | 来源:发表于2017-03-02 12:48 被阅读134次

    原文

    图片加载的工作流

    • 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 中图片的解压缩]

          本文链接:https://www.haomeiwen.com/subject/brphgttx.html