PNG 解碼是指將一張 PNG 圖片的二進制數據轉換成像素點數據 ImageData。html
PNG 的二進制數據有不少方式獲取。node
在瀏覽器中能夠獲得 ArrayBuffer 類型的二進制數據:git
fetch(url).then(res => res.arrayBuffer()).then(res => { console.log(arrayBuffer) });
在 Node.js 中,能夠獲得 Buffer 類型的二進制數據:github
console.log(fs.readFileSync(filename))
在微信小程序中能夠獲得 ArrayBuffer 類型的二進制數據:算法
wx.request({ url, responseType: 'arraybuffer', success: (res) => { console.log(res.data); }, });
ImageData.data 是一個 Uint8ClampedArray,其中以 R、G、B、A 的順序保存了像素點的數據,每四項表示一個像素點。小程序
Uint8ClampedArray 是一種 TypedArray,存儲了 0-255 的數據。其中 U 表示 unsigned,也就是無符號(都是正值)。8 表示 8 位,也就是其中的數據值不能超過 2 ^ 8 = 256。Clamped 表示若是數據值小於 0,則變成 0,若是大於 255,則變成 255。與之相似的 TypedArray 還有 Uint8Array,初始化的值若是不在 0-255 之間,則加或減 256,直到符合要求。還有 Int8Array,裏面的值能夠帶符號,也就是說能夠是負數。微信小程序
TypedArray 是 JavaScript 中用來存儲二進制數據的一種數據格式,相似於數組。可是 TypedArray 中存放的數據類型已經肯定,執行引擎很容易分配內存,因此 TypedArray 更快。在 Node.js 中,在 Int8Array 的基礎上實現了 Buffer,提供了二進制數據流的存儲和操做。api
PNG 的二進制數據能夠分爲 2 大部分:文件簽名(Signature)和數據塊(Chunks)。數組
Chunks 分爲 IHDR、PLTE、TRNS、GAMA、IDAT 和 IEND。瀏覽器
PNG 的文件簽名是 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
,文件頭部須要是這樣的數據。
在處理了 Signature 後,每次讀取 8 個字節的數據做爲 Chunk 標示,8 個字節中的前 4 個字節表示這個 Chunk 佔用的長度,單位是字節;後 4 個字節表示 Chunk 的類型。使用 Buffer 的 readUInt32BE
方法能夠輕鬆地拿到這些數據。
Chunk 之間可能有冗餘的內容,當 Chunk 沒有開始,遇到冗餘內容的時候,咱們須要跳過長度 + 4 個字節。
Chunks 一共分紅 6 個類型(括號中的是 Chunk 標示):IHDR(0x49484452)、PLTE(0x504c5445)、TRNS(0x74524e53)、GAMA(0x67414d41)、IDAT(0x49444154) 和 IEND(0x49454e44)。IHDR 必須是第一個 Chunk;IEND 必須是最後一個;IDAT 存放着壓縮過的圖像數據。
IHDR 中存放了圖片的寬度(width)、高度(height)、像素位數(depth)、顏色類型(colorType)、壓縮方式(compression)(只有 0)、過濾器類型(filter)(只有 0)、是不是漸進式(interlace)。
經過對 Chunks 的分段,咱們能夠拿到 IDAT 部分的數據。使用 zlib 的 inflate 方法能夠把壓縮過的數據解碼出來。解碼後的數據裏,每 5 個字節表示一個像素,其中第一個字節表示過濾器的類型。經過過濾器(filter)把數據還原,最後數據轉換成像素數據,就能夠獲得 ImageData 了。
保存 PNG 圖片的時候能夠選擇是否保存爲漸進式。漸進式的 PNG 圖片能夠在加載過程當中從模糊到清晰。
PNG 的漸進式加載採用了 Adam7 算法。將像素點從新排序,能夠作到先展現較不清晰的圖片,再展現較清晰的圖片。Adam7 算法一共有 7 步。
到第 7 步時能夠拿到所有像素信息,所以在解碼過程當中須要判斷漸進式的開關,並重排像素數據。
編碼過程是一個逆向的解碼。先寫入文件簽名,寫入 IHDR 數據(文件寬高等),寫入 GAMA 信息,將 ImageData 進行過濾(經過過濾能夠提高壓縮率),將過濾後的數據用 zlib 壓縮,做爲 IDAT 寫入,最後寫入 IEND。
源碼能夠參考 png,基於 pngjs 的同步源碼將 Node.js 的 zlib 替換成了瀏覽器端能夠運行的 pako。再添加 buffer 以來就能夠在瀏覽器上運行了。