2. LVGL图片对象基础

图片在嵌入式 UI 里是最常用的元素之一。LVGL 对图片的处理,说白了就是三个核心动作:创建对象、设置数据源、刷新显示。这一节我带你把这几个动作拆开揉碎,看看底层到底发生了什么。

图片对象的生命周期

LVGL 里所有可见元素都是对象(lv_obj_t),图片也不例外。创建一个图片对象,只需要两行代码:

lv_obj_t * img = lv_img_create(lv_scr_act());  // 在当前屏幕创建
lv_obj_set_size(img, 100, 100);                 // 设置显示尺寸

嗯,这里要注意:lv_img_create 只是分配了一个空壳子,它还没有任何图像数据。你想想看,就像你买了个相框,但里面还没放照片。

个人习惯:我通常会在创建图片对象后立即设置对齐方式,比如 lv_obj_align(img, LV_ALIGN_CENTER, 0, 0),这样后面调整位置时心里有数。

核心函数:lv_img_set_src

这个函数是图片加载的入口。它的原型很简单:

void lv_img_set_src(lv_obj_t * img, const void * src);

但 src 参数的类型却藏着不少门道。LVGL 支持三种数据源:

数据源类型 标识方式 典型场景
内部变量 直接传指针 小图标、Logo,编译时嵌入
文件路径 以 file:// 开头 SD 卡、Flash 中的大图
符号字体 以 LV_SYMBOL_ 开头 按钮图标、状态指示

我曾经在项目里踩过一个坑:用文件路径时忘了加 file:// 前缀,结果图片死活显示不出来。调试了半天才发现是字符串格式问题。所以我的建议是——写代码时先把路径打印出来看一眼。

关键点:当使用外部存储加载图片时,lv_img_set_src 的第二个参数必须是指向文件路径的字符串,且路径前缀要匹配你注册的文件系统驱动。

图片描述符结构: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 又包含了:

typedef struct {
    uint32_t cf : 5;         // 颜色格式(色彩深度、压缩方式)
    uint32_t always_zero : 3;
    uint32_t reserved : 2;
    uint32_t w : 12;         // 图片宽度(像素)
    uint32_t h : 12;         // 图片高度(像素)
} lv_img_header_t;

你可能会问:为什么宽度和高度只用了 12 位?因为 LVGL 设计时考虑的是嵌入式场景,单张图片最大 4095 像素,够用了。我见过有人试图加载 4K 图片,结果内存直接爆掉——嗯,这就是没看文档的后果。

颜色格式(Color Format)

cf 字段决定了每个像素占多少字节、如何解析。LVGL 支持的颜色格式很多,但常用的就这几种:

宏定义 描述 每像素字节数
LV_IMG_CF_TRUE_COLOR 真彩色(按全局颜色深度) 2 或 4
LV_IMG_CF_INDEXED_1BIT 索引色,1 位色深 0.125
LV_IMG_CF_INDEXED_2BIT 索引色,2 位色深 0.25
LV_IMG_CF_INDEXED_4BIT 索引色,4 位色深 0.5
LV_IMG_CF_INDEXED_8BIT 索引色,8 位色深 1
LV_IMG_CF_ALPHA_8BIT 仅 Alpha 通道 1
避坑指南:我曾经在 STM32F429 上加载一张 800×480 的真彩色图片,算下来需要 800×480×4 ≈ 1.5MB 内存。而芯片的 SRAM 总共才 256KB。结果可想而知——系统直接 HardFault。所以,大图一定要用外部存储 + 索引色或压缩格式。

图片加载的完整流程

从调用 lv_img_set_src 到图片真正显示在屏幕上,中间经历了这些步骤:

  1. 解析数据源类型:判断 src 是变量、文件还是符号。
  2. 读取图片头:获取宽、高、颜色格式。
  3. 分配解码缓冲区:如果图片是压缩格式,需要临时解压。
  4. 解码像素数据:将原始数据转换为显示驱动需要的格式。
  5. 刷新显示区域:通知 LVGL 重绘图片所在区域。

这里面最耗时的就是第 4 步。如果是外部存储加载,还要加上文件 I/O 的时间。我建议你在性能敏感的项目里,用 lv_img_cache_set_size 开启图片缓存,避免重复解码。

知识结构图

下面这张图把图片对象、描述符、数据源之间的关系画清楚了:

LVGL 图片对象核心结构 lv_img_t (对象) lv_img_dsc_t (描述符) header (宽/高/颜色格式) data_size (数据长度) data (像素数据指针) 内部变量 (RAM/Flash) 文件路径 (外部存储) 符号字体 (内置图标) 说明: lv_img_set_src 根据 src 类型自动选择数据源,解析 header 后填充描述符,最终由显示驱动渲染。

实际项目中的注意事项

结合我自己的经验,给你几个实用建议:

  • 图片尺寸要对齐:LVGL 内部处理时,宽度最好是 2 的倍数,否则某些颜色格式下会有性能损失。
  • 颜色格式匹配:如果显示驱动是 RGB565,图片也尽量用 RGB565,避免运行时转换。
  • 大图分块加载:超过 100KB 的图片,建议用 lv_img_set_zoom 配合缓存,不要一次性解码全部数据。
  • 调试时打印描述符:在 lv_img_set_src 之后,可以打印 img->dsc->header.w 和 img->dsc->header.h,确认图片是否被正确解析。
小技巧:如果你用 LVGL 的在线图片转换工具(LVGL Image Converter),记得勾选「生成描述符代码」。这样可以直接拿到 lv_img_dsc_t 的 C 语言定义,省去手动计算头信息的麻烦。

好了,这一节的内容就到这里。图片对象的基础你已经掌握了,接下来就是如何让 LVGL 从外部存储(比如 SD 卡、SPI Flash)里加载这些图片——那才是真正考验驱动设计能力的地方。