1. 程式人生 > >移動端圖片格式調研

移動端圖片格式調研

圖片通常是移動端流量耗費最多的部分,並且佔據著重要的視覺空間。合理的圖片格式選用和優化可以為你節省頻寬、提升視覺效果。在這篇文章裡我會分析一下目前主流和新興的幾種圖片格式的特點、效能分析、引數調優,以及相關開源庫的選擇。

幾種圖片格式的簡介

首先談一下大家耳熟能詳的幾種老牌的圖片格式吧:

JPEG 是目前最常見的圖片格式,它誕生於 1992 年,是一個很古老的格式。它只支援有失真壓縮,其壓縮演算法可以精確控制壓縮比,以影象質量換得儲存空間。由於它太過常見,以至於許多移動裝置的 CPU 都支援針對它的硬編碼與硬解碼。

PNG 誕生在 1995 年,比 JPEG 晚幾年。它本身的設計目的是替代 GIF 格式,所以它與 GIF 有更多相似的地方。PNG 只支援無失真壓縮,所以它的壓縮比是有上限的。相對於 JPEG 和 GIF 來說,它最大的優勢在於支援完整的透明通道。

GIF 誕生於 1987 年,隨著初代網際網路流行開來。它有很多缺點,比如通常情況下只支援 256 種顏色、透明通道只有 1 bit、檔案壓縮比不高。它唯一的優勢就是支援多幀動畫,憑藉這個特性,它得以從 Windows 1.0 時代流行至今,而且仍然大受歡迎。

在上面這些圖片格式誕生後,也有不少公司或團體嘗試對他們進行改進,或者創造其他更加優秀的圖片格式,比如 JPEG 小組的 JPEG 2000、微軟的 JPEG-XR、Google 的 WebP、個人開發者釋出的 BPG、FLIF 等。它們相對於老牌的那幾個圖片格式來說有了很大的進步,但出於各種各樣的原因,只有少數幾個格式能夠流行開來。下面三種就是目前實力比較強的新興格式了:

APNG 是 Mozilla 在 2008 年釋出的一種圖片格式,旨在替換掉畫質低劣的 GIF 動畫。它實際上只是相當於 PNG 格式的一個擴充套件,所以 Mozilla 一直想把它合併到 PNG 標準裡面去。然而 PNG 開發組並沒有接受 APNG 這個擴充套件,而是一直在推進它自己的 MNG 動圖格式。MNG 格式過於複雜以至於並沒有什麼系統或瀏覽器支援,而 APNG 格式由於簡單容易實現,目前已經漸漸流行開來。Mozilla 自己的 Firefox 首先支援了 APNG,隨後蘋果的 Safari 也開始有了支援, Chrome 目前也已經嘗試開始支援 ,可以說未來前景很好。

WebP 是 Google 在 2010 年釋出的圖片格式,希望以更高的壓縮比替代 JPEG。它用 VP8 視訊幀內編碼作為其演算法基礎,取得了不錯的壓縮效果。它支援有損和無失真壓縮、支援完整的透明通道、也支援多幀動畫,並且沒有版權問題,是一種非常理想的圖片格式。藉由 Google 在網路世界的影響力,WebP 在幾年的時間內已經得到了廣泛的應用。看看你手機裡的 App:微博、微信、QQ、淘寶、網易新聞等等,每個 App 裡都有 WebP 的身影。Facebook 則更進一步,用 WebP 來顯示聊天介面的貼紙動畫。

BPG 是著名程式設計師 Fabrice Bellard 在去年 (2014年) 釋出的一款超高壓縮比的圖片格式。這個程式設計師有些人可能感覺面生,但說起他的作品 FFmpeg、QEMU 大家想必是都知道的。BPG 使用 HEVC (即 H.265) 幀內編碼作為其演算法基礎,就這點而言,它毋庸置疑是當下最為先進的圖片壓縮格式。相對於 JP2、JPEG-XR、WebP 來說,同等體積下 BPG 能提供更高的影象質量。另外,得益於它本身基於視訊編碼演算法的特性,它能以非常小的檔案體積儲存多幀動畫。 Fabrice Bellard 聰明的地方在於,他知道自己一個人無法得到各大瀏覽器廠商的支援,所以他還特地開發了 Javascript 版的解碼器,任何瀏覽器只要載入了這個 76KB 大小的 JS 檔案,就可以直接顯示 BPG 格式的圖片了。目前阻礙它流行的原因就是 HEVC 的版權問題和它較長的編碼解碼時間。儘管這個圖片格式才剛剛釋出一年,但已經有不少廠子開始試用了,比如阿里騰訊

