图像可以是一个文件,也可以是一个存储了bitmap数据本身以及其他元数据的变量。

Store images

可以在两个地方存储图像:

  • 内部存储器(RAM或者ROM)中的变量
  • 文件

Variables

存储在变量中的图像主要由有以下字段的lv_img_dsc_t结构体组成:

  • header
    • cf 颜色格式
    • w 以像素为单位的宽度
    • h 以像素为单位的高度
    • 总是0的三个比特
    • reserved 保留字段
  • data 指向存储图像数据的数组的指针
  • data_size data字段的长度,单位字节

这些通常以C源代码的形式保存在项目里面。他们会像其他常量数据一样链接到最终的可执行文件中。

Files

为了使用文件,需要向LVGL添加驱动。简而言之,驱动是一组注册在LVGL中,执行打开、读、关闭等功能的函数。可以给标准文件系统(SD card上的FAT32)添加一个标准接口,也可以实现你自己的从SPI Flash中读数据的简单文件系统。不管是那种情况,文件驱动就是从存储器进行读写操作的抽象。前往File system章节查看详情。

存储在文件中的图像没有链接到最终的可执行文件,在绘制之前需要读到内存中。所以这种方式与变量相比,在资源占用方面并没有优势。但是这种方式可以在不用重新编译程序的情况下替换资源。

Color fomats

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_conf.h中LV_COLOR_TRANSP定义的颜色,这个像素就变成透明的。
  • LV_IMG_CF_INDEXED_1/2/4/8BIT 使用2、4、16或者256色的调色板,用1、2、4或者8位来存储像素。
  • LV_IMG_CF_ALPHA_1/2/4/8BIT 用1、2、4或者8位只存储Alpha通道。像素的颜色是style.image.color,不透明度为Alpha通道的值。图像原始数据只含Alpha通道。这对于与字体类似的bitmap很有用(整个图像只有单色,且颜色可变)。

LV_IMG_CF_TRUE_COLOR颜色格式的图像的字节按以下顺序存储:

  • 对于32位颜色深度:
    • Byte 0: Blue
    • Byte 1: Green
    • Byte 2: Red
    • Byte 3: Alpha
  • 对于16位颜色深度:
    • Byte 0: Green 3 lower bit, Blue 5 bit
    • Byte 1: Red 5 bit, Green 3 higher bit
    • Byte 2: Alpha byte (only with 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格式存储图像。其颜色格式不是内置格式,需要外部图像解码器来解码图像。

  • LV_IMG_CF_RAW 表示基本Raw图像(如PNG或者JPG图像)
  • LV_IMG_CF_RAW_ALPHA 表示图像有Alpha通道并且每个像素都添加了一个字节的Alpha通道
  • LV_IMG_CF_RAW_CHROME_KEYED 类似上面的LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED

Add and use images

有两种方法能将图像添加到LVGL:

  • 使用在线转换器
  • 手动创建图像

Online converter

在线图像转换器的地址是:https://lvgl.io/tools/imageconverter。

按照下面的步骤可使用在线转换器将图像添加到LVGL。

  1. 首先需要一个BMP、PNG或者JPG格式的图像文件。
  2. 给图像命名。
  3. 选择颜色格式。
  4. 选择图像类型。选择binary,将会生成一个bin文件。这个文件需要分开存储并使用文件系统来读取。选择variable会生成一个标准的C文件,这个文件可以链接到项目。
  5. 点击Convert按钮。一旦转换完成,浏览器将自动下载结果文件。

在转换后的C数组中(variable)包含各种颜色深度的bitmap,但是只有和lv_conf.h中的LV_COLOR_DEPTH匹配的颜色深度会真正链接到最终的可执行文件。

对于bin文件的方式,需要指定需要的颜色格式:

  • RGB332 8位颜色深度
  • RGB565 16位颜色深度
  • RGB565 Swap 16为颜色深度(交换字节序)
  • RGB888 32位颜色深度

Manually create an image

如果是运行时创建图像,可以用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。

Use images

在LVGL中使用图像的最简单的方法是用于lv_img Object。

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)声明。

Image decoder

如在Color fomats章节中所述,LVGL支持数种内置的图像格式。在多数情况下,这些已经足够使用。然而,LVGL并不直接支持通用图像格式如PNG或者JPG。

为了处理非内置图像格式,需要使用外部库并通过Image Decoder接口附加到LVGL中。

Image Decoder包含四个回调:

  • info 获取关于图像的一些基本信息(宽度、高度、颜色格式)。
  • open 打开图像。要么存储解码后的图像,要么设置其为NULL以表明图像可以一行一行的读取。
  • read 如果open没有完全打开图像,这个函数会从指定位置给出解码后的数据(最多一行)。
  • close 关闭打开的图像,释放分配的资源。

可以添加任意数量的图像解码器。当图像需要绘制的时候,LVGL会尝试所有注册过的解码器,直到找到能打开图像(识别格式)的解码器。

LV_IMG_CF_TRUE_COLOR_…、LV_IMG_INDEXED_…和LV_IMG_ALPHA_…这样的格式是内置解码器直接支持的(特别是所有的non-raw格式)。

Custom image formats

