发布于 

Bitmap源码学习:在Android中如何压缩一个Bitmap

Bitmap.createScaledBitmap源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}

// 根据矩阵对象进行缩放操作,并返回缩放后的 Bitmap 对象
return Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
}

这段代码用于将原始的 Bitmap 对象按照指定的宽度和高度进行缩放,生成一个新的经过缩放的 Bitmap 对象。缩放过程通过矩阵对象 m 进行操作,实现宽高比例的调整:

  1. 创建一个 Matrix 对象 m,用于后续的缩放操作。
  2. 获取原始 Bitmap 的宽度和高度。
  3. 判断原始图片的宽度或高度是否与目标宽度或高度不一致。
  4. 如果不一致,则计算宽度和高度的缩放比例,并使用 setScale() 方法设置到矩阵对象 m 中。
  5. 使用 Bitmap.createBitmap() 方法,根据原始 Bitmap、矩阵对象 m、滤波器标志 filter 进行缩放操作,返回缩放后的 Bitmap 对象。

Bitmap.createBitmap

可以看出Bitmap中压缩图片的方法在计算出压缩比例后调用了创建从原Bitmap创建新Bitmap的方法,下面来进一步分析这一过程

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
@Nullable Matrix m, boolean filter) {

// 检查坐标值是否为非负数
checkXYSign(x, y);
// 检查宽度和高度是否为正数
checkWidthHeight(width, height);
// 检查x、y和width、height的和是否小于等于原始图片的宽度和高度
if (x + width > source.getWidth()) {
throw new IllegalArgumentException("x + width must be <= bitmap.width()");
}
if (y + height > source.getHeight()) {
throw new IllegalArgumentException("y + height must be <= bitmap.height()");
}
// 检查源 Bitmap 是否已被回收
if (source.isRecycled()) {
throw new IllegalArgumentException("cannot use a recycled source in createBitmap");
}

// 检查是否可以直接返回原始 Bitmap(无需进行任何操作)
if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
height == source.getHeight() && (m == null || m.isIdentity())) {
return source;
}

// 检查是否为硬件加速的 Bitmap
boolean isHardware = source.getConfig() == Config.HARDWARE;
if (isHardware) {
source.noteHardwareBitmapSlowCall();
source = nativeCopyPreserveInternalConfig(source.mNativePtr);
}

int neww = width;
int newh = height;
Bitmap bitmap;
Paint paint;

// 创建源矩形和目标矩形
Rect srcR = new Rect(x, y, x + width, y + height);
RectF dstR = new RectF(0, 0, width, height);
RectF deviceR = new RectF();

// 设置新的 Bitmap 的配置
Config newConfig = Config.ARGB_8888;
final Config config = source.getConfig();
if (config != null) {
switch (config) {
case RGB_565:
newConfig = Config.RGB_565;
break;
case ALPHA_8:
newConfig = Config.ALPHA_8;
break;
case RGBA_F16:
newConfig = Config.RGBA_F16;
break;
case ARGB_4444:
case ARGB_8888:
default:
newConfig = Config.ARGB_8888;
break;
}
}

ColorSpace cs = source.getColorSpace();

if (m == null || m.isIdentity()) {
// 不需要变换,直接创建新的 Bitmap
bitmap = createBitmap(null, neww, newh, newConfig, source.hasAlpha(), cs);
paint = null; // 不需要绘制操作
} else {
final boolean transformed = !m.rectStaysRect();

// 将目标矩形通过矩阵变换得到设备矩形
m.mapRect(deviceR, dstR);

neww = Math.round(deviceR.width());
newh = Math.round(deviceR.height());

Config transformedConfig = newConfig;
if (transformed) {
if (transformedConfig != Config.ARGB_8888 && transformedConfig != Config.RGBA_F16) {
transformedConfig = Config.ARGB_8888;
if (cs == null) {
cs = Color

Space.get(ColorSpace.Named.SRGB);
}
}
}

// 根据变换后的宽度、高度和配置创建新的 Bitmap
bitmap = createBitmap(null, neww, newh, transformedConfig,
transformed || source.hasAlpha(), cs);

// 创建画笔对象,并设置滤波器和抗锯齿
paint = new Paint();
paint.setFilterBitmap(filter);
if (transformed) {
paint.setAntiAlias(true);
}
}

// 设置新的 Bitmap 的一些属性
bitmap.mDensity = source.mDensity;
bitmap.setHasAlpha(source.hasAlpha());
bitmap.setPremultiplied(source.mRequestPremultiplied);

// 创建画布对象,并进行画布的平移和矩阵的变换
Canvas canvas = new Canvas(bitmap);
canvas.translate(-deviceR.left, -deviceR.top);
canvas.concat(m);
// 在画布上绘制源 Bitmap
canvas.drawBitmap(source, srcR, dstR, paint);
canvas.setBitmap(null);

// 如果是硬件加速的 Bitmap,则复制一个硬件加速的 Bitmap 返回
if (isHardware) {
return bitmap.copy(Config.HARDWARE, false);
}

return bitmap;
}

这段代码的执行过程如下:

  1. 检查输入参数的合法性,包括坐标值、宽度和高度的范围,以及源 Bitmap 是否已被回收。
  2. 检查是否可以直接返回原始 Bitmap(无需进行任何操作)。
  3. 如果原始 Bitmap 是硬件加速的,则创建一个软件拷贝用于后续操作。
  4. 创建新的 Bitmap 对象以及用于绘制的画笔对象。
  5. 根据变换参数和目标矩形计算新的宽度和高度。
  6. 根据源 Bitmap 的配置和颜色空间设置新的 Bitmap 的配置。
  7. 根据是否需要变换进行不同的创建操作。
    • 如果不需要变换,则直接创建新的 Bitmap 对象。
    • 如果需要变换,则根据变换后的宽度、高度和配置创建新的 Bitmap 对象,并设置画笔的滤波器和抗锯齿属性。
  8. 设置新的 Bitmap 的一些属性,如密度、是否有透明度等。
  9. 创建画布对象,并进行画布的平移和矩阵的变换。
  10. 在画布上绘制源 Bitmap,根据裁剪和变换的参数进行绘制操作。
  11. 清空画布对象,并根据是否是硬件加速的 Bitmap 复制一个相同配置的 Bitmap 返回。

至此,我们就得到了压缩后的新Bitmap,期间会涉及到将原始 Bitmap 对象的像素数据按照缩放比例复制到新的 Bitmap 对象中,涉及到插值计算的概念,感兴趣的话可以进一步了解,这里就不赘述了


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @tsparrot 创建,使用 Stellar 作为主题。