屏幕色彩格式与色深(RGB565 / RGB888 等)¶
你可能在 microcontroller 的屏幕驱动、LCM 规格书或 GPU 文档里见过 RGB565、RGB888 这类写法,括号里的数字看着很像,却决定了屏幕能显示多少种颜色、一帧画面占多大内存。本文把这一族「RGB 色彩格式」讲清楚。
一、先搞清楚几个基础概念¶
屏幕上的每张彩色图像,都是由一个个**像素(pixel)**拼出来的。每个像素的颜色,在数字世界里通常被拆成 R(红)、G(绿)、B(蓝) 三个分量来表示——因为人眼里的任意颜色,都可以用这三原色按不同亮度叠加出来。
关键在于:每个分量用多少位(bit)来表示它的「亮度等级」,也就是所谓的色深(bit depth)。
- 1 位只能表示 0 或 1(关 / 开),2 个等级;
- 8 位可以表示从 0 到 255,共 256 个等级;
- 一个像素所有分量的位数加在一起,叫做 bpp(bits per pixel,每像素位数)。
三个分量各占 \(n\) 位时,单个分量的等级数是 \(2^n\),整像素能表达的颜色总数是:
这就是「为什么 RGB888 比 RGB565 颜色多得多」的根本原因。
二、常见 RGB 格式速查表¶
| 格式 | R / G / B 位宽 | 总位宽 (bpp) | 颜色总数 | 俗称 | 典型用途 |
|---|---|---|---|---|---|
| RGB444 | 4 / 4 / 4 | 12 | \(2^{12}=4\,096\) | 增强色 | 早期低端屏、伪彩 |
| RGB555 | 5 / 5 / 5 | 15 | \(2^{15}=32\,768\) | 高彩(旧) | 老式 Windows 16 位色(留 1 位空缺) |
| RGB565 | 5 / 6 / 5 | 16 | \(2^{16}=65\,536\) | 高彩色 (High Color) | MCU / 嵌入式 LCD、RGB 并行接口 |
| RGB666 | 6 / 6 / 6 | 18 | \(2^{18}=262\,144\) | — | 部分 LCD 屏原生 18-bit 接口 |
| RGB888 | 8 / 8 / 8 | 24 | \(2^{24}=16\,777\,216\) | 真彩色 (True Color) | 桌面显示器、GPU 帧缓冲、手机屏 |
| RGBA8888 | 8 / 8 / 8 / 8(A) | 32 | 同上+透明度 | 带 Alpha | 图形合成、UI、游戏 |
| RGB10 / 12-bit | 10 / 10 / 10 等 | 30+ | 10 亿+ | HDR 深色 | HDR 显示器、专业屏 |
注:你提到的「RGB556」大概率是 RGB565 的笔误——5/6/5 才是标准写法(绿色多一位)。真正存在的是 RGB555(三通道都为 5 位)。
三、重点格式详解¶
3.1 RGB565(16 位高彩色)¶
这是嵌入式里最常见的格式,因为 16 位刚好能塞进一个 uint16_t,处理和传输都很省:
- R 占 5 位:0~31
- G 占 6 位:0~63 ← 注意绿色比红蓝多一位
- B 占 5 位:0~31
为什么绿色多给一位?因为人眼对绿色(其实是亮度)最敏感,多分一级绿色,观感上的「颜色阶梯感」比多分红色/蓝色小得多。这是经过感知优化后的经典折中。
3.2 RGB888(24 位真彩色)¶
每个分量各 8 位(0~255),直接对应我们最熟悉的「#RRGGBB」十六进制颜色,也是绝大多数显示器、相机、图像文件的原生表示:
它的 1677 万种颜色已经远超人眼能分辨的界限,所以叫「真彩色」。
3.3 字节序(Endianness)这个坑¶
RGB565 是 16 位值,但内存和接口总线一次传 8 位(1 字节)。把它拆成两个字节时,哪个字节在前,不同 MCU、不同屏驱 IC 的默认不一样:
- 小端(Little-Endian):低字节在前 ——
[低 8 位][高 8 位] - 大端(Big-Endian):高字节在前 ——
[高 8 位][低 8 位]
很多「屏幕颜色发红/发蓝、整体错乱」的 bug,根源就是 MCU 写进去的字节序和屏幕期望的字节序反了。驱动初始化时通常有一个 RGB/BGR 或 数据字节序 的配置位,调出来和你的代码一致即可。
3.4 32 位对齐与 Padding¶
RGB888 严格说是 24 位(3 字节),但很多 GPU / 图形库为了内存访问效率,会用 RGBX8888 / RGBA8888(第 4 字节放透明度或留空)把每个像素补到 32 位(4 字节)。多出来的 1 字节不增加颜色,只是为了让 CPU 用 32 位整数一次读写一个像素更快。
四、为什么需要这么多格式?¶
核心矛盾就一句话:颜色越细腻,占的内存和带宽越大。
以一帧 1920×1080(约 207 万像素)为例,不同格式下「单帧显存」和「60fps 所需带宽」大致是:
| 格式 | 每像素 | 单帧显存 | 60fps 带宽 |
|---|---|---|---|
| RGB565 | 2 字节 | ≈ 4.1 MB | ≈ 248 MB/s |
| RGB888 | 3 字节 | ≈ 6.2 MB | ≈ 373 MB/s |
| RGBA8888 | 4 字节 | ≈ 8.3 MB | ≈ 497 MB/s |
对一块 STM32 这类 MCU 来说,片上 SRAM 可能只有几百 KB,根本放不下一帧 RGB888,所以用 RGB565 是必然的节省;而手机、电脑的 GPU 显存以 GB 计,自然上 RGB888 / 10-bit 追求画质。
经验法则 - 资源紧张、带屏驱 IC 的小屏 → 优先 RGB565 - 需要 UI 透明度、图形合成 → RGBA8888 - 追求画质 / 对接显示器 → RGB888 或更高
五、量化误差与抖动(Dithering)¶
当你把一张 24 位的图(RGB888)硬塞进 16 位(RGB565)时,每个分量要「砍掉」低位:
- 红色 8 位 → 5 位:直接丢掉低 3 位,误差最多 \(\pm 7\) 级
- 蓝色同理
- 绿色 8 位 → 6 位:丢掉低 2 位
结果就是色带(banding)——本该平滑过渡的渐变(如天空、肤色)出现一圈圈阶梯。解决方法是 抖动(Dithering):用「误差扩散」或有序噪点,把舍入误差打散成人眼不易察觉的细颗粒,肉眼看起来反而更平滑。很多屏驱 IC 和 GPU 都内置了 FRC(Frame Rate Control)抖动来「用 6-bit 面板假装 8-bit」。
六、实际处理代码示例¶
下面是一段在嵌入式里最常用的「24 位 ↔ 16 位」互转代码。
#include <stdint.h>
// RGB888 (各 8 位) -> RGB565 (16 位)
// 取 R 的高 5 位、G 的高 6 位、B 的高 5 位
uint16_t rgb888_to_565(uint8_t r, uint8_t g, uint8_t b) {
return (uint16_t)((r & 0xF8) << 8) // R: 取高5位,移到 bit15..11
| (uint16_t)((g & 0xFC) << 3) // G: 取高6位,移到 bit10..5
| (uint16_t)( b >> 3); // B: 取高5位,移到 bit4..0
}
// RGB565 -> RGB888
// 关键:低位要用「复制高位」补满,而不是简单左移补 0(否则颜色会整体变暗)
void rgb565_to_888(uint16_t c, uint8_t *r, uint8_t *g, uint8_t *b) {
uint8_t r5 = (c >> 11) & 0x1F;
uint8_t g6 = (c >> 5) & 0x3F;
uint8_t b5 = c & 0x1F;
*r = (r5 << 3) | (r5 >> 2); // 5 位 -> 8 位:复制高 3 位到低 3 位
*g = (g6 << 2) | (g6 >> 4); // 6 位 -> 8 位
*b = (b5 << 3) | (b5 >> 2); // 5 位 -> 8 位
}
注意
rgb565_to_888里不能写成r5 << 3就完事:5 位最大值 31 左移 3 位只到 248,且低 3 位永远是 0,颜色会偏暗、渐变出现断层。用「高位复制」才能还原出均匀的 0~255。
七、色深与显示面板本身¶
别以为「我给了 RGB888 数据,屏幕就一定显示 1677 万色」。面板本身也有原生色深:
- 6-bit 面板:每个分量只有 64 级,靠 FRC 抖动「假装」8-bit;
- 8-bit 面板:真 256 级,主流显示器;
- 10-bit 面板:1024 级,配合 HDR 才能显现更广的明暗层次。
所以「内容色深、传输格式、面板原生位深」三者要配套,画质才不会被最短的那块木板限制。
八、小结¶
RGBxyz里的数字 = 红 / 绿 / 蓝 各分配多少位,总和就是这个像素的 bpp;- RGB565(16 位 / 6.5 万色) 省内存,是嵌入式屏首选;RGB888(24 位 / 1677 万色) 是真彩色,桌面和手机主流;
- 真正影响观感的还有字节序、量化误差、抖动、面板原生位深这几个工程细节;
- 选格式的本质是在 画质 ↔ 内存 / 带宽 / 功耗 之间做取舍。