引言
在上篇重拾Android之路之图片(高效加载大图、多图解决方案,有效避免程序OOM)中,我提到了移动端加载图片网络资源该选择什么样的格式。
本节转自https://blog.ibireme.com/2015/11/02/mobile_image_benchmark/
摘取些节点
来跟大家分享。
摘录
图片通常是移动端流量耗费最多的部分,并且占据着重要的视觉空间。合理的图片格式选用和优化可以为你节省带宽、提升视觉效果。在这里我会分析一下目前主流和新兴的几种图片格式的特点、性能分析、参数调优,以及相关开源库的选择。
几种图片格式的简介
首先谈一下大家耳熟能详的几种老牌的图片格式吧:
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 目前的图片编解码架构吧:
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 才会被用到。
选型
在移动端,图片一直是流量大头,一个简单的运营网页,图片大小动不动就以MB为单位,为了加快网页呈现的速度,我们必须使用最适合图片质量,这里所说的合适指图片的清晰度和大小达到合格的要求。
前端常常会碰到这种情况,一个网页都是图片,需要你压缩图片到适合的分辨率,分辨率低了容易失真用户体验不好,高了图片质量太大导致加载慢,所以经常会找一个合适的临界点来选择图片的分辨率。
我们选择了某个分辨率来作为合适临界点,却发现图片依然很大,希望它可以再小些,给用户更快的呈现速度。对于 JPEG、PNG 和 GIF 这些图片格式的优化几乎已经达到了极致, 若想改变现状开辟新局面,便要有釜底抽薪的胆量和气魄,而 Google 给了我们一个新选择:WebP。
什么是webP?
WebP(发音 weppy),是一种支持有损压缩
和无损压缩
的图片文件格式
,派生自图像编码格式 VP8。根据 Google 的测试,无损压缩后的 WebP 比 PNG 文件少了 45% 的文件大小,即使这些 PNG 文件经过其他压缩工具压缩之后,WebP 还是可以减少 28% 的文件大小。相比JPEG文件“在质量相同的情况下,WebP格式图像的体积要比JPEG格式图像小40%。2010 年发布的 WebP 已经不算是新鲜事物了,在Google 的明星产品如 Youtube、Gmail、Google Play 中都可以看到 WebP 的身影,而 Chrome 网上商店甚至已完全使用了 WebP。国外公司如 Facebook、ebay 和国内公司如腾讯、淘宝、美团等也早已尝鲜。目前 WebP 也在我厂很多的项目中得到应用,如腾讯新闻客户端、腾讯网、QQ空间等,同时也有一些针对 WebP 的图片格式转换工具,如智图,iSparta(http://isparta.github.io/)等。
知道了webP
图片格式,我们再来看以下两组数据:
webP支持情况:
安卓手机系统分布:
从上面的两组数据我们可以得到:
1: webP在安卓4.3以上浏览器中已经完全支持,其中4.0以上部分大部分支持(这组数据为展示,如有需要查询它的兼容性可以定位这个网站:http://caniuse.com/#search=webp)
2:安卓系统大于4.0占95.7%,大于等于4.3的占64.7%,从这可观的数据来说我们为什么不选择webP的格式来加载图片。
说到这里,可能有人开始要喷水了,为了图片极致加载,你放弃了部分用户,导致这部分用户连你的图片都无法显示,这个方式根本不行,我们需要的是在图片完全兼容的条件下,再选择最优图片格式。的确,我们不应该为了最优图片格式,而放弃图片兼容性,那么这个问题就需要我们给出一个解决方案。我们是不是可以通用用户的系统版本来选择使用哪种图片?这个回答是肯定的,我们完全可以通过判断用户的系统版本来选择加载哪种类型的图片格式。
navigator.userAgent
JS的这个方法作为前端我们再熟悉不过了。Chrome浏览器在移动端调试环境下console中输入:
var userAgent = navigator.userAgent;
alert(userAgent);
不同浏览器中,弹窗内容都不相同,其中内容数据所代表什么意思,这里不再做解释,如有不懂的可以访问这个地址,有很好的解释:http://www.jb51.net/article/48532.htm;
如何使用webP图片
用户的版本我能已经获得了,那么什么情况下使用webP图片的格式,是不是也可以通过JS来判断呢。
这里随便code举个例子:
html:
js:
var userAgent = navigator.userAgent;
var Android = userAgent.indexOf("Android");
var AppleWebKit=userAgent.indexOf('AppleWebKit');
var androidVersion = parseFloat(userAgent.slice(Android+8));
var $img=document.getElementsByTagName('img');
window.onload= function () {
if(Android >= 0 && AppleWebKit>=0&&androidVersion>=4){
forImg('data-original');
}else{
forImg('data-url');
}
}
function forImg(data){
for(var i=0;i<$img.length;i++){
$img[i].setAttribute('src',$img[i].getAttribute(data));
}
}
这段代码用于测试。
结果自己通过控制台去查看,可以选择控制台中的network来对比加载的时间。
图片优化的拓展
有关图片的优化,通常我们会用到LruCache(使用软引用、强制回收的办法),会用到SoftReference(使用url做key,bitmap做value的方法),会用到根据手机屏幕来缩放图片,会及时回收图片所占用的内存等方法。但说实在的,这些方法治标不治本,图片该多大还多大,从软件上我们基本上能做到处理图片的极限,那么只剩下考虑从硬件来上优化图片,这就讲到了今天所要说的webp。
其中webp不仅仅能应用在Android上,同样IOS和web端也同样可以使用。
有关webp的简介,腾讯同学有详细介绍,浓缩的精华!从零开始带你认识最新的图片格式WEBP,我不再多说。
一张279k的png图片可以转换成67.5k的webp图片,而且不失真
step_1
添加webp支持,添加so包和lib包
step_2
添加WebpUtils文件,里面有通过so包来处理webp文件成为byte数组的方法
step_3
应用
效果图:
Android Service和webp讲解源码
附安卓SDK文档给出的官方压缩图片算法:
public static Bitmap getBitmapBySize(String path, int width, int height) {
BitmapFactory.Options option = new BitmapFactory.Options();
option.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, option);
option.inSampleSize = computeSampleSize(option, -1, width * height);
option.inJustDecodeBounds = false;
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeFile(path, option);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}
推荐资源
浓缩的精华!从零开始带你认识最新的图片格式WEBP