3. Framebuffer 像素格式详解:RGB565、RGB888、ARGB8888 的位布局与颜色深度转换

在嵌入式 Framebuffer 驱动开发中,像素格式决定了显存中每个像素的存储方式与颜色表达能力。不同的 RGB 格式在颜色深度、内存占用、显示性能上存在显著差异。本章将深入剖析三种最常用的像素格式——RGB565、RGB888 和 ARGB8888,从位布局、颜色分量映射到深度转换算法,为后续的光标叠加与性能优化奠定基础。

3.1 像素格式的位布局对比

下表列出了三种格式的位宽、颜色分量位数及典型应用场景:

格式名称 总位宽 R 位宽 G 位宽 B 位宽 A 位宽 典型应用
RGB565 16 位 5 位 6 位 5 位 无 低端 MCU、低成本 TFT
RGB888 24 位 8 位 8 位 8 位 无 中端嵌入式 Linux、HDMI 输出
ARGB8888 32 位 8 位 8 位 8 位 8 位 光标叠加、UI 合成、Alpha 混合
关键指标:RGB565 每个像素仅占用 2 字节,内存带宽需求低,但颜色精度有限(最多 65536 色)。ARGB8888 每个像素占用 4 字节,支持 1670 万色与 256 级透明度,是光标叠加与半透明特效的首选格式。

3.2 位布局与内存映射

理解位布局是进行颜色深度转换的前提。以下 SVG 图展示了三种格式在 32 位内存字中的位排列方式:

三种 RGB 格式位布局对比(32 位内存对齐视图) RGB565 16 位有效数据(低 16 位) R[4:0] 5位 G[5:0] 6位 B[4:0] 5位 未使用(高 16 位) RGB888 24 位有效数据(低 24 位) R[7:0] 8位 G[7:0] 8位 B[7:0] 8位 未使用(高 8 位) ARGB8888 32 位全部有效 A[7:0] 8位 R[7:0] 8位 G[7:0] 8位 B[7:0] 8位 说明:内存采用小端序(Little-Endian),低地址存放低字节。ARGB8888 的 Alpha 通道位于最高字节。

3.3 颜色深度转换算法

在实际开发中,经常需要在不同像素格式之间进行转换,例如将 RGB565 的帧缓冲内容转换为 ARGB8888 用于光标叠加。转换的核心是位扩展与位压缩。

3.3.1 RGB565 → RGB888 扩展

将 5 位或 6 位颜色分量扩展到 8 位,需要将高位复制到低位,以填充缺失的精度:

/* RGB565 像素值 */
uint16_t pixel_565 = 0xF800;  /* 红色:R=11111, G=000000, B=00000 */

/* 提取分量 */
uint8_t r5 = (pixel_565 >> 11) & 0x1F;  /* 5 位红色 */
uint8_t g6 = (pixel_565 >> 5)  & 0x3F;  /* 6 位绿色 */
uint8_t b5 = (pixel_565 >> 0)  & 0x1F;  /* 5 位蓝色 */

/* 扩展为 8 位:高位复制到低位 */
uint8_t r8 = (r5 << 3) | (r5 >> 2);   /* 5→8 位 */
uint8_t g8 = (g6 << 2) | (g6 >> 4);   /* 6→8 位 */
uint8_t b8 = (b5 << 3) | (b5 >> 2);   /* 5→8 位 */

/* 合成 RGB888 像素(24 位) */
uint32_t pixel_888 = (r8 << 16) | (g8 << 8) | b8;
扩展原理:对于 5 位值 10101(十进制 21),左移 3 位得到 10101000,再右移 2 位得到 00101010,两者按位或得到 10101010,即 8 位下的近似值。这种方法比简单的左移填充零更接近原始亮度。

3.3.2 RGB888 → RGB565 压缩

从 8 位压缩到 5 位或 6 位时,直接丢弃低位:

/* RGB888 像素值 */
uint32_t pixel_888 = 0xFF8800;  /* R=0xFF, G=0x88, B=0x00 */

/* 提取 8 位分量 */
uint8_t r8 = (pixel_888 >> 16) & 0xFF;
uint8_t g8 = (pixel_888 >> 8)  & 0xFF;
uint8_t b8 = (pixel_888 >> 0)  & 0xFF;

