一、影像從檔案到顯示螢屏程序
① 影像顯示到螢屏原理
通常計算機在顯示是CPU與GPU協同合作完成一次渲染,接下來了解一下CPU/GPU等在這樣一次渲染程序中,具體的分工是什么?
- CPU:計算視圖frame,圖片解碼,需要 繪制紋理圖片通過資料總線交給GPU ;
- GPU: 紋理混合,頂點變換與計算,像素點的填充計算,渲染到幀緩沖區 ;
- 時鐘信號: 垂直同步信號V-Sync / 水平同步信號H-Sync ;
- iOS設備雙緩沖機制:顯示系統通常會引入兩個幀緩沖區, 雙緩沖機制 ,

② 圖片顯示到螢屏上是CPU與GPU的協作完成
對應用來說,圖片是最占用手機記憶體的資源,將一張圖片從磁盤中加載出來,并最終顯示到螢屏上,中間其實經過了一系列復雜的處理程序,
二、圖片加載的作業流程
- 假設我們使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片,這個時候的圖片并沒有解壓縮;
- 然后將生成的 UIImage 賦值給 UIImageView ;
- 接著一個 隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化 ;
- 在主執行緒的下一個 runloop 到來時,Core Animation 提交了這個隱式的 transaction ,這個程序可能會對圖片進行 copy 操作 ,而受圖片是否 位元組對齊 等因素的影響,這個 copy 操作可能會涉及以下部分或全部步驟:
- 分配記憶體緩沖區 用于管理檔案 IO 和解壓縮操作;
- 將檔案資料 從磁盤讀到記憶體中 ;
- 將壓縮的圖片資料解碼成未壓縮的位圖形式 ,這是一個非常耗時的 CPU 操作;
- 最后 Core Animation 中 CALayer 使用未壓縮的位圖資料渲染 UIImageView 的圖層;
- CPU 計算好圖片的 Frame,對圖片解壓之后,就會交給 GPU來做圖片渲染 ,
- 渲染流程
- GPU獲取 獲取圖片的坐標 ;
- 將坐標交給頂點著色器(頂點計算);
- 將圖片 光柵化 (獲取圖片對應螢屏上的像素點);
- 片元著色器計算 (計算每個像素點的最終顯示的顏色值);
- 從幀快取區中渲染到螢屏上 ,
- 圖片的解壓縮是一個非常耗時的 CPU 操作,并且它默認是在主執行緒中執行的,那么當需要加載的圖片比較多時,就會對應用的回應性造成嚴重的影響,尤其是在快速滑動的串列上,這個問題會表現得更加突出,
三、為什么要解壓縮圖片?
- 既然圖片的解壓縮需要消耗大量的 CPU 時間,那么為什么還要對圖片進行解壓縮呢?是否可以不經過解壓縮,而直接將圖片顯示到螢屏上呢?答案是否定的,要想弄明白這個問題,首先需要知道什么是位圖,
- 其實, 位圖就是一個像素陣列 ,陣列中的每個像素就代表著圖片中的一個點,在應用中經常用到的 JPEG 和 PNG 圖片就是位圖,
- 列印rawData,這里就是圖片的原始資料:
UIImage *image = [UIImage imageNamed:@"text.png"];
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
- 其實,不管是 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 :
- data :如果不為 NULL ,那么它應該指向一塊大小至少為 bytesPerRow * height 位元組的記憶體;如果 為 NULL ,那么系統就會為我們自動分配和釋放所需的記憶體,所以一般指定 NULL 即可;
- width 和height : 位圖的寬度和高度 ,分別賦值為圖片的像素寬度和像素高度即可;
- bitsPerComponent : 像素的每個顏色分量使用的 bit 數 ,在 RGB 顏色空間下指定 8 即可;
- bytesPerRow : 位圖的每一行使用的位元組數 ,大小至少為 width * bytes per pixel 位元組,當我們指定 0/NULL 時,系統不僅會為我們自動計算,而且還會進行 cache line alignment 的優化
- space :就是我們前面提到的 顏色空間 ,一般使用 RGB 即可;
- bitmapInfo : 位圖的布局資訊 ,kCGImageAlphaPremultipliedFirst
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);
五、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,
總結
- 圖片檔案只有在確認要顯示時,CPU才會對齊進行解壓縮,因為解壓是非常消耗性能的事情,解壓過的圖片就不會重復解壓,會快取起來,
- 圖片渲染到螢屏的程序: 讀取檔案 -> 計算Frame -> 圖片解碼 -> 解碼后紋理圖片位圖資料通過資料總線交給GPU -> GPU獲取圖片Frame -> 頂點變換計算 -> 光柵化 -> 根據紋理坐標獲取每個像素點的顏色值(如果出現透明值需要將每個像素點的顏色*透明度值) -> 渲染到幀快取區 -> 渲染到螢屏,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/32776.html
標籤:其他
上一篇:分布式服務常用解決方案
