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