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 自定义格式的实现步骤

实现一个自定义格式,你需要做三件事:

  1. 定义文件头结构——告诉解码器图片的宽、高、颜色格式、数据偏移
  2. 实现解码回调——把二进制数据转换成 LVGL 能识别的像素数组
  3. 注册解码器——让 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 图帮你梳理了三种格式的核心要点和适用场景:

图片格式详解(下)知识体系 图片格式详解(下):三种特殊格式 GIF 动图 SVG 矢量图 自定义二进制 自动播放 / 暂停 / 恢复 帧延迟由文件决定 ⚠ 内存占用大,帧数别超 10 无级缩放,不失真 依赖 ThorVG 后端 ⚠ 复杂图形渲染慢 极致压缩 / 特殊像素布局 需实现解码回调 适合硬件加速场景 选择建议 动效用 GIF,图标用 SVG,极致性能用自定义二进制

3.5 小结

这一节我们聊了三种「非主流」但很实用的图片格式:

  • GIF 动图——让界面有生命力,但要注意内存和帧数
  • SVG 矢量图——无级缩放,适合图标和简单插图,但渲染有开销
  • 自定义二进制格式——性能极致,适合有特殊需求的场景