4、色彩与像素管理
色彩管理这事儿,说大不大,说小不小。我见过太多嵌入式项目,功能都调通了,最后死在显示效果上——颜色不对、闪烁、锯齿严重。说白了,就是色彩与像素这块没处理好。
今天咱们就聊聊色彩深度、调色板、Alpha混合和色彩空间转换。这些都是GUI开发躲不开的基础功。
4.1 色彩深度选择:1/4/8/16/24/32 bpp
色彩深度,也叫位深,指的是每个像素用多少位来表示颜色。这个选择直接影响显存占用和显示效果。
1 bpp(黑白)
每个像素只有1位,要么黑要么白。适合电子墨水屏、单色OLED。我在做工业仪表项目时用过,显示数字和简单图标完全够用。
4 bpp(16色)
每个像素4位,支持16种颜色。常用于低端彩屏,比如某些家电的段码屏升级版。说实话,现在用得少了,除非你还在用老款控制器。
8 bpp(256色)
这是嵌入式GUI的经典配置。每个像素1字节,配合调色板使用。我早期做手持设备时,8 bpp配合优化过的调色板,效果能接近16位色。
16 bpp(65,536色)
工业级GUI的标配。常见格式是RGB565(红5位、绿6位、蓝5位)。为什么绿色多1位?因为人眼对绿色最敏感。嗯,这是有科学依据的。
24 bpp(16,777,216色)
真彩色,每个像素3字节。效果最好,但显存占用也最大。320×240分辨率下,一帧就要230KB。如果你的MCU有足够RAM,或者用外部SDRAM,可以考虑。
32 bpp
24位色加上8位Alpha通道。主要用于需要透明效果的场景。注意,很多低端MCU不支持32位色直接输出,需要软件模拟。
我的建议:资源紧张选16 bpp(RGB565),效果和性能最平衡。如果必须透明效果,上32 bpp。8 bpp适合老平台或极小显存场景。
4.2 调色板模式 vs 直接色模式
这两个概念,我当年也混淆过。简单说:
- 调色板模式(Palette):像素值是一个索引,指向颜色表。比如8 bpp,像素值0~255,每个值对应一个24位颜色。
- 直接色模式(Direct Color):像素值直接包含RGB分量。比如16 bpp的RGB565,像素值的高5位是红,中间6位是绿,低5位是蓝。
调色板的优势:
- 显存占用小。8 bpp比16 bpp省一半空间。
- 换皮肤方便。只需修改调色板,不用重绘所有像素。
- 可以做颜色动画。比如让整个界面渐变色,只需循环修改调色板。
直接色的优势:
- 颜色更丰富。16 bpp就有6万多种颜色。
- 处理简单。不需要查表,CPU直接解析RGB值。
- 适合照片、渐变等复杂图像。
避坑指南:我曾经在一个项目里用8 bpp调色板显示照片,结果颜色断层严重,像马赛克一样。后来改成16 bpp直接色,效果立竿见影。调色板适合图标、文字、UI控件,不适合照片。
4.3 Alpha混合与透明度处理
Alpha混合,说白了就是让两个图层叠加时产生透明效果。公式很简单:
// Alpha混合公式
// src: 前景色, dst: 背景色, alpha: 0~255
result_R = (src_R * alpha + dst_R * (255 - alpha)) / 255
result_G = (src_G * alpha + dst_G * (255 - alpha)) / 255
result_B = (src_B * alpha + dst_B * (255 - alpha)) / 255
但实际工程中,有几个坑要注意:
性能问题:每个像素都要做乘法和除法,对MCU负担不小。我常用的优化方法:
- 预计算Alpha表。把alpha/255的结果提前算好存成查找表。
- 用移位代替除法。如果alpha是2的幂次,比如128(半透明),直接用右移7位。
- 只对需要透明的区域做混合。不要全屏无脑混合。
Alpha预乘:有些引擎用预乘Alpha,即颜色值已经乘过Alpha。好处是混合时少一次乘法,但要注意颜色值范围会变小。
注意:Alpha混合在16 bpp下精度有限。RGB565只有6位绿色,混合后容易产生色阶。我建议在24 bpp或32 bpp下做Alpha混合,最后再dither到16 bpp输出。
4.4 色彩空间转换:RGB与HSV
RGB适合硬件显示,但不适合人眼理解。你想想看,让你调一个「偏蓝绿色」的颜色,RGB值怎么调?R=100, G=200, B=180?太反直觉了。
HSV就好理解多了:
- H(色相):0~360度,红橙黄绿青蓝紫
- S(饱和度):0~100%,颜色纯度
- V(明度):0~100%,颜色亮度
什么时候用HSV?
- 颜色选择器。用户拖动色相条选颜色。
- 主题换色。只改H值,保持S和V不变,就能生成不同色系的主题。
- 颜色识别。比如检测红色区域,用H值范围比RGB阈值稳定得多。
RGB转HSV的代码,我贴一个精简版:
// RGB转HSV(简化版,适合嵌入式)
void rgb2hsv(uint8_t r, uint8_t g, uint8_t b,
uint16_t *h, uint8_t *s, uint8_t *v) {
uint8_t max = (r > g) ? (r > b ? r : b) : (g > b ? g : b);
uint8_t min = (r < g) ? (r < b ? r : b) : (g < b ? g : b);
uint8_t delta = max - min;
*v = max; // 明度就是最大值
if (max == 0) {
*s = 0;
*h = 0;
return;
}
*s = (delta * 255) / max; // 饱和度
if (delta == 0) {
*h = 0;
} else {
// 计算色相
if (max == r) {
*h = 0 + 43 * (g - b) / delta;
} else if (max == g) {
*h = 85 + 43 * (b - r) / delta;
} else {
*h = 171 + 43 * (r - g) / delta;
}
if (*h > 255) *h -= 255;
}
}
我的经验:HSV转RGB时,注意浮点运算。我习惯用查表法,把sin/cos值预计算好。另外,H值用0~255表示(而不是0~360),这样刚好1字节,省空间。
4.5 实际项目中的色彩策略
最后分享一个我常用的色彩管理流程:
- 设计阶段:用HSV定义颜色。设计师给色相,工程师调饱和度和明度。
- 资源制作:图片转成RGB565或调色板格式。注意dither处理。
- 运行时:UI控件用直接色,图标用调色板(省空间)。
- 特效处理:Alpha混合只在需要时开启,用完立刻关闭。
嗯,色彩管理这事儿,说白了就是空间换时间、精度换性能的权衡。没有银弹,只有适合你项目的方案。
下一章咱们聊聊字体渲染和字符编码,这也是嵌入式GUI里容易踩坑的地方。