首先介绍有关色彩深度的知识。
计算机显示器上同时能显示的颜色数量是由色彩深度(Color Depth )决定的,例如色彩深度若为16 则同时能显示2 的16 次方即65536 种颜色,色彩深度若为24 则同时能显示2 的24 次方即16777216 种颜色。在显卡驱动设置里通常把16 位色模式称为“增强色”,24 位色模式称为“真彩色”。至于显卡支持的32 位色模式,只是为了更好地处理色彩,实际上液晶面板能支持的色彩深度通常还是24 位,也就是红绿蓝各8 位。
正常人眼可分辨的颜色种类可达几十万种以上,而用测色器则可以分辨出一百万种以上的颜色。所以一般来说24 位色(即真彩色)已经能很好地表现人眼所能看到的自然界真实的色彩,比如数码相机拍摄的照片一般就是以24 位色模式保存的。
一台显示器实际能显示的最大色彩深度由硬件和软件两方面决定。硬件方面,CRT 显示器的R 、G 、B 由模拟信号控制,理论上能显示的色彩种类是无穷多的,液晶显示器则取决于液晶面板和驱动电路对色彩的处理能力,以及显卡支持的色彩深度。目前计算机用的液晶显示器至少都可以支持24 位色显示,显卡也没有任何问题。软件方面,则要看显卡驱动程序和操作系统是否能支持24 位色显示,目前的主流 PC 操作系统如 Windows 系列、Linux 也都没问题。
实际上通常情况下人眼也不易分辨出16 位色与24 位色的差异,若将计算机显卡设为16 位色模式观看数码相机拍的照片,通常看不出来与在真彩色模式下有什么差别。
但如果在屏幕上显示色彩连续渐变的图形,不同的色彩深度就会看到明显的差异。如下图所示(此图片引用自http://www.hi-pda.com/forum/viewthread.php?tid=501100 ),最左边的图片是原始图,颜色从橙色到红色过渡很平滑,中间和右边的图是在16 位色的系统上显示的效果,色彩渐变的过渡带出现了明显的色斑。
一般来说,象这种由于色彩连续渐变导致出现色斑的情形在数码相机拍摄的自然界景物的照片中并不多见,所以数码照片无论是在16 位色还是在真彩色模式下看起来的效果其实差别不大;但在人工设计的图片中则比较常见这种情形,比如在手机产品开发中,有些UI 设计师喜欢用绚丽多变的色彩来吸引用户的眼球,包括大量使用渐变色,他们设计的UI 图片如果在 16 位色的手机上显示就会出现上述的色斑,严重影响了视觉效果,为了解决这种问题,要么修改UI 设计,尽量避免使用渐变色,要么就只能修改软硬件实现真正的24 位真彩色显示。
对于 PC 来说,支持 24 位真彩色显示没有任何问题,但对于手机来说就不一定了。首先手机用的显示屏出于成本的考量不一定支持24 位色彩深度,可能只有18 位、16 位,早期的彩屏手机甚至只支持4096 色;其次手机操作系统也往往不支持16 位以上的色彩深度,比如微软的Windows Mobile 各版本始终只能支持16 位色以至于常遭一些用户诟病(最新的Windows Phone 7 我不清楚是否还是这样?)。
大部分手机不能支持24 位色真彩色显示其实也是有道理的,就象上面说的,只要设计UI 时尽量避免使用渐变色就不会影响用户视觉体验(个人认为这是完全可以做到的,很多优秀的UI 设计并不依靠绚丽的色彩取胜),另一方面使用较少的色彩位数有利于降低对内存和处理器性能的要求,屏也不需要支持24 位,可以大大降低成本。但技术在飞速发展,智能手机的硬件配置和运算能力已经越来越接近PC ,用户对界面显示效果的要求也越来越高,所以手机制造商也考虑在显示上支持24 位真彩色。
最近两年在智能手机市场火起来的Android 的早期版本也象Windows Mobile 一样只支持16 位色,并不支持24 位真彩色显示。去年5 月上市的联想的乐phone 应该是世界上第一款真正支持24 位色显示的Android 手机,为了实现这个特性,我们对乐phone 所使用的Android 1.6 (Donut )进行了修改和优化。下面就以高通QSD8250 平台为例介绍在Donut 上实现 24 位真彩色显示的前提条件和大致方法。
1. 屏必须支持24 位显示。手机用的很多屏本身只支持16 位或18 位的色彩深度,所以要想实现24 位真彩色显示,就必须选用24 位的屏。
2. 将framebuffer 修改为24 位即RGB888 格式。高通Donut 代码默认的framebuffer driver 是16 位即RGB565 的,在硬件上芯片内部的显示控制器将软件输出的16 位数据转换成24 位给到屏。高通的framebuffer driver 支持24 位,只需要改一处代码即可。伴随着将framebuffer 改为24 位,必须调整系统内存分配布局,增大framebuffer (一般位于PMEM 中)的大小,同时可能还需要适当增加用于给SurfaceFlinger 分配Surface 所用的PMEM 的大小,否则可能会出现分配Surface 失败(在后面会再谈到这个问题)。
3. 修改copybit HAL 部分的代码,使其支持RGB888 格式(即24 位色)的blit ,QSD8250 的MDP 本身是支持RGB888 格式的,只是原来的代码没有实现,只要修改几处即可。
4. 修改EGL 与framebuffer 接口代码,即frameworks/base/libs/ui/EGLDisplaySurface.cpp ,原先的代码是按照RGB565 的framebuffer 写死的,需按照RGB888 格式修改有关参数。
5. 最最关键的一步,修改frameworks/base/libs/surfaceflinger/SurfaceFlinger.cpp 中的函数SurfaceFlinger::createNormalSurfaceLocked ,将case PIXEL_FORMAT_OPAQUE: 下面的format = PIXEL_FORMAT_RGB_565; 修改为format = PIXEL_FORMAT_RGBA_8888; 这处修改是实现24 位真彩色UI 视觉效果的关键。但这样一来又引出两个问题,一是格式为PIXEL_FORMAT_OPAQUE 的Surface 分配占用的内存是原先的两倍(RGBA8888 相对于RGB565 多用一倍的内存),在某些情况下可能导致Surface 内存分配失败,可通过适当增大给Surface 分配内存预留的PMEM 大小来解决这个问题,同时也要在UI 设计上尽量避免过于复杂的界面(越复杂同时分配的Surface 越多);第二个问题是造成OpenGL ES 应用程序(主要是3D 游戏)不能正常显示,这是因为绝大多数OpenGL ES 应用程序缺省使用RGB565 显示模式,而Surface 改成了RGBA8888 就造成了两者不匹配,显示出来的画面不正常,解决的办法是在函数SurfaceFlinger::createNormalSurfaceLocked 里利用输入参数flags 区分上层调用者是OpenGL ES 应用程序还是普通应用程序,如果是OpenGL ES 应用程序则flags 里会带有标志位eGPU ,因此只要判断这个eGPU 标志位,只有在普通应用程序时才改成RGBA8888 的格式就OK 了,OpenGL ES 应用程序仍旧使用RGB565 的格式进行显示,实际上这也有利于OpenGL ES 应用程序获得较好的性能,因为RGBA8888 相对于RGB565 会导致占用更多内存和更慢的操作速度,对性能有一定影响,而这对3D 程序来说是不能忽视的。
6. 修改与显示有关的其他代码。由于Android 1.6 (Donut )缺省的显示色彩位数是16 位(即RGB565 模式),很多与显示有关的代码都是按照RGB565 写死的,当framebuffer 改成24 位即RGB888 模式后与显示相关的一些代码也必须相应被修改,包括显示开机logo 画面(system/core/init/logo.c )、DDMS 中的截屏功能(system/core/adb/framebuffer_service.c )等。
7. 此外还要考虑与模拟器(emulator )的兼容性,因为在调试应用程序时通常会在emulator 上进行,但emulator 仍是RGB565 的显示模式,因此要确保同一份代码跑在手机和emulator 上都可以正常显示,可在代码中通过宏等手段来实现兼容性。当然也可以把emulator 改成跟手机一致的RGB888 显示模式,需要下载goldfish (即emulator 上运行的内核)源代码进行修改,目前没有做。
经过上述修改后在Android 1.6 (Donut )上实现了24 位真彩色显示,在UI 设计中如果使用了色彩连续渐变的图就不会出现色斑,使界面色彩更加丰富炫丽,比其他Android 手机更能吸引用户的眼球。总的代码修改量并不大,不影响Android 原有的图形显示架构。
虽然24 位真彩色给UI 设计带来了好处,但因为原生的Donut 并不支持24 位色,上述修改也会带来副作用以及一些没有预见到的问题。
首先是性能问题,如前所述,24 位色下的2D 图形性能稍低于16 位色,也会占用更多内存,经分析瓶颈在2D 图形库skia ,该库是Android 图形显示系统的核心模块,其性能对UI 操作的流畅度影响很大,在Donut 中skia 并未针对硬件平台进行优化,更没有考虑对RGBA8888 色彩模式的优化。为此我们利用ARMv7 的neon 指令对skia 中涉及RGBA8888 模式的关键代码及bionic 中的memcpy 进行优化,同时对UI 动效设计进行优化,结果benchmark 得分及实际用户体验不逊于原生Donut 。
其次是某些应用程序的背景色在原生Donut 上运行时为黑色,但在修改后的24 位色系统上运行时变成透明色,原因在于使用了@android:color/transparent 或@null 作为背景色,对应为全0 的颜色值,而全0 在RGB565 模式就是黑色,但在RGBA8888 模式则是全透明(即alpha 为0 ),其实这是正常现象,不能算是系统的bug ,但为了保持与原生Donut 的一致性,修改了有关代码将上述颜色值强制为使用黑色(即@android:color/black )。
Android升级到Eclair 之后,显示部分的架构发生了较大变化,并逐渐增强了对24 位真彩色模式的支持,同时对skia 进行了针对ARMv7 处理器的优化。在Froyo 里framebuffer 缺省已经是32 位,因此只需较少的修改就可实现24 位真彩色,但标志位eGPU 已被取消,不能在函数SurfaceFlinger::createNormalSurfaceLocked 中利用eGPU 判断是否为OpenGL ES 应用程序,只能修改SurfaceView.java 、GLSurfaceView.java 等处代码来强制OpenGL ES 应用程序使用RGB565 的surface 以便保持向下兼容性,相对来说比在Donut 上的处理要复杂些。
到了Gingerbread 更进了一步,在高通等平台上的Gingerbread 原生代码缺省已经支持完全的24 位真彩色,我们自己不需要再做任何修改,可见Google 也认为有必要在手机等移动设备上支持24 位真彩色显示。阅读代码可见Google 做的修改很多与我们在Froyo 上改的是相同的,但在函数SurfaceFlinger::createNormalSurface 中case PIXEL_FORMAT_OPAQUE: 下面的format 是采用的PIXEL_FORMAT_RGBX_8888 ,这个更合理。