理解iOS中圖片從文件渲染屏幕的過(guò)程
??本文涉及到的一些名詞名詞解釋可以參考 學(xué)習(xí)筆記_OpenGL下專(zhuān)業(yè)名詞解釋
??一般情況下,顯示是由 CPU 和 GPU 協(xié)作來(lái)完成的渲染,一次渲染的過(guò)程中,分工大致如下:
??CPU:計(jì)算 frame,圖片解碼,通過(guò)數(shù)據(jù)總線將需要繪制的紋理圖片交給GPU
??GPU:紋理混合,頂點(diǎn)變換與計(jì)算,像素點(diǎn)的填充計(jì)算,渲染到幀緩沖區(qū)。
圖片加載的工作流程
假設(shè)使用 +imageWithContentsOfFile: 方法從磁盤(pán)中加載一張圖片,這個(gè)時(shí)候的圖片并沒(méi)有解壓縮;
然后將生成的 UIImage 賦值給 UIImageView;
接著一個(gè)隱式的 CATransaction 捕獲到了 UIImageView 圖層樹(shù)的變化;
在主線層的下一個(gè) runloop 到來(lái)時(shí),Core Animation 提交了這個(gè)隱式的 transaction,這個(gè)過(guò)程可能會(huì)對(duì)圖片進(jìn)行 copy 操作,而受圖片是否字節(jié)對(duì)齊等因素的影響,這個(gè) copy 操作可能會(huì)涉及一下部分或全部步驟:
??分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作;
??將文件數(shù)據(jù)從磁盤(pán)堵到內(nèi)存中;
??將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式(這是一個(gè)非常耗時(shí)的 CPU 操作);
??最后 Core Animation 中 CALayer 使用未壓縮的位圖數(shù)據(jù)渲染 UIImageView 的圖層;
??CPU 計(jì)算好圖片的 Frame,對(duì)圖片解壓之后,就會(huì)交給 GPU 來(lái)做圖片渲染。
渲染流程:
??GPU 獲取圖片的坐標(biāo)
??將坐標(biāo)交給頂點(diǎn)著色器(頂點(diǎn)計(jì)算)
??將圖片光柵化(獲取圖片對(duì)應(yīng)屏幕上的像素點(diǎn))
??片元著色器計(jì)算(計(jì)算每個(gè)像素點(diǎn)的最終顯示的顏色值)
??從幀緩存區(qū)中渲染到屏幕上
??由于圖片壓縮是一個(gè)非常耗時(shí)的 CPU 操作,并且它是默認(rèn)在主線程中執(zhí)行,當(dāng)需要加載的圖片比較多的時(shí)候,就會(huì)對(duì)應(yīng)用性能造成嚴(yán)重影響,尤其是在快速滑動(dòng)列表時(shí),這個(gè)問(wèn)題尤其明顯。
為什么要解壓縮圖片
??既然圖片的解壓縮十分消耗性能,為什么還要進(jìn)行圖片的解壓縮而不是直接將圖片顯示在屏幕上呢?為了解釋這個(gè)問(wèn)題,就需要先了解什么是位圖。
??位圖就是一個(gè)像素?cái)?shù)組,數(shù)組中的每個(gè)像素點(diǎn)就代表圖片中的一個(gè)點(diǎn)。我們經(jīng)常接觸到的 JPEG 和 PNG 圖片就是位圖,而他們事實(shí)上是一種壓縮的位圖圖形格式,只不過(guò) PNG 是無(wú)損壓縮,并且支持 alpha 通道,而 JPEG 圖片則是有損壓縮,可以指定 0-100%的壓縮比。因此,在將磁盤(pán)中的圖片渲染到屏幕之前,必須先要得到圖片的原始像素?cái)?shù)據(jù),才能執(zhí)行后續(xù)的繪制操作,這就是為什么要對(duì)圖片解壓縮的原因。
解壓縮原理
??當(dāng)未解壓縮的圖片將要渲染到屏幕時(shí),系統(tǒng)會(huì)在主線程對(duì)圖片進(jìn)行解壓縮,而如果圖片已經(jīng)解壓縮了,系統(tǒng)就不會(huì)再對(duì)圖片進(jìn)行解壓縮,而解壓縮十分消耗性能,我們不希望讓它在主線程執(zhí)行,因此就有了業(yè)內(nèi)常用的解決方案,在子線程提前對(duì)圖片進(jìn)行解壓縮。
??強(qiáng)制解壓縮的原理就是對(duì)圖片進(jìn)行重新繪制,得到一張新的解壓縮后的位圖。其中用到的最核心的函數(shù)就是 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);
1
2
3
4
??data : 如果不為 NULL,那么它應(yīng)該指向一塊大小至少為 bytesPerRow * height 字節(jié)的內(nèi)存;如果為 NULL,那么系統(tǒng)就會(huì)為我們自動(dòng)分配和釋放所需的內(nèi)存,所以一般指定 NULL 即可;
??width 和 height :位圖的寬度和高度,分別賦值為圖片的像素寬度和像素高度即可;
??bitsPerComponent :像素的每個(gè)顏色分量使用的比特?cái)?shù),在 RGB 顏色空間下指定為 8 即可;
??bytesPerRow :位圖的每一行使用的字節(jié)數(shù),大小至少為 width * bytes per pixel 字節(jié)。當(dāng)指定為 0/NULL 時(shí),系統(tǒng)不僅會(huì)自動(dòng)計(jì)算,還會(huì)進(jìn)行 cache line alignment 的優(yōu)化。
??space :就是之前提到的顏色空間,一般使用 RGB 即可 ;
??bitmapInfo :位圖的布局信息。kCGImageAlphaPremultipliedFirst
總結(jié)
圖片文件只有在確認(rèn)要顯示時(shí),CPU 才會(huì)對(duì)其進(jìn)行解壓縮。因?yàn)榻鈮嚎s是非常耗時(shí)性能的事情,解壓過(guò)的圖片就不會(huì)重復(fù)解壓,會(huì)緩存起來(lái)。
圖片渲染到屏幕的過(guò)程:讀取文件 -> 計(jì)算 Frame -> 圖片解碼 -> 解碼后紋理圖片位圖數(shù)據(jù)通過(guò)數(shù)據(jù)總線交給 GPU -> GPU獲取圖片 Frame -> 頂點(diǎn)變換計(jì)算 -> 光柵化 -> 根據(jù)紋理坐標(biāo)獲取每個(gè)像素點(diǎn)的顏色值(如果出現(xiàn)透明值需要將每個(gè)像素點(diǎn)的顏色*透明值度)-> 渲染到幀緩存區(qū) -> 渲染到屏幕
原文鏈接:https://blog.csdn.net/u010682633/article/details/106960755/