4、直接存储 vs 解码存储:BMP/JPEG/PNG的存储差异、运行时解码的内存开销、Bitmap缓存策略
说到图片在嵌入式GUI里的存储方式,很多刚接触TouchGFX的朋友都会问一个问题:图片到底是怎么存在Flash里的?运行时又怎么跑到RAM里去的?
嗯,这个问题其实挺关键的。你想想看,一张图片在PC上可能就几百KB,但到了单片机上,同样的图片可能吃掉你几MB的RAM。为什么会这样?
说白了,就是直接存储和解码存储这两条路,走法完全不同。
4.1 三种格式的存储差异
我们先看看BMP、JPEG、PNG这三种最常见的图片格式,它们在Flash里占的空间到底差多少。
| 格式 | 压缩方式 | Flash占用(相对) | 特点 |
|---|---|---|---|
| BMP | 无压缩 | 最大 | 像素数据原样存储,读取最快 |
| JPEG | 有损压缩 | 最小(通常为BMP的5%~20%) | 适合照片类,压缩比高但有画质损失 |
| PNG | 无损压缩 | 中等(通常为BMP的30%~60%) | 适合图标、UI元素,支持透明通道 |
我记得有一次做智能家居面板的项目,客户要求界面必须高清。我一开始全用了BMP,结果Flash直接爆了。后来换成PNG,同样的图片,Flash占用降了将近一半。这就是格式选择带来的实际差异。
4.2 运行时解码的内存开销
这里有个容易踩的坑:很多人以为Flash里存得小,运行时占的RAM也小。其实完全不是这么回事。
不管是JPEG还是PNG,它们在运行时都需要解码成原始像素数据才能显示。这个原始像素数据有多大?
核心公式:
原始像素数据大小 = 宽度 × 高度 × 每像素字节数
例如:一张 480×272 的 16位色图片,解码后占 480 × 272 × 2 = 261,120 字节 ≈ 255KB
也就是说,不管你Flash里存的是多大的JPEG或PNG,只要显示出来,RAM里就得给它腾出这么大一块地方。
我曾经遇到一个项目,UI设计师给了很多全屏背景图,都是JPEG格式,Flash里每张才几十KB。结果运行时一跑,内存直接溢出。查了半天才发现,解码后的RAM占用才是真正的杀手。
4.2.1 三种格式的解码开销对比
| 格式 | 解码后RAM占用 | 解码CPU开销 | 解码时间 |
|---|---|---|---|
| BMP | 与原始数据相同 | 极低(几乎无解码) | 极快 |
| JPEG | 与原始数据相同 | 高(需要IDCT、反量化等) | 较慢 |
| PNG | 与原始数据相同 | 中等(需要zlib解压缩) | 中等 |
你看,解码后的RAM占用其实是一样的。区别在于解码过程中需要额外的临时缓冲区。JPEG解码时需要一个中间缓冲区来存放MCU(最小编码单元)数据,PNG解码则需要zlib的滑动窗口缓冲区。这些临时开销虽然不大,但在内存紧张的单片机上,有时候就是压死骆驼的最后一根稻草。
4.3 Bitmap缓存策略
既然解码这么费劲,那能不能把解码后的数据缓存起来,避免重复解码?
当然可以。TouchGFX提供了几种缓存策略,我根据自己的项目经验,把它们整理成下面这张图:
4.3.1 全量缓存策略
全量缓存,就是把解码后的像素数据一直放在RAM里,随用随取。TouchGFX里对应的是BitmapCache机制。
适用场景:
- 频繁切换的图标、按钮
- 动画帧序列
- 常驻界面的背景元素
优点:显示速度快,无解码延迟
缺点:占用大量RAM
我个人习惯把那些小于屏幕1/4的UI元素用全量缓存。比如一个48×48的图标,解码后也就4.5KB(16位色),这点RAM还是值得花的。
4.3.2 按需解码策略
按需解码,就是每次显示前才从Flash里读取并解码,显示完就释放RAM。TouchGFX里通过DynamicBitmap实现。
注意:按需解码虽然省RAM,但解码过程会占用CPU时间。如果界面切换频繁,可能会出现卡顿。
我曾经在一个医疗设备项目里,把全屏的X光片用按需解码,结果每次切换界面都要等1秒多。后来改成预解码+缓存,才把切换时间降到100ms以内。
4.3.3 混合策略
实际项目中,我很少只用一种策略。通常的做法是:
- 常驻元素(导航栏、状态图标)→ 全量缓存
- 低频元素(设置页面、帮助页面)→ 按需解码
- 大图背景 → 按需解码,但配合预加载机制
你想想看,一个典型的智能手表界面,表盘背景可能一周才换一次,但指针每秒钟都在动。如果背景也用全量缓存,那RAM就白白浪费了。所以背景按需解码,指针全量缓存,这才是合理的做法。
4.4 实战建议
我的经验总结:
- BMP:除非Flash非常充裕且对显示速度有极致要求,否则不建议用。我只有在做开机Logo这种一次性显示的场景才用BMP。
- JPEG:适合照片、渐变背景等复杂图像。但要注意解码速度,建议在初始化阶段预解码。
- PNG:UI元素的首选。支持透明通道,压缩比适中,解码速度也还可以。
最后说一句:不要只看Flash占用,一定要算RAM账。我见过太多人把Flash省下来的空间,全填到RAM里去了。记住,嵌入式系统的瓶颈往往在RAM,而不是Flash。