Android inpreferredconfig参数分析

标签: android图片解码减少图片内存占用
4165人阅读 评论(2) 收藏 举报
分类:

图片颜色

计算机在表示一个颜色时,都需要将该颜色对应到某一个颜色空间中的某个颜色值。常见的颜色空间有RGB,CMYK等。
计算机中图片文件中的颜色都已经被编码到某一个颜色空间了。JPEG支持RGB和CMYK颜色空间,一张JPG格式图片中的每个像素的颜色可能是用RGB颜色空间来编码的,也可能是用CMYK的颜色空间。而PNG只支持RGB颜色空间,一张PNG的图片中的每个像素的颜色一定是用RGB颜色空间来编码的。
此外,绝大多数显示器都只接受RGB颜色的输入,计算机在显示一张图片时,如果图片本身是非RGB颜色空间编码的,需要将其转化为RGB颜色空间的颜色后再显示,所以一张CMYK编码的图片在显示器上看到的会和图片本身的颜色有失真,当然这只是显示,图片本身还是原来的编码方式。
对RGB颜色空间,一个颜色分为R,G,B三个颜色通道。

图片透明度

对图片来说,每个像素除了有颜色信息外,还可以包含透明度信息。在计算机中透明度用一个单独的通道来表示,通常称为Alpha通道。
并非所有的图片格式都支持透明度,JPEG格式图片不支持透明度,PNG,GIF格式支持透明度。

Android颜色和透明度表示

在Android中通常用一个32位的整数来表示一个像素的颜色和透明度。32位的4个字节从高到低分别表示Alpha,R,G,B四个通道,每个通道用8位表示,每个通道的值范围是[0, 0xFF),对Alpha通道0表示完全透明,0xFF表示完全不透明。对R,G,B三个通道,0表示没有该通道的颜色分量,0xFF表示该通道颜色分量达到最大。R,G,B三个通道均为0为黑色,R,G,B三个通道均为0xFF表示白色。
例如0x00FF0000表示该像素完全透明,颜色为红色。0xFF00FF00表示该像素完全不透明,颜色为绿色。0x7DFFFFFF表示该像素为半透明,颜色为白色。

inpreferredconfig参数

BitmapFactory.Options类是BitmapFactory对图片进行解码时使用的一个配置参数类,其中定义了一系列的public成员变量,每个成员变量代表一个配置参数。参数inpreferredconfig表示图片解码时使用的颜色模式,也就是图片中每个像素颜色的表示方式。

inpreferredconfig参数的可选值

参数inpreferredconfig的可选值有四个,分别为ALPHA_8,RGB_565,ARGB_4444,ARGB_8888。它们的含义列举如下。

参数取值 含义
ALPHA_8 图片中每个像素用一个字节(8位)存储,该字节存储的是图片8位的透明度值
RGB_565 图片中每个像素用两个字节(16位)存储,两个字节中高5位表示红色通道,中间6位表示绿色通道,低5位表示蓝色通道
ARGB_4444 图片中每个像素用两个字节(16位)存储,Alpha,R,G,B四个通道每个通道用4位表示
ARGB_8888 图片中每个像素用四个字节(32位)存储,Alpha,R,G,B四个通道每个通道用8位表示

ALPHA_8模式

ALPHA_8模式表示的图片信息中只包含Alpha透明度信息,不包含任何颜色信息,所以ALPHA_8模式只能用在一些特殊场景。

RGB_565模式

显然RGB_565模式不能表示所有的RGB颜色,它能表示的颜色数只有32 × 64 × 32 = 65536种,远远小于24位真彩色所能表示的颜色数(256 × 257 × 256 = 16677216)。当图片中某个像素的颜色不在RGB_565模式表示的颜色范围内时,会使用相近的颜色来表示。

ARGB_4444模式

ARGB_4444已被Android标记为@Deprecated,以下是Android Bitmap类中截取的关于ARGB_4444的注释,可以看到Android推荐使用ARGB_8888来代替ARGB_4444,原因是ARGB_4444表示出来的图片质量太差,而且在KITKAT(Android4.4)及其以上版本,使用ARGB_4444参数会直接在代码中被忽略,用ARGB_8888替代。所以ARGB_4444参数基本没有使用的必要了。
这里写图片描述

ARGB_8888模式

ARGB_8888模式用8位来表示透明度,有256个透明度等级,用24位来表示R,G,B三个颜色通道,能够完全表示32位真彩色,但同时这种模式占用的内存空间也最大,是RGB_565模式的两倍,是ALPHA_8模式的4倍。

inpreferredconfig参数的使用

网上所有提到Android图片文件读取,图片内存优化的文章都会提到BitmapFactory.Options类中的两个参数,一个是inSmapleSize,还有一个就是inpreferredconfig,对inSmapleSize的用法这里不做介绍。对inpreferredconfig,所有的文章都会给出如下建议:“如果不需要透明度信息,就将其设置为RGB_565,可以减少一半的内存。”
诚然按照inpreferredconfig四个可选值的描述,使用RGB_565相比ARGB_8888模式确实能够减少一半内存,然而实际情况却并非如此。

inpreferredconfig参数的陷阱

考察如下代码

InputStream stream = getAssets().open(file);

Options op1 = new Options();
op1.inPreferredConfig = Config.ALPHA_8;
Bitmap bm1 = BitmapFactory.decodeStream(stream, null, op1);

Options op2 = new Options();
op2.inPreferredConfig = Config.RGB_565;
Bitmap bm2 = BitmapFactory.decodeStream(stream, null, op2);

Options op3 = new Options();
op3.inPreferredConfig = Config.ARGB_8888;
Bitmap bm3 = BitmapFactory.decodeStream(stream, null, op3);

对同一个图片文件,分别将inPreferredConfig设置为ALPHA_8,RGB_565和ARGB_8888三个选项,按照之前的描述,一定可以得出bm1,bm2和bm3三个对象占用的内存大小一定是bm1 < bm2 < bm3这样的顺序。然而实际情况却并不是这样,这里如果将bm1,bm2和bm3中每个像素都取出来进行比较,在大多数情况下会发现,bm1,bm2和bm3中的内容完全相同。

查看官方文档,可以看到对inPreferredConfig的描述是这样的。

这里写图片描述

翻译过来是这样的:如果inPreferredConfig不为null,解码器会尝试使用此参数指定的颜色模式来对图片进行解码,如果inPreferredConfig为null或者在解码时无法满足此参数指定的颜色模式,解码器会自动根据原始图片的特征以及当前设备的屏幕位深,选取合适的颜色模式来解码,例如,如果图片中包含透明度,那么对该图片解码时使用的配置就需要支持透明度,默认会使用ARGB_8888来解码。
也就是说inPreferredConfig指定的配置并非是一个强制选项,而是建议的(preferred)选项,Android在实际解码时会参考此参数的配置,但如果此配置不满足,Android会重新选取一个合适的配置来对图片进行解码。
这里有两个问题。
1. 当出现不满足情况时,使用的合适配置是如何选取的?
2. 什么情况下使用什么样的配置会出现不满足的情况?
对这两个问题,Android在文档中并没有说明。

inpreferredconfig不同选项在读取图片文件时的效果分析

为了回答上述的两个问题,确定 inpreferredconfig参数在选取不同配置时,对读取图片文件的效果。使用如下代码来进行测试。

InputStream stream1 = getAssets().open(file);
InputStream stream2 = getAssets().open(file);
InputStream stream3 = getAssets().open(file);
InputStream stream4 = getAssets().open(file);

Options op1 = new Options();
op1.inPreferredConfig = Config.ALPHA_8;
Bitmap bm1 = BitmapFactory.decodeStream(stream1, null, op1);

Options op2 = new Options();
op2.inPreferredConfig = Config.RGB_565;
Bitmap bm2 = BitmapFactory.decodeStream(stream2, null, op2);

Options op3 = new Options();
op3.inPreferredConfig = Config.ARGB_8888;
Bitmap bm3 = BitmapFactory.decodeStream(stream3, null, op3);

Options op4 = new Options();
op4.inPreferredConfig = null;
Bitmap bm4 = BitmapFactory.decodeStream(stream4, null, op4);

int width = bm1.getWidth();
int height = bm1.getHeight();
int[] pixels1 = new int[width * height];
bm1.getPixels(pixels1, 0, width, 0, 0, width, height);
int[] pixels2 = new int[width * height];
bm2.getPixels(pixels2, 0, width, 0, 0, width, height);
int[] pixels3 = new int[width * bm3.getHeight()];
bm3.getPixels(pixels3, 0, width, 0, 0, width, height);
int[] pixels4 = new int[width * bm4.getHeight()];
bm4.getPixels(pixels4, 0, width, 0, 0, width, height);

