Android图片压缩(二次采样)

一、简介:

在开发过程中,我们或多或少的都会接触到Bitmap这个东西,用的不好的话就会出现OOM问题,同时,也会有压缩的需求,可能有人会说,向Glide不是已经对图片压缩了么,但有时向图片上传到服务器功能,还得需要我们手动处理,去压缩图片后,再上传,否则,会造成上传很慢,尤其是用户网速不好的时候,还会浪费用户流量,甚至上传失败。

二、首先了解一下关于Bitmap的Config的理解

1、)A:透明度 R:红色 G:绿 B:蓝

public enum Config {
    ALPHA_8     (1),
    RGB_565     (3),
    @Deprecated
    ARGB_4444   (4),
    ARGB_8888   (5);
}

Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位

Bitmap.Config ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位

Bitmap.Config RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位

Bitmap.Config ALPHA_8:每个像素占四位,只有透明度,没有颜色。

2、)内存计算

一张 1024 * 1024 像素,采用ARGB8888格式,一个像素32位,每个像素就是4字节,占有内存就是4M若采用RGB565,一个像素16位,每个像素就是2字节,占有内存就是2M。
Glide加载图片默认格式RGB565,Picasso为ARGB8888,默认情况下,Glide占用内存会比Picasso低,色彩不如Picasso鲜艳,自然清晰度就低。
通常我们优化Bitmap时,当需要做性能优化或者防止OOM(Out Of Memory),我们通常会使用Bitmap.Config.RGB_565这个配置,因为Bitmap.Config.ALPHA_8只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多。

图片加载
如果我们想要加载一张大图到内存中,如果不进行压缩的话,那么很显然就会出现OOM的崩溃,

《Android图片压缩(二次采样)》

譬如我们加载一张5440*3000的大图到手机上面,如果不进行压缩处理的话,那么就会出现OOM。
错误日志如下

java.lang.OutOfMemoryErrorandroid.graphics.BitmapFactory.nativeDe
codeStream(Native 
Method)android.graphics.BitmapFactory.decodeStreamInternal(Bitmap
Factory.java:703)android.graphics.BitmapFactory.decodeStream(Bitm
apFactory.java:679)android.graphics.BitmapFactory.decodeFile(Bitma
pFactory.java:446)android.graphics.BitmapFactory.decodeFile(Bitmap
Factory.java:480)com.example.ly.bitmapdemo.MainActivity.onCreate(M
ainActivity.java:21)

导致这种情况的发生的根据原因就是内存溢出,Android给每个APP的内存都是有限的,所以不能容忍这种情况的发生,所以我们就必须进行压缩一下。
压缩后的效果如下:

《Android图片压缩(二次采样)》

图片压缩

我们在上传一张图片到服务器时一般都会先进行压缩一下,这样不仅可以节省流量同时也可以节约上传的时间。最近碰到项目里碰到一个问题:图片上传时,有时会出现超时的问题,客户那边出现的频率非常高,而我的手机却基本没出现过,检查了一下原因,大概有两个:
客户的网速不是太好,3G速度不够,上传图片需要很长时间导致超时产生。图片很大,占了3、4M,没经过压缩直接上传,导致超时产生。

针对以上两种情况,主要对于第二种原因进行优化。总结来说就是将图片进行压缩再上传,经过一系列的操作,发现超时现象基本不会出现了,原先2M的图片,经过压缩只有50Kb不到,压缩率达到60%,效果很明显。

原理分析

如何将一张大图压缩到100kb以下并且保持不失真的特性?这就需要用到下面这个类了BitmapFactory.Options

BitmapFactory.Options缩放图片主要用到inSample采样率,

inSample = 1,采样后图片的宽高为原始宽高
inSample > 1,例如2,宽高均为原图的宽高的1/2

一个采用ARGB8888的1024 *1024 的图片
inSample = 1,占用内存就 1024 *1024 *4 = 4M
inSample = 2,占用内存就 512 *512 * 4 = 1M

