一. 圖像從文件到屏幕過程
接下來我們了解一下CPU和GPU在渲染的過程中的分工是什么?
CPU(中央處理器)
1. 計(jì)算frame.
2.解壓縮圖片.
3. 將需要繪制的紋理圖片通過數(shù)據(jù)總線交給GPU.
GPU(圖形處理器)
1. 頂點(diǎn)的計(jì)算和變換.
2. 像素點(diǎn)的填充計(jì)算和紋理混合.
3. 渲染到幀緩沖區(qū).
圖片顯示到屏幕是CPU和GPU共同完成的
二. 圖片加載的工作流程
1. 假設(shè)我們是用[UIImage imageWithContensOfFile:@"xxx.png"] 方法創(chuàng)建對(duì)象來加載一張圖片, 這個(gè)時(shí)候只是創(chuàng)建一個(gè)管理圖片壓縮二進(jìn)制數(shù)據(jù)的image對(duì)象, 并沒有對(duì)圖片進(jìn)行解碼.
2. 把UIImage對(duì)象賦值給UIImageView, 此時(shí)一個(gè)隱式的CATransaction捕獲到的UIImageView圖形樹的變化.
3. 在線程的下一個(gè)運(yùn)行循環(huán)(Runloop)到來時(shí), Core Animation提交了這個(gè)隱式的transaction, 這個(gè)過程會(huì)對(duì)圖片進(jìn)行copy操作.這個(gè)copy操作可能會(huì)涉及以下部分或全部步驟:
分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作聚凹;
將文件數(shù)據(jù)從磁盤讀到內(nèi)存中;
將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式,這是一個(gè)非常耗時(shí)的 CPU 操作绣的;
最后Core Animation中CALayer使用未壓縮的位圖數(shù)據(jù)渲染UIImageView的圖層速客。
CPU計(jì)算好圖片的Frame,對(duì)圖片解壓之后.就會(huì)交給GPU來做圖片渲染.
4.渲染流程
GPU獲取到圖片的坐標(biāo).
將坐標(biāo)交給頂點(diǎn)著色器.(頂點(diǎn)計(jì)算)
將圖片光柵化.(根據(jù)頂點(diǎn)坐標(biāo)轉(zhuǎn)化成片元, 片元的每一個(gè)元素對(duì)應(yīng)幀緩沖區(qū)的一個(gè)像素點(diǎn), 并且分配顏色值和深度值到各個(gè)區(qū)域, 光柵化是一個(gè)將模擬信號(hào)轉(zhuǎn)化為離散信號(hào)的過程.)
片元著色器計(jì)算.(計(jì)算屏幕上每個(gè)像素點(diǎn)最終顯示的顏色值)
從幀緩沖區(qū)渲染到屏幕上
我們提到了圖片解碼是一個(gè)非常好使的CPU操作, 并且默認(rèn)是在主線程進(jìn)行的, 所以當(dāng)圖片較多時(shí)對(duì)性能會(huì)造成很大的影響. 特別是快速滑動(dòng)的列表上.
三. 為什么要解壓縮圖片
既然是耗時(shí)操作, 是否可以避免解壓縮直接將圖片顯示到屏幕上呢? 答案是否定的. 要想弄明白這個(gè)問題, 我們應(yīng)該先了解一下位圖, 什么是位圖呢?
其實(shí), 位圖是一個(gè)像素?cái)?shù)組, 每一個(gè)元素代表圖片中的一個(gè)點(diǎn). 我們?cè)诔绦蛑惺褂玫腜NG和JPEG圖片就是位圖.
事實(shí)上戚篙,不管是 JPEG 還是 PNG 圖片,都是一種壓縮的位圖圖形格式溺职。只不過 PNG 圖片是無損壓縮岔擂,并且支持 alpha 通道,而 JPEG 圖片則是有損壓縮浪耘,可以指定 0-100% 的壓縮比.
因此智亮,在將磁盤中的圖片渲染到屏幕之前,必須先要得到圖片的原始像素?cái)?shù)據(jù)点待,才能執(zhí)行后續(xù)的繪制操作阔蛉,這就是為什么需要對(duì)圖片解壓縮的原因。
四.解壓縮原理
既然圖片的解壓縮不可避免癞埠,而我們也不想讓它在主線程執(zhí)行状原,影響我們應(yīng)用的響應(yīng)性聋呢,那么是否有比較好的解決方案呢?
我們前面已經(jīng)提到了颠区,當(dāng)未解壓縮的圖片將要渲染到屏幕時(shí)削锰,系統(tǒng)會(huì)在主線程對(duì)圖片進(jìn)行解壓縮,而如果圖片已經(jīng)解壓縮了毕莱,系統(tǒng)就不會(huì)再對(duì)圖片進(jìn)行解壓縮器贩。因此,也就有了業(yè)內(nèi)的解決方案朋截,在子線程提前對(duì)圖片進(jìn)行強(qiáng)制解壓縮蛹稍。
而強(qiáng)制解壓縮的原理就是對(duì)圖片進(jìn)行重新繪制,得到一張新的解壓縮后的位圖部服。其中唆姐,用到的最核心的函數(shù)是 CGBitmapContextCreate:
解壓縮代碼步驟如下(YYImage實(shí)現(xiàn)):
CGBitmapInfobitmapInfo = kCGBitmapByteOrder32Host;
CGContextRef context =CGBitmapContextCreate(NULL, width, height,8,0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);//創(chuàng)建一個(gè)位圖上下文
if(!context)returnNULL;
CGContextDrawImage(context,CGRectMake(0,0, width, height), imageRef);//decode 將原始位圖繪制在上下文中
CGImageRef newImage =CGBitmapContextCreateImage(context);//創(chuàng)建一個(gè)新的解壓縮后的位圖
CFRelease(context);
它接受了一個(gè)原始位圖imageRef, 最終返回了一個(gè)新的解壓縮位圖newImage.
事實(shí)上,SDWebImage 中對(duì)圖片的解壓縮過程與上述完全一致廓八,只是傳遞給?CGBitmapContextCreate?函數(shù)的部分參數(shù)存在細(xì)微的差別.
性能對(duì)比:
在解壓PNG圖片,SDWebImage>YYImage
在解壓JPEG圖片,SDWebImage<YYImage
結(jié)論:
1. 圖片文件只有在確認(rèn)要顯示時(shí)才進(jìn)行解壓縮操作, 因?yàn)槭且粋€(gè)耗時(shí)的CPU操作, 解壓縮后會(huì)進(jìn)行緩存, 不會(huì)對(duì)其重復(fù)解壓縮.
2. 圖片渲染的過程: 讀取文件 -> 計(jì)算frame ->圖片解碼 ->解碼后通過數(shù)據(jù)總線交給GPU ->GPU獲取圖片frame后進(jìn)行頂點(diǎn)變換計(jì)算 ->光柵化 ->根據(jù)紋理坐標(biāo)獲取每一個(gè)像素點(diǎn)的顏色值 -> 交給幀緩沖區(qū) ->渲染到屏幕上.