最近在工作中遇到一個PNG圖片最終渲染出來丟失了Alpha(透明度)的問題,在經(jīng)過不斷地查閱資料和思考后終于解決了這個問題要门,在這里想和大家分享一下過程中學習到的一些知識點。
問題背景
目前所做的項目是一個跨平臺渲染引擎廓啊,使用的是stb_image庫讀取圖片像素數(shù)據(jù)(以RGBA排列)欢搜,之后通過OpenGL的glTexImage2D生成紋理并渲染,iOS平臺使用EAGLLayer來顯示最后的渲染結(jié)果谴轮。
然而發(fā)現(xiàn)了放在Xcode工程下面的PNG圖片能夠正常的顯示炒瘟,而通過下載的PNG圖片卻出現(xiàn)了Alpha信息丟失的情況,如下所示分別是加載Xcode工程中的PNG圖片和加載通過下載的PNG圖片。
那么為什么會從工程中讀取的PNG像素數(shù)據(jù)能正常顯示第步,而下載的PNG圖片卻丟失了Alpha信息呢疮装?我們發(fā)現(xiàn)在Xcode的Build Settings中有個和PNG相關(guān)的選項叫做Compress PNG Files,默認是開啟的粘都,我們嘗試著關(guān)閉這個選項廓推,發(fā)現(xiàn)再加載工程的PNG圖片也出現(xiàn)了Alpha信息丟失的情況了。那么問題就定位到了這個Compress PNG Files了翩隧,到底這個Compress PNG Files到底做了什么事情呢樊展?
關(guān)于PNG
首先我們來簡單看下PNG是啥,維基百科上面的定義是便攜式網(wǎng)絡(luò)圖形(英語:Portable Network Graphics堆生,縮寫:PNG)是一種無損壓縮的位圖圖形格式专缠,支持索引、灰度淑仆、RGB三種顏色方案以及Alpha通道等特性涝婉。
如果想更具體的了解PNG,可以看一下PNG文件結(jié)構(gòu)蔗怠。
關(guān)于Xcode 中的Compress PNG Files
Compress PNG Files從字面上簡單翻譯一下就是"壓縮PNG文件"的意思墩弯,但是當你把這個選項開啟之后會發(fā)現(xiàn)編譯之后的ipa包不但沒有減小,反而增大了寞射,這是怎么回事呢最住?說好的壓縮呢?這個Compress PNG Files到底做了啥事呢怠惶?
當 Xcode 優(yōu)化一個 PNG 文件的時候涨缚,它將 PNG 文件變成一個從技術(shù)上講不再是有效的PNG文件。但是 iOS 可以讀取這種文件策治,并且這比解壓縮正常的 PNG 文件更快脓魏。
它做了以下幾件事情:
- 額外關(guān)鍵塊(CgBI)
- byteswapped(RGBA - > BGRA)像素數(shù)據(jù),大概是用于幀緩沖的高速直接blitting
- 從IDAT塊中刪除了zlib頁眉通惫,頁腳和CRC
- 預乘alpha(
顏色'=顏色* alpha / 255
)
明顯的改動就是在IHDR塊之前插入了CgBI塊來表示這種格式茂翔,CgBI文件格式因其額外標題而得名,是Apple對PNG圖像格式的專有擴展履腋,同時修改了IDAT塊中的數(shù)據(jù)珊燎,原因就是在iPhone中惭嚣,圖像是以BGRA格式在內(nèi)存中處理的,到這里就可以發(fā)現(xiàn)悔政,其實這個所謂的Compress PNG Files晚吞,最主要的目的并不是壓縮圖片的大小,而是將圖片轉(zhuǎn)換成iPhone能更方便處理的格式谋国,加快處理速度槽地。
這里我們重點看下有一個和Alpha相關(guān)的操作預乘Alpha,那么什么是預乘Alpha呢芦瘾,這個操作的作用是什么呢捌蚊?
關(guān)于預乘透明度(Premultiplied Alpha)
簡單地說,比如常規(guī)的半透明半純紅色圖像RGBA歸一化值為(0.5, 0, 0, 0.5)近弟,由預乘透明度圖像方式存儲則RGBA值為(0.25, 0, 0, 0.5)缅糟。由此可知,即每個顏色分量都乘以alpha通道值作為結(jié)果值:
`color.rgb *= color.alpha`
為什么要引入預乘呢祷愉?
這種做法可以加速圖片渲染的速度溺拱,如果你的顏色分量是已經(jīng)經(jīng)過預乘處理過的,則在渲染時可以直接使用而不需要再進行3次的乘法運算谣辞,從而提高渲染效率迫摔。此外,實際上它還解決了以下幾個問題泥从。
- 解決紋理比例縮放映射產(chǎn)生的顏色錯誤問題
- 可以和其他紋理一起正尘湔迹混合而不打破批次渲染
關(guān)于iOS平臺下的渲染
iOS平臺中如果使用CAEAGLLayer
對象作為頂部圖層并且與它下面的圖層進行顏色混合,那么渲染緩存的顏色數(shù)據(jù)必須用一種預先乘好的(premultiplied)alpha格式躯嫉。而讀取出來的像素數(shù)據(jù)沒有經(jīng)過預乘Alpha操作纱烘,所以造成了渲染結(jié)果丟失了Alpha的現(xiàn)象。
綜上祈餐,所在我們讀取出來的RGB值分別乘上Alpha后就能夠正常渲染了:
uint8_t alpha;
for (int y = 0; y < p->s->img_x; y++) {
for (int x = 0; x < p->s->img_y; x++) {
alpha = *(pixels + 3); // 取出Alpha值
*pixels = round(*pixels * alpha / 255.f); // R * Alpha
*(pixels + 1) = round(*(pixels + 1) * alpha / 255.f); // G * Alpha
*(pixels + 2) = round(*(pixels + 2) * alpha / 255.f); // B * Alpha
pixels += 4;
}
}
經(jīng)過手動乘上Alpha處理后的圖片: