概念解释
RGB颜色模型: 最常见的颜色模型,设备相关。R、G、B分别代表红、绿和蓝色三种颜色通道,取值均为[0,255]。
RGB 8位色: 表示使用8位(bit)表示颜色,一共能表示2^8 = 128种颜色。
依次类推RGB 16位色,RGB 24位色,RGB 32位色,使用的位数越多,能表示的颜色越多,24位能表示的颜色数量已经很多了,称之为“真彩色”。
32位和24位能表示的颜色一样多,多一个了透明度。
Android Bitmap使用的三种颜色格式:
- ALPHA_8–每个像素占1个字节,存储透明度信息,没有颜色信息。
- RGB_565--每个像素占2个字节存储颜色信息,R 5位,G 6位,B 5位,能表示2^16种颜色。
- ARGB_8888--每个像素占4个字节存储颜色信息,A R G B各一个字节,能表示2^24种颜色,还有一个字节存储透明度信息。
压缩原理
在Android中进行图片压缩是非常常见的开发场景,主要的压缩方法有两种:其一是下采样压缩,其二是质量压缩。前者是降低图像尺寸,改变图片的存储体积;而后者则是在不改变图片尺寸的情况下,通过损失颜色精度,达到相同目的。
下采样压缩
图片尺寸的修改其实就是通过修改像素数,放大的过程称之为上采样,缩小的过程称之为下采样。在这里,我们只讨论下采样。下采样压缩是通过减少图片的像素数的来减小Bitmap内存占用。
1.使用Options.inSampleSize
代码示例:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Bitmap compress = BitmapFactory.decodeFile("/sdcard/test.png", options);
|
当采样率为inSampleSize = 2时,Bitmap内存宽高各减少一半,内存占用是不缩放情况下的1/4。
关于采样率的选择参考Android官方指南高效加载大图,主要分为两步:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType; |
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
} |
这种方式使用的采样算法是邻近采样(Nearest Neighbour Resampling):
x(x 为 2 的倍数)个像素最后对应一个像素。比如采样率设置为1/2,所以是两个像素生成一个像素。邻近采样的方式比较粗暴,直接选择其中的一个像素作为生成像素,另一个像素直接抛弃。
举例:一个200*200白黑像素相间的图片,sampleSize=2,原图和采样图的对比如下:
2.使用createScaledBitmap或Matrix
代码示例:
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);
或者直接使用 matrix 进行缩放,查看Bitmap.createScaledBitmap源码其实就是使用 matrix 缩放Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);
|
同样是图片宽高各为原来的1/2,这种方式采用双线性采样(Bilinear Resampling):
这个算法不像邻近采样算法直接粗暴的选择一个像素,而是参考了源像素相应位置周围 2x2 个点的值,根据相对位置取对应的权重,经过计算之后得到目标图像。
假设要把一个大小为7 * 7的图片压缩成大小为3 * 3的图片。将源图片和目标图片放到直角坐标系中,图片左上角都和(0,0)点重合。
那么,目标图片的像素点(1,1)对应到源图片的像素点是(1 * 7/3,1 * 7/3)(像素坐标只能是整数,这个是概念上的像素),其周围2x2个像素分别是(2,2)、(2,3)、(3,2)和(3,3)坐标处的像素。
目标图片的像素点(1,1)的值就会参考这4个像素值,距离越近对目标像素影响越大。算法参考双线性插值。这个例子采用的是图片左上角对齐,还有几何中心对齐等方式。
同样使用一个200*200白黑像素相间的图片作采样测试:
不同的采样算法会产生不同效果,除了 Android 中这两种常用的采样算法之外,还有比较常见如:双立方/双三次采样(Bicubic Resampling) 和 Lanczos Resampling等。如果对Android使用的这两种采样算法效果不满意,必要时可以引入其他的算法。
质量压缩
质量压缩的原理损失颜色精度,对于一个使用确定格式(ALPHA_8, RGB565,ARGB8888)的Bitmap,并不会减少其内存占用,因为内存占用=1/2/4 * 宽 * 高;但是保存成文件大小是会减小的,原因是需要表示的颜色少了。
颜色精度损失举例:0xFFFFFF→RGB每个单色低四位归零0xF0F0F0。假设一个图片对于颜色精度要求不高,只保存R G B各个通道的的高4位,那么颜色信息就能节省一半的空间占用。
代码示例:
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//quality 为0~100,0表示最小体积,100表示最高质量,对应体积也是最大bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
|
在上述代码中,我们选择的压缩格式是CompressFormat.JPEG,除此之外还有两个选择:
其一,CompressFormat.PNG, PNG 格式是无损的,它无法再进行质量压缩,quality 这个参数就没有作用了,会被忽略,所以最后图片保存成的文件大小不会有变化;
其二,CompressFormat.WEBP ,这个格式是 google 推出的图片格式,它会比 JPEG 更加省空间。官方表示能节省25%-34%的空间。
图片的质量压缩(编码)最终是由具体的编解码库来完成的。
Android里保存图片的逻辑是:Java 层函数→Native 函数→Skia函数→对应第三库函数(例如 libjpeg)。
Skia是Google开发维护的一个完整的2D图形库,可以绘制文本、几何和图像,广泛用于Chrome, Chrome OS, Android等系统中。它能够方便地挂接其他图形编解码库。
加载图片时BitmapFactory.Options.inPreferredConfig有没有用
使用方法
BitmapFactory.decodeFile(String pathName, Options opts)或者decodeStream(InputStream is, Rect outPadding, Options opts)... |
加载图片时,options.inPreferredConfig参数可选择ALPHA_8, RGB565,和ARGB8888。
试想加载一个32位色的PNG图片,inPreferredConfig参数设置成RGB565,加载进内存的Bitmap是RGB565吗?答案是否定的,PNG 格式无法进行质量压缩。
API文档的解释:
如果这是非空的,解码器将尝试解码成这个内部配置。如果它是null,或者请求不能满足,解码器将尝试根据系统的屏幕深度选择最佳匹配配置,以及原始图像的特性。默认ARGB_8888。