图片加载5:图片压缩

随着科技的发展,越来越多的移动设备被推出,其相机的清晰度是一个重大的特色,提升拍照的分辨率以及像素密度可以增加图像的清晰度,但是这样会导致拍照之后的图像占用内存比较大,一张图片大小达到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,我们常用的色彩模式有三种:

扫描二维码关注公众号,回复: 8974847 查看本文章

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、WEBP
quality 压缩质量,取值范围是[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);
        }
    }

现有图片压缩框架如下:

Compressor

发布了122 篇原创文章 · 获赞 30 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qijingwang/article/details/101224917