1. LVGL 图片系统概述

做嵌入式 GUI 开发,图片处理永远是绕不开的坎。LVGL 的图片系统,说白了就是一套「怎么把像素数据塞进屏幕」的机制。我最早接触 LVGL 时,以为图片就是简单调个 lv_img_set_src() 就完事了,结果项目一上大图,帧率直接崩到个位数……嗯,从那以后我才老老实实把图片系统从头捋了一遍。

这一节,我们先搞清楚三个核心概念:图片对象、图片数据类型、解码流程。后面所有优化手段,都建立在这套基础上。

1.1 图片对象:lv_img 是什么?

lv_img 是 LVGL 里专门用来显示图片的控件。它本质上是一个容器,负责把图片数据渲染到屏幕上。你可以把它理解成「画框」——框本身不存像素,只负责展示。

核心要点:lv_img 对象只管理显示属性(位置、大小、透明度、旋转等),真正的像素数据由图片源(src)提供。

创建一个图片对象很简单:

lv_obj_t * img = lv_img_create(lv_scr_act());
lv_img_set_src(img, &my_image);  // 设置图片源
lv_obj_center(img);              // 居中显示

我在项目中遇到过一个问题:有人把 lv_img_create() 放在循环里反复调用,结果内存暴涨。记住,图片对象创建一次就够了,后续只需要切换 src 或调整属性。

1.2 图片数据类型:两种来源

LVGL 支持两种图片数据来源,定义在 lv_img_src_t 枚举中:

类型 枚举值 数据来源 典型场景
变量 LV_IMG_SRC_VARIABLE 编译时嵌入到固件中的 C 数组 小图标、启动 Logo、固定 UI 元素
文件 LV_IMG_SRC_FILE 外部存储设备中的图片文件 大图、动态更换的壁纸、用户上传的图片

1.2.1 变量图片(LV_IMG_SRC_VARIABLE)

变量图片的数据直接编译进二进制固件。通常用 LVGL 的图片转换工具(如 lv_img_conv 或在线转换器)把 PNG/JPG 转成 C 数组。

举个例子,转换后会生成类似这样的结构:

const lv_img_dsc_t my_image = {
    .header.always_zero = 0,
    .header.w = 80,      // 宽度 80 像素
    .header.h = 60,      // 高度 60 像素
    .data_size = 80 * 60 * LV_COLOR_DEPTH / 8,
    .header.cf = LV_IMG_CF_TRUE_COLOR,  // 颜色格式
    .data = my_image_data,              // 像素数据数组
};

我个人习惯把小于 64×64 的图标都用变量图片。好处是加载快——数据已经在 RAM 或 Flash 里,不需要额外解码。坏处也很明显:固件体积会膨胀。你想想看,一张 320×240 的全彩图,光像素数据就 150KB 左右,MCU 的 Flash 可经不起这么折腾。

小技巧:变量图片的数据可以放在 Flash 中(通过 LV_ATTRIBUTE_IMG_* 宏控制),不占用宝贵的 RAM。但读取速度取决于 Flash 的访问延迟,这点在后续「刷新卡顿」章节会详细分析。

1.2.2 文件图片(LV_IMG_SRC_FILE)

文件图片的数据存储在外部文件系统里,比如 SPI Flash、SD 卡、甚至网络文件系统。使用时只需要传入文件路径:

lv_img_set_src(img, "S:/images/background.bin");

注意,LVGL 默认支持的文件格式是 LVGL 自定义的二进制格式(.bin),不是直接加载 PNG 或 JPEG。为什么?因为嵌入式设备解码这些格式太慢了。LVGL 的图片转换工具会把 PNG/JPEG 转成它自己的二进制格式,解码时只需要简单的内存拷贝,不需要复杂的解压缩算法。

避坑指南:我曾经在项目里直接加载 SD 卡上的 JPEG 文件,结果 LVGL 报错。后来才发现,LVGL 的文件图片只认它自己的 .bin 格式。如果你非要显示 JPEG/PNG,需要额外启用 LV_IMG_CF_RAW 或使用外部解码库(如 libjpeg-turbo),但性能开销很大,不推荐在资源紧张的 MCU 上使用。

1.3 图片解码流程:从数据到像素

不管图片数据来自变量还是文件,LVGL 都要经过一套解码流程才能把像素画到屏幕上。我画了一张流程图,帮你理清这个链路:

LVGL 图片解码流程 LVGL 图片解码流程 图片源 (src) LV_IMG_SRC_VARIABLE LV_IMG_SRC_FILE 直接读取 data 指针 文件系统读取 解码器 (decoder) 解码器内部处理 格式识别 → 颜色空间转换 → 缓存管理 绘制到显示缓冲区

整个流程可以拆成几个关键步骤:

  1. 源识别:LVGL 检查 src 参数,判断是变量还是文件。
  2. 数据获取:
    • 变量图片:直接拿到 lv_img_dsc_t 结构体中的 data 指针。
    • 文件图片:通过文件系统接口读取数据到内存缓冲区。
  3. 解码器处理:LVGL 内置的解码器(lv_img_decoder)负责:
    • 识别图片格式(真彩色、索引色、Alpha 通道等)。
    • 如果需要,进行颜色空间转换(比如 BGR 转 RGB)。
    • 管理解码缓存,避免重复解码。
  4. 绘制到显示缓冲区:解码后的像素数据被送入 LVGL 的显示驱动,最终刷新到屏幕。

关键认知:解码流程中最耗时的环节往往是「文件读取」和「颜色空间转换」。变量图片因为没有 I/O 操作,解码速度通常比文件图片快一个数量级。但变量图片占 Flash,文件图片占 RAM 缓冲区——这就是典型的空间换时间。

1.4 图片描述符:lv_img_dsc_t 结构体

不管是变量还是文件,LVGL 最终都会把图片信息抽象成一个 lv_img_dsc_t 结构体。这个结构体是图片系统的「身份证」:

typedef struct {
    lv_img_header_t header;  // 图片头信息(宽、高、颜色格式)
    uint32_t data_size;      // 数据大小(字节)
    const uint8_t * data;    // 像素数据指针(变量图片用)
} lv_img_dsc_t;

其中 lv_img_header_t 包含:

字段 说明 典型值
w 图片宽度(像素) 240
h 图片高度(像素) 320
cf 颜色格式(Color Format) LV_IMG_CF_TRUE_COLOR
always_zero 保留位,必须为 0 0

颜色格式 cf 直接影响解码速度和内存占用。常用的有:

  • LV_IMG_CF_TRUE_COLOR:真彩色,每个像素占 2 字节(RGB565)或 4 字节(ARGB8888)。画质最好,但最占内存。
  • LV_IMG_CF_INDEXED_1/2/4/8BIT:索引色,用调色板压缩。适合图标和简单图形。
  • LV_IMG_CF_ALPHA_1/2/4/8BIT:仅 Alpha 通道,用于遮罩或字体。

经验之谈:我建议 UI 设计阶段先用真彩色,确保视觉效果。到了性能优化阶段,再把大图转成索引色或压缩格式。LVGL 的图片转换工具支持批量转换,非常方便。

1.5 本章小结

这一节我们覆盖了 LVGL 图片系统的三个基石:

  • lv_img 对象:负责显示,不存数据。
  • 两种数据源:变量(快但占 Flash)和文件(灵活但慢)。
  • 解码流程:从源到像素的完整链路,瓶颈通常在 I/O 和颜色转换。