对各种客户端来说,无论是Web还是移动端,图片占据的容量和传输资源一定是非常大的。对于静态图,我们常见的PNG和JPEG格式在压缩率和画质无损上都存在着不尽如人意的地方,而动图格式的GIF更是存在着很多问题,比如因此,在很多情况下,我们需要迁移到新的图片格式。
GIF
为什么我们不用GIF呢,GIF由于时代限制,存在的天生的问题。GIF的规范最新版本是在1989年制定的,一个24位色都没有普及的时代,因此,GIF规范只支持256色索引颜色,并且只能通过抖动、差值等方式模拟较多丰富的颜色。更为悲剧的是,它的alpha通道只有1bit,换言之,一个像素要么完全透明,要么完全不透明,而不像现在PNG的RGBA的8bit alpha通道,alpha值也可以和RGB一样都有255个透明值。这导致了所有GIF的图片带上透明度以后,边缘会出现明显的锯齿。所以如果你的客户端需要展示带透明度的动图,GIF基本上可以不考虑
实际的在线Demo,建议用Safari或者Chrome+插打开:http://apng.onevcat.com/demo
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工具
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 -> WebPdwebp
:WebP -> PNG/JPEGvwebp
:WebP命令行预览工具webpmux
:多张WebP制作动态WebPgif2webp
:GIF -> 动态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:
Animated WebP:
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:
Animated WebP:
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
字段
1 | Loop Count: 16 bits (uint16) |
规范提到的伪代码描述也表示,loop count为0表示无限循环展示首帧到尾帧,而loop count >= 1,展示首帧到尾帧loop count次。
1 | assert VP8X.flags.hasAnimation |
同样的,APNG对应的num_plays
字段意思是一样的,大家可以使用这个在线测试用例,Safari表现错误而多循环了一次:https://philip.html5.org/tests/apng/tests.html#num-plays-1
解决办法:
由于不能更改浏览器的实现,部分情况也不好引入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就能够轻松解决。