美文网首页
android中Bitmap缩放、压缩、占用内存计算等

android中Bitmap缩放、压缩、占用内存计算等

作者: emdd2016 | 来源:发表于2018-12-08 12:27 被阅读12次

本篇文章主要搞定的问题:

  • (1)Bitmap占用的手机内存怎样计算? 占用内存的大小和那些因素相关?

  • (2)质量压缩

  • (3)尺寸压缩

Bitmap vs 内存

图片占用内存大小的相关因素:

  • (1)图片的宽高

  • (2)图片单位像素占用的字节数

++占用的内存 = 图片的宽(像素) x 图片的高(像素) x 单位像素占用的字节数++

图片常用的压缩格式及单位像素分别占用的字节数

** A -- 透明度 R --红色 G -- 绿色 B--蓝色

  • ALPHA_8 : 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度

  • ARGB_4444 : 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节

  • ARGB_8888 : 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节(Android默认的压缩格式)

  • RGB_56 : 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节

  • RGBA_F16 : 8 个字节

质量压缩

质量压缩是保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的。
图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
质量压缩对png格式的图片没有作用, 因为png格式的图片是 无损压缩的。
质量压缩是耗时的操作,故一般不能放在主线程中进行操作,可能会导致ANR。


    /**

    * 将bitmap按照最大的file size保存到指定文件

    *

    * @param picFilePath 待保存的文件路径

    * @param bitmap      图片

    * @param maxSize    限制保存后的最大大小 单位B

    */

  public static void saveBitmapToFileWithCompress(String picFilePath, Bitmap bitmap,

                                                  final int maxSize) {

      File photoFile = new File(picFilePath);

      FileOutputStream fileOutputStream = null;

      ByteArrayOutputStream baos = new ByteArrayOutputStream();

      int scale = 100;

      try {

          fileOutputStream = new FileOutputStream(photoFile);

          if (bitmap != null) {

              if (bitmap.compress(Bitmap.CompressFormat.JPEG, scale, baos)) {

                  int baosSize = baos.toByteArray().length;

                  while (baosSize > maxSize && scale > 0) {

                      baos.reset();

                      bitmap.compress(Bitmap.CompressFormat.JPEG, scale, baos);

                      baosSize = baos.toByteArray().length;

                      scale -= 5;

                  }

                  // 缩放后的数据写入到文件中

                  baos.writeTo(fileOutputStream);

                  fileOutputStream.flush();

              }

          }

      } catch (FileNotFoundException e) {

          e.printStackTrace();

      } catch (IOException e) {

          photoFile.delete();

          e.printStackTrace();

      } finally {

          try {

              if (fileOutputStream != null)

                  fileOutputStream.close();

          } catch (IOException e) {

              e.printStackTrace();

          }

      }

  }

尺寸压缩

  • (1)通过修改inSimpleSize的值来压缩

  • (2)通过修改inPreferredConfig来压缩

  • (3)通过Bitmap的静态方法 createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
    boolean filter) { ... }

修改inSimpleSize的值来压缩

看了很多文章, 主要有2种写法, 稍微有点区别。


  // 第一种,简单明了, 他是 四舍五入的

  // Math.round 四舍五入 : 原始数据的基础上 + 0.5 再向下取整。

  public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

        final int width = options.outWidth;

        final int height = options.outHeight;

        int inSampleSize = 1;

        if (width > reqWidth || height > reqHeight) {

            if (width > height) {

                inSampleSize = Math.round((float) height / (float) reqHeight);

            } else {

                inSampleSize = Math.round((float) width / (float) reqWidth);

            }

        }

        return inSampleSize;

    }

    // 第二种, 相当于都 舍 而没有 入 的情况, 是直接向下取整。

  private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,

                                      int reqHeight) {

        // 获取原始图片的宽高

        final int height = options.outHeight;

        final int width = options.outWidth;

        // inSimpleSize 为1的时候表示不压缩; 调节为2表示宽高都是原宽高的1/2,

        // 这样按照上边说的计算内存的公式 内存就变成之前的1/4。

        int inSampleSize = 1;

        if (width <= 0 || height <= 0) {

            return inSampleSize;

        }

        // 使用默认的方式取样

        if (reqWidth <= 0 || reqHeight <= 0) {

            // 高大于宽

            if (height >= width) {

                reqWidth = mPicMinLen;

                reqHeight = reqWidth * height / width;

            } else {

                reqHeight = mPicMinLen;

                reqWidth = reqHeight * width / height;

            }

        }

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;

            final int halfWidth = width / 2;

            while ((halfHeight / inSampleSize) >= reqHeight

                    && (halfWidth / inSampleSize) >= reqWidth) {

                inSampleSize *= 2;

            }

        }

        return inSampleSize;

    }

修改inPreferredConfig来压缩

单位像素占用内存大小从小到大排列: [ALPHA_8, RGB_56, ARGB_4444, ARGB_8888, RGBA_F16]

RGB_56: 通过改变内存占用更小的编码格式来达到压缩效果,但是它没有透明度!!!

ARGB_4444: 画质不咋的...

综合考虑使用RGB_56更能符合压缩的要求,相对ARGB_8888能减少一半的内存。


        ...

        BitmapFactory.Options options = new BitmapFactory.Options();

        options.inPreferredConfig = Bitmap.Config.RGB_565;

        bitmap = BitmapFactory.decodeFile(imagePath, options);

        ...

通过 Bitmap的静态方法 createScaledBitmap(...) 来压缩

源码是这样的:


/**

    * Creates a new bitmap, scaled from an existing bitmap, when possible. If the

    * specified width and height are the same as the current width and height of

    * the source bitmap, the source bitmap is returned and no new bitmap is

    * created.

    *

    * @param src      The source bitmap.

    * @param dstWidth  The new bitmap's desired width.

    * @param dstHeight The new bitmap's desired height.

    * @param filter    true if the source should be filtered.

    * @return The new scaled bitmap or the source bitmap if no scaling is required.

    * @throws IllegalArgumentException if width is <= 0, or height is <= 0

    */

    public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,

            boolean filter) {

        Matrix m = new Matrix();

        final int width = src.getWidth();

        final int height = src.getHeight();

        if (width != dstWidth || height != dstHeight) {

            final float sx = dstWidth / (float) width;

            final float sy = dstHeight / (float) height;

            m.setScale(sx, sy);

        }

        return Bitmap.createBitmap(src, 0, 0, width, height, m, filter);

    }

从源码注释可以知道 如果期望的宽高和原Bitmap的宽高是一样的,那么就不会新创建一个bitmap。 有时候这个可能是个大坑,要注意!!!
如: newBitmap = Bitmap.createScaledBitmap (oldBitmap, width, height, true); 紧接着如果你做 oldBitmap.recycle();
此时如果你给的width,height 和oldBitmap一样, newBitmap 就是 oldBitmap!!! 调用recycle()之后,你的 newBitmap 也就被recycle()而不能用了。

如有错误, 请指出。

相关文章

网友评论

      本文标题:android中Bitmap缩放、压缩、占用内存计算等

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