创建自定义图像最简单的方式是使用在线图像转换工具并设置为Raw、Raw with alpha或者Raw with chrome keyed格式。它会使用上传的二进制文件的每一个字节并写入图像的bitmap。然后,使用某个图像解码器,将这个bitmap转换成真正的,可渲染的bitmap。

相应的head.cf的值会是LV_IMG_CF_RAW、LV_IMG_CF_RAW_ALPHA或者LV_IMG_CF_RAW_CHROME_KEYED。需要根据需要选择正确的格式:完全不透明的,使用Alpha通道的还是Chroma key的。

在解码之后,LVGL把Raw格式当作True color。换言之,图像解码器必须将Raw图像解码为Color formats章节介绍的True Color格式。

如果想创建一个自定义图像,需要使用LV_IMG_CF_USER_ENCODED_0…7颜色格式。然而LVGL只能绘制True Color格式(或者Raw格式,但是这也最终会转换成True Color)的图像,所以LVGL是不识别LV_IMG_CF_USER_ENCODED_0…7这些格式的。因此,他们需要转换为那些已知的格式(如Color Format章节所述)。可以先将这些图像解码为non-true color格式(如LV_IMG_INDEXED_4BITS),然后调用内置的解码函数转化为True Color。

如果使用了自定义的编码格式,open函数中的颜色格式(dsc->header.cf)就需要根据新的格式来改变。

Register an image decoder

下面是一个在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);

}

综上所述:

  1. 在decoder_info函数中,需要收集一些图像的基本信息并存储在header中。
  2. 在decoder_open函数中,需要打开dsc->src所指向的图像资源。其类型存储在dsc->src_type == LV_IMG_SRC_FILE/VARIABLE中。如果这种格式/类型是解码器不支持的,返回LV_RES_INV。如果能打开这个图像,指向解码后的True Color图像的指针应该赋值给dsc->img_data。如果能够识别,但是暂时不想解码(如没有足够的内存),设置dsc->img_data = NULL,这样就会调用read_line来获取像素。
  3. 在decoder_close函数中,释放所有分配的资源。
  4. decoder_read函数是可选的。一次解码整个图像需要很多内存和计算,但是如果只解码图像的一行而不是整个文件,就可以节省内存和计算时间。这种情况就需要设置line read函数,同时在open函数中设置dsc->img_data = NULL。

Manually use an image decoder

绘制Raw图像的时候,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);
}

Image caching

有时候打开图像会比较耗时。反复解码PNG图像或者是从速度慢的外置存储器中载入图像会比较低效并且用户体验不好。

因此,LVGL会缓存指定的数量的图像。缓存的意思是一些图像会保持打开状态,因此LVGL可以从dsc->img_data中快速的读取它们,而不是再次解码。

当然,缓存会使用更多的RAM(存储解码的图像),LVGL会尽量优化这个过程(见后面的章节),但是仍然需要评估这种操作对于你的平台来说是否有益。如果是从非常快的存储介质中解码小尺寸图像,图像缓存可能就不值得了。

Cache size

缓存的实体数量可以用lv_conf.h中的LV_IMG_CACHE_DEF_SIZE定义。默认值是1,所以只有最近使用的图像会保持打开状态。

缓存的大小可以在运行时调用lv_img_cache_set_size(entry_num)来设置。

Value of images

当使用的图像数量超过缓存数量限制之后,LVGL没法缓存所有的这些图像。这时候LVGL会关闭先前缓存的图像以释放空间。

为了决定要关闭那个图像,LVGL以打开耗时作为衡量标准。缓存打开慢的实体会更有价值,要尽可能久的缓存它们。

如果想重写LVGL的衡量方法,可以在解码器的open函数里面手动设置打开时间:dsc->time_to_open = time_ms。如果不设置,就由LVGL设置。

每个缓存的实体都有个life值。每当有图像从缓存打开,被打开的图像的life值就会增加time_to_open,这意味着其更活跃,其他的图像的life值会减少,这意味着更老。

如果缓存已满,life值最小的实体会被关闭。

Memory usage

要注意,缓存的图像会持续使用内存。例如,如果缓存了3张PNG图像,在他们保持打开状态的过程中,内存会一直占用。

因此,用户需要保证即使是同时缓存了最大的图像的时候也有足够的内存用于缓存,

Clean the cache

如果已经载入了一个PNG图像到lv_img_dsc_t my_png变量中,并且使用在了lv_img对象上。如果图像已经缓存,然后你改变了其背后的PNG文件,这时候就需要通知LVGL重新缓存图像。否则,LVGL就不知道其背后的文件已经改变,它仍然会绘制老的图像。

为了刷新,调用lv_img_cache_invalidate_src(&my_png)。如果参数传NULL,整个缓存都会清空。

GitHub 加速计划 / lv / lvgl
32
0
下载
嵌入式图形库,用于为任何微控制器(MCU)、微处理器(MPU)和显示类型创建美观的用户界面(UI)。它通过一个专业且价格合理的拖放式UI编辑器——SquareLine Studio——得到增强。
最近提交(Master分支:17 天前 )
344ba9fe 1 天前
96b93e60 Signed-off-by: Bas van Doren <basvdoren@gmail.com> 2 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