PNG 有损压缩省下的体积,大头不在 deflate

2 阅读8分钟

我做过三年视频编解码,脑子里一直有个默认。码率的大头由量化参数定,CABAC 这类熵编码只负责把剩下的冗余挤干净。它能挣一截,改不了量级。PNG 这边我一直没算过同一笔账:换个更狠的 deflate 实现到底能省多少。我想拿实际文件验一下:有损 PNG 压缩器压掉七八成的体积里,几成归量化,几成归 deflate 和过滤器?图片压缩不是我的本行,下面都是站在外面拆文件看到的。

样本是 12 张 PNG,全部来自 Wikimedia Commons。透明图 6 张,图标和插画各 3 张。它们都是 alpha 从 0 到 255 的真透明。照片类和截图各 3 张。照片那 3 张都存成了 RGBA,其中 2 张的 alpha 全是 255。它们等于背着一个没用的通道,这一点后面会影响账目。工具是 pngquant 3.0.3(--quality=65-80 --speed 4)、oxipng 10.2.1(-o 4 --strip safe)和 zopflipng(-y --filters=01234mepb)。这些工具我跑在一台 Apple M4 的 Mac 上,每样一次。画质没有评分,下面只谈体积。平时在浏览器里批量压图,我用的是图映 ImgIng imging.cn/ 的图片压缩,在本地处理。它内部怎么做的我不清楚,我理解大体也属于减色加重压这一路。这篇的数字全部出自上面三个命令行工具。

拆账要先定顺序

pngquant 量化完会自己做一遍 deflate。直接拿它的输出和原图比,量化和重压的功劳就搅在一起了。我拆成两个顺序各算一遍。顺序 B 先量化,再把 pngquant 的输出交给 oxipng。oxipng 多省下的部分记在 deflate 和过滤器头上。顺序 A 反过来,先用 oxipng --nx 只重压,颜色类型和位深都不动。接着放开 oxipng 的无损缩减,最后才量化。--nx 这个开关很关键。它把换过滤器和 deflate 参数这类操作,与 RGBA 改 RGB、改调色板这类操作分开了。不分开的话无损那一栏也是一笔糊涂账。两个顺序的终点是同一个文件,透明组都省 79.1%。区别只在功劳分给谁。

先量化时量化占了97%

先看顺序 B。6 张透明图合计 501,130 字节,pngquant 一步就省掉 77.0%。再过一遍 oxipng 只多省 2.1 个百分点。量化占了总节省的 97%。3 张照片 PNG 是 66.3% 加 4.0,量化占 94%。截图组是 68.2% 加 1.9。这跟视频那边的直觉对得上。deflate 在这里干的是收尾的活。

不过 2.1 个点只是相对原图显得小。照片组里 oxipng 在 pngquant 输出的基础上又省了 11.8%(694,735 → 612,968 字节)。可见 pngquant 写文件时 deflate 和过滤器并没有做满。

先做无损量化也占86%

97% 这个数得打个折看。顺序 B 让量化先动手,能省的它先吃掉。后面的 oxipng 当然只剩边角。这个比例天然偏向量化。顺序 A 公平一些,无损先把能拿的全拿走,量化只拿剩下的。透明组只重压省了 11.4%,无损缩减是 0.0。再量化又省 67.7 个点,量化占 86%。照片组差别就大了。只重压 18.3、无损缩减 6.5,合计 24.8。量化省 45.5,只占 65%。量化的份额不是一个数。它是 65% 到 97% 的区间,看你先算谁。我更愿意报区间。单报 97% 会让人以为 deflate 那边没事可做。照片组 24.8% 的无损空间说明不是这样。

量化先把原始字节砍到四分之一

量化一步为什么能省这么多?看 IDAT 解压之后的原始字节就明白了。PNG 的原始数据一行一行排,每行前面多 1 个字节记过滤器类型。解压后的大小只看宽高和每像素几个字节,跟图里画了什么无关。我写了几行,把两张代表图的数算出来:

def raw_bytes(w, h, bytes_per_px):
    # 每行前面多 1 字节,记这一行用的过滤器类型
    return h * (w * bytes_per_px + 1)

for name, w, h, bpp in [
    ("icon RGBA", 512, 512, 4),
    ("icon PLTE", 512, 512, 1),
    ("photo RGBA", 904, 901, 4),
    ("photo RGB", 904, 901, 3),
    ("photo PLTE", 904, 901, 1),
]:
    print(f"{name:<12}{raw_bytes(w, h, bpp):>12,}")
icon RGBA      1,049,088
icon PLTE        262,656
photo RGBA     3,258,917
photo RGB      2,444,413
photo PLTE       815,405

