图片(Images),图像可以是存储位图本身和一些元数据的文件或变量。
储存图片
我们可以将图像存储在两个位置
- 作为内部存储器(RAM或ROM)中的变量
- 作为文件
变量
内部存储在变量中的图像主要由具有以下字段的lv_img_dsc_t结构组成:
- 标头
- cf 颜色格式。见下文
- w 宽度(以像素为单位)(<= 2048)
- h 高度(以像素为单位)(<= 2048)
- 始终为零 3位需要始终为零
- reserved 保留以备将来使用
- 指向存储图像本身的数组的数据指针
- data_size 数据的长度(以字节为单位)
这些通常作为C文件存储在项目中。它们像其他任何常量数据一样链接到生成的可执行文件中。
文件
要处理文件,您需要将驱动器添加到LVGL。简而言之,驱动器是在LVGL中注册的用于文件操作的功能的集合(打开,读取,关闭等)。您可以向标准文件系统(SD卡上的FAT32)添加接口,也可以创建简单的文件系统以从SPI闪存读取数据。在每种情况下,驱动器都只是一种将数据读取和/或写入内存的抽象。请参阅文件系统部分以了解更多信息。
存储为文件的图像未链接到生成的可执行文件中,并且在绘制之前必须将其读取到RAM中。结果,它们不像可变图像那样对资源友好。但是,它们更容易替换,而无需重新编译主程序。
色彩格式
lvgl支持多种内置颜色格式:
- LV_IMG_CF_TRUE_COLOR 只需存储RGB颜色(使用LVGL配置的任何颜色深度)。
- LV_IMG_CF_TRUE_COLOR_ALPHA 与LV_IMG_CF_TRUE_COLOR类似,但它还会为每个像素添加一个alpha(透明度)字节。
- LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED 与LV_IMG_CF_TRUE_COLOR类似,但是如果像素具有LV_COLOR_TRANSP(在lv_conf.h中设置)颜色,则该像素将是透明的。
- LV_IMG_CF_INDEXED_1/2/4/8BIT 使用2、4、16或256色调色板,并以1、2、4或8位存储每个像素。
- LV_IMG_CF_ALPHA_1/2/4/8BIT 仅将Alpha值存储在1、2、4或8位上。 像素采用style.image.color和设置的不透明度的颜色。源图像必须是Alpha通道。这对于类似于字体的位图是理想的(整个图像是一种颜色,但是您希望能够更改它)。
LV_IMG_CF_TRUE_COLOR图像的字节按以下顺序存储。
对于32位色深:
- Byte 0: 蓝色(Blue)
- Byte 1: 绿色(Green)
- Byte 2: 红色(Red)
- Byte 3: 透明度(Alpha)
对于16位色深:
- Byte 0: 绿色(Green )3位低位,蓝色(Blue)5位
- Byte 1: 红色(Red)5位,绿色(Green )3位
- Byte 2: 透明度(Alpha)字节(仅适用于LV_IMG_CF_TRUE_COLOR_ALPHA)
对于8位色深:
- Byte 0: Red 3 bit, Green 3 bit, Blue 2 bit
- Byte 2: Alpha byte (only with LV_IMG_CF_TRUE_COLOR_ALPHA)
我们可以将图像以Raw格式存储,以指示它不是内置的颜色格式,并且需要使用外部Image解码器来解码图像。
- LV_IMG_CF_RAW 表示基本的原始图像(例如PNG或JPG图像)。
- LV_IMG_CF_RAW_ALPHA 表示图像具有Alpha,并且为每个像素添加了Alpha字节。
- LV_IMG_CF_RAW_CHROME_KEYED 表示该图像已按chrome键锁定,如上文LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED中所述。
添加和使用图像
可以通过下面两种方式将图像添加到LVGL:
- 使用在线转换器
- 手动创建图像
在线转换器
在线图像转换器可在这里找到:https://lvgl.io/tools/imageconverter
通过在线转换器可以很容易地将图像转换并添加到LVGL。
- 准备好要转换的BMP,PNG或JPG图像。
- 给图像起一个将在LVGL中使用的名称。
- 选择颜色格式。
- 选择所需的图像类型。选择二进制文件将生成一个.bin文件,该文件必须单独存储并使用文件支持进行读取。选择一个变量将生成一个标准的C文件,该文件可以链接到您的项目中。
- 点击转换按钮。转换完成后,浏览器将自动下载结果文件。
在转换器C数组(变量)中,所有颜色深度(1、8、16或32)的位图都包含在C文件中,但是只有与lv_conf.h中的LV_COLOR_DEPTH匹配的颜色深度才会实际链接到生成的可执行文件。
如果是二进制文件,则需要指定所需的颜色格式:
- RGB332用于8位色深
- RGB565用于16位色深
- RGB565交换为16位颜色深度(交换了两个字节)
- RGB888用于32位色深
手动创建图像
如果要在运行时生成图像,则可以制作图像变量以使用LVGL显示它。例如:
uint8_t my_img_data[] = {0x00, 0x01, 0x02, ...};
static lv_img_dsc_t my_img_dsc = {
.header.always_zero = 0,
.header.w = 80,
.header.h = 60,
.data_size = 80 * 60 * LV_COLOR_DEPTH / 8,
.header.cf = LV_IMG_CF_TRUE_COLOR, /*Set the color format*/
.data = my_img_data,
};
如果颜色格式为LV_IMG_CF_TRUE_COLOR_ALPHA,则可以将data_size设置为80 60 LV_IMG_PX_SIZE_ALPHA_BYTE。
在运行时创建和显示图像的另一个(可能更简单)的选项是使用Canvas对象。
使用图片
在LVGL中使用图像的最简单方法是使用lv_img对象显示图像:
lv_obj_t * icon = lv_img_create(lv_scr_act(), NULL);
/*From variable*/
lv_img_set_src(icon, &my_icon_dsc);
/*From file*/
lv_img_set_src(icon, "S:my_icon.bin");
如果图像是使用在线转换器转换的,则应使用LV_IMG_DECLARE(my_icon_dsc)在文件中声明要使用它的位置。
图像解码器
如您在“颜色格式”部分中所见,LVGL支持多种内置图像格式。在许多情况下,这些都是您所需要的。 LVGL不直接支持通用图像格式,例如PNG或JPG。
要处理非内置图像格式,您需要使用外部库,并通过图像解码器接口将它们附加到LVGL。
图像解码器包含4个回调:
- info 获取有关图像的一些基本信息(宽度,高度和颜色格式)。
- open 打开图像:存储解码后的图像或将其设置为NULL以指示可以逐行读取图像。
- read 如果打开未完全打开图像,则此功能应从给定位置提供一些解码数据(最多1行)。
- close 关闭打开的图像,释放分配的资源。
自定义图像格式
创建自定义图像的最简单方法是使用在线图像转换器,并将Raw,Raw设置为alpha或chrome键设置为Raw。它只会占用您上传的二进制文件的每个字节,并将其写为图像“位图”。然后,您需要连接一个图像解码器,它将解析该位图并生成真实的可渲染位图。
header.cf将分别为LV_IMG_CF_RAW,LV_IMG_CF_RAW_ALPHA或LV_IMG_CF_RAW_CHROME_KEYED。您应该根据需要选择正确的格式:完全不透明的图像,使用Alpha通道或使用色度键。
解码后,原始格式被库视为真彩色。换句话说,图像解码器必须根据[#color-formats](颜色格式)部分中所述的格式将Raw图像解码为True color。
如果要创建自定义图像,则应使用LV_IMG_CF_USER_ENCODED_0..7颜色格式。但是,该库只能以True color格式(或Raw,但最终应该以True color格式)绘制图像。 因此,库不知道LV_IMG_CF_USERENCODED …格式,因此应从[#color-formats](颜色格式)部分将其解码为已知格式之一。 可以先将图像解码为非真彩色格式,例如LV_IMG_INDEXED_4BITS,然后调用内置的解码器函数将其转换为真彩色。
对于用户编码格式,应根据新格式更改打开功能(dsc-> header.cf)中的颜色格式。
注册图像解码器
这是使LVGL与PNG图像配合使用的示例。
首先,需要先创建一个新的图像解码器并设置一些功能来打开/关闭PNG文件。它看起来应该像这样:
/*Create a new decoder and register functions */
lv_img_decoder_t * dec = lv_img_decoder_create();
lv_img_decoder_set_info_cb(dec, decoder_info);
lv_img_decoder_set_open_cb(dec, decoder_open);
lv_img_decoder_set_close_cb(dec, decoder_close);
/**
* Get info about a PNG image
* @param decoder pointer to the decoder where this function belongs
* @param src can be file name or pointer to a C array
* @param header store the info here
* @return LV_RES_OK: no error; LV_RES_INV: can't get the info
*/
static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header)
{
/*Check whether the type `src` is known by the decoder*/
if(is_png(src) == false) return LV_RES_INV;
/* Read the PNG header and find `width` and `height` */
...
header->cf = LV_IMG_CF_RAW_ALPHA;
header->w = width;
header->h = height;
}
/**
* Open a PNG image and return the decided image
* @param decoder pointer to the decoder where this function belongs
* @param dsc pointer to a descriptor which describes this decoding session
* @return LV_RES_OK: no error; LV_RES_INV: can't get the info
*/
static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc)
{
/*Check whether the type `src` is known by the decoder*/
if(is_png(src) == false) return LV_RES_INV;
/*Decode and store the image. If `dsc->img_data` is `NULL`, the `read_line` function will be called to get the image data line-by-line*/
dsc->img_data = my_png_decoder(src);
/*Change the color format if required. For PNG usually 'Raw' is fine*/
dsc->header.cf = LV_IMG_CF_...
/*Call a built in decoder function if required. It's not required if`my_png_decoder` opened the image in true color format.*/
lv_res_t res = lv_img_decoder_built_in_open(decoder, dsc);
return res;
}
/**
* Decode `len` pixels starting from the given `x`, `y` coordinates and store them in `buf`.
* Required only if the "open" function can't open the whole decoded pixel array. (dsc->img_data == NULL)
* @param decoder pointer to the decoder the function associated with
* @param dsc pointer to decoder descriptor
* @param x start x coordinate
* @param y start y coordinate
* @param len number of pixels to decode
* @param buf a buffer to store the decoded pixels
* @return LV_RES_OK: ok; LV_RES_INV: failed
*/
lv_res_t decoder_built_in_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, lv_coord_t x,
lv_coord_t y, lv_coord_t len, uint8_t * buf)
{
/*With PNG it's usually not required*/
/*Copy `len` pixels from `x` and `y` coordinates in True color format to `buf` */
}
/**
* Free the allocated resources
* @param decoder pointer to the decoder where this function belongs
* @param dsc pointer to a descriptor which describes this decoding session
*/
static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc)
{
/*Free all allocated data*/
/*Call the built-in close function if the built-in open/read_line was used*/
lv_img_decoder_built_in_close(decoder, dsc);
}
因此,总而言之:
在解码器信息(decoder_info)中,您应该收集有关图像的一些基本信息,并将其存储在标头中。
在decoder_open中,应尝试打开dsc-> src指向的图像源。它的类型已经在dsc-> src_type == LV_IMG_SRC_FILE / VARIABLE中。如果解码器不支持此格式/类型,请返回LV_RES_INV。但是,如果可以打开图像,则应在dsc-> img_data中设置指向已解码的真彩色图像的指针。如果格式已知,但您不想在图像解码时(例如没有内存)将dsc-> img_data = NULL设置为调用read_line以获取像素。
在decoder_close中,您应该释放所有分配的资源。
coder_read是可选的。解码整个图像需要额外的内存和一些计算开销。但是,如果可以解码图像的一行而不解码整个图像,则可以节省内存和时间。为了表明这一点,应该使用行读取功能,在打开功能中设置dsc-> img_data = NULL。
手动使用图像解码器
如果您尝试绘制原始图像(即使用lv_img对象),LVGL将自动使用注册的图像解码器,但是您也可以手动使用它们。创建一个lv_img_decoder_dsc_t变量来描述解码会话,然后调用lv_img_decoder_open()。
lv_res_t res;
lv_img_decoder_dsc_t dsc;
res = lv_img_decoder_open(&dsc, &my_img_dsc, LV_COLOR_WHITE);
if(res == LV_RES_OK) {
/*Do something with `dsc->img_data`*/
lv_img_decoder_close(&dsc);
}
图像缓存
有时打开图像需要很多时间。连续解码PNG图像或从速度较慢的外部存储器加载图像会效率低下,并且不利于用户体验。
因此,LVGL缓存给定数量的图像。缓存意味着一些图像将保持打开状态,因此LVGL可以从dsc-> img_data快速访问它们,而无需再次对其进行解码。
当然,缓存图像会占用大量资源,因为它使用更多的RAM(用于存储解码的图像)。LVGL尝试尽可能地优化流程(请参见下文),但是您仍然需要评估这是否对您的平台有利。如果您有一个深度嵌入的目标,可以从相对较快的存储介质中解码小图像,则不建议使用图像缓存。
缓存大小
可以在lv_conf.h的LV_IMG_CACHE_DEF_SIZE中定义高速缓存条目的数量。默认值为1,因此只有最近使用的图像将保持打开状态。
可以在运行时使用lv_img_cache_set_size(entry_num)更改缓存的大小。
图片价值
当使用的图像多于缓存条目时,LVGL无法缓存所有图像。而是,库将关闭其中一个缓存的图像(以释放空间)。
为了决定关闭哪个图像,LVGL使用它先前对打开图像所花费的时间进行的测量。保存打开速度较慢的图像的高速缓存条目被认为更有价值,并尽可能长时间地保留在高速缓存中。
如果要或需要覆盖LVGL的测量值,则可以在dsc-> time_to_open = time_ms的解码器打开功能中手动设置打开时间的值,以提供更高或更低的值。 (将其保留以让LVGL对其进行设置。)
每个缓存条目都有一个“生命”值。每次通过缓存打开图像时,所有条目的寿命都会减少,从而使它们更旧。使用缓存的图像时,其使用寿命会随着打开值的时间增加而增加,从而使其更加生动。
如果缓存中没有更多空间,则始终关闭寿命最小的条目。
内存使用情况
请注意,缓存的图像可能会不断消耗内存。例如,如果缓存了3个PNG图像,则它们在打开时将消耗内存。
因此,用户有责任确保有足够的RAM来缓存,即使同时是最大的图像。
清理缓存
假设您已将PNG图片加载到lv_img_dsc_t my_png变量中,并在lv_img对象中使用了它。如果图像已被缓存,然后您更改了基础PNG文件,则需要通知LVGL重新缓存图像。否则,没有简单的方法可以检测到基础文件已更改并且LVGL仍会绘制旧图像。
为此,请使用lv_img_cache_invalidate_src(&my_png)。如果将NULL作为参数传递,则将清除整个缓存。