移動端圖片型別的支援情況

目前主流的移動端對圖片格式的支援情況如何呢?我們分別來看一下 Android 和 iOS 目前的圖片編解碼架構吧:

mobile_image_arch

Android 的圖片編碼解碼是由 Skia 圖形庫負責的,Skia 通過掛接第三方開源庫實現了常見的圖片格式的編解碼支援。目前來說,Android 原生支援的格式只有 JPEG、PNG、GIF、BMP 和 WebP (Android 4.0 加入),在上層能直接呼叫的編碼方式也只有 JPEG、PNG、WebP 這三種。目前來說 Android 還不支援直接的動圖編解碼。

iOS 底層是用 ImageIO.framework 實現的圖片編解碼。目前 iOS 原生支援的格式有:JPEG、JPEG2000、PNG、GIF、BMP、ICO、TIFF、PICT,自 iOS 8.0 起,ImageIO 又加入了 APNG、SVG、RAW 格式的支援。在上層,開發者可以直接呼叫 ImageIO 對上面這些圖片格式進行編碼和解碼。對於動圖來說,開發者可以解碼動畫 GIF 和 APNG、可以編碼動畫 GIF。

兩個平臺在匯入第三方編解碼庫時,都多少對他們進行了一些修改,比如 Android 對 libjpeg 等進行的調整以更好的控制記憶體,iOS 對 libpng 進行了修改以支援 APNG,並增加了多執行緒編解碼的特性。除此之外,iOS 專門針對 JPEG 的編解碼開發了 AppleJPEG.framework,實現了效能更高的硬編碼和硬解碼,只有當硬編碼解碼失敗時,libjpeg 才會被用到。

靜態圖片的編碼與解碼

由於我目前主要是做 iOS 開發,所以下面的效能評測都是基於 iPhone 的,主要測試程式碼可以在這裡看到。測試素材很少,只有兩個:

dribbble512_pngcrushlena512_weibo

第一張是Dribbble 的 Logo,包含 Alpha 通道,用於測試簡單的、圖形類的影象。
第二張經典的 Lena 圖,用於測試照片類的、具有豐富細節的影象。
每個影象都有 64×64、128×128、256×256、512×512 四種解析度。
測試素材過少可能導致某些測試不夠準確,但作為參考大致是沒問題的。

JPEG

目前比較知名的 JPEG 庫有以下三個:

libjpeg:開發時間最早,使用最廣泛的 JPEG 庫。由於 JPEG 標準過於複雜和模糊,並沒有其他人去實現,所以這個庫是 JPEG 的事實標準。

libjpeg-turbo:一個致力於提升編解碼速度的 JPEG 庫。它基於 libjpeg 進行了改造,用 SIMD 指令集 (MMX、SSE2、NEON) 重寫了部分程式碼,官網稱相對於 libjpeg 有 2 到 4 倍的效能提升。

MozJPEG: 一個致力於提升壓縮比的 JPEG 庫。它是 Mozilla 在 2014 年釋出的基於 libjpeg-turbo 進行改造的庫,相對於 libjpeg 有 5% ~ 15%  的壓縮比提升,但相應的其編碼速度也慢了很多。

除了上面這三個庫,蘋果自己也開發了一個 AppleJPEG,但並沒有開源。其呼叫了晶片提供的 DSP 硬編碼和硬解碼的功能。雖然它不如上面這三個庫功能完善,但其效能非常高。在我的測試中,其編解碼速度通常是 libjpeg-turbo 的 1~2 倍。可惜的是,目前開發者並不能直接訪問這個庫。

下面是 ImageIO (AppleJPEG/libpng) 在 iPhone 6 上的編解碼效能:

jpeg_bench_dribbble jpeg_bench_lena

可以看到,JPEG 編碼中 quality 越小,圖片體積就越小,質量越也差,編碼時間也越短。解碼時間並沒有很大的差距,可能是其大部分時間消耗在了函式呼叫、硬體呼叫上。蘋果在自己的相簿 Demo 中提供的 quality 的預設值是 0.9,在這個值附近,影象質量和體積、編碼解碼時間之間都能取得不錯的平衡。

PNG

相對於 JPEG 來說,PNG 標準更為清晰和簡單,因此有很多公司或個人都有自己的 PNG 編碼解碼實現。但目前使用最廣的還是 PNG 官方釋出的 libpng 庫。iOS 和 Android 底層都是呼叫這個庫實現的 PNG 編解碼。

