Android Bitmap 压缩处理

00.Bitmap的压缩方式

  • 常见压缩方法Api
    • Bitmap.compress(),质量压缩,不会对内存产生影响;
    • BitmapFactory.Options.inSampleSize,内存压缩;
  • Bitmap.compress()质量压缩
    • 质量压缩,不会对内存产生影响
    • 它是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,不会减少图片的像素。进过它压缩的图片文件大小会变小,但是解码成bitmap后占得内存是不变的。
  • BitmapFactory.Options.inSampleSize内存压缩
    • 内存压缩
    • 解码图片时,设置BitmapFactory.Options类的inJustDecodeBounds属性为true,可以在Bitmap不被加载到内存的前提下,获取Bitmap的原始宽高。而设置BitmapFactory.Options的inSampleSize属性可以真实的压缩Bitmap占用的内存,加载更小内存的Bitmap。
    • 设置inSampleSize之后,Bitmap的宽、高都会缩小inSampleSize倍。例如:一张宽高为2048x1536的图片,设置inSampleSize为4之后,实际加载到内存中的图片宽高是512x384。占有的内存就是0.75M而不是12M,足足节省了15倍。
    • 备注:inSampleSize值的大小不是随便设、或者越大越好,需要根据实际情况来设置。inSampleSize比1小的话会被当做1,任何inSampleSize的值会被取接近2的幂值。

