3. 色彩深度与像素格式:RGB565、RGB888、ARGB8888 的内存占用差异

做嵌入式 GUI 移植,第一个绕不开的坎就是选像素格式。说白了,你选什么颜色格式,直接决定了你的显存要多大、刷屏要多快、画面好不好看。这三者之间,永远是个三角博弈。

我个人习惯,在项目启动的第一天就把这个定死。因为一旦代码写起来,中间再改像素格式,那简直是给自己挖坑。今天我们就掰开揉碎,把 RGB565、RGB888、ARGB8888 这三兄弟的内存账算清楚。

3.1 三种主流格式的「内存账单」

先看一张表,把底裤扒干净。

像素格式 每像素位数 (bpp) 每像素字节数 颜色通道 总色数 典型显存占用 (320×240)
RGB565 16 bpp 2 字节 R:5, G:6, B:5 65,536 150 KB
RGB888 24 bpp 3 字节 R:8, G:8, B:8 16,777,216 225 KB
ARGB8888 32 bpp 4 字节 A:8, R:8, G:8, B:8 16,777,216 + 256 级透明 300 KB

看到没?同样是 320×240 的屏幕,RGB565 只要 150KB,ARGB8888 直接翻倍到 300KB。你想想看,如果你的 MCU 只有 512KB 的 SRAM,一个显存就吃掉一半多,剩下的还要跑系统、堆栈、缓冲区……压力山大。

3.2 为什么 RGB565 是嵌入式「默认首选」?

我在项目中遇到过很多次,产品经理拿着手机说「你看这个渐变色有断层」。嗯,这确实是 RGB565 的硬伤——绿色只有 6 位,红色和蓝色只有 5 位,总共才 6 万多种颜色。人眼对绿色最敏感,所以绿色通道多给 1 位,这是有讲究的。

但 RGB565 的好处太明显了:

  • 内存减半:相比 ARGB8888,直接省一半显存
  • 刷屏快:16 位数据总线一次传输一个像素,不用拼接
  • 硬件友好:绝大多数低成本 LCD 控制器原生支持 16 位模式
我的经验: 如果你的屏幕分辨率在 480×272 以下,且不需要透明叠加效果,RGB565 是性价比最高的选择。我曾经在一个 STM32F429 项目上硬上 ARGB8888,结果帧率掉到 15fps,最后乖乖切回 RGB565,帧率直接回到 45fps。

3.3 RGB888:色彩党的「折中方案」

RGB888 每像素 3 字节,比 RGB565 多了 50% 的内存,但颜色数量从 6 万直接飙升到 1600 万。说白了,人眼基本分不出 1600 万色和 1670 万色的区别,但 6 万色和 1600 万色的差距,一眼就能看出来。

什么时候该用 RGB888?

  • 屏幕分辨率较低(比如 240×240),显存总量可控
  • 需要显示照片、渐变背景、医疗影像等色彩丰富的场景
  • 你的 MCU 有足够的内存带宽(比如带 TFT 控制器的 Cortex-M7)
注意: RGB888 在 16 位数据总线上传输时,需要 2 个总线周期才能读完一个像素(先读 2 字节,再读 1 字节)。这会降低刷屏效率。如果你的 LCD 接口是 8 位的,那更惨,需要 3 个周期。我建议优先选硬件支持 24 位模式的 LCD 控制器。

3.4 ARGB8888:功能最全,代价最大

ARGB8888 多了一个 Alpha 通道,也就是透明度。每像素 4 字节,显存直接起飞。但它的价值在于:

  • 窗口叠加:比如一个半透明的弹窗浮在主界面上
  • 抗锯齿字体:文字边缘平滑,没有毛刺
  • UI 动效:淡入淡出、滑动渐变,都需要 Alpha 混合

我曾经在一个智能家居面板项目上,客户非要实现「毛玻璃效果」。嗯,ARGB8888 是唯一选择。但代价是显存从 150KB 涨到 300KB,而且每次刷屏都要做 Alpha 混合运算,CPU 占用率直接飙到 60%。

核心结论: 如果你不需要透明效果,千万别碰 ARGB8888。很多工程师觉得「先选个功能最全的,后面再降级」,结果代码写完了发现内存不够,回头改像素格式,所有位图、字体、颜色常量全要重做。我吃过这个亏,真的痛。

3.5 如何平衡色彩质量与内存?——我的「三板斧」

好,理论说完了,来点实战的。我总结了一套选型流程,你可以直接套用。

  1. 第一步:算显存预算
    先看 MCU 还剩多少 RAM。比如总 RAM 256KB,系统占 80KB,堆栈占 20KB,留给 GUI 的只有 156KB。那 RGB565(150KB)就是天花板。
  2. 第二步:看 UI 需求
    有没有透明叠加?有没有照片级图片?有没有抗锯齿字体?如果都没有,直接 RGB565 走起。
  3. 第三步:考虑「混合模式」
    这是我最常用的技巧——主显存用 RGB565,只有需要透明效果的窗口用 ARGB8888 单独分配缓冲区。说白了,就是「按需分配」,不搞一刀切。
避坑指南: 我曾经在一个项目里,为了省内存,把 16 位色深的图片强行转成 8 位索引色。结果颜色失真严重,客户直接拒收。后来我学乖了——如果内存实在不够,优先降分辨率而不是降色深。320×240 的 RGB565 比 160×120 的 RGB888 看起来舒服得多。

3.6 一张图看懂选型逻辑

下面这张 SVG 图,是我自己总结的「像素格式选型决策树」。每次新项目开始,我都会拿出来过一遍。

像素格式选型决策树 开始选型 需要透明效果? 不需要透明效果 ARGB8888 显存大,CPU 负载高 仅用于需要 Alpha 的窗口 显存够用? 是 → RGB888 否 → RGB565 色彩丰富,适合照片/渐变 注意总线宽度匹配 最省内存,刷屏最快 嵌入式 GUI 默认首选

3.7 代码层面的「内存对齐陷阱」

最后说一个我踩过的坑。RGB565 每像素 2 字节,但很多 MCU 的 DMA 要求源地址 4 字节对齐。如果你把一张 RGB565 的位图放在一个奇数地址上,DMA 传输会直接挂掉。

解决办法很简单:

// 错误示例:位图可能未对齐
const uint16_t image[] = { 0xF800, 0x07E0, 0x001F };

// 正确示例:强制 4 字节对齐
__attribute__((aligned(4)))
const uint16_t image[] = { 0xF800, 0x07E0, 0x001F };

嗯,这个坑我当年查了整整两天。后来养成了习惯,所有位图、字体、调色板数组,一律加 __attribute__((aligned(4)))。不管编译器默认对齐策略是什么,我自己先锁死。

一句话总结: 内存不够用,选 RGB565;需要色彩质量,选 RGB888;需要透明效果,选 ARGB8888,但只给需要的窗口开。别贪心,别偷懒,选型定死再动代码。