下面是 PNG 在 iPhone 6 上的編解碼效能:

jpeg_png_bench

可以看到,在編解碼圖形型別(顏色少、細節少)的圖片時,PNG 和 JPEG 差距並不大;但是對於照片型別(顏色和細節豐富)的圖片來說,PNG 在檔案體積、編解碼速度上都差 JPEG 不少了。

和 JPEG 不同,PNG 是無失真壓縮,其並不能提供壓縮比的選項,其壓縮比是有上限的。目前網上有很多針對 PNG 進行優化的工具和服務,旨在提升 PNG 的壓縮比。下面是常見的幾個 PNG 壓縮工具的效能對比:

png_tools_bench

pngcrush 是 Xcode 自帶的 PNG 壓縮工具,相對於設計師用 Photoshop 生成的圖片來說,它能取得不錯的壓縮效果。ImageOptim 則更進一步,對每張圖用多種縮演算法進行比對,選擇壓縮比更高的結果,進一步縮小了檔案體積。TinyPNG.com 相對於其他工具來說,壓縮比高得不像話。它啟用了類似 GIF 那樣的顏色索引表對 PNG 進行壓縮,所以會導致顏色豐富的圖片丟失掉一部分細節。如果使用 TinyPNG 的話,最好在壓縮完成後讓設計師看一下顏色效果是否可以接受。

WebP

WebP 標準是 Google 定製的,迄今為止也只有 Google 釋出的 libwebp 實現了該的編解碼 。 所以這個庫也是該格式的事實標準。

WebP 編碼主要有幾個引數:

lossless: YES:有損編碼 NO:無損編碼。WebP 主要優勢在於有損編碼,其無損編碼的效能和壓縮比表現一般。

quality: [0~100] 影象質量,0表示最差質量,檔案體積最小,細節損失嚴重,100表示最高影象質量,檔案體積較大。該引數只針對有失真壓縮有明顯效果。Google 官方的建議是 75,騰訊在對 WebP 評測時給出的建議也是 75。在這個值附近,WebP 能在壓縮比、影象質量上取得較好的平衡。

method: [0~6] 壓縮比,0表示快速壓縮,耗時短,壓縮質量一般,6表示極限壓縮,耗時長,壓縮質量好。該引數也只針對有失真壓縮有明顯效果。調節該引數最高能帶來 20% ~ 40% 的更高壓縮比,但相應的編碼時間會增加 5~20 倍。Google 推薦的值是 4。

對於編碼無損圖片來說,quality=0, method=0~3 是相對來說比較合適的引數,能夠節省編碼時間,同時也有不錯的壓縮比。無損編碼圖片,quality=75, method=2~4 是比較合適的引數,能在編碼時間、圖片質量、檔案體積之間有著不錯的平衡。

WebP 解碼有三個引數:

use_threads: 是否啟用 pthread 多執行緒解碼。該引數只對寬度大於 512 的有損圖片起作用。開啟後內部會用多執行緒解碼,CPU 佔用會更高,解碼時間平均能縮短 10%~20%。

bypass_filtering: 是否禁用濾波。該引數只對有損圖片起作用,開啟後大約能縮短 5%~10% 的解碼時間,但會造成一些顏色過渡平滑的區域產生色帶(banding)。

no_fancy_upsampling: 是否禁用上取樣。該引數只對有損圖片起作用。在我的測試中,開啟該引數後,解碼時間反而會增加 5~25%,同時會造成一些影象細節的丟失,線條邊緣會增加雜色,顯得不自然。

通常情況下,這三個引數都設為 NO 即可,如果要追求更高的解碼速度,則可以嘗試開啟 use_threads 和 bypass_filtering 這兩個引數。而 no_fancy_upsampling 在任何情況下都沒必要開啟。

由於 WebP 測試資料較多,這裡只貼一下 512×512 大小的一部分測試結果,感興趣的可以看文章結尾處的 Excel 附件。

webp_bench

對於簡單的圖形型別的影象(比如 App 內的各種 UI 素材),WebP 無失真壓縮的檔案體積和解碼速度某些情況下已經比 PNG 還要理想了,如果你想要對 App 安裝包體積進行優化,可以嘗試一下 WebP。

