1.背景
瀏覽器環(huán)境下,使用最多的圖片格式有 JPEG、PNG、GIF。其中,JPEG 適合色彩復(fù)雜的圖片,PNG 適合色彩單一或者需要透明的圖片,GIF 通常用于動(dòng)圖?,F(xiàn)有的圖片格式體積較大。

從瀑布圖可見,圖片的加載在整個(gè)頁面加載時(shí)間中占據(jù)了很大的比重,個(gè)別 JPEG 圖片甚至達(dá) 200 多 KB,這在移動(dòng)端環(huán)境下非常影響用戶體驗(yàn)。
2.介紹
WebP 是一個(gè)現(xiàn)代的圖片格式,用于在 web 上提供更好的有損和無損壓縮圖片。它能夠在肉眼觀看幾乎一樣的情況下,對(duì)圖片體積進(jìn)行大幅壓縮。在將一張 1.3MB 的 JPG 有損壓縮為 WebP 后,大小僅為483KB。你能分辨出下面兩張圖片有什么差別嗎?


我們來測(cè)試一下 JPG 和 PNG 轉(zhuǎn)成 WebP 后,實(shí)際體積大概減少多少。

根據(jù)測(cè)試結(jié)果可見,對(duì) PNG 進(jìn)行 WebP 無損壓縮后,體積減少了 31%,這與 Google 宣稱的 26% 大體吻合。WebP 有損壓縮的減少比例則更大,將圖片質(zhì)量降低到原來的 75% 后,減少體積達(dá) 90% 左右。值得注意的是,將 JPG 進(jìn)行 WebP 無損壓縮后,圖片大小反而增加了 66%。在實(shí)際應(yīng)用中,推薦使用 WebP 有損壓縮。
另外,WebP 支持 alpha 透明和 24bit 顏色數(shù),不存在 PNG8 色彩不夠豐富和毛邊問題。WebP 也支持真彩動(dòng)圖。因此 WebP 可以替代當(dāng)前大多數(shù)圖片格式,包括 JPG、PNG、GIF 等。
3.原理
下面來分析一下 WebP 有損壓縮的編碼過程:
1. 分塊(MacroBlocking)
將圖片劃分成多個(gè)宏塊(macro blocks),典型的宏塊由一個(gè) 16×16 的亮度像素(luma pixel)塊和兩個(gè) 8×8 的色度像素(chroma pixel)塊組成。分塊越小,預(yù)測(cè)越準(zhǔn),需要記錄的信息也越多。一般來說,細(xì)節(jié)越豐富的地方,分塊越細(xì),即使用 4×4 分塊預(yù)測(cè)。細(xì)節(jié)相對(duì)不豐富的地方使用 16×16 分塊。(這一過程相當(dāng)于 JPEG 編碼中的色彩空間轉(zhuǎn)換)

2. 幀內(nèi)預(yù)測(cè)
WebP 有損壓縮使用了幀內(nèi)預(yù)測(cè)編碼,這一技術(shù)也被用于 VP8 視頻編碼中的關(guān)鍵幀壓縮。VP8 有四種常見的幀內(nèi)預(yù)測(cè)模型。
- H_PRED(horizontal prediction)
像素塊中每一行使用其左邊一列(col L)的數(shù)據(jù)填充(如圖3.2 Horizontal) - V_PRED (vertical prediction)
像素塊中每一列使用其上邊一行(row A)的數(shù)據(jù)填充(如圖3.2 Vertical) - DC_PRED (DC prediction)
像素塊中每個(gè)單元使用 row A 和 col L 的所有像素的平均值填充(如圖3.2 Average) - TM_PRED (TrueMotion prediction)
一種我還沒搞清楚的預(yù)測(cè)模式,比較接近真實(shí)數(shù)據(jù)
下圖展示了 4×4 分塊的所有幀內(nèi)預(yù)測(cè)模型

