什么是 WebP
WebP (发音 weppy
),是一种同时提供了有损压缩与无损压缩的图片文件格式,是Google新推出的影像技术,它可让网页图档有效进行压缩,同时又不影响图片格式兼容与实际清晰度,进而让整体网页下载速度加快。
- WebP 无损压缩的图片可以比同样大小的 PNG 小 26%;
- WebP 有损压缩的图片可以比同样大小的 JPEG 小 25-34%;
- WebP 支持无损的透明图层通道,代价只需增加 22% 的字节存储空间;
- WebP 有损透明图像可以比同样大小的 PNG 图像小3倍。
原理
见 官方描述 。
支持
- WebP 有损压缩支持:
- Google Chrome(桌面版) 17+
- Google Chrome(Android版) 25+
- Opera 11.10+
- Native web 浏览器:Android 4.0+(ICS)
- WebP 有损、无损和透明度支持(libwebp v0.2.0)
- Google Chrome(桌面版) 23+
- Google Chrome(Android版) 25+
- Google Chrome(iOS版) 29+
- Opera 12.10+
- Native web 浏览器:Android 4.2+(JB-MR1)
- 开发者也可以在代码中使用轻量级的 libwebp 库来编码/解码 WebP 图像;
- 命令行工具 cwebp 和 dwebp 可以实现其他格式与 webp 的互转。
- 其他支持工具
性能
目前WEBP与JPG相比较,编码速度慢10倍,解码速度慢1.5倍。
针对1.5倍的解码速度是否影响用户体验的问题,我们可以看看ebay团队的这个测试:50张同样质量的WEBP与jgp加载的速度对比
此测试表明,webp虽然会增加额外的解码时间,但是由于减少了文件体积,缩短了加载的时间,实际上 文件的渲染速度反而变快了。
应用案例
安装
下面介绍 libwebp 在 Mac 下的两种安装方法:
方法一:使用 MacPorts 安装
1、 下载并安装 MacPorts :到 这里 下载安装 MacPorts 。
2、 更新 MacPorts :
3、 安装 libwebp
1
| $ sudo port install webp
|
方法二:使用 HomeBrew 安装
1、 安装 Homebrew
1
| $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
2、 安装 libwebp
安装完 libwebp 后,在类 Unix 系统的 /usr/local/include/webp
可以找到如下几个头文件:
1 2 3
| decode.h encode.h types.h
|
/usr/local/lib/
目录则存放着 libwebp 的静态库和动态库。
格式转换
安装完 libwebp 后,cwebp 和 dwebp 也跟着安装成功了。可以用这两个命令行工具实现其他图片格式与 webp 格式的互转。
1
| $ cwebp [options] input_file -o output_file.webp
|
1
| $ dwebp [options] input_file.webp
|
Android 开发中使用 WebP
对于 Android 4.0 以上的系统,BitmapFactory 可以原生支持 webp 。下面介绍让 Android 4.0 以下的系统支持 webp 的方法。因为 libwebp 是 C 写的,所以可以通过 NDK 调用 libwebp :
- 下载源码 ,放置到
你的工程目录/jni
。
- 修改
Android.mk
文件。修改方法见下文。
- 将 libwebp.jar 引入到工程的 build path 中。
- 创建
jni/src/libwebp_java_wrap.c
文件。内容见下文。
- 创建
jni/Application.mk
文件。内容见下文。
- Android.mk 的修改方法:增加
libwebp_java_wrap.c
,将 include $(BUILD_STATIC_LIBRARY)
改为 include $(BUILD_SHARED_LIBRARY)
。示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS) LOCAL_SRC_FILES := \ src/dec/alpha.c \ src/dec/frame.c \ src/dec/idec.c \ src/dec/layer.c \ src/dec/quant.c \ src/dec/tree.c \ src/dec/vp8.c \ src/dec/webp.c \ src/dec/io.c \ src/dec/buffer.c \ src/dsp/yuv.c \ src/dsp/upsampling.c \ src/dsp/cpu.c \ src/dsp/dec.c \ src/dsp/dec_neon.c \ src/dsp/enc.c \ src/utils/bit_reader.c \ src/utils/bit_writer.c \ src/utils/thread.c \ src/libwebp_java_wrap.c \
LOCAL_CFLAGS := -Wall -DANDROID -DHAVE_MALLOC_H -DHAVE_PTHREAD -DWEBP_USE_THREAD \ -finline-functions -frename-registers -ffast-math \ -s -fomit-frame-pointer -Isrc/webp
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
LOCAL_MODULE:= webp
include $(BUILD_SHARED_LIBRARY)
|
1 2 3
| # The ARMv7 is significanly faster due to the use of the hardware FPU APP_ABI := armeabi armeabi-v7a APP_PLATFORM := android-8
|
其中 APP_PLATFORM
设定为支持的 SDK 最低版本。
应用层的开发工程一般如下:
1. 加载 so 包
1 2 3
| static { System.loadLibrary("webp"); }
|
2. 声明与 Native 方法相对应的方法
native 方法是:
1 2 3 4 5 6 7 8 9 10 11
| SWIGEXPORT jint JNICALL Java_com_google_webp_libwebpJNI_WebPGetDecoderVersion(JNIEnv *jenv, jclass jcls) { jint jresult = 0 ; int result; (void)jenv; (void)jcls; result = (int)WebPGetDecoderVersion(); jresult = (jint)result; return jresult; }
|
native 方法命名为:Java_包名_类名_应用层方法名
。其中包名中的点被下划线替代。
应用层需要声明的方法为:
1
| public static final native int WebPGetDecoderVersion();
|
应用层只用声明,不用定义,但要加上native关键字。
3. 使用应用层声明的方法
下面我们按照上述方法来在应用层使用我们刚生成的so库
记得有一个libwebp.jar文件不,这个jar已经把应用层声明的native方法搞好了,而且帮我们封装了一层,我们只用调用其方法就ok了,但是我们还是要加载so库,因为这个它没有帮我们实现。
我们写两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| static { System.loadLibrary("webp"); } private Bitmap webpToBitmap(byte[] encoded) { int[] width = new int[] { 0 }; int[] height = new int[] { 0 }; byte[] decoded = libwebp.WebPDecodeARGB(encoded, encoded.length, width, height); int[] pixels = new int[decoded.length / 4]; ByteBuffer.wrap(decoded).asIntBuffer().get(pixels); return Bitmap.createBitmap(pixels, width[0], height[0], Bitmap.Config.ARGB_8888); } public static boolean isWebp(byte[] data) { return data != null && data.length > 12 && data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F' && data[8] == 'W' && data[9] == 'E' && data[10] == 'B' && data[11] == 'P'; }
|
基于这两个方法,我们可以对webp图片进行解压缩。一个 Demo 工程可以在这里下载。
还可以使用另一个封装好的库 alexey-pelykh/webp-android-backport 。
iOS 开发中使用 WebP
可以使用封装好的 seanooi/iOS-WebP。
准备工作
方法一:使用CocoaPods
方法二:手动的方法
将 iOS-WebP 目录下的如下三个文件引入到你的工程中:
UIImage+WebP.h
UIImage+WebP.m
WebP.framework
用法
如果使用 Cocoapods ,注意先引用头文件 #import "UIImage+WebP.h"
或 #import <UIImage+WebP.h>
。
接口
如下三个方法分别实现其他格式转WebP,WebP转其他格式及设置图像透明度。
1 2 3 4 5 6 7 8 9
| + (void)imageToWebP:(UIImage *)image quality:(CGFloat)quality alpha:(CGFloat)alpha preset:(WebPPreset)preset completionBlock:(void (^)(NSData *result))completionBlock failureBlock:(void (^)(NSError *error))failureBlock;
+ (void)imageWithWebP:(NSString *)filePath completionBlock:(void (^)(UIImage *result))completionBlock failureBlock:(void (^)(NSError *error))failureBlock;
- (UIImage *)imageByApplyingAlpha:(CGFloat)alpha;
|
为了避免阻塞主线程,图像的编码和解码将在后台的线程中完成并将结果block传回给主线程。
WebP 编码
1 2 3 4 5 6 7 8 9 10 11
| // quality value is [0, 100] // alpha value is [0, 1] [UIImage imageToWebP:[UIImage imageNamed:@"demo.jpg"] quality:quality alpha:alpha preset:WEBP_PRESET_DEFAULT completionBlock:^(NSData *result) { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *webPPath = [paths[0] stringByAppendingPathComponent:@"image.webp"]; if (![result writeToFile:webPPath atomically:YES]) { NSLog(@"Failed to save file"); } } failureBlock:^(NSError *error) { NSLog(@"%@", error.localizedDescription); }];
|
WebPPreset
的可能值包括:
WEBP_PRESET_DEFAULT
(默认值,预设)
WEBP_PRESET_PICTURE
(适合数字图像,如肖像、室内摄影)
WEBP_PRESET_PHOTO
(户外照片,带有自然光照的照片)
WEBP_PRESET_DRAWING
(手或者素描,带有高对比度细节的照片)
WEBP_PRESET_ICON
(小尺寸彩色图)
WEBP_PRESET_TEXT
(类文本图案)
WebP 解码
1 2 3 4 5
| [UIImage imageWithWebP:@"/path/to/file" completionBlock:^(UIImage *result) { UIImageView *myImageView = [[UIImageView alloc] initWithImage:result]; }failureBlock:^(NSError *error) { NSLog(@"%@", error.localizedDescription); }];
|
设置图像透明度
1 2
| //alpha value is [0, 1] UIImage *transparencyImage = [[UIImage imageNamed:image.jpg] imageByApplyingAlpha:0.5];
|
Hybrid 开发中使用 WebP
WebP 在不同版本的浏览器内核的支持程度比较碎片化,要在 WebView 中使用 WebP 格式的图片,并且做到全浏览器兼容,目前主要有三种思路:
- 嵌入 flash ,让 flash 解析 WebP 。但这种方式只能将IMG元素一次性转换成Flash元素,而不支持后期的脚本操作。而且超长的参数会消耗大量CPU和内存。
- 准备一套 WebP 格式的图片,当判断客户端不支持 WebP 时再转换成其他格式。但这样会影响图片加载性能。
- 准备两套格式的图片,然后根据不同的浏览器内核提供不同格式的图片。这样做的代价是服务器存储的数据量将会增加,但牺牲存储成本换来的好处是图片加载性能更高。
思路 3 根据实现机制的不同,又可以分成在服务端进行和在客户端进行两种。下面介绍这两种方式。
方案1. 服务端方式
通过 HTTP 的内容协商(Content Negotiation)信息来判断。简单的思路是:
- 支持 WebP 的客户端在其
Accept
头部中包含 WebP 的建议,服务器制定 Vary: Accept
的规则。
- 服务器和缓存根据
Accept
头部的信息来提供合适的资源。
具体方案可参考:https://github.com/igrigorik/webp-detect 。
方案2. 客户端方式
JS 判断浏览器是否支持 WebP ,不同的 src 写入 img 中,样式中的图片可以通过检测后添加 class 。
脚本代码:
1 2 3 4 5 6 7 8 9 10
| var testWebp = function(callback){ var image = new Image(); image.onerror = function() { callback(false); }; image.onload = function() { callback(image.width == 1); }; image.src = 'data:image/webp;base64,UklGRiwAAABXRUJQVlA4ICAAAAAUAgCdASoBAAEAL/3+/3+CAB/AAAFzrNsAAP5QAAAAAA=='; }
|
src 中的字符串是经过 base64 编码过的一张 WebP 图片。
完整的页面代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| <html> <head> <meta charset="UTF-8" /> <title>WEBP TEST</title> <script charset="utf-8" type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script type="text/javascript"> var testWebp = function(callback){ var image = new Image(); image.onerror = function() { callback(false); }; image.onload = function() { callback(image.width == 1); }; image.src = 'data:image/webp;base64,UklGRiwAAABXRUJQVlA4ICAAAAAUAgCdASoBAAEAL/3+/3+CAB/AAAFzrNsAAP5QAAAAAA=='; },webSrc = function(src){ var suffix = src.lastIndexOf('.'); suffix = src.substr(suffix); if (/png|jpg/.test(suffix)){ return src.substr(0,(src.length-3))+'webp'; }else{ return src; } } $(function(){ var $note = $('.note'); testWebp(function(SUP){ var $img = $('img[data-img]'); if(SUP){ $('body').addClass('webp'); $img.each(function(i,o){ var $o = $(o), src = $o.attr('data-img'); $o.attr('src',webSrc(src)); }); $note.html('你载入的是<strong>webp</strong>格式'); }else{ $('body').addClass('nowebp'); $img.each(function(i,o){ var $o = $(o), src = $o.attr('data-img'); $o.attr('src',src); }); $note.html('你载入的是<strong>png、jpg</strong>格式'); } }); }); </script> <style type="text/css"> *{margin:0;padding:0;} body{font:12px/1.6 arial,\5fae\8f6f\96c5\9ed1;color:#666;} strong{color:#000;padding:0 2px;} img{width:313px;height:219px;} .img{ margin-top:20px; width:313px;height:214px; } .webp .img{ background:url(http://demo.btorange.com/article/webp/demo2.webp) no-repeat; } .nowebp .img{ background:url(http://demo.btorange.com/article/webp/demo2.png) no-repeat; } </style> </head> <body> <p class="note"></p> <img alt="" data-img="http://demo.btorange.com/article/webp/demo1.jpg" data-pinit="registered" /> <div class="img"></div> </body> </html>
|
参考资源