2、图片格式详解(上):BMP格式结构、JPEG格式特点、PNG格式优势与局限
做嵌入式GUI开发,说白了就是跟图片打交道。LVGL要显示一张图,你得先搞清楚这张图到底是什么来头。我见过不少新手,直接把电脑上的图片扔进工程,结果要么显示不出来,要么内存爆了。
今天咱们就聊聊三种最常见的图片格式:BMP、JPEG、PNG。它们各有各的脾气,选对了事半功倍,选错了……嗯,我踩过的坑可以写本书了。
核心观点:没有最好的格式,只有最合适的场景。嵌入式环境资源有限,选格式就是做权衡。
2.1 BMP格式:简单粗暴的原始数据
BMP是Windows的亲儿子,也是所有图片格式里最老实的一个。它不压缩,不耍花招,像素数据怎么来就怎么存。我刚开始做嵌入式时,最喜欢用BMP做测试——因为它好解析,一眼就能看出问题在哪。
BMP的文件结构
一个BMP文件由四部分组成,咱们从上往下看:
| 区块 | 大小 | 内容说明 |
|---|---|---|
| BMP文件头 | 14字节 | 文件类型标识(0x4D42 = "BM")、文件大小、数据偏移 |
| 位图信息头 | 40字节(常见) | 宽度、高度、颜色深度、压缩方式 |
| 调色板 | 可选(仅索引色模式) | 颜色查找表,每个颜色4字节(BGR+保留) |
| 像素数据 | 剩余部分 | 从下到上、从左到右存储的BGR像素 |
小技巧:BMP的像素数据是从左下角开始存的,跟咱们平时从上往下读的习惯相反。解析时记得把行顺序倒过来,否则图片会上下颠倒。我第一回就栽在这上面了。
颜色深度与存储方式
BMP支持多种颜色深度,但嵌入式里常用的就两种:
- 24位真彩色:每个像素3字节(BGR),没有透明通道。文件大小 = 宽 × 高 × 3 + 头开销。
- 32位带Alpha:每个像素4字节(BGRA),多了透明通道。但注意——很多嵌入式解码库不支持BMP的Alpha,存了也白存。
举个例子,一张320×240的24位BMP图片:
文件大小 ≈ 320 × 240 × 3 + 54(头)
= 230,400 + 54
≈ 225 KB
你看,一张小图就200多KB。要是换成800×480的屏幕,一张全屏BMP直接飙到1.1MB。这在单片机上基本是灾难。
注意:BMP在LVGL里通常只适合做小图标或调试用。正式产品里,我建议你把BMP转成LVGL内置的C数组格式(LV_IMG_CF_TRUE_COLOR),省去解码开销。
2.2 JPEG格式:以小博大的有损压缩
JPEG这玩意儿,说白了就是牺牲画质换体积。它能把一张几MB的照片压到几百KB,人眼还看不太出来。但代价是什么?解码复杂,而且不支持透明。
JPEG的核心特点
- 有损压缩:丢弃人眼不敏感的高频细节,压缩比可达10:1甚至更高。
- 不支持透明通道:JPEG天生没有Alpha,做UI时背景只能是矩形。
- 解码开销大:需要做哈夫曼解码、反量化、逆DCT变换,MCU资源吃紧。
我记得有一次做智能家居面板,客户非要放一张高清风景图做壁纸。我用了JPEG,压缩到80KB,效果还行。但解码花了将近200ms,换页时明显卡顿。后来改成预解码成RGB565存到外部Flash,才解决了问题。
JPEG在LVGL中的使用建议
| 场景 | 建议 | 原因 |
|---|---|---|
| 全屏背景图 | 可用JPEG,但建议预解码 | 节省Flash空间,但解码慢 |
| 小图标/按钮 | 不要用JPEG | 压缩比不高,且不支持透明 |
| 照片/渐变图 | JPEG首选 | 人眼对高频不敏感,压缩效果好 |
个人经验:LVGL自带JPEG解码库(TJpgDec),但只支持基线JPEG,不支持渐进式。做图时记得存成"标准"格式,别选"优化"或"渐进",否则解码会崩。
2.3 PNG格式:无损但吃内存的优等生
PNG是我在UI开发里用得最多的格式。为什么?因为它支持透明通道,而且无损。但说实话,PNG在嵌入式里并不好伺候。
PNG的优势
- 无损压缩:用Deflate算法压缩,解压后跟原图一模一样。
- Alpha透明通道:支持0-255级透明度,做圆角按钮、阴影效果必备。
- 色彩深度灵活:支持索引色、灰度、真彩色,甚至48位深色。
PNG的局限
- 解码需要大量RAM:PNG解码器需要一次性读入整张图片的数据,然后逐行解压。一张800×480的32位PNG,光像素数据就占1.5MB RAM。
- 解码速度慢:Deflate解压加上滤镜处理,在低主频MCU上可能耗时数百毫秒。
- 文件体积不一定小:对于照片类图片,PNG可能比JPEG大3-5倍。
避坑指南:我曾经在一个项目里用了大量PNG图标,结果内存不够,系统频繁重启。后来我把所有PNG都转成了LVGL的C数组格式(LV_IMG_CF_TRUE_COLOR_ALPHA),解码开销直接归零。记住——在嵌入式里,预解码永远比实时解码靠谱。
2.4 三种格式的横向对比
咱们用一张表把它们的优缺点说清楚:
| 对比项 | BMP | JPEG | PNG |
|---|---|---|---|
| 压缩方式 | 无压缩 | 有损压缩 | 无损压缩 |
| 透明通道 | 部分支持(32位) | 不支持 | 完美支持 |
| 解码复杂度 | 极低 | 高 | 中高 |
| 解码RAM需求 | 低 | 中 | 高 |
| 文件体积 | 大 | 小 | 中 |
| LVGL推荐场景 | 调试/小图标 | 背景图(预解码) | UI元素(转C数组) |
你想想看,如果你的MCU只有512KB RAM,你敢用PNG做实时解码吗?反正我不敢。我一般的原则是:
- 小图标(< 64×64):直接用BMP转C数组,简单粗暴。
- 全屏背景:用JPEG预解码成RGB565,存外部Flash。
- 带透明的UI元素:用PNG转C数组(带Alpha),解码开销为零。