01.Bitmap质量压缩

  • 质量压缩方法:在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,这样适合去传递二进制的图片数据,比如分享图片,要传入二进制数据过去,限制500kb之内。

    • 1、bitmap图片的大小不会改变
    • 2、bytes.length是随着quality变小而变小的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**
    * 第一种:质量压缩法
    * @param image 目标原图
    * @param maxSize 最大的图片大小
    * @return bitmap,注意可以测试以下压缩前后bitmap的大小值
    */
    public static Bitmap compressImage(Bitmap image , long maxSize) {
    int byteCount = image.getByteCount();
    Log.i("yc压缩图片","压缩前大小"+byteCount);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把ByteArrayInputStream数据生成图片
    Bitmap bitmap = null;
    // 质量压缩方法,options的值是0-100,这里100表示原来图片的质量,不压缩,把压缩后的数据存放到baos中
    image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
    int options = 90;
    // 循环判断如果压缩后图片是否大于maxSize,大于继续压缩
    while (baos.toByteArray().length > maxSize) {
    // 重置baos即清空baos
    baos.reset();
    // 这里压缩options%,把压缩后的数据存放到baos中
    image.compress(Bitmap.CompressFormat.JPEG, options, baos);
    // 每次都减少10,当为1的时候停止,options<10的时候,递减1
    if(options == 1){
    break;
    }else if (options <= 10) {
    options -= 1;
    } else {
    options -= 10;
    }
    }
    byte[] bytes = baos.toByteArray();
    if (bytes.length != 0) {
    // 把压缩后的数据baos存放到bytes中
    bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    int byteCount1 = bitmap.getByteCount();
    Log.i("yc压缩图片","压缩后大小"+byteCount1);
    }
    return bitmap;
    }

    /**

    • 第一种:质量压缩法
    • @param src 源图片
    • @param maxByteSize 允许最大值字节数
    • @param recycle 是否回收
    • @return 质量压缩压缩过的图片
      */
      public static Bitmap compressByQuality(final Bitmap src, final long maxByteSize, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0 || maxByteSize <= 0) {
      return null;
      }
      Log.i(“yc压缩图片”,”压缩前大小”+src.getByteCount());
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
      byte[] bytes;
      if (baos.size() <= maxByteSize) {// 最好质量的不大于最大字节,则返回最佳质量
      bytes = baos.toByteArray();
      } else {
      baos.reset();
      src.compress(Bitmap.CompressFormat.JPEG, 0, baos);
      if (baos.size() >= maxByteSize) { // 最差质量不小于最大字节,则返回最差质量
      bytes = baos.toByteArray();
      } else {
      // 二分法寻找最佳质量
      int st = 0;
      int end = 100;
      int mid = 0;
      while (st < end) {
      mid = (st + end) / 2;
      baos.reset();
      src.compress(Bitmap.CompressFormat.JPEG, mid, baos);
      int len = baos.size();
      if (len == maxByteSize) {
      break;
      } else if (len > maxByteSize) {
      end = mid - 1;
      } else {
      st = mid + 1;
      }
      }
      if (end == mid - 1) {
      baos.reset();
      src.compress(Bitmap.CompressFormat.JPEG, st, baos);
      }
      bytes = baos.toByteArray();
      }
      }
      if (recycle && !src.isRecycled()){
      src.recycle();
      }
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
      Log.i(“yc压缩图片”,”压缩后大小”+bitmap.getByteCount());
      return bitmap;
      }

    /**

    • 第一种:质量压缩法
    • @param src 源图片
    • @param quality 质量
    • @param recycle 是否回收
    • @return 质量压缩后的图片
      */
      public static Bitmap compressByQuality(final Bitmap src, @IntRange(from = 0, to = 100) final int quality, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
      return null;
      }
      Log.i(“yc压缩图片”,”压缩前大小”+src.getByteCount());
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      src.compress(Bitmap.CompressFormat.JPEG, quality, baos);
      byte[] bytes = baos.toByteArray();
      if (recycle && !src.isRecycled()) {
      src.recycle();
      }
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
      Log.i(“yc压缩图片”,”压缩后大小”+bitmap.getByteCount());
      return bitmap;
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9


      ### 02.Bitmap采样率压缩

      - 什么是采样率压缩?

      - 设置inSampleSize的值(int类型)后,假如设为n,则宽和高都为原来的1/n,宽高都减少,内存降低。上面的代码没用过options.inJustDecodeBounds = true;因为我是固定来取样的数据,
      - 为什么这个压缩方法叫采样率压缩?是因为配合inJustDecodeBounds,先获取图片的宽、高(这个过程就是取样)。然后通过获取的宽高,动态的设置inSampleSize的值。当inJustDecodeBounds设置为true的时候, BitmapFactory通过decodeResource或者decodeFile解码图片时,将会返回空(null)的Bitmap对象,这样可以避免Bitmap的内存分配, 但是它可以返回Bitmap的宽度、高度以及MimeType。

      /**
    • 第二种:按采样大小压缩
    • @param src 源图片
    • @param sampleSize 采样率大小
    • @param recycle 是否回收
    • @return 按采样率压缩后的图片
      */
      public static Bitmap compressBySampleSize(final Bitmap src, final int sampleSize, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
      return null;
      }
      Log.i(“yc压缩图片”,”压缩前大小”+src.getByteCount());
      BitmapFactory.Options options = new BitmapFactory.Options();
      options.inSampleSize = sampleSize;
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
      byte[] bytes = baos.toByteArray();
      if (recycle && !src.isRecycled()) {
      src.recycle();
      }
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
      Log.i(“yc压缩图片”,”压缩后大小”+bitmap.getByteCount());
      return bitmap;
      }

    /**

    • 第二种:按采样大小压缩
    • @param src 源图片
    • @param maxWidth 最大宽度
    • @param maxHeight 最大高度
    • @param recycle 是否回收
    • @return 按采样率压缩后的图片
      */
      public static Bitmap compressBySampleSize(final Bitmap src, final int maxWidth, final int maxHeight, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
      return null;
      }
      Log.i(“yc压缩图片”,”压缩前大小”+src.getByteCount());
      BitmapFactory.Options options = new BitmapFactory.Options();
      options.inJustDecodeBounds = true;
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      src.compress(Bitmap.CompressFormat.JPEG, 100, baos);
      byte[] bytes = baos.toByteArray();
      BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
      options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
      options.inJustDecodeBounds = false;
      if (recycle && !src.isRecycled()) {
      src.recycle();
      }
      Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
      Log.i(“yc压缩图片”,”压缩后大小”+bitmap.getByteCount());
      return bitmap;
      }

    /**

    • 计算获取缩放比例inSampleSize
      */
      private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
      final int height = options.outHeight;
      final int width = options.outWidth;
      int inSampleSize = 1;
      if (height > reqHeight || width > reqWidth) {
      final int heightRatio = Math.round((float) height / (float) reqHeight);
      final int widthRatio = Math.round((float) width / (float) reqWidth);
      inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
      }
      final float totalPixels = width * height;
      final float totalReqPixelsCap = reqWidth * reqHeight * 2;
      while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
      inSampleSize++;
      }
      return inSampleSize;
      }
      1
      2
      3
      4
      5
      6
      7
      8


      ### 03.Bitmap缩放法压缩

      - Android中使用Matrix对图像进行缩放、旋转、平移、斜切等变换的。

      - Matrix提供了一些方法来控制图片变换:Matrix调用一系列set,pre,post方法时,可视为将这些方法插入到一个队列。当然,按照队列中从头至尾的顺序调用执行。其中pre表示在队头插入一个方法,post表示在队尾插入一个方法。而set表示把当前队列清空,并且总是位于队列的最中间位置。当执行了一次set后:pre方法总是插入到set前部的队列的最前面,post方法总是插入到set后部的队列的最后面

      setTranslate(float dx,float dy):控制Matrix进行位移。
      setSkew(float kx,float ky):控制Matrix进行倾斜,kx、ky为X、Y方向上的比例。
      setSkew(float kx,float ky,float px,float py):控制Matrix以px、py为轴心进行倾斜,kx、ky为X、Y方向上的倾斜比例。
      setRotate(float degrees):控制Matrix进行depress角度的旋转,轴心为(0,0)。
      setRotate(float degrees,float px,float py):控制Matrix进行depress角度的旋转,轴心为(px,py)。
      setScale(float sx,float sy):设置Matrix进行缩放,sx、sy为X、Y方向上的缩放比例。
      setScale(float sx,float sy,float px,float py):设置Matrix以(px,py)为轴心进行缩放,sx、sy为X、Y方向上的缩放比例。
      1
      2
      3

      - 缩放法压缩工具类代码

      /**
    • 第三种:按缩放压缩
    • @param src 源图片
    • @param newWidth 新宽度
    • @param newHeight 新高度
    • @param recycle 是否回收
    • @return 缩放压缩后的图片
      */
      public static Bitmap compressByScale(final Bitmap src, final int newWidth, final int newHeight, final boolean recycle) {
      return scale(src, newWidth, newHeight, recycle);
      }

    public static Bitmap compressByScale(final Bitmap src, final float scaleWidth, final float scaleHeight, final boolean recycle) {
    return scale(src, scaleWidth, scaleHeight, recycle);
    }

    /**

    • 缩放图片
    • @param src 源图片
    • @param scaleWidth 缩放宽度倍数
    • @param scaleHeight 缩放高度倍数
    • @param recycle 是否回收
    • @return 缩放后的图片
      */
      private static Bitmap scale(final Bitmap src, final float scaleWidth, final float scaleHeight, final boolean recycle) {
      if (src == null || src.getWidth() == 0 || src.getHeight() == 0) {
      return null;
      }
      Matrix matrix = new Matrix();
      matrix.setScale(scaleWidth, scaleHeight);
      Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
      if (recycle && !src.isRecycled()) {
      src.recycle();
      }
      return ret;
      }