简介:这份资源是面向嵌入式开发者的 LVGL 图片转化工具,可将 PNG、BMP 等图像文件直接转换为 C 语言数组,帮助基于 LVGL 图形库的 MCU 项目在有限内存下高效加载并显示图片,省去运行时解码开销,适合从事 GUI 开发、低资源设备适配的工程师参考使用。包内共 9 个文件,压缩包约 3.71MB,主要包括可执行的转换程序、PHP 运行环境所用动态库及脚本、配置文件和示例图片,工具本身作为小型应用可直接运行,便于快速完成图片转换与代码生成。已有 3397 人浏览学习,内容对 LVGL 入门与进阶者均有实用价值。通过该工具,开发者能获得从图片导入、输出配置到生成 C 源码的完整转换流程,还可学习颜色查找表、数据位宽选择等嵌入式图像资源优化思路,提升界面开发效率并降低资源占用。
1. 为什么LVGL项目绕不开“图片转C数组”这道工序
用过LVGL在MCU上做界面的朋友,大概率都经历过类似的场景:界面规划得挺好,图标素材也找好了,结果往工程里一放,发现根本没法直接用。PNG、JPG这种格式在电脑上看着没问题,但到了STM32、ESP32这种单片机上,就成了烫手山芋。
核心原因在于,LVGL的底层绘图引擎不认识PNG压缩格式,也不负责实时解压JPG。它的显示流程要求你把图片先变成它认识的数据结构,而这个数据结构,归根结底就是一张位图——也就是像素点的颜色数值集合。在嵌入式场景里,最通用的承载方式就是C语言数组。图片转化工具干的事情,就是把这个从“图片文件”到“C数组”的转换过程自动化,顺带帮你把像素格式、颜色位数、透明通道这些参数一并处理好,输出成可以直接编译进工程的文件。
我最初接触LVGL时,也琢磨过“能不能运行时解码PNG”,后来一算资源账就放弃了。跑一个解码库需要额外几KB到几十KB的RAM做缓冲,解码速度在几十MHz主频的MCU上又慢得肉眼可见,频繁切换界面时卡顿会非常明显。相比之下,图片转成C数组后是只读常量,可以存放在Flash里,CPU读取它就像读普通变量一样快,虽然牺牲了体积,但换来了“零解码、直接刷屏”的体验。
这正是LVGL在资源受限设备上依然能做到流畅UI的根本原因。
需要明确的是,这个工具不是某个特定软件的名字,而是一类工具的总称。官方在线转换器、GUI Guider附带转换功能、命令行批处理脚本,还有各种社区开源的Python小工具,都算。不同工具适合不同场景,关键是你得先明白转换背后的逻辑,才能选对工具、调对参数。接下来,我把这套流程一步步拆开讲清楚。
2. C数组里到底存了什么:从转换原理反推参数选择
很多人第一次打开转换工具时,面对一堆下拉框是蒙的。什么RGB565、ARGB8888、颜色深度16bit、8bit,看着像加密电报。其实搞懂这些选项的含义,比疯狂试参数管用得多。这一节我不急着告诉你按哪个按钮,而是先讲清楚转换工具在幕后做了什么。
2.1 一张图片在内存里的真实模样
你在电脑屏幕上看到的一张彩色图片,放大到像素级,其实就是密密麻麻的颜色点。每个颜色点由红、绿、蓝三个通道组成,有的还带一个透明度通道,叫Alpha。PNG/JPG文件格式,本质上是这种原始像素数据经过压缩算法处理后的存储形式。
图片转换工具做的事情,就是把这些压缩数据解压还原成原始像素矩阵,然后遍历每一个像素点,把颜色数值按你指定的格式重新排列,最终生成为一个C语言数组。数组中的每个元素,代表图片中某一个像素点的颜色值。
举个直观的例子。一张4x4像素的小图标,转换后你会得到类似这样的数组:
static const lv_color_t img_icon[] = { 0xFFFF, 0xF800, 0xFFFF, 0xFFFF, 0xF800, 0xFFFF, 0xFFFF, 0xF800, // ... 共16个元素 };这里的lv_color_t类型在LVGL中默认就是uint16_t(对应RGB565格式)。每个0xFFFF代表一个颜色点,高5位是红色,中间6位是绿色,低5位是蓝色。屏幕刷新时,LVGL只要把数组首地址告诉底层驱动的绘制函数,DMA或者刷屏循环就能把这一块数据直接搬运到显存里,中间没有任何解码开销。
2.2 为什么颜色位数决定Flash占用和画质
这是转换参数里最关键的一个决策点。同样一张图片,用ARGB8888格式转换,每个像素要占4字节;用RGB565格式,每个像素仅占2字节;如果是黑白图标,还能用1bit格式,省到极致。内存占用直接缩减为原来的八分之一。
但代价也肉眼可见。ARGB8888能表示1670万种颜色,色彩过渡细腻,适合照片类的全彩图;RGB565能表示65536种颜色,对于大多数UI图标、按钮、背景色块来说,肉眼几乎分辨不出差别。1bit格式就只有纯黑和纯白,只适合单色图标。
我见过不少新手一上来就选ARGB8888,理由是“画质最好”,结果一张800x480的全屏背景图,转换出来的数组有1.5MB。中端MCU的Flash总共才1MB或2MB,一张图就快占满了。实际项目中,背景图用RGB565是底线,图标用RGB565或者更小的格式,画质损失完全在可接受范围内,而存储却能省一大半。
在LVGL 8及之后的版本里,官方还引入了一种自动压缩机制,叫LV_COLOR_DEPTH配合LV_IMG_CF_INDEXED索引格式。索引格式会提取图片中出现频率最高的颜色组成调色板,每个像素只存调色板索引号,适合同一张图颜色数量有限的场景。转换工具里一般有对应选项,但需要开发者在代码初始化时设置好调色板,稍微多一步操作,好处是存储还能再压缩不少。
2.3 Alpha透明通道:PNG的透明背景在C数组里怎么表达
另一个高频困惑是透明背景。在电脑上,PNG图片可以带着透明的棋盘格背景,放到深色界面上也没有违和感。但转换成C数组后,数组里每个像素点的数值是死的,它本身不带有任何“通透”的属性。
如果你要在LVGL里实现透明效果,必须使用带Alpha通道的格式,也就是ARGB8888,或者LVGL专有的LV_IMG_CF_TRUE_COLOR_ALPHA。这种格式的数组里,每个像素点除了RGB三个通道的颜色值外,还会记录这个像素点的透明度。透明度为0的像素点,在屏幕刷新时就会和背景色做混合运算,从而实现“透明”的视觉效果。
如果不带Alpha通道的RGB565图片被放到非纯色背景上,边缘附近原本半透明的像素会被工具转成某个固定的颜色值,表现出来就是一圈难看的白色或黑色方块,也就是俗称的“黑底”或“白底”问题。
所以,在转换图标前,先想清楚两个问题:这个图标会放在什么颜色的背景上?背景是否会变化?如果你要把它放在纯白背景上,那直接转成RGB565,背景色设为纯白,视觉上就没问题,还能省一半存储;如果背景会变,或者需要任意叠加,那就必须选带Alpha的格式。
颜色数量少、形状简单的图形,切换成索引格式后画质几乎无损,但存储可以从几十KB降到几KB。 推荐的做法是:图标类素材用RGB565或索引格式控制体积,全屏照片类背景用RGB565,特殊需要的覆盖层动画再用ARGB8888。这样一套组合下来,存储和画质能同时兼顾。
3. 实操详解:用官方在线转换器把PNG变成.h文件
讲完原理,下面进入动手环节。工具我推荐先用官方的在线图片转换器。为什么?因为它是官方维护的,与LVGL每个版本的格式定义严格同步,兼容性最好,而且无需安装,浏览器打开就能用。相比第三方工具可能存在的版本代差,官方工具少了很多“格式不识别”的坑。下面用一次完整转换流程来演示,顺便把容易踩坑的细节标出来。
3.1 Convert步骤:从选择图片到输出文件的完整配置
浏览器打开官方转换器页面后,操作区域分成几个区块。第一块是图片拖拽区,点击或直接拖入你的PNG文件。建议源图片最好是正方形且边界干净,透明区域明确,这样可以避免后续裁切和留边问题。
接着选择输出格式,一般有C array和Binary两种。嵌入式工程里选C array,也就是生成C数组文件。下面的Color format选项,我建议首次上手直接选True color with alpha或True color。前者会生成lv_img_dsc_t结构体加像素数组,适合需要透明通道的图标;后者是纯RGB,适合不透明元素。
Color depth选项决定每个像素用多少位。在大多数LVGL工程里,如果你在lv_conf.h中设置了LV_COLOR_DEPTH 16,那么这里就选16,与工程保持一致。如果选了32但工程是16位色深,显示出来颜色会出现明显错乱。这一点务必注意。
最后一个容易被忽略的是Output format下拉选择。C数组的表达方式有两种,一种是平铺每个像素数值,另一种是按LVGL的lv_img_dsc_t结构体方式输出,附带宽高、色深、数据指针等元信息。LVGL 8以上版本建议选后者,因为代码里可以通过lv_img_set_src直接绑定整个结构体变量,使用起来非常方便。
3.2 生成文件后如何嵌入到你的LVGL工程
转换完成后,工具会给出一个.c文件和一个.h文件。下载下来后,把.c文件加入你的工程源码目录,编译系统需要能搜到它;.h文件放入头文件搜索路径。在需要显示该图片的C文件中引入头文件,然后按下面方式使用:
#include "img_icon.h" lv_obj_t * img = lv_img_create(lv_scr_act()); lv_img_set_src(img, &img_icon); lv_obj_center(img);img_icon就是转换工具生成的结构体变量名,通常和你的图片名一致。短短三行代码,图片就能显示在屏幕上了。
如果你用的是PlatformIO的ESP32工程,直接把.c和.h放到src或include目录,重新编译即可。如果是STM32CubeIDE,把.c文件加入Application/User目录,头文件路径在编译配置里加一下。整个过程不涉及额外库依赖,本质上就是多编译了几个C文件而已。
前置条件是,你的lv_conf.h里LV_USE_IMG功能默认是开启的(默认就是开的),并且主工具有LVGL库正常初始化。
3.3 在线工具的局限:大图、批量与离线环境
在线转换器虽然上手速度快,但它的局限也很明显。一是它一次只能处理单张图片,界面上有几个素材就要反复操作几次,效率低。二是页面加载大图时偶尔会卡死,我遇到过5MB以上的PNG拖进去后浏览器直接无响应,只能压缩后再试。三是某些内网开发环境无法访问外网,在线工具直接不可用。
这时候,就需要命令行工具或GUI Guider出场了。
我之前在公司内网环境搭建LVGL项目时,就遇到过在线工具完全无法访问的情况。后来从同事那边知道了Gui Guider,这个工具是图形化界面设计工具,特别适合整体UI编排场景,图片素材可以通过它内置的资源管理直接导入,自动完成转换和编译资源打包。它的转换逻辑和官方在线工具一致,实际上来自同一套底层代码,生成的格式可以无缝接入LVGL工程的。
但Gui Guider属于相对重量级的工具,对电脑配置有一定要求,也不是拿来即走的轻量方案。如果你只是想快速拿到一个C数组文件,它反而显得有些大材小用。
4. 用命令行工具实现批量转换和自动化
当项目进入中期,素材越来越多,UI版本更替频繁时,再去网页上一个个点就很不现实了。比如我手头有个30多个图标和背景的项目,每次产品经理更新素材,重新命名、重新转换、重新替换文件,靠手动操作简直是一场灾难。这个阶段,就需要脚本化的批量转换工具。
4.1 使用官方Python脚本实现批处理
LVGL官方在GitHub上维护了一套图像转换脚本,底层基于Python和Pillow库。你可以通过pip install Pillow安装依赖后直接调用脚本进行转换。
基础用法:
python script.py -i input.png -o output.c -d 16 -f true_color_alpha常用参数含义:
-i:输入图片路径,支持PNG、JPG、BMP等常见格式-o:输出文件前缀-d:颜色深度,填16或32,必须与工程LV_COLOR_DEPTH一致-f:输出格式,常用true_color、true_color_alpha、indexed_1、indexed_2、indexed_4、indexed_8-s:手动缩放到指定尺寸,比如-s 128 128
批量处理的话,一个简单的shell脚本就能把整个目录下的.png文件全部转换:
#!/bin/bash for f in images/*.png; do echo "convert: $f" python script.py -i "$f" -o "output/$(basename "$f" .png)" -d 16 -f true_color_alpha done有了这层自动化,素材更新后只跑一次脚本,所有C数组文件自动重新生成,省下的时间非常可观。
4.2 自写脚本的注意点:透明边缘和色深匹配
我在用脚本批量转换过程中,遇到最多的坑有两个,提前说出来帮你避开。
第一个是透明边缘。设计交给我的图标,有些外围有一圈完全透明的像素,视觉上没问题,转成C数组后那部分像素还会继续占据存储,透明区域越大浪费越多。解决方法是转换前用Pillow做一次trim操作,把四周透明边界裁掉,再进入转换流程。
第二个是色深匹配。不同的-d参数最终生成的lv_color_t类型声明会不一样。如果脚本里用了16,工程里LV_COLOR_DEPTH也必须是16,两者一旦不一致,编译会报数据类型不匹配的错误,或者显示颜色完全错乱。我建议在脚本开头就把色深定义为固定值,同时把lv_conf.h里对应的宏也统一,避免日后不同素材被不同参数转换而被混淆。
4.3 二进制流数据与外部Flash结合
如果你的图片总存储超过1MB,单片机的内部Flash可能就装不下了。这时候,C数组这个方案的天花板就出现了。
一个替代思路是,把图片转换为纯二进制数据文件,通过离线烧录工具写入外部SPI Flash或SD卡,LVGL运行时不直接引用C数组,而是通过文件系统读取。LVGL支持lv_img_set_src传入文件路径字符串(配合文件系统驱动),显示时按需从外部存储读取数据。这个方案的前期配置复杂度会高不少,但总存储空间几乎不受限。
很多联网硬件产品在量产时会采用这种思路,动态下载UI资源包到外部Flash,代码包保持不变,实现UI远程更新。初始阶段用C数组把功能跑通,架构上不要堵死后续切换的路径就可以了。
5. 从C数组到屏幕显示:工程接入与常见坑排查
图片资源转换好了,代码也写完了,但屏幕显示效果却可能出各种幺蛾子。这里把最常见的几类问题和对应的排查思路整理出来。
5.1 图片显示花屏或颜色怪异的原因定位
现象一:图片能显示出来,但整体颜色像底片一样,蓝色变橙色、红色变绿色,完全对不上。
这类问题九成出在色深不匹配。LCD屏是16位色,LVGL工程也设置16位色,但转换器选了32位;或者反过来。LVGL渲染图像的像素格式与屏幕驱动不一致,颜色通道就会被错位解析。排查方法:确认转换参数Color depth和lv_conf.h中的LV_COLOR_DEPTH完全一致,重新转换图片。显示屏驱动的写点格式需要与LV_COLOR_16BIT_SWAP宏相互配合,这几个配置统一之后,花屏基本能消除。
现象二:图片位置出现倾斜、拉丝、噪点,像数据错位。
这通常是数组长度与图片实际尺寸不匹配导致的。可能是在转换器中设置了缩放,但LVGL侧的lv_img_set_zoom参数设错,也可能是制作素材时分辨率中途改过,但结构体里记录宽高的值没有同步。排查时打印一下生成的lv_img_dsc_t结构体,实际对比header.w和header.h与图片宽高是否一致,不一致则重新转换。
现象三:透明区域变成了黑色或白色。
这在前面原理部分已经说过,就是选了不带Alpha通道的格式,半透明和透明像素被强制填成了实色。换用true_color_alpha格式重新转换即可。另外注意,LVGL 8以上版本,带Alpha格式的图片在显示时对背景颜色也有要求,如果目标容器背景是黑色,透明区域天然呈现黑色,很容易误判为图片转换问题。测试时把容器背景改成白色或粉色,就很容易分辨。
5.2 存储超出Flash空间:计算、压缩与换外置存储
MCU的Flash空间是硬约束。模型:一张RGB565的图片,存储大小约等于宽 x 高 x 2字节。比如一个240x240的图标,就是240 x 240 x 2 = 112.5KB。800x480的全屏背景,就是800 x 480 x 2 = 750KB。这个计算方式可以帮你在一开始就估算总量。
如果超出Flash,可以按优先级依次处理:
- 能矢量化的图标尽量用LVGL的绘图API手绘,不加载位图,存储占用直接变为0;
- 颜色较少的图标改用索引格式,能缩数倍;
- 背景图压缩质量降低后用RGB565;
- 最后才考虑外部存储方案,比如把大图放SD卡。
5.3 LVGL 9用户需要注意的偏移:结构体与API变化
如果你直接用LVGL 9创建新工程,会注意到图片描述结构体和加载接口和LVGL 8有了明显变动。LVGL 9中lv_img_dsc_t引入了更多参数,且部分字段改名,比如header.always_zero被替换,data_size的类型也调整了。官方在线转换器最新版已适配LVGL 9,但如果你使用的是旧教程或第三方工具的代码模板,拷贝过来可能会编译报错。
遇到这种情况,最省事的方式是用LVGL 9配套的官方转换工具重新生成一遍文件,然后对比新旧代码的差异。一般只需要改结构体赋值那一小段,图片本身的数据内容并不受影响。
6. 日常维护中的建议与个人总结
图片转换看起来是个小环节,但在实际项目里,我吃过不少亏,这里分享几条个人经验,能帮忙避开一些弯路。
关于素材源文件的管理,最好在项目目录下专门建一个raw_images文件夹,存放原始PNG和设计源文件,generated文件夹放转换的C文件,脚本参数也一并提交到版本库。这样日后素材改了,能一键批量重生,也不会出现“代码里用的素材和设计稿对不上”的混乱情况。
关于图片的尺寸预处理,在进入转换工具前,先把尺寸缩放到实际显示分辨率。LVGL本身提供lv_img_set_zoom做缩放,但那是运行时缩放,会消耗CPU和额外的RAM,在低主频MCU上界面容易卡顿,尽量在素材阶段时就定好尺寸。
关于版本一致性,这里再啰嗦一遍,LVGL主版本之间不保证图片资源格式的兼容性。升级LVGL主版本后,建议把所有图片重新转换一遍,不要省这一步。
图片转C数组这套机制,看起来不如复杂动画炫酷,却是所有LVGL界面稳定流畅的基石。把原理摸透,参数调对,不仅图片显示不会出问题,存储和运行的自然也能被控制在一个健康范围内。之后你再去做复杂UI设计时,会发现图片资源这部分基本不需要再操心了。
本文还有配套的精品资源,点击获取