拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LVGL GIF 解码器集成指南:在 X-TRACK 码表模拟器中播放 GIF 动画

LVGL GIF 解码器集成指南:在 X-TRACK 码表模拟器中播放 GIF 动画
  • 智能硬件
  • 嵌入式
  • 硬件开发

【免费下载链接】X-TRACK

A GPS bicycle speedometer that supports offline maps and track recording

项目地址:https://gitcode.com/gh_mirrors/xt/X-TRACK
点击查看免费下载

导读

本文围绕 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 16

X-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所需内存
83 × 图片宽 × 图片高
164 × 图片宽 × 图片高
325 × 图片宽 × 图片高

该公式与源码完全吻合: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 解码器可以为此类场景提供一种低成本的动图播放方案,在模拟器中可按如下步骤验证:

  1. 将 lv_conf.h 中的LV_USE_GIF改为1;
  2. 若从文件读取,将LV_USE_FS_STDIO(或LV_USE_FS_POSIX)改为1,并把.gif文件放入对应挂载目录;
  3. 参考 lv_example_gif_1.c 编写页面代码,将lv_gif_create创建的控件添加到目标页面(如Pages下的页面,见 USER/App/Pages);
  4. 重新编译并运行模拟器,即可观察到逐帧播放的动画。

需要注意,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

项目地址:https://gitcode.com/gh_mirrors/xt/X-TRACK
点击查看免费下载

相关推荐

上一篇:jsoup CSS选择器完全教程:像jQuery一样操作HTML的终极指南
下一篇:终极指南:Vimium C跨浏览器兼容性全解析 - Chrome、Firefox、Edge最佳实践与常见问题解决

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表