最有效地使用PNG之姊妹篇:Zopfli优化
原文作者:Jeff Atwood
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 |
Calgary | 1017kb | 979kb | 975kb |
Canterbury | 731kb | 674kb | 670kb |
enwik8 | 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吧。它不是银弹——听了上述建议之后,你拿自己的文件来测试看看——但对于干我们这行的人来说,它真正是给我们送来了免费带宽啊!
- 2楼lanzhengpeng2昨天 11:52
- http://blog.****.net/happydeer/article/details/50489567?ref=myreadn看了您这文章后,正好我自己写了一个TinyAssets for cocos2d-x的工具。也尝试压缩了一下文中提到的图片。n转索引色后,大小是118KB。不转索引色,大小是261KB。这两个都属于有损压缩,png格式。如果敢兴趣,请点一下链接查看。nhttp://pan.baidu.com/s/1gdWQUf5
- Re: happydeer3小时前
- 回复lanzhengpeng2n谢谢分享~ 有损压缩不在本文讨论范围内。
- Re: lanzhengpeng23小时前
- 回复happydeern对于数据编辑,必须用无损格式。但对于网站等图片的发布,其实有损与否并不重要,重要的是损失的质量要在可接受范围内,并尽量保证图片数据尽可能小。njpeg缺陷在于不支持透明通道,git只有一位透明通道。webp支持不够广泛,带透明的图也不支持有损压缩。jpegxr更不流行,只要是微软出的,大家都不会鸟他。目前看起来前途最好的是bpg,可惜才出来,都要支持还需要很长的时间,而且,算法版权也是逃不开的问题。n所以,png的压缩,以及有损压缩,是一个广泛的研究话题。我折腾过一段时间的png压缩后,在Zopfli这里碰到了极限了-----还真没有比他更高压缩比的无损压缩工具了。n后来www.tinypng.com给了我提示和灵感,在8位索引色的基础上,做了12位索引色,以及相关的色彩量化算法的研究,最终通过大量实验,锁定了12位索引色。12位索引色通常能比32位真彩色的png减小大约一半的文件尺寸,大部分情况下,图片质量损失极小。毕竟4096种色彩也算很丰富了,配合抖动,视觉上不容易看出图片有损失。n做这方面对比和研究的人太少,看到楼主这么有兴致,一时忍不住。所以尽管是有损压缩,有违楼主的本意。
- 1楼u010850027昨天 10:41
- 谢谢小伙伴的分享,学习了`(*∩_∩*)′