一文搞懂Bitmap
一、Bitmap简介
Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。在安卓应用中,图片资源是非常消耗内存的,如果处理不好,就容易造成OOM问题。
Bitmap 中有两个内部枚举类:
- Config 是用来设置颜色配置信息
- CompressFormat 是用来设置压缩方式
Config | 单位像素所占字节数 | 解析 |
---|---|---|
Bitmap.Config.ALPHA_8 | 1 | 颜色信息只由透明度组成,占8位 |
Bitmap.Config.ARGB_4444 | 2 | 颜色信息由rgba四部分组成,每个部分都占4位,总共占16位 |
Bitmap.Config.ARGB_8888 | 4 | 颜色信息由rgba四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置 |
Bitmap.Config.RGB_565 | 2 | 颜色信息由rgb三部分组成,R占5位,G占6位,B占5位,总共占16位 |
RGBA_F16 | 8 | Android 8.0 新增(更丰富的色彩表现HDR) |
HARDWARE | Special | Android 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 的分辨率转换规则对它也无效。