icon 是 512×512 的透明图标 Afro Mobile Icon,photo 是 904×901 的照片 PNG Steve Selvin。算出来的数和我拆块读到的解压后字节一个不差。图标从 RGBA 的 1,049,088 降到调色板的 262,656。照片从 3,258,917 降到 815,405。两张都只剩四分之一左右。deflate 还没开始干活,喂给它的数据已经少了四分之三。

我猜还有第二层。调色板索引最多只有 256 种取值,比 RGBA 四个字节的组合好压得多。Afro 量化后只剩 23 色,IDAT 从 23,294 字节掉到 4,015。这一层我没拆开单独量,只能算推断。

透明图的无损余量只剩重压

再回头看顺序 A 里那个 0.0。透明图 6 张都是 alpha 什么档都有的真透明。oxipng 想把 RGBA 降成 RGB 或调色板也一张都降不下来。无损这条路只剩换过滤器和重压 deflate。两个无损工具分别只拿到 11.4%(oxipng)和 14.4%(zopflipng)。

照片组那 6.5 个点的来路很具体。Steve Selvin 的 alpha 全是 255,oxipng 把它降成了 RGB。原始字节从 3,258,917 变成 2,444,413,就是上面输出里 photo RGB 那一行。Alekszej Fedoricsev 那张灰度照片实际只用了 222 种颜色。oxipng 把它无损转成 222 色调色板。文件从 407,150 字节变成 167,225,少了 58.9%。整个过程一个像素都没改。照片组只有 3 张,其中 2 张背着空 alpha。我不敢把 24.8% 当成照片 PNG 的普遍水平。能说的只有一句:没用的 alpha 通道是照片 PNG 多占体积的一个来源。

量化之后差在留了几种颜色

量化既然是大头,不同有损工具之间的差别也主要出在这一步留了多少颜色。同一张 Afro 图标在 pngquant 65–80 档留 23 色,4,171 字节。不设 quality 的默认档留 256 色,8,079 字节。差不多翻了一倍。6 张透明图上默认档是 65–80 档的 1.36 到 1.94 倍。照片和截图上两档差得少,The Book 那张截图两档都到了 256 色。

iLoveIMG 的 3 张 PNG 输出也是调色板,Afro 23 色、Anime girl 32 色。体积和 pngquant 再过 oxipng 的结果很接近,像素却不相同。它内部用了什么我不知道。TinyPNG 官网说它靠减少颜色数来压,它的输出不在这次拆的文件里。

还有两处我没想通。调色板图上 oxipng 默认会重排调色板、挑混合过滤器。Alekszej 因此从 131,963 降到 100,267。Steve Selvin 却反过来,默认档 386,812 比 --nx 的 380,655 还大。单张有正有负,我总结不出规律。另一处是 zopflipng 单独压原图的数字和 2026-10-29 那次对不上。参数没变。照片组上次 1,624,722 字节,这次 1,526,978。原因我还没查,只能当单次结果看。

怎么查一个输出还剩多少余量

拆完能落到操作上的有两点。第一点是顺序固定成先量化后无损。反过来做的话,前面那步无损会被量化重写掉。2026-10-29 那次照片组先 oxipng 再 pngquant 是 694,253 字节,只跑 pngquant 是 694,735,等于白做。第二点是拿到任何一个有损 PNG 输出,都可以再跑一遍 oxipng -o 4 --strip safe 看 Total saved 那一行。省下的字节就是那个工具在 deflate、过滤器和调色板排序上没做满的部分。iLoveIMG 那 3 张跑出来都是 0 字节。想知道量化下手有多重,就读 IHDR 的 color_type(6 是 RGBA,3 是调色板)和 PLTE 的色数。色数比文件体积更能说明问题。手边随便拿一张有损输出就能核对一遍。

文中点名的样本:Afro Mobile Icon(Ben plc / 公有领域)、Anime girl publicdomainq(oksmith / CC0)、Steve Selvin(UC Berkeley Public Health / CC BY 4.0)、Alekszej Fedoricsev(Javelin Bayraktar / CC0)、2020-06-29 Screenshot The Book(Stefan Kühn / CC0)。其余 7 张:Adaptability icon(Unknown author / CC0)、Blue star icon stylized(PrazerCambraia / CC0)、Adenosine triphosphate ball-and-stick(FreeCliparts / CC0)、Apple cartoon with transparent background(Toby8991 / CC0)、Directiva junto a presidente APPA(JaimeRuix / CC BY-SA 4.0)、2011 Talend MDM studio(Copyleft / CC BY-SA 3.0)、2019-06-19 OpenProject by Benoit Soubeyran(Mazuritz / CC BY-SA 4.0)。