4. 字体压缩算法:RLE、LZ77/LZSS 与权衡之道
说到字体压缩,很多刚接触 TouchGFX 的朋友第一反应是:「不就是把字库文件变小点吗?」其实没那么简单。我做过一个项目,字库从 2MB 压到 600KB,结果界面切换时字体加载慢了一倍,用户反馈卡顿。嗯,这就是典型的「压缩率与解压速度」没权衡好。
这一章,咱们就聊聊字体压缩里最常用的两种算法:RLE 和 LZ77/LZSS。我会结合实战经验,告诉你什么时候该用谁,怎么用才不踩坑。
4.1 RLE 游程编码:简单到极致
RLE(Run-Length Encoding)的原理,说白了就是「把重复的东西合并记录」。比如你有一串像素数据:AAAAABBBCCCC,RLE 会把它记成 A5B3C4。是不是很直观?
在字体中,RLE 特别适合处理什么场景呢?我告诉你——空白区域。想想看,一个字模里大部分像素都是背景色(通常是 0),只有笔画部分是前景色。尤其是中文字体,一个「一」字,上下全是空白,RLE 压缩率能到 90% 以上。
RLE 在字体中的典型应用:
- 单色位图字体(1bpp):每个像素 0 或 1,连续空白极多
- 灰度字体(2bpp/4bpp):背景区域连续相同值
- 小字号字体:笔画简单,空白占比大
但 RLE 也有短板。我遇到过一种情况:一个「繁」字,笔画密集,几乎每个像素都在变化。RLE 压缩后反而比原数据还大——因为每个「不同」的像素都要单独记录一次。所以 RLE 适合数据局部重复度高的场景。
4.2 LZ77/LZSS:更聪明的压缩方式
LZ77 和它的改进版 LZSS,是另一种思路。它们不只看「重复的值」,而是看「重复的模式」。比如一个字模里,某个偏旁部首反复出现,LZ77 会记住这个模式的位置和长度,后面直接引用。
我举个例子。假设你有两行像素:
行1: 0x00 0x00 0xFF 0x00 0x00 0xFF
行2: 0x00 0x00 0xFF 0x00 0x00 0xFF
LZ77 会发现行2和行1完全一样,于是记录为「从行1偏移0开始,复制6个字节」。这比 RLE 的逐行记录高效得多。
我的经验:LZSS 是 LZ77 的优化版,它加了一个「是否值得引用」的判断。如果引用的开销比直接存储还大,就不引用。这个优化在字体压缩中很关键——因为有些短模式引用反而更占空间。
4.3 两种算法的对比
为了让你更直观地理解,我整理了一张对比表。这张表是我在实际项目中反复验证过的:
| 维度 | RLE | LZ77/LZSS |
|---|---|---|
| 核心思想 | 记录连续重复值 | 记录重复模式(位置+长度) |
| 压缩率(字体) | 中低(密集笔画时可能负优化) | 中高(尤其对结构化的字体) |
| 解压速度 | 极快(逐字节判断即可) | 较快(需要查表/滑动窗口) |
| 内存占用 | 极低(几乎无额外内存) | 中等(需要滑动窗口缓冲区) |
| 适用场景 | 单色、小字号、空白多的字体 | 灰度、大字号、笔画复杂的字体 |
你看,没有绝对的「谁更好」,只有「谁更适合」。我个人的习惯是:先分析字体特征,再选算法。
4.4 压缩率与解压速度的权衡
这是本章最核心的部分。你想想看,嵌入式系统的资源是有限的——CPU 主频不高,RAM 不大,Flash 也紧张。压缩率高了,解压就慢;解压快了,压缩率就低。怎么选?
我分享一个真实案例。之前做一个智能家居面板,用的是 STM32F4 系列,主频 168MHz。字库包含中英文,总共 3000 多个字符。我试了三种方案:
- 不压缩:字库 1.8MB,Flash 放不下(总共 1MB)
- RLE 压缩:字库 800KB,解压一个字符平均 0.3ms,界面流畅
- LZSS 压缩:字库 450KB,解压一个字符平均 1.2ms,界面偶尔卡顿
最后我选了 RLE。为什么?因为 Flash 空间够用(800KB < 1MB),而且解压速度是关键——用户操作界面时,卡顿感是不能接受的。你想想看,按一个按钮等半秒才显示文字,谁受得了?
避坑指南:我曾经在一个项目里盲目追求高压缩率,用了 LZSS 并设置了很大的滑动窗口(64KB)。结果解压时内存不够,系统直接崩溃。后来才意识到,嵌入式系统的 RAM 是稀缺资源,滑动窗口大小必须根据实际可用内存来定。
4.5 知识体系总览
为了让你对本章内容有个整体把握,我画了一张结构图。它把 RLE、LZ77/LZSS 以及权衡关系串在了一起:
4.6 实战中的选择策略
说了这么多理论,到底怎么选?我总结了一个简单的决策流程:
- 第一步:评估 Flash 空间。如果字库压缩后能放下,优先选解压快的算法。
- 第二步:评估 CPU 负载。如果主频低(比如 72MHz 以下),RLE 是安全牌。
- 第三步:评估 RAM 余量。如果 RAM 紧张(比如只有 64KB),别用大窗口的 LZSS。
- 第四步:实测。我习惯在开发板上跑一个「全字符遍历解压」测试,看最坏情况下的延迟。
一个小技巧:TouchGFX 支持自定义字体压缩插件。你可以把 RLE 和 LZSS 结合起来——对空白多的字符用 RLE,对笔画复杂的字符用 LZSS。我在一个项目中这么做过,压缩率提升了 15%,解压速度只慢了 5%。