3. 颜色深度与像素格式:从1bpp到32bpp的存储哲学
颜色深度,说白了就是一个像素用多少位来表示。这个数字直接决定了你能显示多少种颜色,也决定了同样一张图占多少内存。我刚开始做嵌入式GUI时,总觉得颜色深度越大越好——谁不想显示更细腻的画面呢?但后来被硬件资源狠狠教育过几次,才明白选择合适的颜色深度,往往比追求高画质更重要。
咱们从最基础的开始,一步步往上走。
3.1 1bpp:黑白分明,极致节俭
1bpp,每个像素只占1位。0代表黑色,1代表白色——就这么简单。你想想看,一个字节能存8个像素,存储效率极高。
存储结构:每个字节的8个bit,从左到右(或从右到左,取决于位序)依次对应8个像素。
// 1bpp 像素排列示例(MSB优先)
// 字节 0xAA = 10101010b → 像素: 白 黑 白 黑 白 黑 白 黑
uint8_t pixel_byte = 0xAA;
for (int i = 0; i < 8; i++) {
int pixel = (pixel_byte >> (7 - i)) & 0x01;
// pixel == 1 显示白色,pixel == 0 显示黑色
}
应用场景:
- 电子墨水屏(Kindle等)
- 单色OLED显示屏(0.96寸那种)
- 低端家电的段码屏替代方案
我的经验:做智能手表项目时,表盘界面用1bpp做文字渲染,配合反锯齿算法,视觉效果完全不输4bpp。关键是刷新速度快,功耗低——电池续航能多撑半天。
3.2 2bpp与4bpp:索引色的黄金时代
2bpp支持4种颜色,4bpp支持16种颜色。它们都采用调色板索引的方式:像素值不是直接的颜色值,而是调色板里的索引号。
我记得早期做MP3播放器界面时,用的就是4bpp。16种颜色配合得当,UI效果已经很不错了。
| 颜色深度 | 每像素位数 | 支持颜色数 | 每字节像素数 | 典型调色板大小 |
|---|---|---|---|---|
| 2bpp | 2 | 4 | 4 | 4 × 3字节 = 12字节 |
| 4bpp | 4 | 16 | 2 | 16 × 3字节 = 48字节 |
// 4bpp 像素数据 + 调色板示例
// 调色板:16种RGB888颜色
uint32_t palette[16] = {
0x000000, 0xFFFFFF, 0xFF0000, 0x00FF00,
0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF,
// ... 其余8种自定义颜色
};
// 像素数据:每个字节存2个像素
uint8_t pixel_data[] = { 0x12, 0x34, 0x56, 0x78 };
// 第一个字节: 0x12 → 像素0=1, 像素1=2
// 第二个字节: 0x34 → 像素2=3, 像素3=4
应用场景:
- 早期功能机UI(诺基亚时代)
- 低成本的彩屏MP3播放器
- 工业控制面板的简单图形界面
避坑指南:我曾经在一个项目里用4bpp做渐变色背景,结果因为颜色数太少,出现了明显的色带。后来改用抖动算法(Dithering)才勉强能看。如果你需要平滑渐变,至少上8bpp。
3.3 8bpp:256色的妥协与智慧
8bpp是索引色的天花板。256种颜色,配合精心设计的调色板,可以模拟出相当丰富的色彩。很多经典的嵌入式GUI库(如uC/GUI早期版本)默认就是8bpp。
这里有个关键点:调色板的设计直接影响显示效果。通用的Web安全色、针对自然图像的优化调色板、针对UI的扁平色调色板——不同场景需要不同的调色板。
// 8bpp 像素数据:每个字节一个像素
// 像素值直接作为调色板索引
uint8_t framebuffer[320 * 240]; // 320x240分辨率
// framebuffer[x + y * 320] = palette_index;
// 访问像素颜色
uint32_t get_pixel_color(int x, int y) {
uint8_t index = framebuffer[x + y * 320];
return palette[index]; // 返回RGB888颜色值
}
应用场景:
- 中低端MCU图形界面(STM32F4系列常见)
- 电子相框(低分辨率版本)
- 嵌入式游戏机(如GameBoy Color的进阶)
3.4 16bpp(RGB565):嵌入式GUI的黄金标准
16bpp,尤其是RGB565格式,是我个人最常用的颜色深度。为什么?因为它用16位表示了65536种颜色,存储效率高,显示效果也足够好。
RGB565的分配:红色5位、绿色6位、蓝色5位。绿色多1位是因为人眼对绿色最敏感。
| 颜色分量 | 位数 | 取值范围 | 实际颜色数 |
|---|---|---|---|
| Red | 5 | 0-31 | 32 |
| Green | 6 | 0-63 | 64 |
| Blue | 5 | 0-31 | 32 |
// RGB565 像素格式
// 高字节: RRRRR GGG | 低字节: GGG BB BBB
// 组合方式: (R << 11) | (G << 5) | B
uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) {
// 从8位压缩到5/6/5位
uint16_t r5 = (r >> 3) & 0x1F;
uint16_t g6 = (g >> 2) & 0x3F;
uint16_t b5 = (b >> 3) & 0x1F;
return (r5 << 11) | (g6 << 5) | b5;
}
// 解压回RGB888用于显示处理
void rgb565_to_rgb888(uint16_t color, uint8_t *r, uint8_t *g, uint8_t *b) {
*r = ((color >> 11) & 0x1F) << 3; // 左移3位恢复
*g = ((color >> 5) & 0x3F) << 2;
*b = (color & 0x1F) << 3;
}
应用场景:
- 绝大多数TFT-LCD显示屏(320x240、480x272等分辨率)
- 智能穿戴设备(手环、手表)
- 家用电器彩色触摸屏
为什么RGB565是黄金标准?因为它的帧缓冲区大小刚好是分辨率×2字节。对于320x240的屏幕,帧缓冲区只需要150KB——很多MCU的内部SRAM刚好够用。如果是24bpp,同样的分辨率需要230KB,很多MCU就放不下了。
3.5 24bpp(RGB888)与32bpp(ARGB8888):真彩色时代
24bpp每个像素用3字节,红绿蓝各8位,总共1677万色。32bpp在此基础上多了8位Alpha通道,用于透明度控制。
说实话,在嵌入式领域,24bpp和32bpp用得并不多。原因很简单:内存消耗太大。一个480x272的屏幕,32bpp需要522KB的帧缓冲区——这已经超过了很多MCU的全部RAM了。
// RGB888 像素格式(24bpp)
typedef struct {
uint8_t r; // 0-255
uint8_t g; // 0-255
uint8_t b; // 0-255
} rgb888_t;
// ARGB8888 像素格式(32bpp)
typedef struct {
uint8_t b; // Blue
uint8_t g; // Green
uint8_t r; // Red
uint8_t a; // Alpha (0=透明, 255=不透明)
} argb8888_t;
// 注意:实际存储时可能按BGRA顺序,取决于硬件
应用场景:
- 高端嵌入式设备(树莓派类、全志/Rockchip方案)
- 需要透明效果的UI(弹窗、阴影、图标叠加)
- 图像处理/计算机视觉应用
血的教训:我曾经在一个项目里用32bpp做UI,结果MCU的RAM不够,不得不外挂SDRAM。SDRAM的初始化、刷新、功耗问题折腾了我整整两周。后来我学乖了:除非必须用Alpha通道,否则一律用RGB565。UI的透明效果可以用软件模拟,虽然性能差一点,但稳定多了。
3.6 颜色深度对比与选型指南
| 深度 | 颜色数 | 每像素字节 | 320x240帧缓冲 | 典型硬件 |
|---|---|---|---|---|
| 1bpp | 2 | 0.125 | 9.6KB | 8位MCU |
| 2bpp | 4 | 0.25 | 19.2KB | 8/16位MCU |
| 4bpp | 16 | 0.5 | 38.4KB | 16位MCU |
| 8bpp | 256 | 1 | 76.8KB | 32位MCU |
| 16bpp | 65536 | 2 | 153.6KB | ARM Cortex-M4/M7 |
| 24bpp | 16.7M | 3 | 230.4KB | 需外扩RAM |
| 32bpp | 16.7M+Alpha | 4 | 307.2KB | 需外扩RAM/MPU |
我的选型建议:
- RAM < 64KB:用4bpp或8bpp,配合调色板优化
- RAM 64KB-256KB:首选16bpp(RGB565),性价比最高
- RAM > 256KB:可以考虑24bpp,但先确认是否需要真彩色
- 需要透明效果:32bpp,但做好外扩RAM的准备
3.7 核心知识结构图
下面这张图总结了不同颜色深度的存储结构和适用场景,帮你快速建立整体认知:
3.8 本章小结
颜色深度的选择,本质上是在画质、内存、性能三者之间做权衡。没有绝对的好坏,只有适合不适合。
- 1-8bpp:索引色方案,适合资源受限、颜色需求简单的场景
- 16bpp:嵌入式GUI的甜点区,我90%的项目都用它
- 24/32bpp:真彩色方案,性能开销大,只在必要时使用
记住一点:选颜色深度之前,先算清楚你的RAM预算。帧缓冲区只是开销的一部分,别忘了还有调色板、图层缓存、DMA缓冲区……这些加起来,很容易就把RAM吃光了。