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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/emdd2016/article/details/84890827

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

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

  • (1)Bitmap占用的手机内存怎样计算? 占用内存的大小和那些因素相关?
  • (2)质量压缩
  • (3)尺寸压缩

Bitmap vs 内存

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

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

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

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

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

type 占用的字节数 备注
ALPHA_8 1 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
ARGB_4444 2 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
ARGB_8888 4 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节(Android默认的压缩格式)
RGB_56 2 表示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()而不能用了。

如有错误, 请指出。

猜你喜欢

转载自blog.csdn.net/emdd2016/article/details/84890827