將一張圖片從磁盤中加載出來,並最終顯示到屏幕上,中間其實通過了一系列複雜的處理過程,從文件到屏幕,其中還包括了對圖片的解壓縮操做。數組
如上圖所示,圖片渲染到屏幕上,是CPU和GPU協做完成的。緩存
CPU/GPU 等在這樣一次渲染過程當中的具體分工:bash
什麼叫垂直同步信號和水平同步信號?框架
從上圖看,每一行從左到右就叫水平刷新,從上到下就叫垂直信號。整個屏幕刷新完畢,就會發出V-Sync信號。 能夠簡單的用超市買單的掃碼槍來理解,掃一個商品二維碼後就會出現一個H-Sync信號,掃完全部的商品二維碼後,計算商品總價後就至關於發送一個V-Sync信號。 從CRT顯示器的顯示原理來看,單個像素組成了水平掃描線,水平掃描線在垂直方向的堆積造成了完整的畫面。顯示器的刷新率受顯卡DAC控制,顯卡DAC完成一幀的掃描後就會產生一個垂直同步信號。ide
iOS從磁盤加載一張圖片,使用UIImageView顯示在屏幕上,加載流程以下:函數
咱們使用 +imageWithContentsOfFile:(使用Image I/O建立CGImageRef內存映射數據)方法從磁盤中加載一張圖片,此時,圖像還沒有解碼。;在這個過程當中先從磁盤拷貝數據到內核緩衝區,再從內核緩衝區複製數據到用戶空間oop
生成UIImageView,把圖像數據賦值給UIImageView,若是圖像數據未解碼(PNG/JPG),解碼爲位圖數據性能
隱式CATransaction 捕獲到UIImageView layer樹的變化。優化
在主線程的下一個 runloop 到來時,Core Animation 提交了這個隱式的 transaction ,這個過程可能會對圖片進行 copy 操做,若是數據沒有字節對齊,Core Animation會再拷貝一份數據,進行字節對齊,可能會涉及到如下操做:ui
• 分配內存緩衝區用於管理文件 IO 和解壓縮操做
• 將文件數據從磁盤讀到內存中
• 將壓縮的圖片數據解碼成未壓縮的位圖形式,這是一個很是耗時的 CPU 操做,而且解碼出來的圖片體積與圖片的寬高有關係,而與圖片原來的體積無關
• 最後 Core Animation 中CALayer使用未壓縮的位圖數據渲染 UIImageView 的圖層
• CPU計算好圖片的Frame,對圖片解壓以後.就會交給GPU,GPU處理位圖數據,進行渲染
渲染流程
能夠看到在上面這個工做流程中體現了CPU和GPU的互相配合
圖片的解壓縮是須要消耗大量CPU時間的,那爲何還須要對圖片進行解壓縮操做呢? 首先須要瞭解到什麼是位圖:
位圖(Bitmap),又稱柵格圖(英語:Raster graphics)或點陣圖,是使用像素陣列(Pixel-array/Dot-matrix點陣)來表示的圖像。
複製代碼
其實,位圖就是一個像素數組,數組中的每一個像素就表明着圖片中的一個點。咱們在應用中常常用到的 JPEG 和 PNG 圖片就是位圖。
獲取圖片的原始像素數據,用如下代碼:
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:
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);
複製代碼
這個函數用於建立一個位圖上下文,用來繪製一張寬 width 像素,高 height 像素的位圖。
用於解壓縮圖片的函數 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 {
...
}
}
複製代碼
YYImage加載流程:
SDWebImage 中對圖片的解壓縮過程與上述徹底一致,只是傳遞給 CGBitmapContextCreate 函數的部分參數存在細微的差異。
性能對比:
解壓PNG圖片,SDWebImage>YYImage 解壓JPEG圖片,SDWebImage<YYImage