BitmapFactory 给我们提供了一个解析图片大小的参数类 BitmapFactory.Options ,把这个类的对象的 inJustDecodeBounds 参数设置为 true,这样解析出来的 Bitmap 虽然是个 null,但是 options 中可以得到图片的宽和高以及图片的类型。得到了图片实际的宽和高之后我们就可以进行压缩设置了,主要是计算图片的采样率。

       //第一次采样
        BitmapFactory.Options options = new BitmapFactory.Options();
        //该属性设置为true只会加载图片的边框进来,并不会加载图片具体的像素点,也就是说不会把图片加载到内存中
        options.inJustDecodeBounds = true;
        //第一次加载图片,这时只会加载图片的边框进来,并不会加载图片中的像素点
        BitmapFactory.decodeFile(filePath, options);
        //获得原图的宽和高
        int outWidth = options.outWidth;
        int outHeight = options.outHeight;

接下来就需要进行选定压缩的采样率了。目前市场上的主流手机分辨率一般最低是720-1280了所以就按照此分辨率进行压缩

//原始图片的宽度与720的比值,然后向上取整这里为8 
    int wRatio = (int) Math.ceil(options.outWidth / (float) 720);
 //原始图片的高度与1280的比值,然后向上取整这里为3
     int hRatio = (int) Math.ceil(options.outHeight / (float) 1280); //获取采样率 
      if (wRatio > 1 && hRatio > 1) {
      if (wRatio > hRatio) {
            options.inSampleSize = wRatio; 
           } else { 
              options.inSampleSize = hRatio; 
      } 
  }

经过上面这个采样率进行压缩后的宽和高肯定是小于720-1270的,我们计算的结果是:680-375

《Android图片压缩(二次采样)》

我们来实际比较一下压缩结果:

原先:54403000*如果采用ARGB_8888模式的话,那么如果不压缩直接加载到内存的话,那么它将占:

5440/1024 *3000/1024 *4 = 62.25M,不崩溃才怪呢~

那么现在:680375*见证奇迹的时候,680/1024 *375/1024 *4=0.9M

两者一比较的话,那么效果还是比较明显的,相差大约64倍,所以还是可以的。当然了经过上面的压缩方法,我们将压缩后的图片上传到服务器的话,那么将会大大的减少流量同时也会减少上传超时的几率的。

当然了,如果还嫌大的话,我们可以进一步增加压缩的比例,可以设置成480800*,那么这样的话,质量肯定是有所下降的。

这里是图片二次采样的代码

public class BitmapUtils {
    /**
     * @param filePath   要加载的图片路径
     * @param destWidth  显示图片的控件宽度
     * @param destHeight 显示图片的控件的高度
     * @return
     */
    public static Bitmap getBitmap(String filePath, int destWidth, int destHeight) {
        //第一次采样
        BitmapFactory.Options options = new BitmapFactory.Options();
        //该属性设置为true只会加载图片的边框进来,并不会加载图片具体的像素点
        options.inJustDecodeBounds = true;
        //第一次加载图片,这时只会加载图片的边框进来,并不会加载图片中的像素点
        BitmapFactory.decodeFile(filePath, options);
        //获得原图的宽和高
        int outWidth = options.outWidth;
        int outHeight = options.outHeight;
        //定义缩放比例
        int sampleSize = 1;
        while (outHeight / sampleSize > destHeight || outWidth / sampleSize > destWidth) {
            //如果宽高的任意一方的缩放比例没有达到要求,都继续增大缩放比例
            //sampleSize应该为2的n次幂,如果给sampleSize设置的数字不是2的n次幂,那么系统会就近取值
            sampleSize *= 2;
        }
        /********************************************************************************************/
        //至此,第一次采样已经结束,我们已经成功的计算出了sampleSize的大小
        /********************************************************************************************/
        //二次采样开始
        //二次采样时我需要将图片加载出来显示,不能只加载图片的框架,因此inJustDecodeBounds属性要设置为false
        options.inJustDecodeBounds = false;
        //设置缩放比例
        options.inSampleSize = sampleSize;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        //加载图片并返回
        return BitmapFactory.decodeFile(filePath, options);
    }
}
    原文作者:唠嗑008
    原文地址: https://www.jianshu.com/p/04d18e83eb87
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