對於複雜的影象(比如照片)來說,WebP 無損編碼表現並不好,但有損編碼表現卻非常棒。相近質量的圖片解碼速度 WebP 相距 JPEG 也已經相差不大了,而檔案壓縮比卻能提升不少。

BPG

BPG 是目前已知最優秀的有失真壓縮格式了,它能在相同質量下比 JPEG 減少 50% 的體積。下面是經典的 Lena 圖的對比,你也可以在這裡看到大量其他圖片的 BPG、JPEG、JPEG2000、JPEG-XR、WebP 壓縮效果的線上對比,效果非常明顯。

bpg_demo

BPG 目前只有作者釋出的 libbpg 可用。但作者基於 libbpg 編譯出了一個 Javascript 解碼器,很大的擴充套件了可用範圍。bpg 可以以無損和有失真壓縮兩種方式進行編碼,有失真壓縮時可以用 quality 引數控制壓縮比,可選範圍為 0~51,數值越大壓縮比越高。通常來說,25 附近是一個不錯的選擇,BPG 官方工具預設值是 28。

libbpg 目前並沒有針對 ARM NEON 做優化,所以其在移動端的效能表現一般。下面是 iPhone 6 上的效能測試:

bpg_bench

由於 bpg 編碼時間太長,我並沒有將資料放到表格裡。可以看到相同質量下,BPG 的解碼速度還是差 JPEG 太多,大約慢了 3~5 倍。目前來說,BPG 適用於那些對流量非常敏感,但對解碼時間不敏感的地方。從網上的新聞來看,手機淘寶和手機QQ都已經有所嘗試,但不清楚他們是否對 BPG 解碼進行了優化。

動態圖片的編碼與解碼

動圖在網路上非常受歡迎,它近似視訊,但通常實現簡單、檔案體積小,應用範圍非常廣泛。動圖的始祖是 GIF,它自 Windows 1.0 時代就在網際網路上流行開來,直到今天仍然難以被其他格式取代。儘管它非常古老,但其所用的原理和今天幾種新興格式幾乎一樣。

下面是一張 GIF 格式的 QQ 大表情:

bench_gif_demo

這張表情由 6 幅靜態圖構成,每幅圖片有一定的存活時間,連貫播放就形成了動畫:

bench_gif_demo1

這幾張圖中,大部分內容是相近的,為了壓縮檔案體積,通常動圖格式都支援一些特殊的方式對相似圖片進行裁剪,只保留前後幀不同的部分:

bench_gif_demo2

在解碼動圖時,解碼器通常採用所謂”畫布模式”進行渲染。想象一下:播放的區域是一張畫布,第一幀播放前先把畫布清空,然後完整的繪製上第一幀圖;播放第二幀時,不再清空畫布,而是隻把和第一幀不同的區域覆蓋到畫布上,就像油畫的創作那樣。

像這樣的第一幀就被稱為關鍵幀(即 I 幀,幀內編碼幀),而後續的那些通過補償計算得到的幀被稱為預測編碼幀(P幀)。一個壓縮的比較好的動圖內,通常只有少量的關鍵幀,而其餘都是預測編碼幀;一個較差的壓縮工具製作的動圖內,則基本都是關鍵幀。不同的動圖壓縮工具通常能得到不同的結果。

除此之外,動圖格式通常有更為詳細的引數控制每一幀的繪製過程,下面是 GIF/APNG/WebP 通用的幾個引數:

Disposal Method (清除方式)
Do Not Dispose:把當前幀增量繪製到畫布上,不清空畫布。
Restore to Background:繪製當前幀之前,先把畫布清空為預設背景色。
Restore to Previous:繪製下一幀前,把先把畫布恢復為當前幀的前一幀

Blend Mode (混合模式)
Blend None: 繪製時,全部通道(包含Alpha通道)都會覆蓋到畫布,相當於繪製前先清空畫布的指定區域。
Blend over:繪製時,Alpha 通道會被合成到畫布,即通常情況下兩張圖片重疊的效果。

上面這些技術,就是常見動圖格式的基礎了,下面分別介紹一下不同動圖格式的特點。

GIF

GIF 缺陷非常明顯:它通常只支援 256 色索引顏色,這導致它只能通過抖動、差值等方式模擬較多豐富的顏色;它的 Alpha 通道只有 1 bit,這意味著一個畫素只能是完全透明或者完全不透明。

gif_apng_demo

上面這是騰訊部落格裡的一張演示圖,可以看到 GIF 由於 Alpha 通道的問題,產生了嚴重的”毛邊”現象。目前通常的解決方案是在圖片的邊緣加一圈白邊,以減輕這種視覺效果:

