Android常用的图片压缩详解

Android客户端获取相册图片多张一起上传服务器时,如果不进行图片处理,可能会导致内存泄漏问题。这时图片压缩就至关重要了。但是又不能影响图片的清晰度。

一.质量压缩

详见:Bitmap详解(下)_jianning-wu的博客-CSDN博客

二.二分法压缩

1.思路

1.如果 传入图片字节大小,比要压缩的字节大小还小 直接返回不需要压缩。



2.否则 执行压缩


  首先读取100%的图片ByteArrayOutputStream对象 然后获取其字节的大小
  
  //首先读取100%的图片ByteArrayOutputStream对象
  bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
  //然后获取其字节的大小
  byteArrayOutputStream.size()


  如果100%的图片ByteArrayOutputStream对象的字节大小大于要压缩的值 需要压缩


  获取0%的图片ByteArrayOutputStream对象


  通过二分法不断找到[0,100]的某个位置 然后拿到这个最佳位置position 最终还是通过
  bitmap.compress(Bitmap.CompressFormat.JPEG, position, byteArrayOutputStream);方法
  获取ByteArrayOutputStream对象对象 然后获取ByteArrayOutputStream对象的字节流返回即可
  result = byteArrayOutputStream.toByteArray();

2.代码

/**
 * 图片质量压缩,采用二分法
 *
 * @param bytes       原图字节流
 * @param maxByteSize 压缩图片最大字节 K为单位
 *                    大致原理:bitmap.compress(Bitmap.CompressFormat.JPEG, midPosition, byteArrayOutputStream);方法
 *                    不断变化第二个参数 来压缩获取ByteArrayOutputStream对象 然后比较ByteArrayOutputStream对象的大小和要压缩的字节大小
 */

private byte[] compressByQuality(final byte[] bytes, final long maxByteSize) {
    if (bytes == null || bytes.length == 0) return null;
    //压缩图片最大字节
    long maxSize = maxByteSize * 1024;
    //图片字节流的大小
    int srcSize = bytes.length;
    //图片字节流的大小 小于等于 压缩图片最大字节 不需要压缩
    if (srcSize <= maxByteSize) return bytes;
    //图片字节流的大小 大于 压缩图片最大字节 二分法压缩
    //1.根据传入的图片字节流获取Bitmap
    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, srcSize);
    //2.读取100%质量的图片流ByteArrayOutputStream对象
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
    //3.result最终的图片字节数
    byte[] result;
    //如果 100%质量的图片流ByteArrayOutputStream对象的大小 还比要压缩的大小 小于等于 直接返回100%质量的图片流ByteArrayOutputStream对象
    if (byteArrayOutputStream.size() <= maxSize) {
        result = byteArrayOutputStream.toByteArray();
    } else {//需要压缩
        //4.收回掉100%质量的图片流ByteArrayOutputStream对象
        byteArrayOutputStream.reset();
        //5.获取0%质量的图片流ByteArrayOutputStream对象
        bitmap.compress(Bitmap.CompressFormat.JPEG, 0, byteArrayOutputStream);
        //如果 0%质量的图片流ByteArrayOutputStream对象的大小 还比要压缩的大小 大于等于 直接返回0%质量的图片流ByteArrayOutputStream对象
        if (byteArrayOutputStream.size() < maxSize) {// 二分法寻找最佳质量
            int startPosition = 0;
            int endPosition = 100;
            int midPosition = 0;
            while (startPosition < endPosition) {
                midPosition = (startPosition + endPosition) / 2;
                byteArrayOutputStream.reset();
                bitmap.compress(Bitmap.CompressFormat.JPEG, midPosition, byteArrayOutputStream);
                int len = byteArrayOutputStream.size();
                if (len == maxSize) {//找到位置对应的字节流等于要压缩的字节流 退出while循环
                    break;
                } else if (len > maxSize) {//中间位置的字节流大于要压缩的字节流 在前半部分找
                    endPosition = midPosition - 1;
                } else {//中间位置的字节流小于等于要压缩的字节流 在后半部分找
                    startPosition = midPosition + 1;
                }
            }
            if (endPosition == midPosition - 1) {
                byteArrayOutputStream.reset();
                bitmap.compress(Bitmap.CompressFormat.JPEG, startPosition, byteArrayOutputStream);
            }
        }
        result = byteArrayOutputStream.toByteArray();
    }
    //最后回收掉Bitmap
    bitmap.recycle();
    //返回图片字节流
    return result;
}

