2、图像对象创建:lv_img_create、lv_img_set_src、图像对齐与缩放模式

好,咱们接着聊图像显示。上一节我们把图像数据怎么塞进内存讲清楚了,这一节就来看看怎么在屏幕上把它“亮”出来。

说白了,LVGL里显示一张图,核心就三步:创建对象、绑定数据源、设置显示方式。我刚开始用LVGL时,觉得这有啥难的?不就是new一个对象,传个指针嘛。结果真上手才发现,坑全在对齐和缩放上。你想想看,一张800x480的图,硬塞进一个200x200的框里,不设置缩放模式,那画面简直没法看。

2.1 创建图像对象:lv_img_create

创建图像对象,API极其简单:

lv_obj_t * img = lv_img_create(lv_scr_act());  // 在当前屏幕创建

嗯,就这么一行。但这里有个细节我提一下——父对象的选择。我个人习惯,如果图像是背景或者装饰,直接挂到屏幕下;如果是某个容器里的图标,那就挂到容器下。这样后续做移动、隐藏,层级关系清晰很多。

创建完之后,默认的图像对象是空的,啥也不显示。你得给它“喂”数据。

2.2 绑定图像数据源:lv_img_set_src

这是最核心的一步。数据源可以是三种东西:

  • 内部图像(C数组):用转换工具生成的 .c 文件里的数组
  • 外部文件(文件系统):比如SD卡里的 .bin 或 .png
  • 符号/字符串:显示字体图标

代码示例:

// 方式一:内部图像(最常见)
extern const lv_img_dsc_t my_image;
lv_img_set_src(img, &my_image);

// 方式二:文件系统中的图片
lv_img_set_src(img, "S:/images/logo.bin");

// 方式三:符号(字体图标)
lv_img_set_src(img, LV_SYMBOL_HOME);

我的经验: 内部图像加载最快,没有I/O延迟。但如果你要频繁更换图片(比如相册应用),用文件系统更灵活。我在一个项目里就吃过亏——把所有图标都转成C数组,结果固件体积暴涨,后来改成从SPI Flash动态加载,才把体积降下来。

这里有个容易踩的坑:图像描述符必须保持有效。我曾经把图像数组定义在函数内部,结果函数退出后数组被释放,屏幕直接花屏。嗯,血的教训。图像数据要么用 const 修饰放在全局区,要么用动态内存分配并保证生命周期。

2.3 图像对齐模式

图像对象本身是一个矩形区域,图像在这个区域里怎么摆放?这就涉及到对齐了。

LVGL提供了几个对齐标志:

对齐方式宏定义说明
左上LV_IMG_ALIGN_TOP_LEFT默认,图像左上角对齐对象左上角
居中LV_IMG_ALIGN_CENTER图像中心对齐对象中心
右下LV_IMG_ALIGN_BOTTOM_RIGHT图像右下角对齐对象右下角
自定义偏移LV_IMG_ALIGN_STRETCH配合偏移量使用

设置对齐的API:

lv_img_set_align(img, LV_IMG_ALIGN_CENTER);

你可能会问:这有啥用?我举个例子。假设你有一个100x100的按钮,里面放一个20x20的图标。如果不设置对齐,图标默认贴在左上角,丑不丑?设成居中,图标就乖乖待在正中间了。

注意: 对齐模式只在图像尺寸小于对象尺寸时才有意义。如果图像比对象大,对齐效果会被裁剪覆盖掉。

2.4 缩放模式

这才是重头戏。图像缩放,说白了就是图像和显示区域尺寸不匹配时,系统该怎么处理。

LVGL提供了三种缩放模式:

模式宏定义行为
不缩放LV_IMG_ZOOM_NONE保持原始尺寸,超出部分裁剪
缩放填充lv_img_set_zoom(img, 256 + offset)按比例缩放,256为原始大小
拉伸填充配合对象尺寸强制拉伸到对象大小,可能变形

代码示例:

// 放大到200%
lv_img_set_zoom(img, 512);  // 256 = 100%, 512 = 200%

// 缩小到50%
lv_img_set_zoom(img, 128);  // 128 = 50%

// 设置旋转角度(配合缩放)
lv_img_set_angle(img, 300); // 300/10 = 30度

我曾经踩过的坑: 缩放会消耗额外内存和CPU。如果你在低端MCU上把一张大图缩放到很小,或者频繁改变缩放值,帧率会明显下降。我有个项目用STM32F4,在界面上放了4个可缩放的图像预览框,结果拖动时卡成PPT。后来改成预先生成缩略图缓存,才流畅起来。

还有一个容易被忽略的点:缩放后的图像质量。LVGL默认使用最近邻插值,速度快但锯齿明显。如果你追求画质,可以开启抗锯齿:

lv_img_set_antialias(img, true);

但代价是性能开销。嗯,这里就是个取舍问题了——你要速度还是要画质?

2.5 综合示例:一个带缩放和居中的图像

来,看一个完整的例子:

/* 创建图像对象 */
lv_obj_t * img = lv_img_create(lv_scr_act());
lv_obj_set_size(img, 200, 200);          // 对象大小200x200
lv_obj_center(img);                       // 对象居中

/* 绑定图像数据 */
extern const lv_img_dsc_t my_photo;
lv_img_set_src(img, &my_photo);

/* 设置对齐和缩放 */
lv_img_set_align(img, LV_IMG_ALIGN_CENTER);  // 图像居中
lv_img_set_zoom(img, 384);                   // 放大到150%
lv_img_set_antialias(img, true);             // 开启抗锯齿

这段代码跑起来,你会看到一张居中的、放大了1.5倍的、边缘平滑的图像。嗯,效果还不错。

2.6 小结

这一节的内容其实就三个核心点:

  • 创建lv_img_create 指定父对象
  • 绑定lv_img_set_src 选对数据源类型
  • 显示:对齐 + 缩放,决定图像怎么呈现

我个人觉得,对齐和缩放是UI美观度的关键。很多嵌入式设备的界面看起来“土”,就是因为图像要么贴边、要么变形。你花点心思调好这两个参数,界面质感能提升一个档次。

下一节,我们会深入聊聊图像缓存——怎么让图像显示更快、更省内存。到时候见。