随着科技的发展,越来越多的移动设备被推出,其相机的清晰度是一个重大的特色,提升拍照的分辨率以及像素密度可以增加图像的清晰度,但是这样会导致拍照之后的图像占用内存比较大,一张图片大小达到10M都有可能。
当我们将拍照之后的图片上传服务器,再将服务器中的图片展示在客户端时,如果一张图片有10M,那么会导致大大消耗手机性能,往往消耗内存过大导致OOM的问题。
想要解决OOM的问题就必须将图片压缩,下面将重点介绍图片压缩方式。
首先我们准备一张图片和一段代码,图片的信息如下:
图片.png
代码如下:
/**
* 得到bitmap的大小
*/
private int getBitmapSize(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //API 19
return bitmap.getAllocationByteCount();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12
return bitmap.getByteCount();
}
// 在低版本中用一行的字节x高度
return bitmap.getRowBytes() * bitmap.getHeight(); //earlier version
}
操作一:
直接获取图片的bitmap
Bitmap bitmap = BitmapFactory.decodeFile(FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "pic0.jpg");
图片解码成bitmap之后,计算出bitmap的大小是6.591797M,这个数据已经足够说明bitmap是有多么的消耗内存了,1.05M大小的图片尚且如此,那么10M大小的图片呢?
操作二:
修改图片的色彩模式
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeFile(FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "pic0.jpg", options);
大部分图片的默认色彩模式ARGB_8888
,我们常用的色彩模式有三种:
ARGB_8888:
图像默认色彩模式,本文举例使用的图片也是这个模式,这个模式一个像素占用4的字节;
其解码后的bitmap大小为:
6.591797M
ARGB_4444:
这个模式一个像素占用2的字节,由于其质量太低,已被ARGB_8888
替代;
其解码后的bitmap大小为:
3.2958984M
RGB_565:
手机屏幕默认色彩模式,这个模式一个像素占用2的字节;
其解码后的bitmap大小为:
3.2958984M
总结:
修改图片的色彩模式不能改变其分辨率,只能改变图片的大小。
操作三:
修改图片的分辨率
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeFile(FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "pic0.jpg", options);
inSampleSize
可以按照倍数修改,假如原图的分辨率是width x height,那么图片的最终分辨率是:
(width / inSampleSize) x (height / inSampleSize) = (1920 / 2) x (900 / 2) = 960 x 450
最终大小是:
实际大小 / (inSampleSize * 2) = 1.6479492M
操作四:
修改像素密度
BitmapFactory.Options options = new BitmapFactory.Options();
//设置这个Bitmap是否可以被缩放,默认值是true,表示可以被缩放。
options.inScaled = true;
DisplayMetrics dm = getResources().getDisplayMetrics();
//表示这个bitmap的像素密度,当inDensity为0时,系统默认赋值为屏幕当前像素密度
options.inDensity = dm.densityDpi;
//表示要被画出来时的目标像素密度,当inTargetDensity为0时,系统默认赋值为屏幕当前像素密度
options.inTargetDensity = options.inDensity * 2;
//表示实际设备的像素密度
options.inScreenDensity = 0;
Bitmap bitmap = BitmapFactory.decodeFile(FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "pic0.jpg", options);
图片分辨率缩放的比例是:
scale = inTargetDensity / inDensity
计算结果是:
宽:3840
高:1800
大小:26.367188M
有关像素密度的知识,我已经在这篇博客上介绍了图片加载<第二篇>:BitmapFactory.Options详解
操作五:
矩阵缩放
setScale(float sx, float sy)
按比例缩放图的分辨率。
BitmapFactory.Options options = new BitmapFactory.Options();
String filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "pic0.jpg";
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
if(bitmap == null){
return;
}
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
if(bm == null){
Log.d("aaa", "bitmap is null");
}else{
Log.d("aaa", "最终:"+String.valueOf(getBitmapSize(bm) * 1.0f / 1024 / 1024));
iv_load.setImageBitmap(bm);
}
操作六:
质量压缩
使用Bitmap的compress方法,可以将bitmap进行质量压缩
boolean compress(CompressFormat format, int quality, OutputStream stream)
返回值为true时,则压缩成功,否则压缩失败。format
表示压缩后的文件格式,JPEG、PNG、WEBPquality
压缩质量,取值范围是[0,100],100表示不压缩,0表示压缩到最小stream
将压缩后的数据保存到stream中
需要注意的是,compress
仅仅压缩质量,影响图片占用存储空间的大小,但不影响图片的分辨率。压缩之后图片会失真。
代码实现如下:
BitmapFactory.Options options = new BitmapFactory.Options();
String filePath = FileDirUtil.getInstance().getExternalStorageDirectory() + File.separator + "pic0.jpg";
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
Bitmap bitmap_1 = null;
int fileMaxSize = 100;//限定图片最大为300KB
//进行有损压缩
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options_ = 100;
bitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos);
//质量压缩方法,把压缩后的数据存放到baos中 (100表示不压缩,0表示压缩到最小)
int baosLength = baos.toByteArray().length;
Log.d("aaa", "压缩前的大小为:" + baosLength / 1024 + "KB");
while (baosLength / 1024 > fileMaxSize) {
//循环判断如果压缩后图片是否大于maxMemmorrySize,大于继续压缩
baos.reset();//重置baos即让下一次的写入覆盖之前的内容
options_ = Math.max(0, options_ - 10);
//图片质量每次减少10
bitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos);
//将压缩后的图片保存到baos中
baosLength = baos.toByteArray().length;
if (options_ == 0){
//如果图片的质量已降到最低则,不再进行压缩
break;
}
}
//可以将ByteArrayOutputStream写到FileOutputStream中,最终将流写到本地
//FileOutputStream fos = new FileOutputStream(filePath);
//baos.writeTo(fos);
//也可以使用baos生成一个新的bitmap
if(baosLength != 0){
Log.d("aaa", "压缩后的大小为:" + baosLength / 1024 + "KB");
bitmap = null;
bitmap_1 = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baosLength);
}
if(bitmap == null && bitmap_1 == null){
Log.d("aaa", "bitmap is null");
}else{
if(bitmap_1 != null){
Log.d("aaa", "最终1:"+String.valueOf(getBitmapSize(bitmap_1) * 1.0f / 1024 / 1024));
iv_load.setImageBitmap(bitmap_1);
}else{
Log.d("aaa", "最终2:"+String.valueOf(getBitmapSize(bitmap) * 1.0f / 1024 / 1024));
iv_load.setImageBitmap(bitmap);
}
}
现有图片压缩框架如下: