一文搞懂Bitmap

2 阅读5分钟

一文搞懂Bitmap

一、Bitmap简介

Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。在安卓应用中,图片资源是非常消耗内存的,如果处理不好,就容易造成OOM问题。

Bitmap 中有两个内部枚举类:

  • Config 是用来设置颜色配置信息
  • CompressFormat 是用来设置压缩方式
Config单位像素所占字节数解析
Bitmap.Config.ALPHA_81颜色信息只由透明度组成,占8位
Bitmap.Config.ARGB_44442颜色信息由rgba四部分组成,每个部分都占4位,总共占16位
Bitmap.Config.ARGB_88884颜色信息由rgba四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置
Bitmap.Config.RGB_5652颜色信息由rgb三部分组成,R占5位,G占6位,B占5位,总共占16位
RGBA_F168Android 8.0 新增(更丰富的色彩表现HDR)
HARDWARESpecialAndroid 8.0 新增 (Bitmap直接存储在graphic memory)
Bitmap内存占用 ≈ 像素数据总大小 = 图片宽像素 × 图片高像素 × 每个像素的字节大小
例如,我们准备一张分辨率为 1920x1080的bitmap,格式为ARGB_8888, 则占用内存 = 1920 X 1080 X 4 = 8294400 byte = 7.91M

二、Bitmap解码

BitmapFactory顾名思义:从类名上,就可以看出它是Bitmap的工厂类,也就是生产Bitmap的。 BitmapFactory.Options常用属性

参数作用
参数作用
inJustDecodeBounds如果将这个值置为true,那么在解码的时候将不会返回bitmap,只会返回这个bitmap的尺寸。这个属性的目的是,如果你只想知道一个bitmap的尺寸,但又不想将其加载到内存时。这是一个非常有用的属性。
outWidth和outHeight表示这个Bitmap的宽和高,一般和inJustDecodeBounds一起使用来获得Bitmap的宽高
inSampleSize缩放比例。当它小于1的时候,将会被当做1处理,这个参数需要是2的幂函数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高各降为1 / 2,像素数降为1 / 4。
inPreferredConfig这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes。
inBitmap这个参数用来实现 Bitmap 内存的复用,但复用存在一些限制,具体体现在:在 Android 4.4 之前只能重用相同大小的 Bitmap 的内存,而 Android 4.4 及以后版本则只要后来的 Bitmap 比之前的小即可。使用 inBitmap 参数前,每创建一个 Bitmap 对象都会分配一块内存供其使用,而使用了 inBitmap 参数后,多个 Bitmap 可以复用一块内存,这样可以提高性能。
inTempStorage解码时的临时空间,建议 16K;
inMutable配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段

普通解码

BitmapFactory创建Bitmap的几种方法

//从指定的字节数组中解码成一个Bitmap
public static Bitmap decodeByteArray ( byte[] data, int offset, int length)
//从文件中解码成一个Bitmap``
public static Bitmap decodeFile (String pathName)

采样解码

采样解码借助inJustDecodeBounds与inSampleSize实现

  • 设置inJustDecodeBounds=true,仅获取图片的宽高数据
  • 使用获取的宽高数据与实际展示的宽高对比,计算合适的inSampleSize
  • 设置inJustDecodeBounds=false,真正的加载图片

代码如下

/**
     * 计算降采样率的算法
     * @param options
     * @param maxWidth
     * @param maxHeight
     * @return
     */
    public static int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
        //这里其实是获取到默认的高度和宽度,也就是图片的实际高度和宽度
        final int height = options.outHeight;
        final int width = options.outWidth;
        //默认采样率为1,也就是不变嘛。
        int inSampleSize = 1;
        
        //===============核心算法啦====================
        if (width > maxWidth || height > maxHeight) {
            if (width > height) {
                inSampleSize = Math.round((float) height / (float) maxHeight);
            } else {
                inSampleSize = Math.round((float) width / (float) maxWidth);
            }
            final float totalPixels = width * height;
            final float maxTotalPixels = maxWidth * maxHeight * 2;
            while (totalPixels / (inSampleSize * inSampleSize) > maxTotalPixels) {
                inSampleSize++;
            }
        }
        //=============核心算法end================
        return inSampleSize;
    }
    
    
   
//Google给出的示例代码,使用inSampleSize前要先把 options.inJustDecodeBounds = true;
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions,仅获取bitmap大小,不真正加载,降低内存
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // 设置采样
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

三、Bitmap压缩

Android 中图片主要以 Bitmap 的形式存在,所以压缩图片主要就是减少 Bitmap 的大小。Bitmap 的大小可以通过如下的公式计算得到:size = width * height * 单个像素所占字节数。因此压缩图片通过改变公式中的三个变量即可实现。 juejin.cn/post/695984…

四、Bitmap复用

options.inBitmap还没理解,慢慢理解

五、应用与思考

1、一个ImageView 上展示一张图片,图片的占用内存大小是如何计算的参考文章

总结:

  • Bitmap内存 = Bitmap.width( ) * Bitmap.height() * 单个像素点占用字节数;
  • ARGB_8888(4B)、RGB_565(2B)常用;
  • 图片来源是 res 内的不同资源目录时,系统会根据设备当前的 dpi 值以及资源目录所对应的 dpi 值,做一次分辨率转换,规则如下:新分辨率 = 原图横向分辨率 * (设备的 dpi / 目录对应的 dpi ) * 原图纵向分辨率 * (设备的 dpi / 目录对应的 dpi )。
  • 当使用 Glide 时,如果有设置图片显示的控件,那么会自动按照控件的大小,降低图片的分辨率加载。图片来源是 res 的分辨率转换规则对它也无效。