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吧。它不是銀彈——聽了上述建議之後,你拿自己的文件來測試看看——但對於幹我們這行的人來說,它真正是給我們送來了免費帶寬啊!