图片压缩基础:有损压缩 vs 无损压缩,JPEG、PNG、WebP 在嵌入式中的取舍

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

你想想看,一块 320×240 的屏幕,如果全用未压缩的 BMP,一张图就能吃掉 225KB 的 Flash。放个启动动画,十几张图下去,2MB 的 Flash 直接见底。更别提解码速度——有些低端 MCU 光读 BMP 就能卡半秒。

所以,压缩不是「要不要」的问题,而是「怎么选」的问题。

这一章,咱们就把 JPEG、PNG、WebP 这三兄弟在嵌入式环境下的底裤扒干净。我会结合自己踩过的坑,告诉你什么时候该用谁。

核心结论先放这:嵌入式 GUI 里没有「最好的格式」,只有「最合适的格式」。选型取决于你的瓶颈是 Flash 容量、RAM 大小、还是解码速度。

2.1 有损 vs 无损:本质区别在哪?

先说人话版本:

  • 有损压缩——扔掉人眼不太敏感的信息,换体积。你盯着看可能觉得「还行」,但放大看细节已经糊了。
  • 无损压缩——像素一个不丢,但压缩率有限。适合文字、图标、UI 控件这些「差一个像素就变味」的场景。

我在一个医疗设备项目里就吃过亏。客户要求屏幕截图必须能看清 6 号字体的小字,我一开始用了 JPEG 压缩,结果字边缘全是振铃效应,根本没法看。后来换成 PNG,问题才解决。

特性 有损压缩 无损压缩
像素还原度 不完全还原 100% 还原
压缩率 高(通常 5:1 ~ 20:1) 低(通常 2:1 ~ 5:1)
适用场景 照片、渐变背景、自然图像 文字、图标、UI 控件、医疗影像
解码复杂度 较高(需要 IDCT、Huffman) 较低(主要是 LZ77 + 过滤)

2.2 JPEG:老将,但别乱用

JPEG 在嵌入式里是个「又爱又恨」的存在。

爱的是它压缩率确实高。一张 640×480 的照片,BMP 要 900KB,JPEG 质量 85 压下来可能就 60KB。省了 15 倍的空间。

恨的是它的解码开销。我做过一个 STM32F429 的项目,跑 libjpeg-turbo 解码一张 800×480 的 JPEG,花了 180ms。用户按一下按钮,等 180ms 才看到下一张图——这体验,说实话挺糟糕的。

避坑指南:我曾经在一个电池供电的手持设备里用了 JPEG 做背景图。结果每次切换界面都要解码,电流直接从 50mA 飙到 180mA。后来换成预解码的 RAW 格式,功耗才降下来。JPEG 的解码功耗,在低功耗场景里是个隐形杀手。

JPEG 在嵌入式里的典型用法:

  • 照片浏览——如果设备有相册功能,JPEG 是唯一选择
  • 启动画面——只解码一次,存到显存里,后面不再解码
  • 网络传输——从云端拉图,JPEG 体积小,省流量

但千万别用 JPEG 存:

  • UI 控件(按钮、滑块、图标)——边缘会糊
  • 文字截图——字会「长毛」
  • 需要二次编辑的图片——每次重压缩都会损失

2.3 PNG:无损之王,但体积是硬伤

PNG 在嵌入式 GUI 里其实用得比 JPEG 多。为什么?因为大部分嵌入式 UI 都是「扁平化设计」——大色块、少渐变、多文字。这种图 PNG 压缩率反而比 JPEG 高。

举个例子:一张 240×240 的纯蓝色按钮,PNG 压完可能只有 2KB,JPEG 反而要 8KB(因为 JPEG 不适合平坦区域)。

PNG 的压缩原理说白了就是两步:

  1. 过滤(Filtering)——把像素值转换成差值,让数据更「平」
  2. Deflate 压缩——用 LZ77 + Huffman 把差值数据压紧

