3、图片格式详解(下):GIF动图支持、SVG矢量图支持、自定义二进制格式
上一节我们把 PNG、JPEG、BMP 这些常规格式聊透了。这一节咱们继续,把剩下的几个「特殊选手」搞定——GIF 动图、SVG 矢量图,还有我们自己定义的二进制格式。
说实话,GIF 和 SVG 在嵌入式 GUI 里属于「用得好是神器,用不好是坑」的类型。我最早做智能手表项目时,就因为在 GIF 解码上踩了坑,导致开机动画卡了整整两秒。嗯,今天我把这些经验全盘托出,你照着做基本不会翻车。
3.1 GIF 动图支持:让界面「活」起来
GIF 动图在 LVGL 里是通过 lv_gif 组件实现的。它本质上是一个动画版的图片对象,底层依赖一个轻量级的 GIF 解码器。
核心要点:LVGL 的 GIF 支持不是内置的,需要你手动开启宏定义 LV_USE_GIF 并链接解码库。
3.1.1 开启 GIF 支持
在 lv_conf.h 中找到这一行:
/* 1: 启用 GIF 解码器 */
#define LV_USE_GIF 1
然后确保你的项目里包含了 lv_gif.c 和 lv_gif.h。LVGL 自带了一个精简的 GIF 解码器,不需要额外引入第三方库。
3.1.2 加载与播放 GIF
加载 GIF 的方式和普通图片类似,但返回的是一个 lv_gif_t 对象:
/* 从文件系统加载 */
lv_obj_t * gif = lv_gif_create(lv_scr_act());
lv_gif_set_src(gif, "S:/animations/loading.gif");
/* 从内存数组加载(推荐用于资源受限场景) */
extern const uint8_t loading_gif_map[];
lv_gif_set_src(gif, &loading_gif_map);
GIF 一旦加载成功,就会自动开始播放。你可以通过以下 API 控制播放行为:
| API 函数 | 功能说明 |
|---|---|
lv_gif_restart(gif) |
从头开始播放 |
lv_gif_pause(gif) |
暂停当前帧 |
lv_gif_resume(gif) |
继续播放 |
lv_gif_get_duration(gif) |
获取总时长(毫秒) |
lv_gif_get_frame_num(gif) |
获取总帧数 |
我的经验:GIF 的帧延迟时间由文件本身决定。如果你发现动图跑得太快或太慢,别急着改代码——先用工具检查 GIF 的帧延迟设置。我遇到过好几次,问题出在设计师导出时没调好参数。
3.1.3 避坑指南:GIF 的内存占用
GIF 解码需要把每一帧解压到内存里。一个 320x240 的 256 色 GIF,单帧就需要 76.8KB。如果动图有 30 帧,解码缓冲区会吃掉不少 RAM。
我曾经在一个仅有 64KB RAM 的 MCU 上尝试播放 20 帧的 GIF,结果系统直接崩溃。后来我把 GIF 裁剪到 5 帧,并改用 LV_COLOR_DEPTH_16 才勉强跑起来。
建议:
- GIF 帧数控制在 10 帧以内
- 尺寸不要超过屏幕面积的 1/4
- 优先使用调色板模式(256 色)而非真彩色
3.2 SVG 矢量图支持:无级缩放的利器
SVG 在 LVGL 里是个「高级功能」。它不像 PNG 那样直接渲染像素,而是先解析 XML 描述,再实时绘制成位图。
3.2.1 开启 SVG 支持
LVGL 的 SVG 支持依赖 ThorVG 或 NanoVG 后端。在 lv_conf.h 中:
/* 1: 启用 SVG 解码器 */
#define LV_USE_SVG 1
/* 选择后端:0 = ThorVG(推荐),1 = NanoVG */
#define LV_SVG_USE_THORVG 1
我个人强烈推荐 ThorVG。它更轻量,对 SVG 标准的支持也更完整。NanoVG 虽然老牌,但有些高级特性(如渐变、滤镜)支持得不好。
3.2.2 加载 SVG 文件
SVG 的加载方式和普通图片完全一致:
/* 从文件加载 */
lv_obj_t * svg = lv_img_create(lv_scr_act());
lv_img_set_src(svg, "S:/icons/settings.svg");
/* 从内存加载 */
extern const uint8_t settings_svg_map[];
lv_img_set_src(svg, &settings_svg_map);
关键区别在于:SVG 可以随意缩放而不失真。你只需要设置 lv_obj_set_size(),LVGL 会自动重新渲染:
/* 放大到 200x200,依然清晰 */
lv_obj_set_size(svg, 200, 200);
3.2.3 SVG 的性能代价
SVG 虽好,但渲染开销不小。每次缩放或旋转,LVGL 都要重新解析并光栅化。我在一个 240MHz 的 Cortex-M4 上测试过:
| SVG 复杂度 | 首次加载时间 | 缩放重绘时间 |
|---|---|---|
| 简单图标(10 个节点) | ~5ms | ~2ms |
| 中等插图(50 个节点) | ~30ms | ~15ms |
| 复杂图形(200+ 节点) | ~120ms | ~60ms |
我的建议:SVG 适合做图标、Logo、简单插图。复杂的渐变、阴影、文字路径尽量少用。如果你发现某个 SVG 加载慢,可以用 SVGOMG 这类工具先优化一下。
3.2.4 避坑指南:SVG 的兼容性
不是所有 SVG 文件都能在 LVGL 里正常显示。ThorVG 目前支持的 SVG 特性包括:
- 基本形状(rect、circle、path)
- 线性/径向渐变
- 变换(translate、scale、rotate)
- 分组(g)
不支持的特性:
- 滤镜(filter)
- 动画(animate)
- 嵌入字体
- 外部引用
我曾经从设计师那里拿到一个带滤镜的 SVG 图标,在浏览器里显示完美,但烧到板子上直接黑屏。排查了半天才发现是滤镜不兼容。从那以后,我要求设计师导出 SVG 时务必「扁平化」所有效果。
3.3 自定义二进制格式:为性能而生
LVGL 允许你定义自己的图片格式。说白了,就是把图片数据按你想要的布局打包,然后注册一个自定义解码器。
3.3.1 什么时候需要自定义格式?
我总结了几种典型场景:
- 极致压缩:你的图片颜色种类很少(比如只有 16 色),用 PNG 都嫌大
- 特殊像素布局:比如摄像头采集的 Bayer 格式,或者 YUV 数据
- 硬件加速:某些 LCD 控制器支持直接喂特定格式的数据,跳过解码步骤
- 加密保护:不想让别人直接用图片查看器打开你的资源文件
3.3.2 自定义格式的实现步骤
实现一个自定义格式,你需要做三件事:
- 定义文件头结构——告诉解码器图片的宽、高、颜色格式、数据偏移
- 实现解码回调——把二进制数据转换成 LVGL 能识别的像素数组
- 注册解码器——让 LVGL 知道这个格式的存在
下面是一个简单的 16 色自定义格式示例:
/* 1. 定义文件头 */
typedef struct {
uint16_t w; /* 宽度 */
uint16_t h; /* 高度 */
uint8_t bpp; /* 4 = 16色 */
uint32_t data_offset;/* 像素数据偏移 */
} my_img_header_t;
/* 2. 实现解码回调 */
lv_res_t my_img_decoder(lv_img_decoder_t * dsc) {
my_img_header_t * hdr = (my_img_header_t *)dsc->src;
uint8_t * src_data = (uint8_t *)dsc->src + hdr->data_offset;
/* 将 4bpp 数据转换为 LVGL 的 16bpp 格式 */
lv_color_t * cbuf = lv_mem_alloc(hdr->w * hdr->h * sizeof(lv_color_t));
for (uint32_t i = 0; i < (hdr->w * hdr->h / 2); i++) {
uint8_t byte = src_data[i];
cbuf[i*2] = palette_16[byte & 0x0F];
cbuf[i*2+1] = palette_16[byte >> 4];
}
dsc->img_data = cbuf;
return LV_RES_OK;
}
/* 3. 注册解码器 */
void my_img_decoder_init(void) {
lv_img_decoder_t * dec = lv_img_decoder_create();
lv_img_decoder_set_info_cb(dec, my_img_info);
lv_img_decoder_set_open_cb(dec, my_img_decoder);
}
3.3.3 自定义格式的性能对比
我用一个 128x128 的图标做过对比测试:
| 格式 | 文件大小 | 解码时间 | 内存占用 |
|---|---|---|---|
| PNG(32位) | 8.2 KB | ~12ms | 64 KB |
| 自定义 16 色 | 2.1 KB | ~0.5ms | 32 KB |
| 自定义 RLE 压缩 | 1.4 KB | ~1.2ms | 32 KB |
结论:自定义格式在文件大小和解码速度上都有明显优势,但需要你手动处理颜色转换。如果你的项目对性能和存储有极致要求,这条路值得走。
3.4 本章知识体系总览
下面这张 SVG 图帮你梳理了三种格式的核心要点和适用场景:
3.5 小结
这一节我们聊了三种「非主流」但很实用的图片格式:
- GIF 动图——让界面有生命力,但要注意内存和帧数
- SVG 矢量图——无级缩放,适合图标和简单插图,但渲染有开销
- 自定义二进制格式——性能极致,适合有特殊需求的场景