boolean bIdentical1 = true;
boolean bIdentical2 = true;
boolean bIdentical3 = true;
for (int i = 0; i < width * height; i++) {
    int px1= pixels1[i];
    int px2= pixels2[i];
    int px3= pixels3[i];
    int px4= pixels4[i];
    if ( px1 != px4 ) {
        bIdentical1 = false;
    }
    if ( px2 != px4 ) {
        bIdentical2 = false;
    }
    if ( px3 != px4 ) {
        bIdentical3 = false;
    }
    if (!bIdentical1 && !bIdentical2 && !bIdentical3) {
        break;
    }
}

对同一个图片文件,分别设置inPreferredConfig为ALPHA_8,RGB_565,ARGB_8888和null四个选项,得到四个Bitmap对象,然后分别读取四个Bitmap对象中的每个像素,比较inPreferredConfig为ALPHA_8,RGB_565,ARGB_8888的解码结果和inPreferredConfig为null的解码结果是否相同。为了让测试结果更加全面,这里分别在Android2.3,Android4.1,Android4.2,Android4.4四个Android系统的设备上进行了测试,同时分别使用不同类型的图片源文件进行测试,包括CMYK颜色空间的JPEG图片,RGB颜色空间的JPEG图片(分为8位RGB和24位RGB两种),GIF图片(又分为有透明像素和无透明像素两种),PNG图片(分为8位RGB无透明像素,8位RGB有透明像素,24位RGB无透明像素,24位RGB有透明像素四种),和BMP图片(分为16位RGB565,24位RGB,32位RGB三种)。
最终结果比较复杂,这里直接给出第一个问题的结论(分两点描述)。
1. 如果inPreferredConfig为null,解码时使用的颜色模式会根据图片源文件的类型进行选取,如果图片文件的颜色模式为CMYK,或RGB565,则选取RGB_565。如果是其他类型,则选取ARGB_8888。
2. 如果inPreferredConfig指定的选项在解码时无法满足,并不会再根据图片文件的类型来选取合适的选项,而是直接使用ARGB_8888选项来解码。例如,图片源文件为RGB566编码的BMP图片,使用ALPHA_8选项来解码时属于不满足的情况,这时会选取ARGB_8888选项来解码,而不是选取RGB565。和inPreferredConfig为null时选取的“合适的”选项并不相同。

根据这个结论,inPreferredConfig指定的选项在不满足时,会直接使用ARGB_8888选项来解码。因此可以用ARGB_8888的结果作为基准,将ALPHA_8,RGB_565的解码结果和ARGB_8888的结果做对比,来判断何时会出现不满足的情况。这里在原始结果的基础上整理了如下两个表格。表格的F表示此选项不满足,T表示此选项满足,–表示Android系统不支持该种类型的图片格式。例如第一张表格中JPEG-32行,Android4.1列为–,表示在Android4.1系统不支持CMYK编码的JPEG格式图片的解码。第一张表格中PNG-8行,Android2.3列为F,表示在Android2.3系统上,解码PNG-8图片时使用ALPHA_8选项不满足,实际会使用ARGB_8888来解码。第二张表格中第BMP-16行,Android4.4列为T,表示在Android4.4系统上,对16位RGB565编码的BMP图片进行解码时,使用RGB565选项可以满足。

这里写图片描述

这里写图片描述

这两张表格也就回答了上述第二个问题,也就是什么情况下使用什么样的配置会出现不满足的情况。总结如下。

  1. 所有情况下ARGB_8888配置都可以满足
  2. 所有情况下ALPHA_8配置都不满足
  3. 绝大多数情况下RGB565选项都不满足

所以,所谓设置RGB565选项可以减少内存的说法根本不成立,而且在所有RGB565选项可以满足的情况中,除Android4.4系统以上8位RGB编码的JPG图片(即灰度图)外 ,其他情况使用null选项也会使用RGB565选项。所以,基本上指定inPreferredConfig为RGB565和不设置inPreferredConfig的效果是一样的。

6
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:258011次
    • 积分:2911
    • 等级:
    • 排名:第12834名
    • 原创:50篇
    • 转载:1篇
    • 译文:1篇
    • 评论:61条
    博客专栏