1. 字体资源概述:为什么需要压缩字体?
各位做嵌入式GUI的同行,咱们开门见山。你辛辛苦苦调好了一个界面,中文字体一放进去,Flash直接爆了。这种事我遇到过不下五次。说白了,字体资源是嵌入式GUI里最容易被低估的“存储杀手”。
今天这章,我们就来聊聊字体资源的本质。为什么它这么占地方?TouchGFX是怎么处理字体的?以及——我们到底为什么要费劲去压缩它。
1.1 字体在嵌入式GUI中的角色
字体不是“显示文字”那么简单。它是GUI的骨架。你想想看,一个没有文字的界面,跟一张图片有什么区别?按钮上的标签、传感器的读数、菜单的选项……这些全得靠字体来承载。
在TouchGFX里,字体承担了三个核心角色:
- 信息载体:所有用户能读懂的文本内容,都得通过字体渲染出来
- 视觉元素:字体的粗细、字号、风格直接影响UI的美观度
- 资源消耗大户:尤其是中文字体,动辄几MB甚至十几MB
一个残酷的现实:在MCU上,Flash资源通常只有几MB到十几MB。一套完整的中文字体(GB2312,约6763个汉字),不压缩的情况下,光字形数据就能吃掉2~4MB。你剩下的空间还要放代码、图片、其他资源……
所以,字体压缩不是“锦上添花”,而是“不得不做”。
1.2 TouchGFX字体引擎的工作原理
要理解为什么需要压缩,得先搞清楚TouchGFX是怎么处理字体的。我刚开始用TouchGFX时,也踩过不少坑,后来才慢慢摸清它的脾气。
TouchGFX的字体引擎,本质上是一个字形渲染管线。它不直接使用操作系统的字体服务,而是自己管理一套预渲染好的字形数据。流程大致是这样的:
- 字体文件输入:你指定一个.ttf或.otf文件
- 字形提取:TouchGFX的字体生成工具(TextConverter)从字体文件中提取你需要的字符
- 栅格化:将矢量字形转换成固定大小的位图(像素点阵)
- 打包存储:将位图数据按特定格式打包成二进制资源
- 运行时渲染:MCU上的字体引擎读取打包数据,直接绘制到屏幕上
这里有个关键点:TouchGFX不实时做矢量渲染。它用的是预栅格化的位图。好处是运行时性能极高,坏处是——每个字号、每种样式都得单独生成一套位图数据。这就是字体资源膨胀的根本原因。
我的经验:如果你在项目中用了多个字号(比如12px、16px、24px),每个字号都会生成独立的字形位图。我曾经接手过一个项目,设计师用了7种字号,结果字体资源占了整个固件的一半。后来我建议统一到3种字号,资源直接砍掉60%。
1.3 为什么需要压缩字体?
好,现在我们来回答核心问题:为什么非压缩不可?
我列几个数字,你感受一下:
| 字体类型 | 字符数 | 未压缩大小(16px) | 压缩后大小 | 节省比例 |
|---|---|---|---|---|
| ASCII(英文字母+数字) | 95 | ~30KB | ~15KB | 50% |
| GB2312(常用中文) | 6763 | ~2.8MB | ~1.2MB | 57% |
| GBK(扩展中文) | 21886 | ~9MB | ~3.5MB | 61% |
| Unicode CJK(完整中文) | 20992+ | ~12MB | ~4.5MB | 62% |
看到没?压缩后能省一半以上。对于Flash只有2MB的MCU来说,这1~2MB的差距,可能就是“能跑”和“跑不了”的区别。
压缩的好处不止是省空间:
- 减少Flash占用:最直接的好处,省下来的空间可以放更多功能代码
- 降低烧录时间:固件小了,生产线上烧录速度更快
- 减少OTA升级包大小:如果产品支持远程升级,字体压缩能显著减小升级包体积
- 提高缓存效率:压缩后的字形数据在解压后,更容易被缓存命中
注意:压缩不是免费的午餐。运行时解压需要消耗CPU周期和RAM。如果你的MCU主频很低(比如几十MHz),或者RAM非常紧张(比如只有几十KB),就需要在压缩率和解压开销之间做权衡。我曾在STM32F103上做过测试,LZW压缩的解压时间大约增加5~10%的渲染开销,但Flash节省了40%。这个取舍,值不值,得看你的项目。
1.4 TouchGFX支持的压缩算法
TouchGFX内置了几种压缩方式,我简单列一下:
| 压缩方式 | 压缩比 | 解压速度 | RAM开销 | 适用场景 |
|---|---|---|---|---|
| 无压缩(RAW) | 1:1 | 最快 | 无 | Flash充足,追求极致性能 |
| RLE(游程编码) | 1.5:1 ~ 3:1 | 快 | 低 | 字形简单、大面积空白多的字体 |
| LZW(Lempel-Ziv-Welch) | 2:1 ~ 4:1 | 中等 | 中等 | 通用场景,平衡压缩率和性能 |
| LZ4 | 1.5:1 ~ 2.5:1 | 极快 | 低 | 对渲染帧率要求高的场景 |
我个人习惯是:默认用LZW。它在压缩率和解压速度之间取得了不错的平衡。如果项目对性能要求极高,我会换成LZ4。如果Flash实在不够用,才会考虑RLE——但RLE对复杂字形的压缩效果一般。
1.5 字体资源压缩的核心逻辑
说了这么多,我们来画一张图,把整个逻辑串起来。
这张图把整个流程串起来了。从字体文件开始,经过字形提取、栅格化,到压缩决策,最后打包成二进制资源。每一步都有优化的空间,但压缩这一步,是性价比最高的。
1.6 一个真实的项目案例
最后,我分享一个真实案例。去年我做了一个智能家居面板的项目,主控是STM32H750,Flash 2MB,RAM 1MB。UI需要显示中文菜单和传感器数据。
一开始,设计师选了思源黑体,用了16px和20px两个字号,字符集覆盖GB2312。TextConverter生成出来的字体资源,足足3.2MB。而我的固件总大小限制在1.8MB以内(还要留空间给Bootloader和OTA)。
怎么办?
我做了三件事:
- 精简字符集:实际用到的汉字只有1200多个,我手动提取了这些字符,而不是用完整的GB2312
- 启用LZW压缩:在TouchGFX的字体配置里,把压缩方式从None改成LZW
- 合并字号:把16px和20px统一成18px,用缩放来适配
结果:字体资源从3.2MB降到了680KB。省下来的空间,我甚至多加了两个动画页面。
避坑指南:我曾经犯过一个错误——为了极致压缩,选了RLE算法,结果发现某些复杂汉字(比如“龘”)渲染出来有锯齿。后来才意识到,RLE对笔画密集的字形压缩效果差,而且解压后质量会下降。所以,压缩算法的选择一定要结合你的实际字形来测试,别想当然。