oxipng 是 Rust 写的 PNG 无损压缩器,命令行工具,一个 exe 拖进 PATH 就能跑。它不动像素,做的是重排压缩块、试不同的行过滤组合、删掉多余的元数据块,解码出来的图和原图逐字节一致。同一张网页截图跑一遍能省 24.3%,整批跑完只用 23.3 秒。

要不要装它,就看两件事:一张网页截图能瘦多少,一批图跑完要多久。下面这些都是在 Windows 上跑出来的记录,素材直接用站里攒的 135 张真实截图。

解压出一个 exe,没有安装过程

Release 页给的是 windows-msvc 压缩包,解出来只有 oxipng.exe,不带运行库也不写注册表。拷到任意目录,命令行给全路径就能用,或者把目录加进 PATH。

oxipng --version
oxipng 10.2.1

oxipng -o 4 --strip safe shot.png

第二条是日常写法。-o 4 是优化等级,--strip safe 去掉不影响显示的元数据块,保留让颜色正确的部分。加上 -r 可以直接喂目录,它自己找扩展名是 png 和 apng 的文件。

四档预设,收益在 o4 就到底了

拿同一张网页截图当样本,原始 252,197 字节,每档跑一遍记体积和耗时:

预设压完体积(字节)压缩率(%)耗时(秒)比 o2 多省(字节)
-o 021570614.50.16-24753
-o 2(默认)19095924.30.570
-o 418925725.01.071702
-o 618925725.01.911702
-o max18925725.01.981702

o4 之后体积停在 189,257 字节,往上只加时间不加压缩率。o0 只用 0.16 秒,压缩率 14.5%;o2 用 0.57 秒,压缩率 24.3%;o4 用 1.07 秒,压缩率 25.0%;o6 用 1.91 秒,o max 用 1.98 秒,这两档的输出跟 o4 完全一样,一个字节都没再少。

看帮助文档能对上:0 到 4 这几档都带 --fast,靠多试几种过滤组合取胜;更高的档位才开 --brute-level 暴力搜索,而这张图的块结构早就没得可淘了。

135 张截图整批跑

oxipng -o 2 --strip safe .\shots\*.png
Files processed: 135/135
Input size: 33.4 MiB (35020545 bytes)
Output size: 27.9 MiB (29273044 bytes)
Total saved: 5.48 MiB (16.4%)
用时 23.3 秒,平均每张 0.17 秒

一次跑完 135 张,省下 5.48 MB,整体降了 16.4%,用时 23.3 秒:

指标数值
文件数(张)135
压缩前(字节)35020545
压缩后(字节)29273044
总体积下降(%)16.4
总耗时(秒)23.3
平均每张(秒)0.17

单张能到 25%,整批只有 16.4%,差距来自图本身。界面截图有大片纯色和规则线条,PNG 的行过滤本来就压得动,可挤的水分不多;噪点多、颜色杂的图几乎压不动,那几张拉低了平均。

无损是可以自己验的

压完随手抽 15 张,把原图和结果都转成 RGBA 再比字节流:

from PIL import Image
a = Image.open("before.png").convert("RGBA")
b = Image.open("after.png").convert("RGBA")
print(a.size == b.size, a.tobytes() == b.tobytes())
# True True

抽检 15 张:像素一致 15 张,不一致 0 张

这里有个细节容易误会。原图是 RGB,压完再打开可能显示成 P 模式:颜色数不足 256 的图,oxipng 会改成索引色存储,这仍然是无损的,只是存放方式变了。直接拿 mode 做比较会判成不一致,先把两边都 convert("RGBA") 才对得上。

线程数加不出速度

同一批 24 张截图,改线程数各跑一遍,输出体积完全一样,耗时:

线程参数24 张耗时(秒)单张耗时(毫秒)
默认(全核)11.101021
-j 111.161054
-j 411.171021
-j 811.251028

从 1 线程调到 8 线程,24 张的耗时在 11.10 秒到 11.25 秒之间晃;单张是 1054 毫秒对 1028 毫秒,差的那点在测量噪声以内。deflate 的熵编码本身是串行的,多给核心也用不上,-j 只能加速被拆开的块尝试。

所以批量压缩的时间主要取决于图有多少张、每张多大,换成核心更多的机器也不会更快。一天要过几千张图,就按每张 0.17 秒估时间。

这几种输入它会当场退出

oxipng -o 2 photo.jpg
photo.jpg: Invalid header detected; Not a PNG file
Files processed: 0/1

oxipng -o 2 missing.png
missing.png: Failed to read from missing.png: 系统找不到指定的文件。 (os error 2)

oxipng -o max already.png
Files processed: 1/1   189257 -> 189257  (0 字节改动)

它只认 PNG。如果喂一张 JPEG,程序在文件头就报错停下,输出 Invalid header detected,退出码 1(exit code 1),整批处理算失败。如果路径写错,报的是系统的 os error 2,不是它自己的错误码,区别只在输出文字上。

如果图已经压到极限,再跑一次体积不变,退出码却仍然是 0,脚本一旦靠退出码判断「有没有真的变小」就会失效,得自己比前后体积。带透明通道的图另有坑:一旦加上 --strip all,颜色配置会被一起删掉,浏览器里可能显示成偏色,日常用 --strip safe 更稳。

什么图值得过一遍

截图、图标、界面素材。这类图色块规整,一张 252,197 字节的截图能稳定瘦到 189,257 字节,整站图片库过一遍省下的量很实在。

照片转的 PNG。压缩率常年贴着零,噪点让每个像素都不同,行过滤几乎没有发挥空间,换成 WebP 或 AVIF 才是正解。这类图不值得花 1.07 秒去过一遍 oxipng。

需要保留完整元数据的图。摄影素材带 EXIF、带 ICC、带 XMP,--strip safe 会删掉一部分,要全都留着就别加这个参数,压缩率会掉一截。

还没测的三项:APNG 逐帧一致性 / 16 位色深压缩率 / 与 pngquant 的体积对比。前两项要专门的素材,第三项会把话题带到有损压缩上。