客戶端上動態圖格式對比和解決方案
對各種客戶端來說,無論是Web還是移動端,圖片佔據的容量和傳輸資源一定是非常大的。對於靜態圖,我們常見的PNG和JPEG格式在壓縮率和畫質無損上都存在著不盡如人意的地方,而動圖格式的GIF更是存在著很多問題,比如因此,在很多情況下,我們需要遷移到新的圖片格式。PS: 你們沒發現題圖(APNG)在Safari或者Chrome+插件下是動圖嗎
GIF
為什麼我們不用GIF呢,GIF由於時代限制,存在的天生的問題。GIF的規範最新版本是在1989年制定的,一個24位色都沒有普及的時代,因此,GIF規範只支持256色索引顏色,並且只能通過抖動、差值等方式模擬較多豐富的顏色。更為悲劇的是,它的alpha通道只有1bit,換言之,一個像素要麼完全透明,要麼完全不透明,而不像現在PNG的RGBA的8bit alpha通道,alpha值也可以和RGB一樣都有255個透明值。這導致了所有GIF的圖片帶上透明度以後,邊緣會出現明顯的鋸齒。所以如果你的客戶端需要展示帶透明度的動圖,GIF基本上可以不考慮
實際的在線Demo,建議用Safari或者Chrome+插打開:APNG Sample
APNG
APNG是Mozilla在2008年發布的圖片格式,本質上是在PNG的基礎上加上一個擴展,而且非常簡單即可實現。因此能夠完全支持RGBA。規範可以參見APNG Specification。
雖然這個規範沒有加入PNG開發組,但是很多瀏覽器已經支持了APNG。最主推的是Apple的Safari(OS X 10.10以後的Safari,以及iOS 8以後的Safari和內置WebView),已經完全支持。Firefox親兒子當然一直是支持的。Chrome桌面端已經從Chrome 59開始支持,現在就差Edge了。具體支持程度參見瀏覽器兼容性。
APNG的優勢,在於時間比較長,各種動圖製作工具,優化工具都有相應的項目來支持。而且在iOS上的WebView裡面是除GIF外,唯一官方支持的動圖格式,因此如果做移動端開發需要WebView頁引入動圖,APNG還是必不可少的。
當然,APNG終究是在PNG的基礎上擴展,並沒有引入特別出色的壓縮演算法,而且遺憾的是,短期內APNG還沒有引入到Chrome,也就意味著Android平台的WebView也沒有原生支持,因此,移動開發又會面臨兩端兼容性問題,這個後話再說。
APNG,Chrome需要59或者更高:
https://upload.wikimedia.org/wikipedia/commons/1/14/Animated_PNG_example_bouncing_beach_ball.png
相關APNG工具
- APNG圖形化製作工具和在線預覽:iSparta
- APNG大小優化:APNG Optimizer
- APNG Chrome插件:APNG for Chrome
WebP
WebP是Google在2010年發布的圖片格式,完全開源,使用了VP8(就是WebM視頻所用到的解碼器)作為幀壓縮編碼器,而且在Chrome,Android上得到了原生的支持,具體規範參見:WebP
同樣的支持RGBA,而且靜態WebP的壓縮率比起同質量PNG平均要高上20%左右。現在各大App廠商已經有開始遷移WebP。除了靜態的WebP,還有動態WebP格式(Animated WebP)支持,不過動態WebP需要libwebp 0.4以後才正式支持,並需要mux和demux模塊,如果自行編譯需要注意。
Google官方提供了libwebp這個解碼庫在各個平台的二進位版本和Makefile,並且可以定製開啟的功能。不過由於不像APNG那樣基於PNG擴展,相關的工具很欠缺,基本全靠WebP Project提供的工具。
- cwebp:PNG/JPEG -> WebP
- dwebp:WebP -> PNG/JPEG
- vwebp:WebP命令行預覽工具
- webpmux:多張WebP製作動態WebPgif2webp:GIF -> 動態WebP
Animated WebP,Safari不支持:
http://7xsf4p.com1.z0.glb.clouddn.com/image/0/9e/63a17a7087e6ab1a209460b54136b.webp
WebP工具
基本上來說,手動製作WebP會比較麻煩,因為Google沒有提供WebP Optimizer之類的東西,如果我有100幀基本無差別的圖使用webpmux合成動圖,最終輸出的文件大小會比較大。因此,一般推薦的做法,是先通過PNG製作APNG(比如iSparata),經過APNG Optimizer之後,再從APNG轉換到動態WebP,這個流程可以用這個項目來一鍵搞定。同時,也可以使用ffmpeg來轉換視頻到Animated WebP,一般使用MOV封裝格式(UE常用的Pr導出的MOV可以支持alpha通道)。不過經過測試轉換出來的Anmimated WebP大小相對比較大的(尤其同樣的lossless下),不如PNG->APNG->Animtated Webp這個流程效果好。
- apng2webp:APNG -> Animated WebP
- ffmpeg:MOV -> Animated WebP
其他粗暴的解決方案
像國內的微博桌面版,提供的動圖是通過PNG配合CSS Spirit,靠著不斷JS輪播切換PNG子圖所拼出來的,這個帶來的帶寬消耗會是非常高的,因為完全是多張圖片混合,除非有著兼容性包袱(IE之類),一般不推薦使用。
暴力實現
APNG和WebP各平台實現
Web
APNG 瀏覽器支持 WebP 瀏覽器支持,注意Animated WebP支持
iOS
APNG:
- YYImage
- APNGKit
Animated WebP:
- YYImage
WebP:
- SDWebImage,注意SD使用的libwebp並沒有加入mux和demux,故無法支持Animated WebP
WebView:
- UIWebView,WKWebView和SafariViewController均只支持APNG(iOS 8以後),不支持Webp和Animated WebP
YYImage,對顯示動態圖,使用了一個UIImageView的子類YYAnimatedImageView,通過直接插入了一個CALayer來作為圖片的渲染layer,並用CADisplayLink這個幀定時器來刷新動圖幀,通過非同步線程處理解碼,還有一些C的動態分配和回收內存來避免非常高的內存佔用,保證了性能。並且自動處理了從視圖消失以及滾動(可以切換到RunLoopCommonMode來滾動時候依然顯示動圖而不暫停)情況的問題,實現也非常有意思,有興趣的人可以看一看。
Android
APNG:
- APNG View
Animated WebP:
- Fresco
WebView:
- Android 4.3以後才支持帶lossless和alpha的WebP
Android基本上對APNG可以說是沒有什麼支持的,所以如果是移動開發兩個平台兼顧,建議同時準備APNG(for iOS WebView)和Animated WebP,客戶端上建議都是用Animated WebP,因為VP8的解碼速度相對於APNG有一些優勢。
存在的坑
Web和移動端對於APNG和Animated WebP循環次數不同
這個是一個非常大的坑,在Safari for iOS(Safari for macOS正常)Chrome預覽APNG和Animated WebP的時候,動圖的循環次數為對應原圖的loop+1。比如Animated WebP有100幀,loop為2,那麼Chrome會循環總計展示300幀
剛開始我以為是移動端實現庫的問題,畢竟Google和Apple這種大廠一般不會出現問題。但是再參閱了APNG和Animated WebP的規範,發現確實是Safari和Chrome本身的問題,可以參考APNG規範中的num_plyas欄位,和WebP規範的loop_count欄位
Loop Count: 16 bits (uint16)The number of times to loop the animation. 0 means infinitely.This chunk MUST appear if the Animation flag in the VP8X chunk is set. If the Animation flag is not set and this chunk is present, it SHOULD be ignored.
規範提到的偽代碼描述也表示,loop count為0表示無限循環展示首幀到尾幀,而loop count >= 1,展示首幀到尾幀loop count次。
assert VP8X.flags.hasAnimationcanvas ← new image of size VP8X.canvasWidth x VP8X.canvasHeight with background color ANIM.background_color.loop_count ← ANIM.loopCountdispose_method ← ANIM.disposeMethodif loop_count == 0: loop_count = ∞frame_params ← nilassert next chunk in image_data is ANMFfor loop = 0..loop_count - 1 clear canvas to ANIM.background_color or application defined color until eof or non-ANMF chunk frame_params.frameX = Frame X frame_params.frameY = Frame Y frame_params.frameWidth = Frame Width Minus One + 1 frame_params.frameHeight = Frame Height Minus One + 1 frame_params.frameDuration = Frame Duration #...... Show the contents of the canvas for frame_params.frameDuration * 1ms.
同樣的,APNG對應的num_plays欄位意思是一樣的,大家可以使用這個在線測試用例,Safari表現錯誤而多循環了一次:APNG tests
解決辦法:由於不能更改瀏覽器的實現,部分情況也不好引入JS來手動實現,因此,對於APNG,一般只用在iOS的WebView上,因此可以直接製作APNG圖的時候,把循環減一。而Animated WebP,可以在客戶端實現加一個Hack,如果loop不是0手動減一,保持和Web一致性(當然,也可以專門提供一個loop count加一的圖給Chrome/Android的WebView),希望之後兩大瀏覽器是否可以把這個Bug修復了(當然,不排除聯合一起更改了規範的可能性)
總結
GIF作為一個動圖格式已經太過於古老了,尤其是當前移動和Web站需要引入各種動態表情,頭像的時候,GIF的透明問題已經是不可接受的。WebP長期發展也是比較看好(相比APNG沒有進入PNG開發組,基本不再活躍),開源外加無授權費用,或許能夠和WebM一樣,成為互聯網下首選的圖片和視頻格式。而移動客戶端,在很多種需求下(動態表情,用戶標誌,廣告)等上面,採用這種APNG和Animated WebP就能夠輕鬆解決。
推薦閱讀:
※android v7包里的Toolbar,怎麼定製圖標、字體居中的效果?
※某熊周刊系列:一周推薦外文技術資料(1.4)
※怎麼解決安卓4.4.1和4.4.2 webview 不支持<input type="file" />?
※UWA 新功能 | 優化方法進階—定位子函數的開銷
※[譯] 移動端設計最佳實踐