我这里把我目前所了解的图片压缩方式分为三种,以下一一道来,文章末尾附上一个小Demo,欢迎下载!
一、质量压缩 |
质量压缩其实是为了减少图片存储在SD卡中的文件大小,以及在网络上传输的输出流的大小,对图片以bitmap形式在内存中的大小是没有影响的,因为质量压缩是减少了图片的位深和透明度,同化了一些颜色相近的像素,但是bitmap长宽的像素是没有变化的,而bitmap的大小计算方式是长 * 宽 * 每个像素的字节数,因此内存大小不变。以下是质量压缩的代码:
/**
* 质量压缩
* @param bmp 要压缩的图片
* @param targetSize 最终要压缩的大小(KB)
* @return
*/
private Bitmap qualityCompress(Bitmap bmp, int targetSize) {
//压缩后的图片输出到baos中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
// 压缩程度,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
while (baos.toByteArray().length / 1024 > targetSize) {
// 循环判断如果压缩后图片是否大于target,大于继续压缩
baos.reset();// 重置baos即清空baos
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 逐渐减少压缩率,把压缩后的数据存放到baos中
options -= 10;// 每次都减少10
if (options <= 0) {
break;
}
}
// ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
byte[] bytes = baos.toByteArray();
// 得到最终质量压缩后的Bitmap
Bitmap targetBmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
//输出相关信息
StringBuilder sb = new StringBuilder();
sb.append("压缩后图片以bitmap形式存在的内存大小:").append(targetBmp.getByteCount() / 1024)
.append("KB\n")
.append("像素为:").append(targetBmp.getWidth() + "*" + targetBmp.getHeight())
.append("\n在SD卡中的文件大小为:").append(bytes.length/1024).append("KB");
tv_compress.setText(sb);
iv.setImageBitmap(targetBmp);
return targetBmp;
}
注意,由于像素数量没有改变,所以存在一个压缩极限,例如quality
设置为0和10最终得到的文件大小可能是一样的。效果如下:
可以看到,bitmap大小没变,这是因为像素没有变化,但SD卡中文件大小确确实实是变小了
二、尺寸压缩 |
尺寸压缩就是通过对原图片缩放以达到缩小图片像素的目的
代码如下:
/**
* 尺寸压缩
*
* @param bmp
* @param targetW 目标图片的宽(像素)
* @param targetH 目标图片的高(像素)
* @return .
*/
private Bitmap scaleCompress(Bitmap bmp, int targetW, int targetH) {
Bitmap targetBmp = Bitmap.createScaledBitmap(bmp, targetW, targetH, true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
targetBmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
//输出相关信息
StringBuilder sb = new StringBuilder();
sb.append("压缩后图片以bitmap形式存在的内存大小:").append(targetBmp.getByteCount() / 1024)
.append("KB\n")
.append("像素为:").append(targetBmp.getWidth() + "*" + targetBmp.getHeight())
.append("\n在SD卡中的文件大小为:").append(baos.toByteArray().length / 1024).append("KB");
tv_compress.setText(sb);
iv.setImageBitmap(targetBmp);
return targetBmp;
}
运行效果如下:
这时候对应bitmap的内存大小是减少了的,但是不知为何在SD卡中的大小却变大了,唉,原理层面没搞懂啊,再去研究研究,之后再做补充吧。
三、采样率压缩 |
这种方法我个人认为比较合适用在防止内存溢出,即避免图片太大导进内存的时候crash掉。因此会事先设置Options.inJustDecodeBounds
为true
,这样在不加载图片到内存的情况下计算图片的宽高,就不OOM了。代码如下:
/**
* @param filePath 图片路径
* @param targetW 压缩的目标宽度(像素)
* @param targetH 压缩的目标高度(像素)
* @return
*/
private Bitmap sampleCompress(String filePath, int targetW, int targetH) {
BitmapFactory.Options opts = new BitmapFactory.Options();
//设置为true的时候不会真正加载图片到内存中,但是仍可以获得图片的分辨率
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, opts);
//设置压缩比例,只能是正数,如果是2表示长宽压缩至原来的1/2,4就是1/4
opts.inSampleSize = inSampleSizeCalculate(opts, targetW, targetH);
//这下子是真的加载到内存了,但是加载进来的是已经压缩了的图片了,所以不会OOM
opts.inJustDecodeBounds = false;
Bitmap targetBmp = BitmapFactory.decodeFile(filePath, opts);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
targetBmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
StringBuilder sb = new StringBuilder();
sb.append("图片大小:")
.append(targetBmp.getByteCount() / 1024 + "KB")
.append("\n压缩后图片的分辨率为:")
.append(targetBmp.getWidth() + "*" + targetBmp.getHeight())
.append("\n图片在SD卡中的大小为:").append(baos.toByteArray().length / 1024).append("KB");
tv_compress.setText(sb);
iv.setImageBitmap(targetBmp);
return targetBmp;
}
private int inSampleSizeCalculate(BitmapFactory.Options opts, double targetW, double targetH) {
//options就是在inJustDecodeBounds设置为false时,计算的图片长宽
int w = opts.outWidth;
int h = opts.outHeight;
//默认不压缩
int inSampleSize = 1;
//原始长宽任一大于目的长宽就需要计算压缩率
if (w > targetW || h > targetH) {
int ratioW = (int) Math.ceil(w / targetW);
int ratioH = (int) Math.ceil(h / targetH);
//取二者中大的
inSampleSize = Math.max(ratioH, ratioW);
}
return inSampleSize;
}
运行效果:
有一些博文说
Options.inSampleSize
必须设置为2的整数次幂,但我实践中发现似乎并不需要,设置为3、5、7等等是真实地把宽高压缩为对应的1/3、1/5、1/7,这里存疑。
由于Options.inSampleSize
只能为整数,所以实际压缩跟目标压缩率会存在差异。
四、格式转换 |
Android Bitmap一般以ARGB8888格式显示,因此每个像素点占四个字节,如果对图片的透明度不作要求的话可以设置为ARGB565,每个像素点占两个字节,内存占用减少一半,代码如下:
/**
* 改变图片为RGB565
* @param filePath
* @return
*/
private Bitmap change2Rgb565(String filePath){
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inPreferredConfig= Bitmap.Config.RGB_565;
Bitmap targetBmp=BitmapFactory.decodeFile(filePath,opts);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
targetBmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
StringBuilder sb = new StringBuilder();
sb.append("图片大小:")
.append(targetBmp.getByteCount() / 1024 + "KB")
.append("\n压缩后图片的分辨率为:")
.append(targetBmp.getWidth() + "*" + targetBmp.getHeight())
.append("\n图片在SD卡中的大小为:").append(baos.toByteArray().length / 1024).append("KB");
tv_compress.setText(sb);
// iv.setImageBitmap(targetBmp);
return targetBmp;
}
运行效果如下:
可以看到内存占用减少了一半。
PS:对图片的处理一般都是比较耗时的,建议不要在UI线程处理,开启子线程吧。
最后附上源码:
Android图片压缩Demo