详解Bitmap尺寸压缩与质量压缩

转载请注明出自flowsky37的博客,尊重他人辛苦劳动!

在Android系统中,关于图片处理的是一个既常见又比较棘手的问题。一个应用中,存在需要展示大量的图片的布局,那我们必须要小心翼翼的处理了,不然OOM会就像一个定时炸弹一样出现。由于大量位图加载导致的内存溢出是Android中内存溢出常见场景之一。关于图片的处理,一般情况下就是要对bitmap进行合适的处理跟优化。

在Android应用中,图片的主要存在方式:

  • 以File的形式存在于SD卡中
  • 以Stream的形式存在于内存中
  • 以Bitmap形式存在于内存中

在SD卡中图片占用的内存与以Stream形式大小一样,均小于Bitmap形式下内存占比。既当图片从SD卡中以流的形式加载到内存中时大小是不会发生变化的,但是stream转化为Bitmap时,其大小会突然变大。这也是为什么当需要加载大量图片时容易出现OOM的主要原因。

BitmapFactory提供了4类方法加载Bitmap对象:

 1. decodeFile 从文件中加载Bitmap对象
 2. decodeResource 从资源中加载Bitmap对象
 3. decodeStream 从输入流中加载Bitmap对象
 4. decodeByteArray 从字节数组中加载Bitmap对象

其中decodeFile和decodeResource间接的调用了decodeStream方法。

为什么转化为Bitmap时大小会突然变大呢?

以任意一张图片为例,我本地存了一张分辨率为750*1334,大小为119K。如果将这张图片以bitmap形式加载到内存中,它占用的大小是多少,如何计算呢?它的计算公式:

图片的占用内存 = 图片的长度(像素单位) * 图片的宽度(像素单位) * 单位像素所占字节数

其中单位占用字节数的大小是变化的,由BitmapFactory.Options的inPreferredConfig
决定,为Bitmap.Config类型,一般情况下默认为:ARGB_8888。不同类型占字节大小如下表所示:

这里写图片描述

可以计算出此图片以bitmap形式加载消耗内存为:750*1334*4=3.81M,还是由于我测试的图片任意找的一张图,现在手机拍的高清图基本上都是2000+ * 2000+ * 4 = 15M+ 了。如果不做任何处理的图片大量使用到我们APP上,结局你懂得!

现在对于图片处理基本两大方式:

  • 尺寸压缩

尺寸压缩会改变图片的尺寸,即压缩图片宽度和高度的像素点,从上面的计算公式我们可以得知,这样会降低图片由Stream转化为bitmap内存的占用,从而一定程度上减少OOM的概率。但是要注意,如果压缩比太大,也会由于像素点降低导致图片失真严重,最后图片有高清成了马赛克。

如何进行尺寸压缩呢?就是采用BitmapFactory.Options来加载所需要的尺寸。记住,是加载所需要合适的尺寸!例如一个ImageView显示图片,基本上ImageView所需要的尺寸不需要原始图片那么大,这时候我们就需要对图片进行一些必要的处理。按照ImageView尺寸大小,通过BitmapFactory.Options来设置恰当的inSampleSize参数值来压缩图片,显示在ImageView上,既避免了出现了OOM,也提高了Bitmap的加载性能。BitmapFactory提供了加载图片的四个方法都支持BitmapFactory.Options参数,通过他们就可以实现对图片进行压缩处理。

通过BitmapF.Options压缩图片的核心就在于inSampleSize参数,采样率或者采样率。采样率为整数,且为2的n次幂,n可以为0。即采样率为1,处理后的图片尺寸与原图一致。当采样率为2时,即宽、高均为原来的1/2,像素则为原来的1/4.其占有内存也为原来的1/4。当设置的采样率小于1时,其效果与1一样。当设置的inSampleSize大于1,不为2的指数时,系统会向下取一个最接近2的指数的值。

举个例子:
当imageView的尺寸为200*300,,图片的原始尺寸为600 * 900,则压缩比为3即可。但是如果图片尺寸为1000 * 1800呢?如果采样率设置为5,缩放后的图片尺寸为 200*360,此时还是能接受的。但是如果采样率设置为6,缩放后的图片会小于200 * 300。此时图片会被拉伸导致变形。

压缩图片及获取采样率常规步骤:

 1. 将BitmapFactory.Options的inJustDecodeBounds参数设置为true加载图片。
 2. 从BitmapFactory.Options中获取图片原始的宽高(outWidth/outHeight)值。
 3. 根据采样率规则并结合所需要大小计算出合适的inSampleSize。
 4. BitmapFactory.Options参数设置为false,然后重新加载图片。

