2007年,我写过一篇文章介绍使用PNGOUT来产生非常小的PNG图片。我仍然经常提起这个话题,因为8年后的今天,我随便在网上看到的PNG图片很有可能是未经优化的。
举个例子,来看看pbfcomics.com上最近发布的这张卡通图:
把图片从网站直接保存下来,我们发现,这幅漫画是800 x 1412、32位的PNG图像,文件大小为671012字节。让我们把它保存为几种不同的(无损)格式,来看一看这幅图像要占用多少空间:
- BMP,24位 3,388,854
- BMP,8位 1,130,678
- GIF,8位,无抖动 147,290
- GIF,8位,最大抖动 283,162
- PNG,32位 671,012
PNG表现不错,因为它像GIF一样采用了压缩算法;与GIF不同的是,你的图像位深不会被限制在令人不快的8位(256色)。现在,我们用PNGOUT(点击这里下载)来处理一下,看看情况会怎样?
- 原始的PNG 671,012
- PNGOUT 623,859 7%
随便从哪里拿来一个PNG,用PNGOUT处理一下,你很可能获得大约10%的文件大小缩减,也许还能更多。记住,这是无损压缩!输出的图像质量是完全相同的。在网上传输的文件会变得更小;而且文件越小,解压缩就越快。同学们,这是免费带宽啊!还有比这更好的事吗?
嗯,还真有……
2013年,谷歌推出了一种完全向后兼容新算法,他们称之为Zopfli:
Zopfli产生的输出通常比zlib在最大压缩比的情况下还要小3~8%。我们相信,Zopfli代表了Deflate压缩算法的当前工艺水平。为了可移植性,Zopfli用C语言写成。这个库只支持压缩;现有的软件都能对它解压缩。Zopfli与gzip、Zip、PNG、HTTP请求等使用的压缩是位流兼容的。
非常抱歉,这个情况我了解得太晚了!还是让我们来验证一下他们的大胆狂言吧。拿上面这幅图片来试验,会发生什么呢?
- 原始的PNG 671,012
- PNGOUT 623,859 7%
- ZopfliPNG 585,117 13%
看起来不错啊!不过,那只是一张图片。在discourse.org上,我们都喜欢用表情图片。让我们试试第一版表情Emoji One——那是一整套64 x 64、32位的842个PNG文件:
- 原始的PNG 2,328,243
- PNGOUT 1,969,973 15%
- ZopfliPNG 1,698,322 27%
哇,此等好事,我岂能错过!
在我的测试中,Zopfli处理过的PNG图像总能比PNGOUT小3~8%,尽管PNGOUT已经非常强大了——这真是令人难以置信的成就!而且,任何使用标准gzip压缩的资源都能从Zopfli获益,比如jQuery:
我们再来看一看各种标准的压缩测试:
测试素材 | gzip -9 | kzip | Zopfli |
Alexa 10k | 128mb | 125mb | 124mb |
1017kb | 979kb | 975kb | |
731kb | 674kb | 670kb | |
36mb | 35mb | 35mb |
(奇怪得很,我之前都没有听说过kzip。事后证明,那又是我们的老朋友Ken Silverman的杰作。他也许使用了跟PNGOUT工具一样的压缩技巧。)
不过,有一个问题,因为问题总是有的——它同时也慢80倍。不,我没有搞错。你也没看错!
- gzip -9 5.6s
- 7zip mm=Deflate mx=9 128s
- Kzip 336s
- Zopfli 454s
Gzip压缩一般比上表里的速度还要快一点,因为第9级是比较慢的:
| 时间 | 大小 |
gzip -1 | 11.5s | 40.6% |
gzip -2 | 12.0s | 39.9% |
gzip -3 | 13.7s | 39.3% |
gzip -4 | 15.1s | 38.2% |
gzip -5 | 18.4s | 37.5% |
gzip -6 | 24.5s | 37.2% |
gzip -7 | 29.4s | 37.1% |
gzip -8 | 45.5s | 37.1% |
gzip -9 | 66.9s | 37.0% |
你看吧,为了获得gzip -7和gzip -9之间那区区0.1%的压缩比差异,是否值得花上双倍的CPU时间呢?再说开一点,这也是为什么几乎所有的压缩工具的“极端”压缩级别或模式通常都不靠谱。你从算法悬崖边上快速掉了下去,因此你须待在曲线的中间或者最理想的位置,这常常就是缺省的压缩级别。他们选择那些缺省参数总是有原因的。
PNGOUT也不快,别急着用它;想象一下为了压缩一幅图像或一个文件会慢80倍的(这还是好的情况!),这肯定让人望而却步。如果只是处理小图片,你可能注意不到这个问题。但如果PNG比较大,你基本上可以出去吃一个三明治;或者如果你有一个多核CPU,处理完PNG的时间够你吃4~16个三明治。这也是为什么使用Zopfli来处理用户上传的图片可能是不明智的,因为第一台尝试用Zopfli处理10k x 10k PNG图片的服务器会掉入绝望的深渊。
然而,请记住解压缩的速度是一样的,并且绝对安全。这意味着你可能只想用Zopfli处理一些可以预处理的资源,也就是说,压缩一次,然后用于几百万次的下载场景——这跟你的用户上传一些PNG图片,不管你对这些图片做何等的优化,可能最多也只会被浏览几百次或几千次的应用场景是不同的。
举个例子吧。在discourse.org,我们有一个默认头像渲染器,可以为用户基于他们的用户名里的第一个字母产生一个很好看的PNG格式的头像,并且根据用户名的哈希值选定一种颜色。噢,对了,我们还用了很漂亮的来自谷歌的开源字体Roboto。
我们在输出头像图片的优化上花费了很多时间,因为这些头像会被使用几百万次。基于这些约束条件:
- 10个数字
- 26个字母
- 大约256种颜色
- 5种大小
预先把这些头像生成出来就是45000个文件,也并不是毫无道理。我们还有一个所有discourse.org实例都能访问的中央https CDN,需要的话也可以用于部署这些头像图片,这样可以进一步减小负载、提高缓存命中率。
因为这些图像都是单色的,为了节省空间,我把调色板降低到了8位(实际上用了128种颜色)。当然,我用PNGOUT优化处理了这些文件。它们差不多已经小到极限了。当我对这些头像文件执行Zopfli时,我超级兴奋,因为我期望看到3~8%的文件大小缩减。不过,在处理命令执行完之后,我看到的是……分别省了1字节、5字节、2字节……我有点忧伤!
(没错,生成“有损的”PNG图像在技术上是可行的,尽管有点奇怪,但我想那样有悖于PNG的精神——它就是为无损图像而生的!如果你想要有损的图像,那就用JPEG或其他格式吧。)
Zopfli的最大特色是,假设你不介意极高的CPU要求,它就是“用完就丢”的一次性优化步骤,你可以应用在任何地方,而且不会受到任何伤害。好吧,除了可能会烧掉很多空闲的CPU周期。
如果你参与的项目需要提供压缩素材,仔细看一看Zopfli吧。它不是银弹——听了上述建议之后,你拿自己的文件来测试看看——但对于干我们这行的人来说,它真正是给我们送来了免费带宽啊!