- 智能硬件
- 嵌入式
- 硬件开发
【免费下载链接】X-TRACK
A GPS bicycle speedometer that supports offline maps and track recording
导读
本文围绕 LVGL 官方文档 libs/gif.md 展开,系统讲解在 LVGL 中启用并播放 GIF 动图的完整链路:从lv_conf.h开关配置、lv_gif_create/lv_gif_set_src核心 API、两种图片来源(C 数组与文件系统)到帧播放与内存占用原理。X-TRACK 的 PC 模拟器正是基于 LVGL.Simulator 构建(见 Simulator/LVGL.Simulator 目录),本文同时结合仓库内lvgl/src/extra/libs/gif的源码实现进行纵深剖析,读完你可以在 X-TRACK 模拟器环境中复现动图播放、为开机动画等场景接入 GIF 资源,并能准确评估不同色深下的 RAM 开销。
GIF 解码器是什么
LVGL 内置的 GIF 解码器允许在 LVGL 应用中直接使用 GIF 图片,其底层基于轻量级 GIF 解码库gifdec(lecram/gifdec的移植),仓库内实现位于 lvgl/src/extra/libs/gif:
- gifdec.h / gifdec.c:GIF 文件解析、LZW 解压与逐帧渲染;
- lv_gif.h / lv_gif.c:面向 LVGL 对象的封装,把解码器输出包装成一个可自动逐帧刷新的控件。
在文档 gif.md 中明确:在lv_conf.h中启用LV_USE_GIF后,即可调用lv_gif_create(parent)创建 GIF 控件,而lv_gif_set_src(obj, src)的用法与lv_img_set_src高度一致——图片来源既可以是内存中的图片变量(lv_img_dsc_t),也可以是文件路径。
第一步:在 lv_conf.h 中启用 GIF 支持
LVGL 各功能模块默认由lv_conf.h中的编译宏控制。在 X-TRACK 模拟器工程中,配置文件位于 Software/X-Track/Simulator/LVGL.Simulator/lv_conf.h,其中第 681 行:
#define LV_USE_GIF 0当前仓库默认将该功能关闭(值为0)。要使用 GIF 动画,需将其改为1并重新编译工程:
#define LV_USE_GIF 1与之联动的是一个重要宏——颜色深度。同文件第 29 行:
#define LV_COLOR_DEPTH 16X-TRACK 模拟器运行在 16bit 色深下,这直接决定了 GIF 解码所需的内存规模(详见下文"内存需求"小节)。
文件来源还需启用文件系统驱动
如果打算从文件系统读取.gif文件,还需要在lv_conf.h中开启至少一个文件系统驱动。文档 gif.md 指出:必须注册文件系统驱动后才能通过文件路径打开图片,可选的开关形如LV_USE_FS_...。仓库lv_conf.h第 636~661 行提供了四类候选:
#define LV_USE_FS_STDIO 0 #define LV_USE_FS_POSIX 0 #define LV_USE_FS_WIN32 0 #define LV_USE_FS_FATFS 0- PC 模拟器(Linux/Windows 桌面环境)下通常开启
LV_USE_FS_STDIO或LV_USE_FS_POSIX,其挂载盘符在宏下方的配置块中设定(如'A'或'S'); - 若只使用 C 数组形式的内存来源,则无需任何文件系统驱动。
注意:
LV_USE_GIF、LV_COLOR_DEPTH、LV_USE_FS_*均为编译期配置,改动后必须全量重新编译 LVGL 及其上层应用。
核心 API 与对象模型
三个公开接口
从 lv_gif.h 可以看到 GIF 模块对外只暴露三个接口:
lv_obj_t * lv_gif_create(lv_obj_t * parent); /* 创建 GIF 控件 */ void lv_gif_set_src(lv_obj_t * obj, const void * src); /* 设置图片来源 */ void lv_gif_restart(lv_obj_t * gif); /* 从头重新播放 */lv_gif_create创建一个继承自lv_img_class的控件(源码中lv_gif_class的.base_class = &lv_img_class,见 lv_gif.c),因此它也拥有普通图片控件的全部布局、对齐、事件能力;lv_gif_set_src负责打开 GIF 并启动播放;lv_gif_restart内部调用gd_rewind(gifobj->gif)(lv_gif.c),将解码位置回绕到第一帧重新播放,适合"点击重播"类交互。
控件内部结构
lv_gif.h 中的lv_gif_t揭示了实现机制:
typedef struct { lv_img_t img; /* 内嵌一个图片控件作为显示载体 */ gd_GIF * gif; /* gifdec 解码器上下文 */ lv_timer_t * timer; /* 帧刷新定时器 */ lv_img_dsc_t imgdsc; /* 每帧解码结果包装成的图片描述符 */ uint32_t last_call; /* 上一帧的时间戳,用于计算帧间隔 */ } lv_gif_t;可以推断:GIF 控件本质上是"一个图片控件 + 一个周期性定时器"的组合。定时器在对象构造时创建、默认暂停(lv_gif.c),直到lv_gif_set_src成功加载资源后才恢复运行。
两种图片来源与完整示例
方式一:使用 C 数组(内存变量)
将 GIF 文件转换为字节数组后,以lv_img_dsc_t变量形式传入。转换方式参考文档 gif.md:使用 LVGL 官方提供的在线图片转换工具(Image Converter),输出格式选择"C array",颜色格式选择"Raw"。转换结果形如仓库示例 img_bulb_gif.c 中声明的img_blub_gif_map[]——一个以0x47, 0x49, 0x46(即 ASCII "GIF")开头的原始字节数组,本质就是原 GIF 文件的完整二进制内容。
使用时代码如下:
LV_IMG_DECLARE(img_bulb_gif); /* 声明外部变量 */ lv_obj_t * gif = lv_gif_create(lv_scr_act()); /* 创建控件 */ lv_gif_set_src(gif, &img_bulb_gif); /* 以变量形式传入 */ lv_obj_align(gif, LV_ALIGN_CENTER, 0, 0); /* 与普通图片一致的布局接口 */方式二:从文件系统读取
文档给出的文件路径示例为:
lv_gif_set_src(obj, "S:path/to/example.gif");其中S:是文件系统挂载盘符,path/to/example.gif为盘符内的相对路径。仓库示例 lv_example_gif_1.c 演示了与变量方式并存的使用:
img = lv_gif_create(lv_scr_act()); /* 假定文件系统挂载在盘符 'A' 上 */ lv_gif_set_src(img, "A:lvgl/examples/libs/gif/bulb.gif"); lv_obj_align(img, LV_ALIGN_RIGHT_MID, -20, 0);完整可运行示例
结合两种来源,一个完整的播放示例可组织为:
#include "../../lv_examples.h" #if LV_USE_GIF && LV_BUILD_EXAMPLES void lv_example_gif_1(void) { LV_IMG_DECLARE(img_bulb_gif); /* 引用转换好的 GIF 字节数组 */ /* 1) 从内存变量加载 */ lv_obj_t * img = lv_gif_create(lv_scr_act()); lv_gif_set_src(img, &img_bulb_gif); lv_obj_align(img, LV_ALIGN_LEFT_MID, 20, 0); /* 2) 从文件加载(需已启用 LV_USE_FS_* 且挂载盘符为 'A') */ img = lv_gif_create(lv_scr_act()); lv_gif_set_src(img, "A:lvgl/examples/libs/gif/bulb.gif"); lv_obj_align(img, LV_ALIGN_RIGHT_MID, -20, 0); } #endif播放结束后如需从头再来,调用lv_gif_restart(img)即可。
源码级原理:解码、定时与帧渲染
打开与校验流程
lv_gif_set_src首先判断来源类型(lv_gif.c):
if(lv_img_src_get_type(src) == LV_IMG_SRC_VARIABLE) { gifobj->gif = gd_open_gif_data(img_dsc->data); /* 内存变量 */ } else if(lv_img_src_get_type(src) == LV_IMG_SRC_FILE) { gifobj->gif = gd_open_gif_file(src); /* 文件路径 */ }gd_open_gif_file/gd_open_gif_data(gifdec.c)先建立统一的gd_GIF上下文,再交由gif_open解析头部(gifdec.c),校验要点包括:
- 文件签名必须为
GIF、版本必须为89a,否则直接失败并输出LV_LOG_WARN; - 逻辑屏幕描述符中必须存在全局颜色表(
fdsz & 0x80),缺失同样判定失败; - 读取宽度、高度、色深、背景色索引等字段填入
gd_GIF结构。
打开成功后,lv_gif_set_src将解码缓冲区包装成LV_IMG_CF_TRUE_COLOR_ALPHA格式的图片描述符并交给底层lv_img_set_src(lv_gif.c),随后恢复定时器并立即渲染首帧。
逐帧播放的定时机制
GIF 控件在构造函数中创建了一个10ms 周期的 LVGL 定时器(lv_gif.c),回调next_frame_task_cb负责推进帧。其核心逻辑(lv_gif.c):
uint32_t elaps = lv_tick_elaps(gifobj->last_call); if(elaps < gifobj->gif->gce.delay * 10) return; /* 帧间隔未到,跳过 */ gifobj->last_call = lv_tick_get(); int has_next = gd_get_frame(gifobj->gif); /* 解码下一帧 */ if(has_next == 0) { /* 播放到结尾 */ if(gifobj->gif->loop_count == 1) { /* 只播一次:发就绪事件 */ lv_event_send(obj, LV_EVENT_READY, NULL); } else { /* 循环播放 */ if(gifobj->gif->loop_count > 1) gifobj->gif->loop_count--; gd_rewind(gifobj->gif); } } gd_render_frame(gifobj->gif, (uint8_t *)gifobj->imgdsc.data); lv_img_cache_invalidate_src(lv_img_get_src(obj)); lv_obj_invalidate(obj);几个值得注意的细节:
- 帧间隔:GIF 图形控制扩展(GCE)中的
delay单位是 1/100 秒,源码中乘以 10 换算为毫秒后与 10ms 定时器节拍比较,决定是否绘制新帧; - 循环次数:
gd_GIF.loop_count由 NETSCAPE 应用扩展读取。等于 1 表示只播一次,播完发送LV_EVENT_READY(可监听该事件做"播放结束"回调);大于 1 则每轮递减并回绕继续播放; - 渲染与失效:
gd_render_frame将当前帧合成到canvas缓冲区,随后使图片缓存失效并标记控件重绘,触发 LVGL 在下一轮刷新周期显示新帧。
内存需求与 X-TRACK 色深评估
文档 gif.md 给出的内存公式为(解码并显示一张 GIF 动画所需 RAM):
| LV_COLOR_DEPTH | 所需内存 |
|---|---|
| 8 | 3 × 图片宽 × 图片高 |
| 16 | 4 × 图片宽 × 图片高 |
| 32 | 5 × 图片宽 × 图片高 |
该公式与源码完全吻合:gifdec.c 在gif_open阶段按色深一次性分配:
#if LV_COLOR_DEPTH == 32 gif = lv_mem_alloc(sizeof(gd_GIF) + 5 * width * height); #elif LV_COLOR_DEPTH == 16 gif = lv_mem_alloc(sizeof(gd_GIF) + 4 * width * height); #elif LV_COLOR_DEPTH == 8 || LV_COLOR_DEPTH == 1 gif = lv_mem_alloc(sizeof(gd_GIF) + 3 * width * height); #endif从源码结构可以推断,多出的内存用于同时容纳canvas(完整帧合成画布)与frame(当前帧解码缓冲)两块区域,色深越高、单像素字节越多,开销越大。X-TRACK 模拟器配置的是LV_COLOR_DEPTH 16(lv_conf.h),因此播放一张 100×100 的 GIF 约需4 × 100 × 100 = 40 KB额外堆内存,设计界面动图资源时应提前按此公式估算 RAM 预算;若目标为真机(如 AT32F4xx 系列,见 MDK-ARM_F435 平台),还需要与 LVGL 显示的帧缓冲等内存需求一并统筹。
在 X-TRACK 模拟器中的应用与验证
X-TRACK 是支持离线地图与轨迹记录的 GPS 自行车码表,其 Simulator/LVGL.Simulator 是一个可在 PC 上运行的 LVGL 模拟器工程(入口见 LVGL.Simulator.cpp),仓库根目录的 Images/开机动画.mp4 也说明项目存在开机动画类动效需求。GIF 解码器可以为此类场景提供一种低成本的动图播放方案,在模拟器中可按如下步骤验证:
- 将 lv_conf.h 中的
LV_USE_GIF改为1; - 若从文件读取,将
LV_USE_FS_STDIO(或LV_USE_FS_POSIX)改为1,并把.gif文件放入对应挂载目录; - 参考 lv_example_gif_1.c 编写页面代码,将
lv_gif_create创建的控件添加到目标页面(如Pages下的页面,见 USER/App/Pages); - 重新编译并运行模拟器,即可观察到逐帧播放的动画。
需要注意,GIF 解码属于逐帧软件渲染,帧率受 CPU 与内存带宽影响,模拟器上的表现与真机存在差异;若页面较多、动画尺寸较大,建议先用上文内存公式评估开销,再决定动画素材的分辨率与帧数。
常见问题与排查清单
- 控件创建后不显示:确认
LV_USE_GIF已置 1 并重新编译;确认图片来源路径/变量声明无误(lv_gif_set_src加载失败会打印LV_LOG_WARN("Could't load the source"),见 lv_gif.c); - 文件方式打不开:检查
LV_USE_FS_*是否启用、盘符字母是否与lv_conf.h挂载配置一致(示例中为A:,文档示例为S:); - 解码失败:确保 GIF 为 89a 版本且带全局颜色表(参见 gifdec.c 的校验逻辑);部分老旧 87a 文件或仅含局部色表的动图可能无法解析;
- 内存不足:按
LV_COLOR_DEPTH对应倍数核算每帧缓冲占用(16bit 为 4×W×H),必要时压缩 GIF 尺寸或帧数; - 仅播放一次/不循环:检查 GIF 的 NETSCAPE 循环扩展设置(
loop_count),单次播放会触发LV_EVENT_READY事件,可据此实现"播放完毕跳转页面"等逻辑。
小结
LVGL 的 GIF 解码器为嵌入式 UI 提供了一条"开箱即用"的动图能力:LV_USE_GIF一个宏即可开启,lv_gif_create+lv_gif_set_src两个函数即可完成播放,来源支持 C 数组与文件两种形态,颜色深度直接决定内存开销。结合 X-TRACK 模拟器工程与 gif.md 文档,你可以快速在模拟器中完成从配置、加载到播放的完整闭环,并以此评估真机移植时的 RAM 预算。
- 智能硬件
- 嵌入式
- 硬件开发
【免费下载链接】X-TRACK
A GPS bicycle speedometer that supports offline maps and track recording
相关推荐
在 Remotion 中播放 GIF / APNG / AVIF / WebP 动画图片:`<AnimatedImage>` 与 `<Gif>` 组件完全指南
在 Remotion 中播放 GIF / APNG / AVIF / WebP 动画图片: <AnimatedImage 与 <Gif 组件完全指南 本篇指南聚
人工智能AI 应用前端MCP 服务【亲测免费】 jsgif: 在浏览器中生成和播放GIF动画的JavaScript库
jsgif: 在浏览器中生成和播放GIF动画的JavaScript库 是一个轻量级、易于使用的JavaScript库,用于在浏览器中生成和播放GIF动画。它提供
前端音视频开发工具Chart.js终极指南:从零开始掌握数据可视化神器
Chart.js终极指南:从零开始掌握数据可视化神器 Chart.js是一款功能强大且易用的数据可视化库,它允许开发者通过简单的JavaScript代码创建各种
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考