图片(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。

  1. 准备好要转换的BMP,PNG或JPG图像。

  2. 给图像起一个将在LVGL中使用的名称。

  3. 选择颜色格式。

  4. 选择所需的图像类型。选择二进制文件将生成一个.bin文件,该文件必须单独存储并使用文件支持进行读取。选择一个变量将生成一个标准的C文件,该文件可以链接到您的项目中。

  5. 点击转换按钮。转换完成后,浏览器将自动下载结果文件。

在转换器C数组(变量)中,所有颜色深度(1、8、16或32)的位图都包含在C文件中,但是只有与lv_conf.h中的LV_COLOR_DEPTH匹配的颜色深度才会实际链接到生成的可执行文件。

如果是二进制文件,则需要指定所需的颜色格式:

  • RGB332用于8位色深

  • RGB565用于16位色深

  • RGB565交换为16位颜色深度(交换了两个字节)

  • RGB888用于32位色深

手动创建图像

如果要在运行时生成图像,则可以制作图像变量以使用LVGL显示它。例如:

 1    uint8_t my_img_data[] = {0x00, 0x01, 0x02, ...};
 2
 3    static lv_img_dsc_t my_img_dsc = {
 4            .header.always_zero = 0,
 5            .header.w = 80,
 6            .header.h = 60,
 7            .data_size = 80 * 60 * LV_COLOR_DEPTH / 8,
 8            .header.cf = LV_IMG_CF_TRUE_COLOR,          /*Set the color format*/
 9            .data = my_img_data,
10    };

如果颜色格式为LV_IMG_CF_TRUE_COLOR_ALPHA,则可以将data_size设置为80 * 60 * LV_IMG_PX_SIZE_ALPHA_BYTE。

在运行时创建和显示图像的另一个(可能更简单)的选项是使用Canvas对象。

使用图片

在LVGL中使用图像的最简单方法是使用lv_img对象显示图像:

1    lv_obj_t * icon = lv_img_create(lv_scr_act(), NULL);
2
3    /*From variable*/
4    lv_img_set_src(icon, &my_icon_dsc);
5
6    /*From file*/
7    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_USER_ENCODED _…格式,因此应从[#color-formats](颜色格式)部分将其解码为已知格式之一。 可以先将图像解码为非真彩色格式,例如LV_IMG_INDEXED_4BITS,然后调用内置的解码器函数将其转换为真彩色。

对于用户编码格式,应根据新格式更改打开功能(dsc-> header.cf)中的颜色格式。

注册图像解码器

这是使LVGL与PNG图像配合使用的示例。

首先,需要先创建一个新的图像解码器并设置一些功能来打开/关闭PNG文件。它看起来应该像这样:

 1    /*Create a new decoder and register functions */
 2    lv_img_decoder_t * dec = lv_img_decoder_create();
 3    lv_img_decoder_set_info_cb(dec, decoder_info);
 4    lv_img_decoder_set_open_cb(dec, decoder_open);
 5    lv_img_decoder_set_close_cb(dec, decoder_close);
 6
 7    /**
 8     * Get info about a PNG image
 9     * @param decoder pointer to the decoder where this function belongs
10     * @param src can be file name or pointer to a C array
11     * @param header store the info here
12     * @return LV_RES_OK: no error; LV_RES_INV: can't get the info
13     */
14    static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header)
15    {
16      /*Check whether the type `src` is known by the decoder*/
17      if(is_png(src) == false) return LV_RES_INV;
18
19      /* Read the PNG header and find `width` and `height` */
20      ...
21
22      header->cf = LV_IMG_CF_RAW_ALPHA;
23      header->w = width;
24      header->h = height;
25    }
26
27    /**
28     * Open a PNG image and return the decided image
29     * @param decoder pointer to the decoder where this function belongs
30     * @param dsc pointer to a descriptor which describes this decoding session
31     * @return LV_RES_OK: no error; LV_RES_INV: can't get the info
32     */
33    static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc)
34    {
35
36      /*Check whether the type `src` is known by the decoder*/
37      if(is_png(src) == false) return LV_RES_INV;
38
39      /*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*/
40      dsc->img_data = my_png_decoder(src);
41
42      /*Change the color format if required. For PNG usually 'Raw' is fine*/
43      dsc->header.cf = LV_IMG_CF_...
44
45      /*Call a built in decoder function if required. It's not required if`my_png_decoder` opened the image in true color format.*/
46      lv_res_t res = lv_img_decoder_built_in_open(decoder, dsc);
47
48      return res;
49    }
50
51    /**
52     * Decode `len` pixels starting from the given `x`, `y` coordinates and store them in `buf`.
53     * Required only if the "open" function can't open the whole decoded pixel array. (dsc->img_data == NULL)
54     * @param decoder pointer to the decoder the function associated with
55     * @param dsc pointer to decoder descriptor
56     * @param x start x coordinate
57     * @param y start y coordinate
58     * @param len number of pixels to decode
59     * @param buf a buffer to store the decoded pixels
60     * @return LV_RES_OK: ok; LV_RES_INV: failed
61     */
62    lv_res_t decoder_built_in_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, lv_coord_t x,
63                                                                                                      lv_coord_t y, lv_coord_t len, uint8_t * buf)
64    {
65       /*With PNG it's usually not required*/
66
67       /*Copy `len` pixels from `x` and `y` coordinates in True color format to `buf` */
68
69    }
70
71    /**
72     * Free the allocated resources
73     * @param decoder pointer to the decoder where this function belongs
74     * @param dsc pointer to a descriptor which describes this decoding session
75     */
76    static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc)
77    {
78      /*Free all allocated data*/
79
80      /*Call the built-in close function if the built-in open/read_line was used*/
81      lv_img_decoder_built_in_close(decoder, dsc);
82
83    }

因此,总而言之:

在解码器信息(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()。

1    lv_res_t res;
2    lv_img_decoder_dsc_t dsc;
3    res = lv_img_decoder_open(&dsc, &my_img_dsc, LV_COLOR_WHITE);
4
5    if(res == LV_RES_OK) {
6      /*Do something with `dsc->img_data`*/
7      lv_img_decoder_close(&dsc);
8    }

图像缓存

有时打开图像需要很多时间。连续解码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作为参数传递,则将清除整个缓存。

相关API

TODO