美文网首页图片学习程序猿
Android图片压缩以及优化

Android图片压缩以及优化

作者: 08_carmelo | 来源:发表于2017-06-05 23:16 被阅读4843次

    前言

    图片压缩在Android技术中已经属于烂大街,上周看了2个开源库然后对自己项目的压缩做了对比,发现一些新东西,记录与此。

    为何要压缩

    • 体积的原因
      如果你的图片是要准备上传的,那动辄几M的大小肯定不行的,况且图片分辨率大于设备分辨率的话毫无意义。
    • 内存原因
      如果图片要显示下Android设备上,ImageView最终是要加载Bitmap对象的,就要考虑单个Bitmap对象的内存占用了,如何计算一张图片的加载到内存的占用呢?其实就是所有像素的内存占用总和:
      bitmap内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
      起决定因素就是最后那个参数了,Bitmap'常见有2种编码方式:ARGB_8888和RGB_565,ARGB_8888每个像素点4个byte,RGB_565是2个byte,一般都采用ARGB_8888这种。那么常见的1080*1920的图片内存占用就是:
      1920 x 1080 x 4 = 7.9M

    压缩原理

    从上面可以总结出,图片压缩应该从两个方面入手同时进行:先是降低分辨率,然后降低每个像素的质量也就是内存占用。

    分辨率压缩

    假设有张原图是3840x2400,我想压缩成1920x1080,实际是不可能100%能压缩这个值的。因为图片压缩要保证宽高比,试想一下800x100的横向图可能压成20x200竖向图吗? 不可能的.。这里常见的算法就是在1920x1080的范围内保证较短边,然后按照比例压缩整个图:

    这里原图的宽高比是 3840/2400 = 1.6,目标图的宽高比是1920/1080 = 1.78>1.6,较短边是高。所以就应该按照高的比例来压缩。
    2400/1080=2.22,这样真实目标值就是:1728x1080,压缩比四舍五入是:2,然后通过下面代码进行压缩:

      private Bitmap compressPixel(String filePath){
        Bitmap bmp = null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        //setting inSampleSize value allows to load a scaled down version of the original image
        options.inSampleSize = 2;
    
        //inJustDecodeBounds set to false to load the actual bitmap
        options.inJustDecodeBounds = false;
        options.inTempStorage = new byte[16 * 1024];
        try {
          //load the bitmap from its path
          bmp = BitmapFactory.decodeFile(filePath, options);
          if (bmp == null) {
    
            InputStream inputStream = null;
            try {
              inputStream = new FileInputStream(filePath);
              BitmapFactory.decodeStream(inputStream, null, options);
              inputStream.close();
            } catch (FileNotFoundException exception) {
              exception.printStackTrace();
            } catch (IOException exception) {
              exception.printStackTrace();
            }
          }
        } catch (OutOfMemoryError exception) {
          exception.printStackTrace();
        }finally {
          return bmp;
        }
      }
    

    看起来没什么问题,看看实测结果,原图 3840*2400,大小2.2M,我选4个分辨率当做目标值来压缩:

    image.png

    可以看出压缩后的4张图没有一张达到目标值,而且偏差较大,原因就是options.inSampleSize这个属性,他只能是2的N次方,如果算出来是7,Android会取近似值8,以此类推导致这个值不能压缩到目标值。看了一下Compressor这个开源库他对此做了处理,把压缩后的图片在Canvas上面按照目标尺寸重绘,得到一个新的bitmap:

    核心代码:

    Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale(ratioX, ratioY, 0, 0);
    
        Canvas canvas = new Canvas(scaledBitmap);
        canvas.setMatrix(scaleMatrix);
        canvas.drawBitmap(bmp, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
    

    用Compressor开源库压缩的图片对比下:

    image.png

    可以看出每次都能压缩到真实目标值。(注意不是目标值,注意区分目标值和真实目标值)

    质量压缩

    Bitmap有个方法 compress(CompressFormat format, int quality, OutputStream stream),quality就是压缩质量传入0-100,数值越小压缩的越厉害。
    不过我们一般不直接设置这个数值,而是自定义一个压缩后大小比如300KB,然后动态计算这个quality,核心代码:

    //进行有损压缩ByteArrayOutputStream baos = new ByteArrayOutputStream();int options_ = 100;actualOutBitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos);//质量压缩方法,把压缩后的数据存放到baos中 (100表示不压缩,0表示压缩到最小)int baosLength = baos.toByteArray().length;while (baosLength / 1024 > maxFileSize) {//循环判断如果压缩后图片是否大于maxMemmorrySize,大于继续压缩 baos.reset();//重置baos即让下一次的写入覆盖之前的内容 options_ = Math.max(0, options_ - 10);//图片质量每次减少10 actualOutBitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos);//将压缩后的图片保存到baos中 baosLength = baos.toByteArray().length; if (options_ == 0)//如果图片的质量已降到最低则,不再进行压缩 break;}
    

    压缩实践

    • 目前成熟的开源库有Luban:https://github.com/Curzibn/Luban
      这个开源库算法比较复杂,根据效果图前后对比逆向推算了微信朋友圈的压缩,最后效果和微信差不多,如果你对压缩要求很高可以使用这个。不过方法调用是异步的,回调形式反馈结果,这个不太好。。
    • Compressor:https://github.com/zetbaitsu/Compressor
      这个开源库就是在普通的压缩算法上做了优化改进,源码很容易看懂,推荐!下面是用Compressor对三张大图不同目标值做的压缩测试(BV是我们项目的压缩,忽略就好),质量参数设的是80%
    image.png

    相关文章

      网友评论

      • sIN_110803:差评 我觉得作者自己都没有十分理解这个。。。写得太高深了
      • d07fbfa57fea:你这原理上有点问题;
        1,图片文件大小 和 图片内存大小,两个值计算方式完全不一样的。
        2,图片文件大小 约等于 宽度 * 高度 * (位深/8) * (1 - 压缩算法效率)

        质量压缩其实不可逆的压缩方式,因为它实际上是将多个相近的像素,近似成一个像素,从而减少占用的文件大小。但它单个像素占用的byte值并不会减少。除非重新生成的bitmap
      • 920611c8bbbb:大佬,你说,“图片分辨率大于设备分辨率的话毫无意义”,这个要怎么定义呢?比如我上传一张长图5400x600分辨率的,不可能压缩成1920x1080吧?
        920611c8bbbb:@锅拌饭 好的。谢谢
        08_carmelo:是楼上的意思。比手机分辨率大的图片,没啥作用(不会更加精细)
        eca08540232d:“图片分辨率大于设备分辨率的话毫无意义”,这句话是针对本地显示的。
      • 01654f526b77:Compressor循环遍历会报错
        08_carmelo:@木鱼而歌 好像是有这么回事,记不太清
        阿满学习计划:是啊,后来我把他的库下下来,改了源码
        08_carmelo:@爱拍照的程序猿 哪个位置?什么错

      本文标题:Android图片压缩以及优化

      本文链接:https://www.haomeiwen.com/subject/kspbfxtx.html