三.LuBan压缩

1.效果与对比

2.代码

private void lubanMethod(File file){
    Luban.with(this)//上下文对象
            .load(file)//原图路径
            .ignoreBy(100)//不压缩的阈值 单位为K 即图片小于此阈值时不压缩
            .setFocusAlpha(false)//设置是否保留透明通道
            .setTargetDir(setLuBanPath())//设置缓存压缩图片路径
            .filter(new CompressionPredicate() {
                @Override
                public boolean apply(String path) {
                    return !(TextUtils.isEmpty(path));//图片路径不为空 返回true压缩
                }
            })//设置开启压缩条件
            .setCompressListener(new OnCompressListener() {
                @Override
                public void onStart() {

                }

                @Override
                public void onSuccess(File file) {
                    addthumbArg(file);
                    Log.d("TAG","压缩成功后的路径:"+file.getAbsolutePath());
                }

                @Override
                public void onError(Throwable e) {

                }
            })//压缩回调接口
            .setRenameListener(new OnRenameListener() {
                @Override
                public String rename(String filePath) {
                    String result=savenum+".jpg";
                    savenum++;
                    return result;
                }
            })//压缩前重命名接口
            .launch();//压缩
}

3.效果

压缩前

压缩后

4.原文链接:https://github.com/Curzibn/Luban

5.代码详情:https://github.com/wujianning/LuBanDemo

6.算法

<1> 前言

Luban是图片压缩工具,通过参考或者自创压缩规则推求极致的压缩效果 目前的版本压缩效果主要参考微信。因为微信用户量最大,如果压缩后的图片越接近微信则越被用户接受。

<2> 说明

目前的Luban只是压缩结果接近微信,自身的算法只是为了达到这个效果而设计的。与微信并无任何联系,也不敢妄称是微信的算法。

<3> 算法步骤

(1) 判断图片比例值,是否处于以下区间内。
[1, 0.5625) 即图片处于 [1:1 ~ 9:16) 比例范围内
[0.5625, 0.5) 即图片处于 [9:16 ~ 1:2) 比例范围内
[0.5, 0) 即图片处于 [1:2 ~ 1:∞) 比例范围内

 

(2) 判断图片最长边是否过边界值。
[1, 0.5625) 边界值为:1664 * n(n=1), 4990 * n(n=2), 1280 * pow(2, n-1)(n≥3)
[0.5625, 0.5) 边界值为:1280 * pow(2, n-1)(n≥1)
[0.5, 0) 边界值为:1280 * pow(2, n-1)(n≥1)

 

(3) 计算压缩图片实际边长值,以第2步计算结果为准,超过某个边界值则:width / pow(2, n-1),height/pow(2, n-1)
 

(4) 计算压缩图片的实际文件大小,以第2、3步结果为准,图片比例越大则文件越大。
size = (newW * newH) / (width * height) * m;
[1, 0.5625) 则 width & height 对应 1664,4990,1280 * n(n≥3),m 对应 150,300,300;
[0.5625, 0.5) 则 width = 1440,height = 2560, m = 200;
[0.5, 0) 则 width = 1280,height = 1280 / scale,m = 500;注:scale为比例值

 

(5) 判断第4步的size是否过小
[1, 0.5625) 则最小 size 对应 60,60,100
[0.5625, 0.5) 则最小 size 都为 100
[0.5, 0) 则最小 size 都为 100

 

(6) 将前面求到的值压缩图片 width, height, size 传入压缩流程,压缩图片直到满足以上数值

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值