極簡的 PNG 編碼函數 svpng()
1. 緣起
許多時候,學習 C/C++ 語言也只看到黑白的控制台輸出,總覺得有點乏味。
(題圖 Photo by Denis Bayer)
學習計算機圖形,可以嘗試用不同演算法生成有趣的圖形,例如在《如何用 C 語言畫「心形」》(更新4)中,渲染了一個三維的心形隱函數,並以 portable pixmap format(PPM,一種 Netpbm)圖片格式。
PPM 是一個極簡單的圖片格式,但問題是,很少人知道這個格式,也很少軟體可以讀取這種格式。
2. PNG
相對地,PNG(Portable Network Graphics)就是一個廣為人知的圖片格式。如果可以把影像直接儲存成 PNG,不是更理想么?
然而,在 C/C++ 中寫入 PNG 一般需要鏈接一些程序庫,例如 PNG 的標準參考程序庫是 libpng。它很強大,支持 PNG 所有功能,但對於初學者而言,配置、編譯並學習如何使用這些程序庫,可能已足夠打消動手的念頭。
可以簡單一點么?
3. svpng
為此,我在周末嘗試寫一個極簡的 C 函數 Github miloyip/svpng(save PNG 的縮寫),它僅能寫入 24-bit RGB 或 32-bit RGBA、無壓縮的 PNG。它只有一個 32 行代碼的函數。
用法如下:
#include "svpng.inc"nnvoid test_rgb(void) {n unsigned char rgb[256 * 256 * 3], *p = rgb;n unsigned x, y;n FILE *fp = fopen("rgb.png", "wb");n for (y = 0; y < 256; y++)n for (x = 0; x < 256; x++) {n *p++ = (unsigned char)x; /* R */n *p++ = (unsigned char)y; /* G */n *p++ = 128; /* B */n }n svpng(fp, 256, 256, rgb, 0);n fclose(fp);n}n
就會輸出這個 rgb.png 文件:
這個函數的聲明很簡單,預設配置下是這樣的:
/*!n brief 以 PNG 格式存儲 RGB/RGBA 影像n param out 輸出流(預設使用 FILE*)。n param w 影像寬度。(<16383)n param h 影像高度。n param img 影像像素數據,內容為 24 位 RGB 或 32 位 ARGB 格式。n param alpha 影像是否含有 alpha 通道。n*/nvoid svpng(FILE* out, unsigned w, unsigned h, const unsigned char* img, int alpha);n
相信這樣的函數時使對初學者而言,也極易使用。也不需要另外生成程序庫,只要複製到項目便可使用。
4. 實現
這裡簡單介紹實現要點,對此沒興趣的讀者也可略過。
根據 Portable Network Graphics (PNG) Specification (Second Edition) ,PNG 文件由多個 chunk 組成。每個 chunk 的類型以 4 個字元表示。最基本的 PNG 文件內容是:
- 8 位元組 magic number:用於識別 PNG 格式
- IHDR(Image Header) chunk:描述影像的維度、色彩深度、色彩格式、壓縮類型等
- IDAT(Image Data)chunk:存儲影像的像素數據
- IEND(Image End)chunk:PNG數據流結束
每個 chunk 的結構是:
- chunk 內容長度(4 位元組)
- chunk 類型(4 位元組)
- chunk 內容
- chunk 的 CRC(包括類型和內容)
PNG 里的數據是以大端(big endian)編碼的,但在 IDAT 中,每個 block 的長度則以小端存儲。另外,實現的難點之一,是要同時實現 CRC-32 及 Adler-32 校驗和(checksum)的生成。
編碼實現如下(文字版請移玉步至 svpng.inc):

為了減少代碼大小,使用宏去避免加入多個函數。另外,為了簡化實現,把每一行像素寫成一個 block,這樣可能會浪費一點空間,但對於這函數而言也不是問題。
5. 結語
本文介紹了一個極簡的 C 函數 svpng,方便在 C/C++ 中把圖像存儲成 PNG 文件,並簡介了當中的實現。希望讀者能利用此函數,進入計算機圖形學之門。
推薦閱讀:
※幻影坦克架構指南(一)
※計算機輔助設計與製造CAE/CAM目前有哪些技術問題需要解決?
※【《Real-Time Rendering 3rd》 提煉總結】(八) 第九章 · 全局光照:光線追蹤、路徑追蹤與GI技術進化編年史
※如何渲染分子結構?
※【UnrealEngine4】距離場的使用技巧與應用


