探討iOS 中圖片的解壓縮到渲染過程

一.圖像從文件到屏幕過程

image

一般計算機在顯示是CPU與GPU協同合做完成一次渲染.接下來咱們瞭解一下CPU/GPU等在這樣一次渲染過程當中,具體的分工是什麼?git

  • CPU: 計算視圖frame,圖片解碼,須要繪製紋理圖片經過數據總線交給GPU
  • GPU: 紋理混合,頂點變換與計算,像素點的填充計算,渲染到幀緩衝區
  • 時鐘信號:垂直同步信號V-Sync / 水平同步信號H-Sync
  • iOS設備雙緩衝機制:顯示系統一般會引入兩個幀緩衝區,雙緩衝機制

圖片顯示到屏幕上是CPU與GPU的協做完成github

對應應用來講,圖片是最佔用手機內存的資源,將一張圖片從磁盤中加載出來,並最終顯示到屏幕上,中間其實通過了一系列複雜的處理過程。面試

二.圖片加載的工做流程

  1. 假設咱們使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片,這個時候的圖片並無解壓縮;數組

  2. 而後將生成的 UIImage 賦值給 UIImageView緩存

  3. 接着一個隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化;bash

  4. 在主線程的下一個 runloop 到來時,Core Animation 提交了這個隱式的 transaction ,這個過程可能會對圖片進行 copy 操做,而受圖片是否字節對齊等因素的影響,這個 copy 操做可能會涉及如下部分或所有步驟:框架

    • 分配內存緩衝區用於管理文件 IO 和解壓縮操做;
    • 將文件數據從磁盤讀到內存中;
    • 將壓縮的圖片數據解碼成未壓縮的位圖形式,這是一個很是耗時的 CPU 操做;
    • 最後 Core AnimationCALayer使用未壓縮的位圖數據渲染 UIImageView 的圖層。
    • CPU計算好圖片的Frame,對圖片解壓以後.就會交給GPU來作圖片渲染
  5. 渲染流程ide

    • GPU獲取獲取圖片的座標
    • 將座標交給頂點着色器(頂點計算)
    • 將圖片光柵化(獲取圖片對應屏幕上的像素點)
    • 片元着色器計算(計算每一個像素點的最終顯示的顏色值)
    • 從幀緩存區中渲染到屏幕上

咱們提到了圖片的解壓縮是一個很是耗時的 CPU 操做,而且它默認是在主線程中執行的。那麼當須要加載的圖片比較多時,就會對咱們應用的響應性形成嚴重的影響,尤爲是在快速滑動的列表上,這個問題會表現得更加突出。函數

三.爲何要解壓縮圖片

既然圖片的解壓縮須要消耗大量的 CPU 時間,那麼咱們爲何還要對圖片進行解壓縮呢?是否能夠不通過解壓縮,而直接將圖片顯示到屏幕上呢?答案是否認的。要想弄明白這個問題,咱們首先須要知道什麼是位圖oop

其實,位圖就是一個像素數組,數組中的每一個像素就表明着圖片中的一個點。咱們在應用中常常用到的 JPEG 和 PNG 圖片就是位圖

你們能夠嘗試

UIImage *image = [UIImage imageNamed:@"text.png"];
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));

複製代碼

打印rawData,這裏就是圖片的原始數據.

事實上,不論是 JPEG 仍是 PNG 圖片,都是一種壓縮的位圖圖形格式。只不過 PNG 圖片是無損壓縮,而且支持 alpha 通道,而 JPEG 圖片則是有損壓縮,能夠指定 0-100% 的壓縮比。值得一提的是,在蘋果的 SDK 中專門提供了兩個函數用來生成 PNG 和 JPEG 圖片:

// return image as PNG. May return nil if image has no CGImageRef or invalid bitmap format
UIKIT_EXTERN NSData * __nullable UIImagePNGRepresentation(UIImage * __nonnull image);

// return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least)                           
UIKIT_EXTERN NSData * __nullable UIImageJPEGRepresentation(UIImage * __nonnull image, CGFloat compressionQuality);

