0. Ericsson 壓縮算法官方 GitHub
1. 基本思想
ETC 壓縮算法的基本思想
將圖片分成 4x4 的若干個(gè)像素塊嗅骄,每個(gè)像素塊按照一定規(guī)則編碼成為一個(gè) 64 位(8字節(jié))的數(shù)據(jù)戏自,大概的想法是計(jì)算像素塊的平均顏色辣之,然后記錄這個(gè)平均顏色和每個(gè)像素相對(duì)平均顏色的差值徒像,平均顏色只耗費(fèi)了一個(gè)像素的數(shù)據(jù)轮纫,而差值也并不記錄完全真實(shí)的差值,而是從一個(gè)固定的靜態(tài)數(shù)據(jù)中找到最接近的差值(RGB三個(gè)通道差值一樣)舷蟀,每個(gè)像素只需要記錄其差值在靜態(tài)數(shù)據(jù)中的索引即可冒版。壓縮比
對(duì)于 RGB24 圖片,每個(gè)塊的數(shù)據(jù)由 4x4x3 = 48字節(jié)歌殃,壓縮為 8字節(jié)乔妈,壓縮比為 6:1,針對(duì)Alpha圖片氓皱,由4x4x1 = 16字節(jié) 壓縮為 8 字節(jié)路召,所以勃刨,對(duì)于普通的 RGBA 分離為 RGB24 和 Alpha 之后分別進(jìn)行 ETC1 壓縮的圖片,整體壓縮比為 (48 + 16) : (8 + 8) = 4:1文件頭數(shù)據(jù)
除了編碼后的數(shù)據(jù)塊之外股淡,還會(huì)存儲(chǔ)一部分文件頭數(shù)據(jù)身隐,用來表示文件的特征碼、寬高等
2. 像素塊編碼思想
如何將一個(gè) 4x4 的像素塊編碼為 64 位數(shù)據(jù)呢?
- 將 4x4 的像素塊分為兩個(gè) 4x2 的子塊唯灵,有水平和豎直兩種分法贾铝。使用 1位數(shù)據(jù)flipbit來表示是哪一種分法,還剩下 63 位數(shù)據(jù)
flipbit表示子塊分法
分別計(jì)算兩個(gè)分塊中 8 個(gè)像素顏色的平均值埠帕,根據(jù)兩個(gè)塊顏色平均值的差值垢揩,確定使用 individual模式還是 differential 模式。使用 1位數(shù)據(jù)diffbit來表示是哪種模式敛瓷,還剩下 62位數(shù)據(jù)
存放兩個(gè)子塊的平均顏色信息叁巨,individual模式用R4G4B4 的格式分別表示兩個(gè)子塊的平均顏色,differential 模式使用 R5G5B5 格式表示第一個(gè)子塊的平均顏色琐驴,R3G3B3 格式表示第二個(gè)子塊與第一個(gè)子塊平均顏色的差值俘种。這里使用了 8 * 3 = 24 位數(shù)據(jù),還剩下 62-24 = 38 位數(shù)據(jù)
可以這么理解:當(dāng)兩個(gè)子塊的平均顏色值相差比較小時(shí)绝淡,基本顏色可以使用更高精度的 R5G5B5 來替代R4G4B4獲得更少的信息損失
所有的圖片共享一個(gè)全局的映射表數(shù)據(jù),這個(gè)數(shù)據(jù)是固定的全局靜態(tài)數(shù)據(jù)苍姜,并不會(huì)進(jìn)入到編碼數(shù)據(jù)中牢酵,這個(gè)表是一個(gè) 8 x 4 的二維數(shù)組,使用 3 位數(shù)表示第一個(gè)子塊在映射表中查詢的第一維索引衙猪,需要3位來表示 0-7 的下標(biāo)馍乙,第二個(gè)子塊同樣需要 3 位數(shù)來表示,還剩下 38-3*2 = 32位數(shù)據(jù)
4x4像素塊中的每個(gè)像素垫释,使用2位數(shù)來表示該像素在映射表中查詢的第二維索引丝格,需要2位數(shù)來表示0-3的下標(biāo),所以消耗了 4x4x2 = 32 位數(shù)據(jù)
以上就是編碼后的 64 位數(shù)據(jù)塊表示的意義
3. 內(nèi)存布局和解碼過程
以RGB555基本色和RGB3333顏色差表示的編碼為例棵譬,每個(gè)4x4 像素塊經(jīng)過ETC1 編碼后的 64 位數(shù)據(jù)的內(nèi)存布局大概是這樣
假如編碼前像素塊表示為下圖
我們需要得到圖中編號(hào)2對(duì)應(yīng)像素的顏色显蝌,需要進(jìn)行如下的解碼步驟:
獲取目標(biāo)所在的子塊
根據(jù)第32位 flipbit 標(biāo)志位,知道這個(gè)像素塊采用的是橫版劃分子塊订咸,2號(hào)像素處在子塊2中獲取子塊1基本顏色
首先根據(jù)第 33 位 diffbit 標(biāo)志位得知曼尊,這里采用的是 R5G5B5 基本色 + R3G3B3 差值的方式。分別從59-63位(11100)脏嚷、51-55位(00100)和43-47位(00011)讀取子塊1基本色 RGB1=(11100, 00100, 00011) = (28, 4, 3)獲取子塊2的顏色差值
這里是 differential 模式骆撇,所以需要讀取子塊2的顏色差值,從56-58位(100)父叙、48-50位(010)和40-42位(000)獲得顏色差值 RGB_offset=(100, 010, 000) = (-4, 2, 0)神郊,注意這里的3位數(shù)據(jù)中最高位是符號(hào)位肴裙,所以差值部分的取值范圍是[-4, 3]計(jì)算子塊2的基本色
將子塊1基本色和子塊2差值相加,得到子塊2的基本色涌乳,RGB2=RGB1 + RGB_offset = (28 - 4, 4+2, 3+0) = (24, 6, 3)蜻懦,轉(zhuǎn)為5位二進(jìn)制表示為 RGB2=(11000, 00110, 00011)擴(kuò)展子塊2基本色分量為8位
對(duì)5位標(biāo)識(shí)的基本色補(bǔ)位為8位表示,得到 RGB1 = (11000110, 00110110, 00011011) = (198, 54, 27)爷怀,這就是子塊2的基本色
補(bǔ)位規(guī)則:
individual 模式阻肩,直接將4位數(shù)復(fù)制到尾部,得到8位
differential 模式运授,將5位中的高3位復(fù)制到尾部烤惊,得到8位
differential 模式,一定是將子塊1基本色和子塊2偏移值相加后再進(jìn)行補(bǔ)位
-
獲得目標(biāo)像素的顏色偏移值
目標(biāo)像素下標(biāo)為2吁朦,在編碼數(shù)據(jù)的第2位得到映射表的下標(biāo)的低位(lsb)為1柒室,第18 位得到映射表的下標(biāo)高位(msb)為1,假如使用如下的映射表逗宜,則可以得到映射表下標(biāo)為(lsb, msb)=(1,1)雄右,對(duì)應(yīng)下標(biāo)為 -b
映射表
這里的映射表是一個(gè)全局?jǐn)?shù)據(jù),不會(huì)編碼到文件中纺讲。但是這個(gè)全局?jǐn)?shù)據(jù)是怎么來的擂仍,放在什么地方,沒有查到相關(guān)資料熬甚。不過因?yàn)榻獯a時(shí)要用逢渔,所以我估計(jì)是存放在紋理的某個(gè)全局區(qū)域
上面知道目標(biāo)像素位于子塊2,這里還需要從編碼數(shù)據(jù)的34-36獲得子塊2的修正表索引乡括,得到索引為(1,1,0)=6肃廓,根據(jù)上面的映射表,根據(jù)下標(biāo)(6, -b) 可以索引到像素的顏色差值為-106
- 計(jì)算目標(biāo)像素的最終顏色值
這里RGB三個(gè)分量的差值相同诲泌,目標(biāo)像素最終的顏色值為子塊2的基本顏色 + 目標(biāo)像素的顏色偏移值:
RGB_target = RGB2 + (-106, -106, -106) = (198 - 106, 54 - 106, 27 - 106)盲赊,修正后得到目標(biāo)顏色值 RGB_target = (92, 204, 177)
4. 編碼過程
其實(shí)從上面的解壓過程可以推測(cè)出編碼的過程
- 將圖劃分為4x4的像素塊,如果不夠4x4敷扫,則將這些像素填充在4x4塊的左上角哀蘑。
- 針對(duì)每個(gè)4x4的像素塊嘗試以下編碼,取解碼后和原像素差值最小的那種編碼作為結(jié)果呻澜。
(1)確定flipbit递礼,并計(jì)算兩個(gè)子塊的平均顏色值,這里我猜測(cè)是先將8個(gè)像素的R8G8B8取均值得到像素的平均值羹幸,然后將每個(gè)分量的后三位直接拋棄脊髓,得到R5G5B5
(2)根據(jù)兩個(gè)子塊顏色值的差值,確定diffbit栅受,根據(jù)上面得到的兩個(gè)子塊的 R5G5B5将硝,計(jì)算差值恭朗,如果差值在[-4, 3] 之間,說明差值可以用3位帶符號(hào)的二進(jìn)制數(shù)表示依疼,可以用differential模式痰腮,否則用 individual 模式
(3)枚舉不同的子塊索引,確定每個(gè)子塊使用映射表中的哪一組偏移值
(4)枚舉每個(gè)像素的映射下標(biāo)律罢,確定像素使用映射表中的哪一個(gè)偏移值
(5)針對(duì)第3步和第4步的枚舉膀值,可以得到很多組不同的編碼,將編碼結(jié)果解壓后和原始像素?cái)?shù)據(jù)對(duì)比误辑,取相差最小的一組編碼作為最終結(jié)果 - 將圖片中各個(gè)像素塊編碼合并
4. 遺留或未考查清楚的問題
- 映射表里的數(shù)據(jù)是怎么確定的沧踏?映射表數(shù)據(jù)存放在哪里?
- 取子塊平均色時(shí)巾钉,原本R8G8B8的數(shù)據(jù)是怎么壓縮成R5G5B5的翘狱?
是直接拋棄后三位嗎?
還是按照比例來壓縮?比如 8 位時(shí) 11111111 表示 256砰苍,5位時(shí) 111111(實(shí)際值是31) 表示256潦匈,原本8位時(shí)的值 x,則5位時(shí)的y值赚导,y= x * (31/256) 取整得到的5位二進(jìn)制表示茬缩?從解碼的補(bǔ)位過程看起來應(yīng)該是直接拋棄掉后三位。后三位的數(shù)據(jù)其實(shí)就損失了吼旧。
參考:
UI圖集壓縮優(yōu)化寒屯,以及對(duì)Dither和ETC1算法的深入了解
幾種主流貼圖壓縮算法的實(shí)現(xiàn)原理詳解
OES_compressed_ETC1_RGB8_texture