Android图片压缩

我这里把我目前所了解的图片压缩方式分为三种,以下一一道来,文章末尾附上一个小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.inJustDecodeBoundstrue,这样在不加载图片到内存的情况下计算图片的宽高,就不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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值