Milo的编程
首发于Milo的编程

极简的 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"

void test_rgb(void) {
    unsigned char rgb[256 * 256 * 3], *p = rgb;
    unsigned x, y;
    FILE *fp = fopen("rgb.png", "wb");
    for (y = 0; y < 256; y++)
        for (x = 0; x < 256; x++) {
            *p++ = (unsigned char)x;    /* R */
            *p++ = (unsigned char)y;    /* G */
            *p++ = 128;                 /* B */
        }
    svpng(fp, 256, 256, rgb, 0);
    fclose(fp);
}

就会输出这个 rgb.png 文件:

这个函数的声明很简单,缺省配置下是这样的:

/*!
    \brief 以 PNG 格式存储 RGB/RGBA 影像
    \param out 输出流(缺省使用 FILE*)。
    \param w 影像宽度。(<16383)
    \param h 影像高度。
    \param img 影像像素数据,内容为 24 位 RGB 或 32 位 ARGB 格式。
    \param alpha 影像是否含有 alpha 通道。
*/
void svpng(FILE* out, unsigned w, unsigned h, const unsigned char* img, int alpha);
相信这样的函数时使对初学者而言,也极易使用。也不需要另外生成程序库,只要复制到项目便可使用。

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-32Adler-32 校验和(checksum)的生成。

编码实现如下(文字版请移玉步至 svpng.inc):

为了减少代码大小,使用宏去避免加入多个函数。另外,为了简化实现,把每一行像素写成一个 block,这样可能会浪费一点空间,但对于这函数而言也不是问题。

5. 结语

本文介绍了一个极简的 C 函数 svpng,方便在 C/C++ 中把图像存储成 PNG 文件,并简介了当中的实现。希望读者能利用此函数,进入计算机图形学之门。

编辑于 2017-04-25

文章被以下专栏收录