图标颜色修改(1)
222.3.21 图标颜色修改(1)
#define COLOR_RED 0xff,0x0,0x0 // 定义红色的 R、G、B 参数列表;展开后分别是 255、0、0
#define COLOR_WHITE 0xff,0xff,0xff // 定义白色的 R、G、B 参数列表;三个通道均为 255
#define COLOR_YELLOW 0xff,0xff,0x0 // 定义黄色的 R、G、B 参数列表;红、绿通道为 255,蓝通道为 0
lv_color_t color_value; // 定义 LVGL 颜色变量,用于保存 lv_color_make() 返回的目标颜色
color_value = lv_color_make(COLOR_RED); // 把 COLOR_RED 展开成三个实参,并生成适配当前 LVGL 色深的红色值
lv_obj_set_style_img_recolor(ui_ImageLight, color_value, 0); // 为灯光图标的主部件、默认状态设置红色重着色值
lv_obj_set_style_img_recolor_opa(ui_ImageLight, 255, 0); // 把重着色混合强度设为 255,使红色完全作用于图标的可见像素
上述代码按本节视频结束前的最终画面逐行核对,只收录本节新增的业务代码。三个颜色宏和
color_value分别位于main.c的用户宏定义区、用户变量区,后三行位于while (1)内、HAL_Delay(5)之后。SquareLine 为分析接口而临时生成的转向灯代码、被注释掉的中间试验语句,以及下一节才加入的其余三个图标改色与定时逻辑均不列入本节源码。
一、课程介绍
本节开始完成仪表盘项目的第三阶段:不再只显示 SquareLine 生成的静态界面,而是从 main.c 主动修改 LVGL 对象的显示样式。课堂的实际目标是:
- 阅读
ui_Screen1.c,把 SquareLine 中的控件与导出的 C 对象对应起来; - 通过“修改 SquareLine 属性—重新导出—比较代码”的方法找到图片改色接口;
- 在
main.c中把ui_ImageLight灯光图标重着色为红色; - 用
lv_color_make()理解 RGB 参数,并预先定义红、白、黄三组参数宏。
本节最终只有第一个灯光图标被业务代码改为红色。白色和黄色宏已经定义,但尚未用于屏幕效果;转向灯、水温和安全带三个对象的业务改色,以及颜色交替显示,属于第 223 节内容,不能提前算入本节。
这次修改完全发生在 LVGL 绘图层,没有增加 GPIO、传感器或其他外设,也没有改变 LCD 接线。所谓“灯变红”是屏幕中的灯光图标像素变红,不是开发板上的实体 LED 被点亮。
二、界面代码分析
课堂先打开 24Lvgl_Dashboard/midware/ui/screens/ui_Screen1.c。这是 SquareLine 导出的屏幕初始化文件,其中的对象创建、素材绑定、坐标、对齐方式和样式,均与上一节的可视化设计对应。
主要对象关系如下:
| 界面元素 | C 对象 | 关键含义 |
|---|---|---|
| 屏幕 | ui_Screen1 | 由 lv_obj_create(NULL) 创建;父对象为 NULL,因此它是一个 Screen |
| 仪表盘背景 | ui_ImageDashboard | 以 ui_Screen1 为父对象创建的 Image |
| 仪表指针 | ui_ImageNeedle | 坐标为 X=-86、Y=71,Pivot=(10,-30),初始 Angle=2500 |
| 灯光图标 | ui_ImageLight | 本节业务代码实际修改的对象 |
| 安全带图标 | ui_ImageSfatyBelt | 保留课程工程中的实际拼写 SfatyBelt |
| 水温图标 | ui_ImageWatertemp | 水温状态图片对象 |
| 转向灯图标 | ui_ImageTurnlight | 课堂用于在 SquareLine 中试验并寻找接口的对象 |
ui_Screen1 更准确地说是一个 lv_obj_t * 对象指针,不是硬件“屏幕句柄”。它指向 LVGL 在内存中创建的屏幕对象。lv_img_create(ui_Screen1) 则创建一个以该屏幕为父对象的 Image;随后由 lv_img_set_src() 绑定图片资源。
上一节设置的黑色背景在导出代码中表现为 lv_color_hex(0x000000),指针的 -86、71、10、-30 和 2500 也能逐项找到。这说明可视化属性并非独立于代码存在,而是由 SquareLine 转换为 LVGL API 调用。
四个图标原先没有启用 Image Recolor,因此最初导出的对应代码段中看不到重着色设置。这并不表示 LVGL 没有改色能力,只表示该属性尚未被设置,SquareLine 没有必要生成相关调用。
ui_ImageLight 等对象在 ui.c 中定义,在 ui.h 中以 extern lv_obj_t * 声明;main.c 已经包含 ui.h,并且在进入主循环前调用了 ui_init(),所以主循环可以直接使用这些对象指针。必须先完成 ui_init():对象尚未创建时就把空指针传给样式接口,会导致异常。
还要区分两个修改位置:
ui_Screen1.c是 SquareLine 自动生成代码,再次导出时可能被覆盖,适合观察生成结果;main.c的USER CODE区域承载运行时业务逻辑,适合根据车辆状态动态修改对象。
本节在 SquareLine 中改转向灯只是为了找出对应 API,真正烧写验证的业务代码改的是 main.c 中的 ui_ImageLight。
三、改颜色分析
3.1 改颜色相关函数分析
课堂没有凭函数名猜接口,而是采用了很实用的“生成代码作差”方法:
- 在 SquareLine 中选中
ImageTurnlight; - 进入
Style Settings → Style Main → Image Recolor; - 启用 Recolor,选一个红色,并把 Recolor Opa 设为 255;
- 重新执行
Export UI Files; - 在 VS Code 中打开新生成的
ui_Screen1.c,与修改前对比。
导出结果在 ui_ImageTurnlight 初始化代码后新增了两类设置:重着色颜色 lv_obj_set_style_img_recolor(),以及重着色混合强度 lv_obj_set_style_img_recolor_opa()。课程工程中这次试验生成的颜色是 lv_color_hex(0xE82727),选择器写作 LV_PART_MAIN | LV_STATE_DEFAULT。
两个接口的参数可以这样理解:
| 参数 | img_recolor 中的含义 | img_recolor_opa 中的含义 |
|---|---|---|
| 第 1 个参数 | 要修改的 LVGL 对象指针 | 要修改的 LVGL 对象指针 |
| 第 2 个参数 | lv_color_t 类型的目标颜色 | lv_opa_t 类型的混合强度,范围 0~255 |
| 第 3 个参数 | 样式选择器,即部件与状态的组合 | 样式选择器,即部件与状态的组合 |
这里必须纠正原笔记中的“opa 控制整张图片透明度”:img_recolor_opa 控制的是重着色颜色与原图的混合强度,不是整个 Image 对象的可见度。
- 值为 0:不混入重着色颜色,基本保持原图颜色;
- 值在 1~254:按比例把目标色混入原图;
- 值为 255:完全使用重着色颜色处理可见像素。
图片素材自身的 Alpha 透明通道仍然参与绘制。因此强度为 255 时,图标有内容的部分会呈目标颜色,原来透明的背景仍然透明,不会变成一块实心矩形。若要控制整张图片的可见透明度,应使用 img_opa 属性,而不是 img_recolor_opa。
视频在第三个参数中直接填写 0。在本项目使用的 LVGL 8.3 中,LV_PART_MAIN 和 LV_STATE_DEFAULT 的枚举值都为 0,所以这里的 0 等价于 LV_PART_MAIN | LV_STATE_DEFAULT,即“对象主体的默认状态”。SquareLine 生成的符号写法更容易读懂,但根据“课程源码原样保留”的要求,标题下仍保留视频实际写入的 0。
3.1.1 改转向灯颜色
课堂先在 SquareLine 里选择转向灯,是因为任何一个图标都可以用于验证 Image Recolor。选中颜色后图标一开始没有明显变化,是因为只指定了 Recolor 颜色,而重着色混合强度仍是默认值 0;把 Recolor Opa 调到 255 后,转向灯才完整显示为红色。
这一步的价值不只是得到红色效果,更重要的是建立一种查接口的方法:当不熟悉 LVGL 时,可以先在界面设计器中做一个最小改动,再比较导出代码,定位设计属性对应的 LVGL API。比较时应只改变一个属性,否则同时出现多处代码差异,很难判断哪一行对应哪项设置。
SquareLine 的这次转向灯设置只用于接口探索。课堂并没有把重新导出的整套 UI 覆盖进 24 号 STM32 工程后作为最终业务方案,而是取出两种 API 的调用方式,转到 main.c 控制第一个灯光图标。
3.1.1.1 复制函数至主函数
老师通过 Keil 的 Go To 功能查看 lv_obj_style_gen.c 中约 419、427 行附近的接口实现,确认函数名和参数类型,然后在 main.c 的 while (1) 内、HAL_Delay(5) 后加入调用。
这里复制的是接口的调用形式,不是把 LVGL 库函数实现复制到 main.c。LVGL 库已经编译进工程,业务代码只需调用公开 API。若真的复制库函数实现,既会造成重复定义,也会破坏库与应用代码的边界。
代码必须写在 STM32CubeMX 保留的 USER CODE BEGIN WHILE 与 USER CODE END WHILE 之间。这样后续重新生成 CubeMX 工程时才不会轻易丢失。视频转录中的“主函数 y1”“北跟 N”分别是 while (1) 和 BEGIN/END 的识别错误。
3.1.2 改灯的颜色
真正传入第一个参数的是 ui_ImageLight。它代表屏幕左下方四个状态图标中的灯光图标,不能因为前面用 ui_ImageTurnlight 找接口,就把两个对象混为一谈。
最终调用关系为:
COLOR_RED 参数列表 → lv_color_make() → color_value → ui_ImageLight 的 img_recolor → img_recolor_opa=255
第一条样式调用只指定“混入什么颜色”,第二条样式调用才使混色强度达到最大;少任何一条都不能得到课堂中的完整红色效果。编译并烧写后,设备画面中的第一个灯光图标变为红色,证明对象名、颜色值与样式调用均正确。
本节把这三行放在无限循环中,因此每轮都会重复设置同一颜色。这样便于初次验证,也与视频源码一致;对于固定不变的初始化样式,工程化代码通常只需设置一次,但本节不能擅自把课堂代码搬到别处。后续动态状态控制应在状态发生变化时更新样式,并让 lv_timer_handler() 保持周期运行。
四、RGB百科知识学习
RGB 是加色模型,R、G、B 分别表示红、绿、蓝三个发光通道。每个参数在 lv_color_make(uint8_t r, uint8_t g, uint8_t b) 中占 8 位,取值范围为 0~255:0 表示该通道不发光,255 表示该通道强度最大。
| 颜色 | R(十进制/十六进制) | G(十进制/十六进制) | B(十进制/十六进制) | 0xRRGGBB |
|---|---|---|---|---|
| 黑色 | 0 / 0x00 | 0 / 0x00 | 0 / 0x00 | 0x000000 |
| 红色 | 255 / 0xFF | 0 / 0x00 | 0 / 0x00 | 0xFF0000 |
| 绿色 | 0 / 0x00 | 255 / 0xFF | 0 / 0x00 | 0x00FF00 |
| 蓝色 | 0 / 0x00 | 0 / 0x00 | 255 / 0xFF | 0x0000FF |
| 黄色 | 255 / 0xFF | 255 / 0xFF | 0 / 0x00 | 0xFFFF00 |
| 青色 | 0 / 0x00 | 255 / 0xFF | 255 / 0xFF | 0x00FFFF |
| 品红色 | 255 / 0xFF | 0 / 0x00 | 255 / 0xFF | 0xFF00FF |
| 白色 | 255 / 0xFF | 255 / 0xFF | 255 / 0xFF | 0xFFFFFF |
0xFF 与十进制 255 是同一个数,前缀 0x 只表示按十六进制书写。视频中的“RGB”多次被转录成“RJB”,应统一纠正为 RGB。
LVGL 提供了两种本节涉及的颜色创建方法:
lv_color_make(r, g, b):分别传入三个 0~255 的 RGB 分量;lv_color_hex(0xRRGGBB):用一个uint32_t参数承载 24 位 RGB 写法。
lv_color_hex() 的参数类型是 32 位整数,不等于这里使用了 32 位 ARGB 颜色;本例有效的颜色数据仍按低 24 位 RRGGBB 解释。两种函数最终都返回 lv_color_t,所以都可传给重着色接口。老师选择 lv_color_make(),是因为三个通道与 RGB 表格能直接对应,更适合当前入门演示。
本工程 lv_conf.h 配置为 LV_COLOR_DEPTH 16,因此 lv_color_make() 接收 8 位 R、G、B 后,会转换为工程使用的 16 位 RGB565 颜色:红色保留 5 位、绿色保留 6 位、蓝色保留 5 位。也就是说,输入参数仍写 0~255,但中间的很多相近颜色在 RGB565 中会量化成同一个值。LV_COLOR_16_SWAP 1 只负责交换 RGB565 两个字节在显示链路上的顺序,不会把 RGB 参数顺序改成 BGR。
从软件到硬件的显示链路可概括为:
PNG 像素及 Alpha → LVGL 按 img_recolor_opa 混入目标色 → 转为 RGB565 → 写入显示缓冲区 → LCD 接口发送像素 → 屏幕显示
因此重着色并没有修改 PNG 数组本身,也没有改动 LCD 背光电压;它是在 LVGL 绘制对象时计算显示颜色。使用同一份灰色/白色图标素材就能呈现多个状态颜色,可以避免为红、黄、白分别保存一套图片资源。
五、传参改颜色
5.1 例题: 传参改颜色
课堂先直接使用 lv_color_make(0xff,0x0,0x0) 作为 lv_obj_set_style_img_recolor() 的颜色参数:R=255、G=0、B=0,所以结果是纯红色。编译没有报错,烧写后第一个灯光图标变红,完成了最小闭环验证。
调试过程中还曾单独写过一次 lv_color_make(0xff,0x0,0x0);。这个表达式虽然会计算并返回一个 lv_color_t,但返回值没有被保存或使用,对显示没有作用,因此随后被注释。真正产生效果的是把返回值直接传给重着色接口,或像最终代码那样先保存到 color_value 再传入。
如果改用十六进制整色值,红色可以表示为 lv_color_hex(0xFF0000);不能把 0xFF0000 直接当作已经转换好的 lv_color_t 随意传入。颜色创建函数负责根据 LV_COLOR_DEPTH 生成当前工程需要的内部表示。
5.2 定义变量改颜色
直接在函数参数中反复写三个通道不利于辨认颜色,因此课堂继续做了两层整理:
- 定义
lv_color_t color_value,接收lv_color_make()的返回值; - 用宏给常用 RGB 参数列表命名。
lv_color_t 是 LVGL 的颜色类型,能够随 LV_COLOR_DEPTH 的配置选择正确内部格式。这里不应把 color_value 随意换成 uint32_t,因为本工程最终使用的是 RGB565 lv_color_t,而不是把 24 位网页颜色原样存入对象样式。
三个宏的右侧不是字符串,也不是完整的颜色对象,而是由逗号分隔的三个函数实参。例如预处理后:
lv_color_make(COLOR_RED) → lv_color_make(0xff,0x0,0x0)
因此原笔记中的 #define COLOR_RED "#FF0000" 是错误写法:双引号会生成字符串指针,类型与 lv_color_make() 需要的三个整数参数完全不匹配。当前宏只适合放进需要三个 RGB 实参的位置,不能把 COLOR_RED 单独当作一个普通整数颜色常量使用。
5.2.1 定义变量传参改颜色
color_value 在用户变量区定义,主循环中先执行 color_value = lv_color_make(COLOR_RED),再把它传给 lv_obj_set_style_img_recolor()。这样的数据流比在较长的样式调用中直接塞三个数字更容易阅读:变量类型说明它是 LVGL 颜色,宏名说明当前选择的是红色。
本节只有一个颜色消费者,使用全局变量并非必需;但这是视频实际写法,所以原样保留。以后若在中断、RTOS 多任务或多个界面回调之间共享颜色变量,还需要考虑访问时机与线程安全,不能仅凭“全局变量方便”无限扩展作用域。
5.2.2 定义白色从而改颜色
白光由三个通道同时达到最大值组成,所以课堂定义:
COLOR_WHITE → 0xff,0xff,0xff → RGB(255,255,255) → 白色。
本节只完成宏定义,没有把主循环中的 COLOR_RED 换成 COLOR_WHITE 进行最终烧写。该宏是为下一步切换图标状态准备的。英文应拼作 WHITE,视频中老师也通过翻译确认了这一拼写。
5.2.3 定义黄色从而改颜色
RGB 加色模型中的黄色由红光和绿光叠加得到,蓝色通道为 0,所以课堂定义:
COLOR_YELLOW → 0xff,0xff,0x0 → RGB(255,255,0) → 黄色。
这里最后一个参数必须是蓝色分量 0;若写成三个 0xff,得到的是白色。YELLOW 是正确英文拼写。本节同样只是定义该参数宏,没有在最终画面中切换为黄色。
常见问题可按下表排查:
| 现象 | 可能原因与检查项 |
|---|---|
| 设置了颜色但图标仍保持原色 | 是否漏掉 lv_obj_set_style_img_recolor_opa(..., 255, ...),或强度仍为 0 |
| 改错了图标 | 检查传入的是 ui_ImageLight、ui_ImageTurnlight 还是其他对象 |
| 编译提示对象未定义 | 检查 main.c 是否包含 ui.h,以及对象名是否与 SquareLine 导出结果完全一致 |
| 运行时异常 | 检查调用时是否已经执行 ui_init()、对象指针是否已创建 |
| 把透明区域也误认为会变成红色 | Recolor 只改变绘制颜色,素材 Alpha 为 0 的区域仍透明 |
使用 "#FF0000" 后类型报错 | 这是字符串,不是本课所需的 RGB 参数列表或 lv_color_t |
| 重新导出后手工改动消失 | 不要把长期业务逻辑直接写在 SquareLine 自动生成的 ui_Screen1.c 中 |
| 屏幕整体颜色异常 | 检查 LV_COLOR_DEPTH、LV_COLOR_16_SWAP 与 LCD 驱动的数据格式,而不是先改 RGB 参数顺序 |
本节最终建立了“找到对象—构造颜色—设置重着色—设置混合强度”的完整方法。下一节才会把同样的方法扩展到其余三个图标,并处理颜色交替时 LVGL 刷新与阻塞延时的问题。