複製代碼

所以,在將磁盤中的圖片渲染到屏幕以前,必須先要獲得圖片的原始像素數據,才能執行後續的繪製操做,這就是爲何須要對圖片解壓縮的緣由。

四.解壓縮原理

既然圖片的解壓縮不可避免,而咱們也不想讓它在主線程執行,影響咱們應用的響應性,那麼是否有比較好的解決方案呢?

咱們前面已經提到了,當未解壓縮的圖片將要渲染到屏幕時,系統會在主線程對圖片進行解壓縮,而若是圖片已經解壓縮了,系統就不會再對圖片進行解壓縮。所以,也就有了業內的解決方案,在子線程提早對圖片進行強制解壓縮。

而強制解壓縮的原理就是對圖片進行從新繪製,獲得一張新的解壓縮後的位圖。其中,用到的最核心的函數是 CGBitmapContextCreate :

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

複製代碼
  • data :若是不爲 NULL ,那麼它應該指向一塊大小至少爲 bytesPerRow * height 字節的內存;若是 爲 NULL ,那麼系統就會爲咱們自動分配和釋放所需的內存,因此通常指定 NULL 便可;
  • widthheight :位圖的寬度和高度,分別賦值爲圖片的像素寬度和像素高度便可;
  • bitsPerComponent :像素的每一個顏色份量使用的 bit 數,在 RGB 顏色空間下指定 8 便可;
  • bytesPerRow :位圖的每一行使用的字節數,大小至少爲 width * bytes per pixel 字節。當咱們指定 0/NULL 時,系統不只會爲咱們自動計算,並且還會進行 cache line alignment 的優化
  • space :就是咱們前面提到的顏色空間,通常使用 RGB 便可;
  • bitmapInfo :位圖的佈局信息.kCGImageAlphaPremultipliedFirst

五.YYImage\SDWebImage開源框架實現

  • 用於解壓縮圖片的函數 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;

        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
        if (!context) return NULL;

        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
        CGImageRef newImage = CGBitmapContextCreateImage(context);
        CFRelease(context);

        return newImage;
    } else {
        ...
    }
}

複製代碼

它接受一個原始的位圖參數 imageRef ,最終返回一個新的解壓縮後的位圖 newImage ,中間主要通過了如下三個步驟:

  • 使用 CGBitmapContextCreate 函數建立一個位圖上下文;
  • 使用 CGContextDrawImage 函數將原始位圖繪製到上下文中;
  • 使用 CGBitmapContextCreateImage 函數建立一張新的解壓縮後的位圖。

事實上,SDWebImage 中對圖片的解壓縮過程與上述徹底一致,只是傳遞給 CGBitmapContextCreate 函數的部分參數存在細微的差異

性能對比:

  • 在解壓PNG圖片,SDWebImage>YYImage
  • 在解壓JPEG圖片,SDWebImage<YYImage

總結

  1. 圖片文件只有在確認要顯示時,CPU纔會對齊進行解壓縮.由於解壓是很是消耗性能的事情.解壓過的圖片就不會重複解壓,會緩存起來.

  2. 圖片渲染到屏幕的過程: 讀取文件->計算Frame->圖片解碼->解碼後紋理圖片位圖數據經過數據總線交給GPU->GPU獲取圖片Frame->頂點變換計算->光柵化->根據紋理座標獲取每一個像素點的顏色值(若是出現透明值須要將每一個像素點的顏色*透明度值)->渲染到幀緩存區->渲染到屏幕

  3. 面試中若是能按照這個邏輯闡述,應該沒有大的問題.不過,若是細問到離屏渲染和渲染中的細節處理.就須要掌握OpenGL ES/Metal 這個2個圖形處理API. 面試過程可能會遇到不在本身技術能力範圍問題,儘可能知之爲知之不知爲不知.

github.com/SDWebImage/…

github.com/ibireme/YYI…

相關文章
相關標籤/搜索