2. 常见图像格式分析:BMP、PNG、JPEG在嵌入式系统中的优劣对比
做触摸屏开发,选对图像格式,有时候比优化算法还管用。我见过不少项目,UI 卡顿、闪存爆满,追根溯源,往往是图片格式没选对。
今天咱们就掰开揉碎,聊聊嵌入式系统里最常见的三种格式:BMP、PNG、JPEG。它们各自什么脾气,适合什么场景,踩过哪些坑,我都会一一说到。
2.1 BMP:简单粗暴,但代价不小
BMP 是 Windows 时代的元老。说白了,它就是像素点的直接排列,不压缩,或者只做最简单的 RLE 压缩。
核心特点:解码零开销,直接内存映射就能显示。但文件体积巨大,一张 800×480 的 24 位真彩色图,裸数据就要 800×480×3 ≈ 1.1 MB。
我在一个早期的工控屏项目里用过 BMP。当时 MCU 主频只有 72 MHz,Flash 也紧巴巴的。UI 就几个静态页面,我图省事,直接把 BMP 存到外部 SPI Flash 里。结果呢?每次切屏都要等 1 秒多,因为 SPI 读取速度太慢,1 MB 数据读出来就得几百毫秒。
嵌入式场景下的 BMP 优缺点:
| 维度 | 评价 | 说明 |
|---|---|---|
| 解码速度 | ★★★★★ | 几乎不需要 CPU 参与,直接 DMA 搬运即可 |
| 存储占用 | ★☆☆☆☆ | 体积最大,对 Flash 容量是巨大考验 |
| 内存占用 | ★★☆☆☆ | 解码时需要整帧缓冲区,小 RAM 设备吃不消 |
| 适用场景 | — | 仅适合 RAM/Flash 充裕、对延迟极度敏感的场景 |
我曾经踩过的坑:有个项目用了 320×240 的 BMP 做开机 Logo,结果 16 MB 的 SPI Flash 塞了 4 张图就满了。后来换成 PNG 解码,Flash 占用直接降到 1/10。所以,除非你的 Flash 大得用不完,否则别碰 BMP。
2.2 PNG:无损压缩的利器,但解码有代价
PNG 是嵌入式 UI 的常客。它用 Deflate 算法做无损压缩,对色块均匀的图标、按钮效果极好。你想想看,一张纯色背景的按钮,PNG 能压缩到原始大小的 5% 以下。
但 PNG 有个「软肋」——解码需要内存。它用的是逐行扫描 + 滤波器预测,解码过程中需要维护至少一行像素的上下文。对于大尺寸图片,内存开销不容小觑。
PNG 在嵌入式中的关键参数:
- 色深支持:支持 1/2/4/8/16/24/32 位,索引色模式对嵌入式特别友好
- Alpha 通道:原生支持透明,适合做异形按钮、图标叠加
- 压缩率:图标类 5:1~10:1,照片类 2:1~3:1
- 解码库:常见的有 libpng、picoPNG、TinyPNG(嵌入式裁剪版)
我的个人习惯:对于 64×64 以下的小图标,我会用索引色 PNG,调色板只保留 16 色或 256 色。这样解码速度快,体积也小。对于大尺寸背景图,我一般不用 PNG,因为解码时间可能超过 200 ms,用户能明显感觉到卡顿。
2.3 JPEG:照片级压缩,但细节有损
JPEG 是照片和复杂渐变场景的王者。它用离散余弦变换(DCT)做有损压缩,能把一张照片压到原始大小的 1/10 甚至更小。
但 JPEG 在嵌入式系统里有个大问题:解码计算量大。它需要做 IDCT、反量化、色彩空间转换,每一步都涉及浮点运算或定点数模拟。对于没有 FPU 的 MCU,解码一张 VGA 级别的 JPEG 可能要花 1~2 秒。
JPEG 解码的内存布局:
JPEG 数据流 → 霍夫曼解码 → 反量化 → IDCT → 色彩转换 → RGB 帧缓冲
↓
MCU(最小编码单元)逐块处理
↓
需要 8×8 或 16×16 的临时缓冲区
嗯,这里要注意:JPEG 是逐块解码的,不需要一次性加载整张图到内存。这对 RAM 小的设备是个好消息。但代价是 CPU 占用率高,而且解码时间不可预测。
| 维度 | JPEG | PNG | BMP |
|---|---|---|---|
| 压缩类型 | 有损 | 无损 | 无压缩 |
| 解码复杂度 | 高(IDCT + 霍夫曼) | 中(Deflate + 滤波器) | 低(直接拷贝) |
| 内存需求 | 低(逐块解码) | 中(需行缓冲区) | 高(整帧缓冲区) |
| 透明支持 | 不支持 | 支持 | 不支持 |
| 典型压缩比 | 10:1~20:1 | 2:1~10:1 | 1:1 |
我曾经遇到的一个教训:有个项目用 JPEG 做全屏壁纸,MCU 是 Cortex-M4,没有硬件 JPEG 解码器。结果每次开机解一张 800×480 的 JPEG 要 3 秒多,用户投诉开机太慢。后来我把壁纸换成预解码的 RGB565 数据,开机时间降到 0.5 秒。所以,如果对启动时间有要求,JPEG 解码最好放在生产阶段完成,运行时只加载裸数据。
2.4 三种格式的选型决策树
说了这么多,到底怎么选?我习惯用下面这个决策逻辑:
- 是否需要透明? → 是 → 选 PNG(索引色优先)
- 是否是照片/渐变背景? → 是 → 选 JPEG(注意解码时间)
- 是否是纯色图标/文字? → 是 → 选 PNG(压缩率高)
- RAM 是否小于 64 KB? → 是 → 避免 BMP,优先 JPEG 或小尺寸 PNG
- Flash 是否小于 2 MB? → 是 → 避免 BMP,优先 JPEG 或压缩后的 PNG
- 解码时间是否要求 < 50 ms? → 是 → 考虑 BMP 或预解码数据
说白了,没有完美的格式,只有适合场景的格式。我个人的经验是:图标用 PNG,照片用 JPEG,BMP 能不用就不用。除非你的硬件资源真的非常宽裕。
2.5 三种格式的核心对比图
下面这张图,是我自己总结的三种格式在嵌入式系统中的核心对比。你可以把它当作选型时的快速参考。
2.6 实战建议:如何选择与优化
最后,分享几条我这些年总结出来的实战建议:
- 图标资源:统一用 PNG 索引色模式,调色板控制在 16 色以内。解码时直接映射到 LCD 的索引色表,省去色彩转换。
- 背景图片:如果必须用 JPEG,建议在 PC 端预处理成 RGB565 或 RGB888 的裸数据,烧录到 Flash 中。运行时直接 DMA 到显存,零解码开销。
- 动画序列帧:不要用 BMP 逐帧存储。我习惯把多帧 PNG 打包成一个自定义格式,解码时只解当前帧,内存复用。
- 透明叠加层:PNG 的 Alpha 通道在嵌入式里实现起来比较重。如果只是简单的二值透明(全透或全不透),可以用 1 位掩码图代替,体积小、速度快。
一个小技巧:很多嵌入式 GUI 库(如 LVGL、TouchGFX)都支持直接加载 PNG 或 JPEG。但它们的解码器往往是裁剪过的,不支持所有特性。比如裁剪版 libpng 可能不支持隔行扫描。所以,在选型前,一定要确认你的解码库支持哪些 PNG/JPEG 子集。我曾经因为用了隔行扫描的 PNG,在目标板上解码花屏,排查了整整两天。