在上面4步处理后图片基本上就是缩放后的图片了。解释一下inJustDecodeBounds参数,当设置为true时,BitmapFactory只会解析图片的原始宽/高信息,不会真正的去加载图片,所以这个操作是轻量级的。

示例代码:

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class ImageUtil {


    /**
     * 根据目标View的尺寸压缩图片返回bitmap
     * @param resources
     * @param resId
     * @param width 目标view的宽
     * @param height 目标view的高
     * @return
     */
    public static Bitmap decodeBitmapFromResource(Resources resources, int resId,int width ,int height){

        BitmapFactory.Options options = new BitmapFactory.Options();

        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(resources,resId,options);
        //获取采样率
        options.inSampleSize = calculateInSampleSize(options,width,height);

        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeResource(resources,resId,options);

    }

    /**
     * 获取采样率
     * @param options
     * @param reqWidth 目标view的宽
     * @param reqHeight 目标view的高
     * @return 采样率
     */
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

        int originalWidth = options.outWidth;
        int originalHeight = options.outHeight;

        int inSampleSize = 1;


        if (originalHeight > reqHeight || originalWidth > reqHeight){
            int halfHeight = originalHeight / 2;
            int halfWidth = originalWidth / 2;
            //压缩后的尺寸与所需的尺寸进行比较
            while ((halfWidth / inSampleSize) >= reqHeight && (halfHeight /inSampleSize)>=reqWidth){
                inSampleSize *= 2;
            }

        }

        return inSampleSize;



    }
}

上面示例代码中采用的是decodeResource方法,其它几个方法基本类似.

  • 质量压缩

    质量压缩不会改变图片的像素点,即前面我们说到的在转化为bitmap时占用内存变大时状况不会得到改善。但是能方便的设置压缩百分比,达到我们需要的大小。还是先看方法:
    Bitmap.compress(CompressFormat format, int quality, OutputStream stream)

    简单解释一下这三个参数,第一个表示Bitmap被压缩成的图片格式;第二个表示压缩的质量控制,范围0~100,很好理解。quality为80,表示压缩为原来80%的质量效果。有些格式,例如png,它是无损的,这个值设置了也是无效的。因为质量压缩是在保持像素前提下改变图片的位深及透明度等来压缩图片的,所以quality值与最后生成的图片的大小并不是线性关系,比如大小为 300k的图片,当quality为90时,得到的图片大小并不是为270K。大家通过代码测试一下就会发现了。

    核心代码:

/**
     * 质量压缩并存到SD卡中
     * @param bitmap
     * @param reqSize 需要的大小
     * @return
     */

    public static String qualityCompress1(Bitmap bitmap ,int reqSize){

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //这里100表示不压缩,把压缩后的数据存放到baos中
        bitmap.compress(Bitmap.CompressFormat.JPEG,100,baos);
        int options = 95;
        //如果压缩后的大小超出所要求的,继续压缩
        while (baos.toByteArray().length / 1024 > reqSize){
            baos.reset();
            bitmap.compress(Bitmap.CompressFormat.JPEG,options,baos);

            //每次减少5%质量
            if (options>5){//避免出现options<=0
                options -=5;
            } else {
                break;
            }

        }


        //存入SD卡中
        SimpleDateFormat formatYMD = new SimpleDateFormat("yyyy/MM/dd");

        String compressImgUri = LOCAL_URL + "000/"
                + formatYMD.format(new Date()) + "/" + System.currentTimeMillis() + "a.jpg";

        File outputFile = new File(compressImgUri);
            if (!outputFile.exists()) {
                outputFile.getParentFile().mkdirs();
            } else {
                outputFile.delete();
            }
            FileOutputStream out = null;
            try {
                out = new FileOutputStream(outputFile);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

            bitmap.compress(Bitmap.CompressFormat.JPEG, options, out);

            return outputFile.getPath();

    }

代码相当简单,前面基本解释过了。

图片很多时压缩的过程比较耗时,不要放到主线程执行。由于质量压缩并不会减少图片转换为bitmap时的内存消耗,避免出现OOM,建议先进行合适的尺寸压缩,然后再进一步进行质量压缩。

如果有不明白的,可以留言;如果某些地方有误,非常欢迎指出,谢谢!

项目地址:点击下载

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值