2. 图片格式选型:JPEG、PNG、GIF、BMP、WebP、AVIF 在嵌入式场景下的优劣对比

做嵌入式 GUI 这几年,我踩过最大的坑之一,就是「图片格式选型」。

你想想看,MCU 的 Flash 就那么大,RAM 更是抠着用。一张 320×240 的 BMP 全屏图,裸数据就要 225KB——这在很多 Cortex-M4 芯片上,直接占掉四分之一的 Flash。选错格式,后面优化到哭都来不及。

所以这一章,我把主流图片格式在嵌入式场景下的底裤都扒干净。咱们不聊虚的,直接看数据、看场景、看实战。

2.1 六种格式的核心差异

先给个总览表。我习惯把格式分成两类:无损和有损。无损的适合图标、文字、UI 控件;有损的适合照片、背景图、渐变图。

格式 压缩类型 色深 透明通道 解码复杂度 典型压缩比(同质量)
BMP 无压缩 24bit / 32bit 支持(32bit) 极低 1:1
GIF LZW 无损 8bit(256色) 支持(1bit) 低 约 3:1 ~ 5:1
PNG Deflate 无损 8bit / 24bit / 32bit 支持(8bit α) 中 约 2:1 ~ 10:1
JPEG DCT 有损 24bit 不支持 中 约 10:1 ~ 20:1
WebP VP8L 无损 / VP8 有损 24bit / 32bit 支持 高 约 15:1 ~ 30:1
AVIF AV1 有损/无损 24bit / 32bit / HDR 支持 极高 约 20:1 ~ 40:1

核心结论:嵌入式场景下,解码复杂度往往比压缩比更重要。BMP 虽然体积大,但解码几乎零开销;AVIF 压缩率最高,但解码需要几十 KB 的 RAM 缓冲区,很多低端 MCU 根本跑不动。

2.2 逐个拆解:每个格式的「脾气」

2.2.1 BMP —— 简单粗暴,但别滥用

BMP 说白了就是像素数据的裸转储。没有压缩,没有头信息之外的任何花活。

优点:解码就是 memcpy,CPU 零负担。我在做 STM32F103 的仪表盘项目时,启动 Logo 就用 BMP——因为那时候 Flash 还有富余,解码快才是王道。

缺点:体积爆炸。一张 480×272 的 32bit BMP 是 480×272×4 = 522KB。很多芯片的 Flash 总共才 1MB,一张图就吃一半。

我的习惯:BMP 只用于两种场景——① 启动 Logo(只显示一次,解码速度优先);② 调试阶段临时测试图片。正式产品里,BMP 基本可以判死刑。

2.2.2 GIF —— 动图还行,静态图别碰

GIF 只有 256 色。你想想看,一张渐变的蓝天白云图,用 GIF 存出来全是色块,惨不忍睹。

但 GIF 有一个不可替代的优势:帧动画。嵌入式里做加载动画、进度条动画,GIF 可以直接用,不需要自己写帧缓冲管理。

我曾经在一个温控器项目里用 GIF 做风扇旋转动画。256 色对于简单的图标动画完全够用,而且解码库(如 giflib)非常轻量,ROM 占用不到 10KB。

注意:GIF 的透明通道只有 1bit(要么全透,要么全不透)。如果你需要半透明效果,GIF 做不到,老老实实用 PNG。

2.2.3 PNG —— 无损之王,但解码有代价

PNG 是嵌入式 GUI 里用得最多的格式。原因很简单:无损 + 8bit α 透明通道。

做 UI 控件、图标、字体图片,PNG 是首选。我做过一个智能家居面板,所有按钮图标都是 32bit PNG,透明背景叠加到壁纸上,效果非常干净。

但 PNG 的解码有个坑:逐行扫描 + 滤波器。解码一张 800×480 的 PNG,在 72MHz 的 Cortex-M4 上可能要 200ms 以上。如果 UI 切换时同时解码 3~4 张 PNG,卡顿感就出来了。

避坑指南:我曾经在一个项目里把所有 PNG 都设成「无滤波器」重新编码,解码速度提升了 40%。具体做法是用 pngcrush 或 optipng 加 --nofilter 参数。代价是压缩比会下降 5%~10%,但解码速度的收益完全值得。