使用哪種分塊預(yù)測(cè)模式是動(dòng)態(tài)決定的。編碼器會(huì)將所有可能的預(yù)測(cè)模式都計(jì)算出來,然后選出錯(cuò)誤程度最小的模式。
3. DCT(離散余弦變換)
將預(yù)測(cè)部分的原圖像數(shù)據(jù)減去預(yù)測(cè)出來的數(shù)據(jù),得到差值矩陣,最后對(duì)差值進(jìn)行 DCT。此步驟會(huì)生成一個(gè)頻率系數(shù)矩陣,左上的系數(shù)幅度最大,右下最小。幅度值越小,頻率越高。大部分圖片信息都在左上區(qū)域。這一步的作用就是找出圖片的高頻和低頻區(qū)域。
4. 量化
人眼對(duì)高頻部分不敏感,這一步會(huì)將高頻部分舍去。對(duì)上一步的頻率系數(shù)表和量化表進(jìn)行計(jì)算,將頻率系數(shù)表和量化表按位相除,并四舍五入位整數(shù)。最終生成一個(gè)量化矩陣。
5. 算法編碼
WebP使用 Arithmetic entropy encoding,該算法相比JPEG上使用的 Huffman encoding,在壓縮表現(xiàn)上更出色。

總結(jié)一下,WebP 對(duì)圖片進(jìn)行分塊,然后對(duì)待填充的宏塊使用了幀間預(yù)測(cè)技術(shù),根據(jù)其附近已編碼宏塊的數(shù)據(jù),預(yù)測(cè)出當(dāng)前塊數(shù)據(jù)。相比 JEPG 對(duì)圖像原值進(jìn)行編碼,WebP 編碼的是預(yù)測(cè)值和原值的差值,這是 WebP 體積更小的主要原因。最后,WebP 使用了更優(yōu)秀的算數(shù)編碼。
4.應(yīng)用

WebP 完全兼容 Android 4.4 及其以上版本,而在另一大移動(dòng)平臺(tái) iOS 上,則是完全不兼容 WebP。因此,我們需要在前端進(jìn)行平臺(tái)檢測(cè),對(duì)于支持 WebP 的平臺(tái)輸出 WebP,在不支持的平臺(tái)上采用降級(jí)方案。
方案一:
JavaScript 判斷瀏覽器是否支持 WebP。
function check_webp_feature(feature, callback) {
var kTestImages = {
lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
};
var img = new Image();
img.onload = function () {
var result = (img.width > 0) && (img.height > 0);
callback(feature, result);
};
img.onerror = function () {
callback(feature, false);
};
img.src = "data:image/webp;base64," + kTestImages[feature];
}
如果平臺(tái)支持 WebP,則在請(qǐng)求頭 Accept 中帶上 image/webp,這樣服務(wù)器就會(huì)知道瀏覽器是否支持 WebP。
方案二:
Google 開發(fā)的 PageSpeed 模塊,可以自動(dòng)將圖像轉(zhuǎn)出 WebP 或者其他格式。以 Nginx 為例。
首先在 http 模塊中開啟 pagespeed 屬性。
pagespeed on;
pagespeed FileCachePath "/var/cache/ngx_pagespeed/“;
然后在你的主機(jī)配置添加如下一行代碼,就能啟用這個(gè)特性。
pagespeed EnableFilters convert_png_to_jpeg,convert_jpeg_to_webp;
我們可以看下經(jīng)過轉(zhuǎn)換后的代碼:
頁面原始代碼:

Chrome 打開后源碼如下:

Safari 打開如下:

5.總結(jié)
顯然,WebP 是個(gè)好東西,它在肉眼效果幾乎一樣的情況下,大幅減少了圖片體積。本文對(duì) WebP 這種圖片格式進(jìn)行了性能測(cè)試分析,并解釋了 WebP 有損壓縮的實(shí)現(xiàn)原理,最后給出了兩種應(yīng)用方案。
參考文獻(xiàn):
- https://isux.tencent.com/introduction-of-webp.html
- https://developers.google.com/speed/webp/docs/compression#adaptive_block_quantization
- https://medium.com/@duhroach/how-webp-works-lossly-mode-33bd2b1d0670
- https://modpagespeed.com/doc/configuration
- https://juejin.im/entry/5791843bc4c9710054f55751
- https://aotu.io/notes/2016/06/23/explore-something-of-webp/index.html