Bitmap技术总结
android可以使用的图片格式:
- PNG
- JPEG
- WebP
- SVG:即xml描述的图片
- GIF
PNG是无损压缩格式的图片,JPEG是有损压缩格式的图片。
Android中的PNG默认压缩使用libpng库压缩,触发PNG压缩的场景:
- 编译阶段:在编译阶段通过aapt打包工具会对drawable目录下png图片进行压缩,压缩率大约在40%以下
- 对于.9图片,则会在编译阶段变大。主要是因为aapt会对它做额外处理,增加2~3个不同类型的Chunk块
- 可以关闭aapt的压缩(不影响.9图),只需要:
android { aaptOptions { cruncherEnabled false } }
- API层调用进行压缩,即调用decodeXXX方法。
Android中的JPEG默认压缩使用libjpeg(7.0以前),7.0以后做了2点优化:
- 内部使用的JPEG压缩库改为libjpeg-turbo,这是一个基于libjpeg的涡轮增压库,主要的一特点就是速度比libjpeg快
- 使用
Huffman编码替代Arithmetic编码
色值
色值的表示有:RGB、YUV、CMYK、YCCK。
我们只讨论RGB,它最大表示2的24次方种颜色。RGB根据每个分量的所占位数不同又可以分为这两种:RGB_565、RGB_888,其中带alpha通道的有这两种:ARGB_4444、ARGB_8888。
- RGB_565:每个像素占两个字节,R分量占5位,G分量占6位,B分量占5位,最多能表示2^16(65536)中颜色
- RGB_888:每个像素占三个字节,R、G、B分量各占8位,最多能表示2^24(16777216)中颜色
- ARGB_4444:每个像素占两个字节,A、R、G、B分量各占4位,最多能表示2^12(4096)中颜色,成像效果比较差,所以Google给了它一个Deprecated,并且v4.4+后如果使用了它会自动转成用ARGB_8888
- ARGB_8888:每个像素占四个字节,A、R、G、B分量各占8位,最多能表示2^24(16777216)中颜色,其中前面8位alpha(0~255)通道表示每个像素点的透明度
PNG是啥?
由于是无损压缩,所以PNG的大小比相同像素宽高的JPEG要大。但是由于不丢失图像数据,并且支持alpha通道且无锯齿,所以PNG还是广泛使用的。
一个最简单的PNG图像文件数据的结构:

- Signature:占8个字节,表示这是个PNG文件,内容固定
- IHDR:文件头数据块。占25个字节,表示图像的基本信息,如图像的宽高和位深。必须是第一个数据块。
- IDAT:占n个字节,用于表示图像的数据信息,它存储真实的图像数据。数量大于等于1。
- IEND:占12个字节,表示数据库内容结束,内容固定,必须在最后。
每个块的结构如下:

- Length:占4个字节,表示该Chunk中Data域的长度
- Type:占4个字节,表示该Chunk的类型,如:IHDR、IDAT等
- Data:占n个字节,存储着该Chunk的数据
- CRC:占4个字节,循环冗余校验码
NinePatch(.9)
.9图片是在上面的结构体的基础上,额外的增加npTc和npOl数据块,当在加载图片时,会在判断该图片是否为.9图来选择性的构造一个NinePatchDrawable还是BitmapDrawable对象。
byte[] npChunk = bitmap.getNinePatchChunk();
if (npChunk != null && NinePatch.isNinePatchChunk(npChunk)) {
NinePatchDrawable npDrawable = new NinePatchDrawable(getResources(), bitmap, npChunk, new Rect(), null);
//...
}
JPEG是啥?
JPEG的数据结构:

JPEG就是由多个segment段组成的,每个segment由标志码和数据组成。标识码由两个字节组成,第一个为固定值0xFF,区分主要是依据第二个字节:
- FFD8:表示图像的开始,段名为SOI
- FFE0:表示JFIF数据块,段名为APP0
- FFC0:表示图像帧开始,段名为SOFO
- FFC4:表示Huffman表,段名为DHT
- FFDA:表示从上往下开始扫描图像,段名为SOS
- FFD9:表示图像结束,段名为EOI
Bitmap的存储
sdk10及以前
Android2.3以下版本,bitmap像素数据存储在native内存中,释放内存需主动调用recycle()方法。
sdk11以上
在Android2.3版本引入了并发的垃圾回收器后,在3.0以后的版本bitmap的像素数据则存储在虚拟机堆中,不需要主动调用recycle()来回收内存,gc会主动回收。
基本的压缩方式
降低色彩位数
整个主要是RGB各个分量的位数,即ARGB_8888、RGB_565的切换。切换主要是涉及到压缩、补偿。
压缩就是只取高位数据,这样精度丢失最小。 补偿用原分量的值进行填充,剩下的用原分量的低位进行循环补偿。
尺寸压缩
SamplingSize
public Bitmap decode(String filePath) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
int outWidth = options.outWidth;
int outHeight = options.outHeight;
options.inSampleSize = computeSampleSize(outWidth, outHeight);
return BitmapFactory.decodeFile(filePath, options);
}
存在的问题是只能支持2的幂次方的值进行缩放,所以bitmap大小往往不是我们预期的大小。
Matrix矩阵变换
种方式可以精确的缩放到符合我们预期的bitmap大小。
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
Matrix matrix = new Matrix();
float rate = computeScaleRate(bitmapWidth, bitmapHeight);
matrix.postScale(rate, rate);
Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true);
质量压缩
对于质量压缩,也就是图片的所占物理内存的大小,主要是通过一些lib库进行压缩,如Android默认的bitmap.compress(),可选择PNG与JPEG等进行压缩,如果不满足于内置的lib压缩库效果,可自己选择替换系统api进行压缩。