2.2.4 JPEG —— 照片专用,UI 慎用

JPEG 的 DCT 压缩对自然照片非常友好。一张 2MB 的相机原图,JPEG 质量 85 可以压到 150KB 左右,肉眼几乎看不出区别。

但 JPEG 有两个硬伤:不支持透明通道、有损压缩对文字和线条不友好。你试试把一张带细线表格的截图存成 JPEG,质量 80 以下,表格线边缘全是振铃效应,糊成一片。

所以我的原则是:照片、渐变背景、纹理用 JPEG;UI 控件、文字、图标永远不用 JPEG。

2.2.5 WebP —— 压缩率优秀,但解码库偏重

WebP 是 Google 推出的格式,有损模式压缩率比 JPEG 高 25%~35%,无损模式比 PNG 小 20%~30%。听起来很完美对吧?

但嵌入式场景下,WebP 的解码库 libwebp 编译出来大约 60~80KB ROM,而且解码时需要额外的行缓冲。我在一个基于 ESP32-S3 的项目里试过 WebP,解码一张 320×240 的图片需要约 30KB 的临时 RAM——对于只有 512KB RAM 的芯片来说,压力不小。

我的建议:如果你的芯片有 2MB+ Flash 和 512KB+ RAM(比如 ESP32、RT1052、全志 V3s),WebP 是很好的选择。如果是 STM32F4 这种 1MB Flash / 192KB RAM 的芯片,建议优先考虑 JPEG 或 PNG。

2.2.6 AVIF —— 未来趋势,但今天还太「重」

AVIF 基于 AV1 编码,压缩率是目前所有格式里最高的。同样质量下,AVIF 比 JPEG 小 50% 以上。

但代价也很明显:解码复杂度极高。AV1 解码需要大量的计算和内存,在 PC 和手机上没问题,但在 MCU 上——嗯,我目前还没见过哪个嵌入式项目敢用 AVIF 做实时解码。至少需要 Cortex-A 级别 + 硬件解码器才能跑得动。

所以 AVIF 在嵌入式 GUI 里,目前只适合预解码场景:在 PC 上把 AVIF 转成 RGB565 数组,然后烧录到 Flash 里。这样既享受了 AVIF 的高压缩率(减少 Flash 占用),又避免了运行时解码开销。

2.3 场景化选型决策树

说了这么多,到底怎么选?我画了一张决策图,你照着走就行。

嵌入式图片格式选型决策树 开始选型 需要透明通道(α 通道)? 是 → 需要多少色深? ≤256色 → GIF >256色 → PNG 否 → 图片内容类型? 照片/渐变 → JPEG UI/文字/线条 → PNG 芯片资源是否充裕? 充裕 → WebP 紧张 → 预解码AVIF 注:BMP 仅用于启动 Logo 或调试,正式产品不建议使用

2.4 实战选型清单

最后,我把自己做项目时常用的选型清单列出来。你直接抄作业就行。

场景 推荐格式 理由
启动 Logo BMP 或 预解码数组 解码零开销,显示最快
UI 图标(带透明) PNG-8 或 PNG-32 无损 + α 通道,图标边缘清晰
UI 图标(无透明) PNG-8 或 JPEG PNG-8 体积小,JPEG 适合复杂图标
照片背景图 JPEG 质量 80~85 压缩比高,解码快
渐变背景图 JPEG 或 WebP 渐变用 JPEG 会有轻微色块,WebP 更好
加载动画 GIF 或 PNG 序列 GIF 直接播放,PNG 序列更灵活
字体图片(Sprite) PNG-8 256 色足够,体积最小
高压缩率存档 AVIF(预解码) 压缩率最高,减少 Flash 占用

最后提醒一句:不要迷信「压缩率越高越好」。嵌入式系统里,解码速度、内存占用、库体积都是硬约束。我见过有人为了省 50KB Flash 选了 WebP,结果解码库占掉 80KB ROM,得不偿失。选型之前,先算清楚你芯片的 Flash、RAM、CPU 余量,再做决定。