QOI 圖像格式 -- 理論 & 實現(xiàn)
QOI (The?Quite OK Image Format) 是一種新的無損圖像壓縮方式,? 它在保持壓縮率與 PNG 相近的同時 (比 PNG 大 ~35%),? 編碼速度達到了 PNG 的 20~50 倍,? 而解碼速度也有 PNG 的 3~4 倍,? 并且它極簡的編碼解碼方式也是一個極大的亮點.? 可以在 [QOI 的主頁](https://qoiformat.org/) 上找到更多信息.
下面就講述 QOI 的工作原理并使用 Julia 實現(xiàn) QOI 的編碼解碼.

QOI 的圖像格式
QOI 圖像只能儲存 24 位 RGB 或 32 位?RGBA 格式的圖像,? 對于其他顏色格式的圖像 (64 位 RGBA 或 HSV 等) 可以先轉(zhuǎn)為合適的格式后再編碼儲存,? 但這種轉(zhuǎn)換并不是無損的.
QOI 圖像文件以 `.qoi` 為文件后綴,? 文件的數(shù)據(jù)內(nèi)分為 3 個部分:? 文件頭,? 數(shù)據(jù)塊 和 結(jié)束比特串.? 其中結(jié)束串由 7 個 0 和 1 個 1 (共 8 個字節(jié))組成,? 并且結(jié)束串對圖像解碼沒有影響.
文件頭由 14 個字節(jié)構(gòu)成:? 第 0~3?字節(jié)為 QOI 圖像的標識符 "qoif",? 解碼器由這 4 字節(jié)識別是否為 QOI 編碼;? 第 4~7 字節(jié)為圖像的寬度,? 是一個以大端儲存的 uint32;? 第 8~11 字節(jié)為圖像高度,? 同樣是大端 uint32;? 第 12 字節(jié)聲明圖像的顏色通道,? 3 表示為 RGB,? 4 為 RGBA;? 第 13 字節(jié)聲明圖像的顏色空間,? 0 表示 sRGB,? 1 表示線性 RGB (無論顏色空間,? 透明通道 alpha 都是線性的),? 但說實話目前來說并沒有看到不同的顏色空間有什么不一樣的地方.? 文件頭僅作為對圖像大小和格式的聲明,? 并不會影響數(shù)據(jù)塊的編碼.

QOI 的圖像編碼
在進行編碼時,? 圖像像素排列為行優(yōu)先的一維數(shù)組,? 數(shù)組索引如下圖所示 (圖像的左上角索引為 0)

QOI 里對像素編碼一共 6?種方式:? QOI_OP_RGB, QOI_OP_RGBA, QOI_OP_DIFF, QOI_OP_LUMA, QOI_OP_RUN 和 QOI_OP_INDEX.? 圖像編碼是逐像素進行的,? 通過對比當前像素與上一個像素的顏色值選擇合適的編碼方式.? 當處理第一個像素時 (index = 0),? 使用?(r?= 0, g?= 0, b?= 0, a?= 255) 當作上一個像素的顏色值.? 下面來逐個介紹編碼方式:
QOI_OP_RGB & QOI_OP_RGBA
這個編碼方式是直接將當前像素的顏色值寫入文件內(nèi),? 并不會進行壓縮編碼.? 為了不讓顏色值與其他編碼產(chǎn)生的數(shù)據(jù)弄混,? 還要在顏色值前放 1 字節(jié)的標簽:? 0xFE (RGB)?或 0xFF (RGBA).? 這種編碼的壓縮率為 133% (RGB) 或 125% (RGBA)


QOI_OP_DIFF
如果當前像素與上一個像素的顏色值差異非常小的時候,? 當前像素可以編碼為 1 字節(jié).? 記 dr, dg, db, da 為當前像素與上一個像素顏色值的差異 (RGBA) (dr = 當前 r - 上一個 r),? 當 da = 0,? 并且 dr, dg, db 都在 -2~1 內(nèi)時進行編碼?(-2~1 在?+2 后為 0~3,? 剛好可以寫進 2 比特),? 壓縮率為 33% (RGB) 或 25% (RGBA)

QOI_OP_LUMA
與 QOI_OP_DIFF 類似,? 但這個編碼方式允許顏色差異較大,? 并且編碼后為?2?個字節(jié).? 當滿足 da = 0,? dg 在?-32~31 內(nèi),? dr-dg 和 db-dg?在?-8~7 內(nèi)時編碼為,??(選擇 dg 為主要變化的顏色是因為臨近像素的色調(diào)變化可能會比亮度變化要小,? 并且綠色通道對亮度貢獻是最大的)? 壓縮率為 67% (RGB) 或 50% (RGBA)

QOI_OP_RUN
如果當前像素與上一個像素的顏色值完全相等時,? 意味著有可能后面接著的像素顏色值也相等,? 那么這時候僅需要記錄這串相同顏色值的像素的長度即可.? 以當前像素開始往后數(shù),? 記顏色相同的像素個數(shù)為 run,? 為了保證編碼后只得出 1 字節(jié),? run 應(yīng)該在 1~62 內(nèi),? 當實際上 run 大于 62 時,? 應(yīng)該進行多次?QOI_OP_RUN 編碼.? (如果 run 取 63, 64 編碼為 0xFE, 0xFF,? 這與 QOI_OP_RGB(A) 的標簽相同,? 會導(dǎo)致解碼器錯誤).? 壓縮率為 ≤33% (RGB) 或 ≤25% (RGBA)

QOI_OP_INDEX
在 QOI 編碼時,? 除了兩個像素之間的比較外,? 還有 1 個長度為 64 的數(shù)組 running array,? 這個數(shù)組的全部元素初始化為 (r = 0, g = 0, b = 0, a = 0).? 在歷遍像素數(shù)組時,? 計算上一個像素的顏色值在 running array 里的索引 index_position?= (3r + 5g + 7b + 11a) % 64 (RGB 顏色的 a 為 255),? 然后在數(shù)組上的這個位置寫入顏色值.? 如果當前像素的顏色值在 running array 上也存在時,? 則可以使用相應(yīng)的索引表示顏色 (索引的計算方式確保在相同顏色的索引也是一樣的,? 但索引相同不一定顏色相同).? 壓縮率為?33% (RGB) 或 25% (RGBA)

-
QOI 圖像格式對編碼方式的選取并沒有強行要求,? 就算全部像素都是用 QOI_OP_RGB(A)?編碼保存也是可以的,? 但這就達不到壓縮的目的了.? 上面每種編碼方式都給出了壓縮率,? 所以編碼時應(yīng)該盡可能選取壓縮率小的編碼方式:? QOI_OP_RUN ≤?QOI_OP_INDEX = QOI_OP_DIFF < QOI_OP_LUMA < QOI_OP_RGB(A),? 盡管?QOI_OP_INDEX 和 QOI_OP_DIFF 有相同的壓縮率,? 但是前者的計算量稍小于后者,? 所以兩者都可時,? 選擇前者會更好.

QOI 的圖像解碼
解碼也就是編碼的逆過程:? 讀取文件頭判斷是否為 QOI 圖像 (頭 4 字節(jié)應(yīng)為 "qoif"),? 獲得圖像的寬度,? 高度,?顏色通道 和 顏色空間后,? 即可以對數(shù)據(jù)塊進行解碼.? 解碼就是逐字節(jié)讀取,? 判斷標簽 (字節(jié)里的高 2 位),??然后根據(jù)標簽選擇不同的解碼方式.? (注意,? QOI_OP_RGB(A) 與?QOI_OP_RUN 的高 2 位都是 11)

下面就是個人在 Julia 的實現(xiàn),? 對不熟悉 Julia 的人說一下,? 里面用到了大量的 點語法,? 點語法就是逐元素施加相同的操作,? 舉個例子:? floor.([r, g, b]?.+ 8) 展開為 [floor(r+8), floor(g+8), floor(b+8)].? 還有?Julia 的索引起始為 1,? 而不是大部分語言那樣的 0,? 如果要指指點點的話,? 我們數(shù)學人真的是給你們計算機人丟臉了.
另外不得不說的就是,? 如果真的很在乎計算速度的話可以出門右轉(zhuǎn) C/C++,? 盡管 Julia 可以做到原生 C/C++ 的速度,? 但可能會造成代碼篇幅過長,? 所以我想說的是,? 下面的實現(xiàn)僅確保"足夠"簡短,? 不確保計算速度.
在代碼里出現(xiàn)了宏 @inbounds,? 這個宏會取消當前代碼塊里所有的索引邊界檢查以提升效率,? 但這時出現(xiàn)越界的話,? 輕則程序閃退,? 重則系統(tǒng)崩潰.? 所以自己寫代碼的時候,? 除非有足夠的信心 (理論上確保或做過足夠多的測試),? @inbounds 是不推薦寫上的.
在開始正片前,? 首先引入官方包 ColorTypes 和寫幾個有用的工具函數(shù)

因為 Julia 支持的顏色格式多種多樣,? 所以這里直接寫了一個 _toT4 函數(shù)把顏色轉(zhuǎn)為 RGBA 元組.? 下面是編碼器的大體結(jié)構(gòu):? 判斷參數(shù)合理, 打開文件,?寫入文件頭,?主循環(huán) 和 寫入結(jié)束串:

這里需要注意 Julia 是列優(yōu)先的,? 所以需要 transpose 圖像.? encoding 部分為:

當寫好編碼器后,? 就可以測試了.? 目前來說,? qoi 圖像還沒廣泛普及,? 眾多圖片查看器都未適配,? 一個挺好的[在線 qoi 查看器](https://floooh.github.io/qoiview/qoiview.html).? 在 [QOI 官方 gayhub 頁面](https://github.com/phoboslab/qoi) 可以找到更多信息.
下面是相應(yīng)的解碼器主體

decoding 部分

-

摸了.
日常推澀弔圖群:??274767696
封面 pid:?99017205