我个人习惯在嵌入式项目里用 PNG 存所有 UI 资源。但有个前提——必须用 索引色模式(PNG-8),而不是真彩色(PNG-24)。

PNG 子类型 色深 典型体积 适用场景
PNG-8(索引色) 8-bit(256 色) 很小 图标、按钮、小控件
PNG-24(真彩色) 24-bit(1600 万色) 较大 照片级背景(不推荐)
PNG-32(带 Alpha) 32-bit(含透明通道) 最大 需要半透明效果的 UI
小技巧:如果你用 PNG-8,记得在 Photoshop 或 TinyPNG 里手动优化调色板。把颜色数从 256 降到 128 甚至 64,体积能再砍一半。我在一个智能手表项目里,把 32 个图标的调色板统一成同一个,体积直接省了 40%。

2.4 WebP:新秀,但嵌入式支持参差不齐

WebP 是 Google 搞出来的格式,号称「比 JPEG 小 30%,比 PNG 小 50%」。说实话,在 PC 和移动端它确实香。但在嵌入式里……嗯,情况有点复杂。

WebP 有两种模式:

  • 有损 WebP——基于 VP8 帧内编码,压缩率比 JPEG 高
  • 无损 WebP——类似 PNG,但用了更先进的预测算法

我在一个基于全志 V3s 的 Linux 嵌入式项目里试过 WebP。解码库用的是 libwebp,解码速度比 libjpeg-turbo 慢大概 20%,但体积确实小了。不过,在裸机或 RTOS 环境下,WebP 的解码器移植起来挺麻烦的——内存占用大,代码量也大。

对比项 JPEG PNG WebP
压缩率(照片) 高 低 更高
压缩率(UI 图) 低 高 高
解码速度 快 中等 中等偏慢
解码 RAM 需求 中等(~50KB) 低(~20KB) 高(~100KB+)
库体积 小(libjpeg-turbo ~100KB) 小(libpng ~80KB) 大(libwebp ~200KB)
嵌入式生态 成熟 成熟 一般
注意:WebP 的 Alpha 通道解码在低端 MCU 上是个大坑。我见过一个项目,用 STM32H743 解码带透明通道的 WebP,RAM 直接爆了。如果你需要透明效果,在嵌入式里还是老老实实用 PNG-32 吧。

2.5 嵌入式场景下的选型决策树

说了这么多,到底怎么选?我画了一张图,把决策逻辑串起来了。

嵌入式图片格式选型决策树 图片资源 需要透明通道? 不需要透明通道? PNG-32(首选) 是照片还是 UI? 照片 → JPEG UI 图 → PNG-8 解码速度够吗? 颜色数 < 256? 够 → 用 JPEG 不够 → 预解码 RAW 是 → PNG-8 否 → PNG-24 💡 决策要点: • 有透明通道 → 只能用 PNG(或 WebP,但嵌入式支持差) • 照片且解码速度够 → JPEG;解码速度不够 → 预解码为 RAW

2.6 我的实战建议

做了这么多年嵌入式 GUI,我总结了一套自己的「选型三板斧」:

  1. UI 资源(图标、按钮、背景)——全部用 PNG-8。颜色数控制在 128 色以内,用 TinyPNG 再压一遍。这是最稳妥的方案。
  2. 照片类资源——如果 MCU 主频 > 200MHz 且有硬件 JPEG 解码器,用 JPEG。否则,在 PC 端预解码成 RGB565 的 RAW 格式,直接烧进 Flash。
  3. WebP 暂时别碰——除非你的项目跑 Linux 且有足够 RAM(> 64MB),否则 WebP 带来的体积优势会被解码开销吃掉。
一个省 Flash 的小技巧:如果你有多张尺寸相同的 PNG 图片,可以合并成一张 Sprite 图(雪碧图),然后用坐标裁剪。我在一个智能家居面板项目里,把 40 个图标合并成一张 512×512 的 PNG-8,体积从 120KB 降到了 18KB。解码也只需要一次。