Android 中图片的缩放和压缩问题
最近接的一个外包项目中有拍照上传的操作,而且坑爹的是需要连续拍 16 张后一起上传!这样就不得不对图片进行压缩处理了,而且客户对尺寸和大小都有要求。
大小压缩比较常用的是通过 bitmap.compress()
方法中的 quality
参数控制,而尺寸压缩的方法就比较多了:
直接调用系统裁剪组件
com.android.camera.action.CROP
;调用
Bitmap
的createScaledBitmap()
方法;调用
ThumbnailUtils
工具类中的extractThumbnail()
方法;通过指定
BitmapFactory.Options
中的inSampleSize
实现;
第 1 种简单,而且尺寸大小都能压缩,但客户不想看到裁剪页面;
第 2 种以前通过友盟统计发现有 OOM 的风险;
第 3 种虽然也方便,但是图片是从中间截取的,而且通过源码可以看到,中见产生了一些临时 Bitmap
,这无疑也增大了 OOM 的风险;
第 4 种以前虽然知道这个用法,但更多的只是用于 decode 本地图片的时候进行采样避免 OOM,并没有来做过精确的尺寸压缩。于是正好想尝试下这种方法。
思路如下:
首次 decode 的时候指定
inJustDecodeBounds = true
,只读取边界参数得到图片的宽度和高度;通过得到的宽高值和指定的数值计算
inSampleSize
,并指定inJustDecodeBounds = false
得到缩放后的Bitmap
;通过
bitmap.compress()
,指定合适的quality
压缩保存为文件。
/**
*
* @param srcPath
* @param dstPath
* @param maxWidth
* @param maxHeight
* @param maxSize
* @param format
*/
public static void compress(String srcPath, String dstPath, int maxWidth, int maxHeight, long maxSize, CompressFormat format) {
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inJustDecodeBounds=true;
Bitmap bitmap=BitmapFactory.decodeFile(srcPath, opts);
opts.inJustDecodeBounds=false;
int w=opts.outWidth;
int h=opts.outHeight;
int size=0;
if(w<=maxWidth&&h<=maxHeight){
size=1;
}else{
size=w>=h?w/maxWidth:h/maxHeight;
}
opts.inSampleSize=size;
bitmap=BitmapFactory.decodeFile(srcPath, opts);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
int quality=100;
bitmap.compress(format, quality, baos);
while(baos.toByteArray().length>maxSize){
baos.reset();
bitmap.compress(format, quality, baos);
quality-=10;
}
try {
baos.writeTo(new FileOutputStream(dstPath));
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{
try {
baos.flush();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
但测试发现,大小压缩和尺寸缩放比例都没问题,但有时候尺寸大小有问题:可能宽度和高度稍微超过限制。
这个 inSampleSize
取大于等于 1 的整数,默认为 1,即不采样;表示取 N 分之一的缩略图。按理说上面处理应该没问题。
正在仔细琢磨上面算法的时候,脑海突然想起来:记得以前貌似在哪里看到资料说这个 inSampleSize
好像是 2 的 N 次方。
看了官方文档,果然这么说的:默认会找一个最接近的 2 的 N 次方的整数。
很显然问题就出在这里,它所找的最接近的很可能并不是我们所需要的,可能已经超过了限制。
我们需要先对这个压缩比取 2 的对数,然后向上取整,最后再取2的指数得到正确的 inSampleSize
。
最终代码如下:
/**
*
* @param srcPath
* @param dstPath
* @param maxWidth
* @param maxHeight
* @param maxSize
* @param format
*/
public static void compress(String srcPath, String dstPath, int maxWidth, int maxHeight, long maxSize, CompressFormat format) {
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inJustDecodeBounds=true;
Bitmap bitmap=BitmapFactory.decodeFile(srcPath, opts);
opts.inJustDecodeBounds=false;
int w=opts.outWidth;
int h=opts.outHeight;
int size=0;
if(w<=maxWidth&&h<=maxHeight){
size=1;
}else{
//The decoder uses a final value based on powers of 2,
//any other value will be rounded down to the nearest power of 2.
//So we use a ceil log value to keep both of them under limits.
//See doc: http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inSampleSize
double scale=w>=h?w/maxWidth:h/maxHeight;
double log=Math.log(scale)/Math.log(2);
double logCeil=Math.ceil(log);
size=(int) Math.pow(2, logCeil);
}
opts.inSampleSize=size;
bitmap=BitmapFactory.decodeFile(srcPath, opts);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
int quality=100;
bitmap.compress(format, quality, baos);
while(baos.toByteArray().length>maxSize){
baos.reset();
bitmap.compress(format, quality, baos);
quality-=10;
}
try {
baos.writeTo(new FileOutputStream(dstPath));
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{
try {
baos.flush();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}