gif_wrong_demo

可以仔細觀察一下 QQ、微信等 App 裡面的動畫表情,幾乎每個表情都被一圈白邊所環繞,不得不說是一種很無奈的解決方案。

GIF 的製作工具有很多,但效果好、壓縮比高的工具非常少。對於已經制作好的 GIF 來說,用imagemagick 處理一下可以把檔案體積壓縮不少。如果需要將視訊轉為 GIF,Cinemagraph Pro 是個不錯的傻瓜化工具。這裡有一篇文章介紹如何用 ffmpeg 壓縮 GIF,雖然引數調節有點麻煩,但效果非常理想。

下面是沒有經過優化的 GIF 和經過 ffmpeg 優化編碼的 GIF,可以看到差距非常大。

bbb-trans bbb-nodither

APNG

APNG 目前並沒有被 PNG 官方所接受,所以 libpng 並不能直接解碼 APNG。但由於 APNG 只是基於 PNG 的一個簡單擴充套件,所以在已經支援 PNG 的平臺上,可以很輕鬆的用少量程式碼實現 APNG 的編解碼。Chromium 為了支援 APNG 播放,只增加了不到 600 行程式碼 ,我自己也用大概 500 行 C 程式碼實現了一個簡單的 APNG 編解碼工具。另外,在支援 canvas 的瀏覽器上,可以用 apng-canvas 直接顯示 APNG 動畫。APNG 壓縮最好的工具目前是 apngasm,大部分圖形化工具比如騰訊的 iSparta 都是基於這個工具開發的。

就目前而言, APNG 是 GIF 最好的替代了:實現簡單,可用範圍廣,壓縮比不錯,顯示效果好。

WebP

WebP 在 2010 年 釋出時並沒有支援動圖。2012 年 libwebp v0.2 的時候,Google 才開始嘗試支援動畫,但其實現有很多問題,效能也非常差,以至於 Chrome 團隊一直都沒有接受。直到 2013 年,libwebp v0.4 時,動畫格式才穩定下來才被 Chrome 所接受。

WebP 動圖實際上是把多個單幀 WebP 資料簡單打包到一個檔案內,而並不是由單幀 WebP 擴充套件而來,以至於動圖格式並不能向上相容靜態圖。如果要支援動圖,首先在編譯 libwebp 時需要加上 demux 模組,解碼 WebP 時需要先用 WebPDemuxer 嘗試拆包,之後再把拆出來的單幀用 WebPDecode 解碼。為了方便編譯,我寫了個指令碼用於打包 iOS 的靜態庫,加入了 mux 和 demux 模組。

Google 提供了兩個簡單的命令列工具用於製作動圖:gif2webp 能把 GIF 轉換為 WebP, webpmux 能把多個 WebP 圖片打包為動態圖,並且有著很多引數可以調節。這兩個工具對相近幀的壓縮並不太理想,以至於有的情況下壓縮比還不如 APNG,但除此以外也沒有其他什麼更好的工具可以用了。

BPG

BPG 本身是基於 HEVC (H.265) 視訊編碼的,其最開始設計時就考慮到了動圖的實現。由於它充分利用了 HEVC 的高壓縮比和視訊編碼的特性,其動圖壓縮比遠超其他格式。這裡這裡有幾張 BPG 動圖示例,可以看到相同質量下 BPG 動圖只有 APNG/WebP/GIF 幾十分之一的大小。

我在這裡寫了個簡單的利用 libbpg 解碼動圖的方法,如有需要可以參考下。

動圖效能對比

我把下面這張 GIF 分別轉為 WebP、APNG、BPG 動圖,並在 iPhone 6 上對其所有幀進行解碼。

gif_ermilio

評測結果如下:

anim_bench

APNG 在檔案體積上比 GIF 略有優勢,解碼時間相差不多。WebP 在體積和解碼時間上都具有較大的優勢。BPG 在體積上優勢最大,但解碼時間也最長。這麼看來,APNG 和 WebP 都是不錯的選擇,而 BPG 還有待效能優化。

最後做一個小廣告:如果你是 iOS 平臺的開發者,可以試試我開發的 YYWebImage,它支援 APNG、WebP、GIF 動圖的非同步載入與播放、編碼與解碼,支援漸進式影象載入,可以替代 SDWebImage、PINRemoteImage、FLAnimatedImage 等開源庫。

評測資料