轉(zhuǎn)[談?wù)?iOS 中圖片的解壓縮]

原文

圖片加載的工作流

  • 1.假設(shè)我們使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片,這個(gè)時(shí)候的圖片并沒有解壓縮;
  • 2.然后將生成的 UIImage 賦值給 UIImageView ;
  • 3.接著一個(gè)隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化;
  • 4.在主線程的下一個(gè) run loop 到來時(shí),Core Animation 提交了這個(gè)隱式的 transaction ,這個(gè)過程可能會(huì)對(duì)圖片進(jìn)行 copy 操作,而受圖片是否字節(jié)對(duì)齊等因素的影響,這個(gè) copy 操作可能會(huì)涉及以下部分或全部步驟:
    a.分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作;
    b.將文件數(shù)據(jù)從磁盤讀到內(nèi)存中;
    c.將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式,這是一個(gè)非常耗時(shí)的 CPU 操作;
    d.最后 Core Animation 使用未壓縮的位圖數(shù)據(jù)渲染 UIImageView 的圖層

在上面的步驟中,我們提到了圖片的解壓縮是一個(gè)非常耗時(shí)的 CPU 操作,并且它默認(rèn)是在主線程中執(zhí)行的。那么當(dāng)需要加載的圖片比較多時(shí),就會(huì)對(duì)我們應(yīng)用的響應(yīng)性造成嚴(yán)重的影響,尤其是在快速滑動(dòng)的列表上,這個(gè)問題會(huì)表現(xiàn)得更加突出。

為什么需要解壓縮

位圖:位圖就是一個(gè)像素?cái)?shù)組,數(shù)組中的每個(gè)像素就代表著圖片中的一個(gè)點(diǎn)。我們應(yīng)用中經(jīng)常用的png和jpeg就是位圖,都是一種壓縮的位圖圖形格式。

解壓縮后的圖片大小 = 圖片的像素寬 30 * 圖片的像素高 30 * 每個(gè)像素所占的字節(jié)數(shù) 4

在磁盤中的圖片渲染到屏幕之前,必須得到原始的圖片像素?cái)?shù)據(jù),這就是為什么要對(duì)圖片進(jìn)行解壓縮操作。

強(qiáng)制解壓縮的原理

/* 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
函數(shù)中每個(gè)參數(shù)所代表的具體含義:

  • data :如果不為 NULL,那么它應(yīng)該指向一塊大小至少為 bytesPerRow * height 字節(jié)的內(nèi)存;如果 為 NULL
    ,那么系統(tǒng)就會(huì)為我們自動(dòng)分配和釋放所需的內(nèi)存,所以一般指定 NULL 即可;
  • width 和 height:位圖的寬度和高度,分別賦值為圖片的像素寬度和像素高度即可;
  • bitsPerComponent :像素的每個(gè)顏色分量使用的 bit 數(shù),在 RGB 顏色空間下指定 8 即可;
  • bytesPerRow:位圖的每一行使用的字節(jié)數(shù),大小至少為 width * bytes per pixel 字節(jié)。有意思的是,當(dāng)我們指定 0 時(shí),系統(tǒng)不僅會(huì)為我們自動(dòng)計(jì)算,而且還會(huì)進(jìn)行 cache line alignment 的優(yōu)化,更多信息可以查看 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? ,親測(cè)可用;
  • space :就是我們前面提到的顏色空間,一般使用 RGB 即可;
  • bitmapInfo :就是我們前面提到的位圖的布局信息。

開源庫(kù)的實(shí)現(xiàn)

首先,我們來看看 YYKit 中的相關(guān)代碼,用于解壓縮圖片的函數(shù) 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 函數(shù)創(chuàng)建一個(gè)位圖上下文
        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
        if (!context) return NULL;
//使用 CGContextDrawImage 函數(shù)將原始位圖繪制到上下文中;
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
//使用 CGBitmapContextCreateImage 函數(shù)創(chuàng)建一張新的解壓縮后的位圖
        CGImageRef newImage = CGBitmapContextCreateImage(context);
        CFRelease(context);

        return newImage;
    } else {
        ...
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容