/* 压缩为 5/6 位 */
uint8_t r5 = r8 >> 3;   /* 保留高 5 位 */
uint8_t g6 = g8 >> 2;   /* 保留高 6 位 */
uint8_t b5 = b8 >> 3;   /* 保留高 5 位 */

/* 合成 RGB565 像素 */
uint16_t pixel_565 = (r5 << 11) | (g6 << 5) | b5;

3.3.3 ARGB8888 与 RGB888 互转

ARGB8888 转 RGB888 时,直接丢弃 Alpha 通道;反向转换时,通常将 Alpha 设为 0xFF(完全不透明):

/* ARGB8888 → RGB888 */
uint32_t argb = 0x80FF8800;   /* A=0x80, R=0xFF, G=0x88, B=0x00 */
uint32_t rgb  = argb & 0x00FFFFFF;  /* 掩码去除 Alpha */

/* RGB888 → ARGB8888(Alpha 设为不透明) */
uint32_t rgb_in  = 0xFF8800;
uint32_t argb_out = 0xFF000000 | rgb_in;  /* A=0xFF */

3.4 格式转换流程与性能考量

以下 SVG 图展示了从 RGB565 帧缓冲到 ARGB8888 光标叠加层的典型转换流水线:

RGB565 → ARGB8888 转换流水线 RGB565 帧缓冲 16 位/像素 逐像素 分量提取 R5/G6/B5 位扩展 5/6→8 位 合成 ARGB8888 A=0xFF + R8/G8/B8 性能优化建议: 使用查表法(LUT)替代移位运算,可减少 CPU 开销;利用 NEON/SIMD 指令并行处理多个像素。

3.5 常见问题与避坑指南

  • 字节序问题:ARM 平台通常使用小端序,RGB565 的低字节在前。直接使用 memcpy 拷贝像素数据时,务必确认字节序是否匹配。
  • Alpha 预乘:在 ARGB8888 光标叠加中,如果使用半透明光标,建议采用 Alpha 预乘(Premultiplied Alpha)格式,避免边缘出现黑边。
  • 颜色失真:RGB888 压缩为 RGB565 时,低位截断会导致颜色条带(Color Banding)。可在压缩前加入 1 位随机抖动(Dithering)改善视觉效果。
  • 内存对齐:Framebuffer 的起始地址通常要求 4 字节对齐。RGB565 格式每像素 2 字节,行宽可能不是 4 的倍数,需要填充补齐。
性能陷阱:不要在帧同步中断(VSync IRQ)中执行逐像素格式转换。转换操作应放在后台线程或利用 DMA2D 硬件加速完成,否则会导致显示撕裂(Tearing)或帧率下降。

3.6 实战:RGB565 与 ARGB8888 互转函数

以下代码实现了高效的查表法转换,适用于光标叠加场景:

/* 预计算 5→8 位扩展表 */
static uint8_t lut_5to8[32];
static uint8_t lut_6to8[64];

void init_conversion_lut(void) {
    for (int i = 0; i < 32; i++) {
        lut_5to8[i] = (i << 3) | (i >> 2);
    }
    for (int i = 0; i < 64; i++) {
        lut_6to8[i] = (i << 2) | (i >> 4);
    }
}

/* 将 RGB565 像素转换为 ARGB8888(Alpha=0xFF) */
uint32_t rgb565_to_argb8888(uint16_t pixel) {
    uint8_t r = lut_5to8[(pixel >> 11) & 0x1F];
    uint8_t g = lut_6to8[(pixel >> 5)  & 0x3F];
    uint8_t b = lut_5to8[(pixel >> 0)  & 0x1F];
    return (0xFF << 24) | (r << 16) | (g << 8) | b;
}

/* 将 ARGB8888 像素转换为 RGB565(丢弃 Alpha) */
uint16_t argb8888_to_rgb565(uint32_t pixel) {
    uint8_t r = (pixel >> 19) & 0x1F;  /* 取高 5 位 */
    uint8_t g = (pixel >> 10) & 0x3F;  /* 取高 6 位 */
    uint8_t b = (pixel >> 3)  & 0x1F;  /* 取高 5 位 */
    return (r << 11) | (g << 5) | b;
}
查表法优势:预计算 32 字节和 64 字节的查找表,将每次转换的 3 次移位+3 次或运算简化为 3 次查表操作。在批量转换大量像素时,性能提升可达 30% 以上。