版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、WebP
1.什么是 WebP
WebP(发音 weppy),是一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式,派生自视频编码格式 VP8,被认为是 WebM 多媒体格式的姊妹项目,是由 Google 在购买 On2 Technologies 后发展出来,以 BSD 授权条款发布。
WebP 最初在2010年发布,目标是减少文件大小,但达到和 JPEG 格式相同的图片质量,希望能够减少图片档在网络上的发送时间。 2011年11月8日,Google 开始让 WebP 支持无损压缩和透明色(alpha 通道)的功能,而在 2012 年 8 月 16 日的参考实做 libwebp 0.2.0 中正式支持。根据 Google 较早的测试,WebP 的无损压缩比网络上找到的 PNG 档少了 45% 的文件大小,即使这些 PNG 档在使用 pngcrush 和 PNGOUT 处理过,WebP 还是可以减少 28% 的文件大小。
WebP 支持的像素最大数量是16383x16383。有损压缩的 WebP 仅支持 8-bit 的 YUV 4:2:0 格式。而无损压缩(可逆压缩)的 WebP 支持 VP8L 编码与 8-bit 之 ARGB 色彩空间。又无论是有损或无损压缩皆支持 Alpha 透明通道、ICC 色彩配置、XMP 诠释数据。
这是一段来自维基百科对 WebP 的介绍,简单的说,就是 WebP 比 PNG 等其他图片小得多。
2.WebP 图片的生成
使用 AS 进行转换:
在 Android Studio 中,右键图片或文件夹,选择 Convert to WebP ,即可生成 WebP 图片。在 Android Studio 中,要使用 WebP ,需要 Android API 在 18 以上。
原先 6.6KB 的 JPG 图片转换成 WEBP 的图片,只有1.6 KB。这是非常可观的。
使用工具生成
使用 iSparta 进行转换,点击进行下载使用。(个人使用 win 10 笔记本,不知道什么原因,不能选择图片。所以就不再这边进行使用讲解。)
3.WebP 性能
这边使用 WEBP 格式与 JPEG 格式进行比较。
对同一张 png 图片进行编码生成不同 quality 的 WEBP 和 JPEG 图片,记录时间,然后把生成的图片在进行解码,记录时间,进行对比。
MainActivity:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xiaoyue_png);
compress(bitmap, Bitmap.CompressFormat.JPEG, 100);
compress(bitmap, Bitmap.CompressFormat.JPEG, 70);
compress(bitmap, Bitmap.CompressFormat.JPEG, 50);
compress(bitmap, Bitmap.CompressFormat.JPEG, 30);
compress(bitmap, Bitmap.CompressFormat.JPEG, 0);
compress(bitmap, Bitmap.CompressFormat.WEBP, 100);
compress(bitmap, Bitmap.CompressFormat.WEBP, 70);
compress(bitmap, Bitmap.CompressFormat.WEBP, 50);
compress(bitmap, Bitmap.CompressFormat.WEBP, 30);
compress(bitmap, Bitmap.CompressFormat.WEBP, 0);
}
/**
* 压缩图片到文件
* @param bitmap 待压缩图片
* @param format 压缩的格式
* @param quality 质量
*/
private void compress(Bitmap bitmap, Bitmap.CompressFormat format, int quality){
String path;
if (format == Bitmap.CompressFormat.WEBP){
path = Environment.getExternalStorageDirectory().getPath()
+ "/xiaoyue_png" + quality + ".webp";
}else {
path = Environment.getExternalStorageDirectory().getPath()
+ "/xiaoyue_png" + quality + ".jpeg";
}
long time = System.currentTimeMillis();
FileOutputStream outputStream;
try{
outputStream = new FileOutputStream(path);
bitmap.compress(format, quality, outputStream);
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Log.e(TAG, "111 zx 编码 " + format + " 图片, 质量 " + quality
+ " 耗时:" + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
BitmapFactory.decodeFile(path);
Log.e(TAG, "111 zx 解码 " + format + " 图片, 质量 " + quality
+ " 耗时:" + (System.currentTimeMillis() - time));
}
}
在 AndroidManifest.xml 中配置文件读写权限。
AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
结果:
生成的图片文件:
结论: 相同的 quality 下:
编码 :WEBP 比 JPEG 耗时多很多
解码:耗时差不多,JPEG 稍微快一点点,可忽略不计
内存:WEBP 的图片内存比 JPEG 的图片内存小很多
注: 我们把 drawable 下的图片换成 WEBP,这时候生成图片耗时对应用是没有影响的,但是可以缩小图片的大小。其他情况自行选择。
4.WebP 的兼容现状
在安卓中国中有明确的说明:https://developer.android.google.cn/guide/topics/media/media-formats.html
4.2.1 + 对于 WEBP 的d ecode、encode 是完全支持的(包含半透明的webp图)
4.0 + 到 4.2.1 ,只支持完全不透明的 decode、encode 的 WEBP 图
4.0 以下,应该是默认不支持 WEBP了
从上面可以发现,对 WEBP 完全支持必须是 4.2.1 以上,目前大部分安卓手机应该都在这个范围。如果说要在安卓 4.0 以下使用 WEBP 的话,需要使用 libwebp 进行支持。
二、libwebp
1.libwebp 的配置
1 下载
在谷歌中国中进行 libweb 源码下载。
解压出来,可以发现压缩包中提供多种编码方式,这边直接采用 mk 方式进行编码。
2 修改 Android.mk
在 Android.mk 文件中进行修改,添加:
ENABLE_SHARED := 1
在 libwebp 下面配置中添加一个源文件:
swig/libwebp_java_wrap.c \
3 新建 Application.mk
在 libwebp 目录下新建 Application.mk 文件。
指定支持的 CPU 架构以及安卓版本,根据实际情况进行调整:
APP_ABI := armeabi-v7a x86
APP_PLATFORM = android-14
4 编译
修改目录名为 jni,在父级目录开启命令行。
执行 ndk-build.cmd 命令。
等待编译成功,会生成一个 jni 同级目录 libs,在这个目录下有我们需要的 CPU 架构对应的库文件。
5 导入 so
在 main 下面新建 jnilibs 文件夹,把上面编译生成的 libwebp.so 拷贝到项目中。
6 添加 jar
把 libwebp 下的 libwebp.jar 这个包添加到项目中。
二、libwebp 的使用
1.编码
/**
* 将 bitmap 使用 libwebp 编码为 webp 图片
*
* @param bitmap
*/
private void encodeWebp(Bitmap bitmap, int quality) {
long time = System.currentTimeMillis();
//获取bitmap 宽高
int width = bitmap.getWidth();
int height = bitmap.getHeight();
//获得bitmap中的 ARGB 数据
ByteBuffer buffer = ByteBuffer.allocate(bitmap.getByteCount());
bitmap.copyPixelsToBuffer(buffer);
//编码 获得 webp格式文件数据
byte[] bytes = libwebp.WebPEncodeRGBA(buffer.array(), width, height, width * 4, quality);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(Environment
.getExternalStorageDirectory() + "/libwebp" + quality +".webp");
fos.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != fos) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Log.e(TAG, "111 zx libwebp 编码图片, 质量 " + quality
+ " 耗时:" + (System.currentTimeMillis() - time));
}
libwebp.WebPEncodeRGBA 需要五个参数,分别是图片数据、宽、高、一行数据的字节数、图片质量。(这边 bitmap 默认·是 ARGB_8888,所以是 width * 4)。
2.解码
/**
* libwebp 解码 webp图片
*/
private Bitmap decodeWebp(String path) {
byte[] bytes = null;
InputStream is = null;
try {
is = new FileInputStream(path);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer,0,len);
}
bytes = bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//将webp格式的数据转成 argb
int[] width = new int[1];
int[] height = new int[1];
byte[] argb = libwebp.WebPDecodeARGB(bytes, bytes.length, width, height);
//将argb byte数组转成 int数组
int[] pixels = new int[argb.length/4];
ByteBuffer.wrap(argb).asIntBuffer().get(pixels);
//获得bitmap
Bitmap bitmap = Bitmap.createBitmap(pixels, width[0], height[0], Bitmap.Config.ARGB_8888);
return bitmap;
}
3.效率
跟上面的 webp 编解码对比,在编码速度上相对会快一些,解码速度差不多。(不同手机结果可能有差异)
三、完整代码
MainActivity :
package com.xiaoyue.libwebp;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.google.webp.libwebp;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("webp");
}
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xiaoyue_png);
compress(bitmap, Bitmap.CompressFormat.JPEG, 100);
compress(bitmap, Bitmap.CompressFormat.JPEG, 70);
compress(bitmap, Bitmap.CompressFormat.JPEG, 50);
compress(bitmap, Bitmap.CompressFormat.JPEG, 30);
compress(bitmap, Bitmap.CompressFormat.JPEG, 0);
compress(bitmap, Bitmap.CompressFormat.WEBP, 100);
compress(bitmap, Bitmap.CompressFormat.WEBP, 70);
compress(bitmap, Bitmap.CompressFormat.WEBP, 50);
compress(bitmap, Bitmap.CompressFormat.WEBP, 30);
compress(bitmap, Bitmap.CompressFormat.WEBP, 0);
encodeWebp(bitmap, 100);
encodeWebp(bitmap, 70);
encodeWebp(bitmap, 50);
encodeWebp(bitmap, 30);
encodeWebp(bitmap, 0);
}
/**
* 压缩图片到文件
* @param bitmap 待压缩图片
* @param format 压缩的格式
* @param quality 质量
*/
private void compress(Bitmap bitmap, Bitmap.CompressFormat format, int quality){
String path;
if (format == Bitmap.CompressFormat.WEBP){
path = Environment.getExternalStorageDirectory().getPath()
+ "/xiaoyue_png" + quality + ".webp";
}else {
path = Environment.getExternalStorageDirectory().getPath()
+ "/xiaoyue_png" + quality + ".jpeg";
}
long time = System.currentTimeMillis();
FileOutputStream outputStream;
try{
outputStream = new FileOutputStream(path);
bitmap.compress(format, quality, outputStream);
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Log.e(TAG, "111 zx 编码 " + format + " 图片, 质量 " + quality
+ " 耗时:" + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
BitmapFactory.decodeFile(path);
Log.e(TAG, "111 zx 解码 " + format + " 图片, 质量 " + quality
+ " 耗时:" + (System.currentTimeMillis() - time));
}
/**
* libwebp 解码 webp图片
*/
private Bitmap decodeWebp(String path) {
byte[] bytes = null;
InputStream is = null;
try {
is = new FileInputStream(path);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer,0,len);
}
bytes = bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//将webp格式的数据转成 argb
int[] width = new int[1];
int[] height = new int[1];
byte[] argb = libwebp.WebPDecodeARGB(bytes, bytes.length, width, height);
//将argb byte数组转成 int数组
int[] pixels = new int[argb.length/4];
ByteBuffer.wrap(argb).asIntBuffer().get(pixels);
//获得bitmap
Bitmap bitmap = Bitmap.createBitmap(pixels, width[0], height[0], Bitmap.Config.ARGB_8888);
return bitmap;
}
/**
* 将 bitmap 使用 libwebp 编码为 webp 图片
*
* @param bitmap
*/
private void encodeWebp(Bitmap bitmap, int quality) {
long time = System.currentTimeMillis();
//获取bitmap 宽高
int width = bitmap.getWidth();
int height = bitmap.getHeight();
//获得bitmap中的 ARGB 数据
ByteBuffer buffer = ByteBuffer.allocate(bitmap.getByteCount());
bitmap.copyPixelsToBuffer(buffer);
//编码 获得 webp格式文件数据
byte[] bytes = libwebp.WebPEncodeRGBA(buffer.array(), width, height, width * 4, quality);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(Environment
.getExternalStorageDirectory() + "/libwebp" + quality +".webp");
fos.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != fos) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Log.e(TAG, "111 zx libwebp 编码图片, 质量 " + quality
+ " 耗时:" + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
decodeWebp(Environment
.getExternalStorageDirectory() + "/libwebp" + quality +".webp");
Log.e(TAG, "111 zx libwebp 解码图片, 质量 " + quality
+ " 耗时:" + (System.currentTimeMillis() - time));
}
}