Image Decoders

Images that are encoded (i.e.

Edit on GitHub

What is an Image Decoder?

Images that are encoded (i.e. outside of the list of built-in supported Color Formats) are dealt with through an Image Decoder. An Image Decoder is a body of logic that can convert a coded image into one of the recognized formats.

Built-In Image Decoders

LVGL comes with a number of image decoders to support a number of popular image formats:

Formatlv_conf.h Symbol to SetReferenceExternal Library Required?FormRAM Cost
BMPLV_USE_BMPBMP DecoderNoFile onlyLow (1 draw buffer)
PNGLV_USE_LODEPNGLodePNG DecoderNoFile or variableFull image
PNGLV_USE_LIBPNGlibpng DecoderYesFile or variableFull image
JPGLV_USE_LIBJPEG_TURBOlibjpeg-turbo DecoderYesFile only8x8 Pixel Tiles
JPGLV_USE_TJPGDTiny JPEG DecoderYesFile or variable8x8 Pixel Tiles
WEBPLV_USE_LIBWEBPWebP DecoderYesFile onlyFull image
SVGLV_USE_SVGSVG DecoderNoFile or variableFull image
GIFLV_USE_GIFGIF (lv_gif)NoUse GIF Widget (File)Widget + 1 frame
LottieSee referenceLottie (lv_lottie)NoUse Lottie WidgetWidget + 1 frame

Once you have the appropriate symbol set to 1 in lv_conf.h, to use a file, simply make the file accessible on an external storage device. You will need to then pass the file-system path to the file containing your image as the src argument to lv_image_set_src(icon, "S:my_icon.png"), and LVGL takes care of the rest.

To use the encoded file as a variable, choose one of these approaches:

  • Use the online- or offline converter to convert the file to an lv_image_dsc_t object + data into a .c file and compile and link it into your project. The color format is stored in the header.cf field of the lv_image_dsc_t struct.

  • There could be case where it was needed to load an image file into dynamically-allocated RAM at run-time and free it later, thus avoiding storing the images as part of the application. In this case, you could manually create a lv_image_dsc_t and point the data field to the byte array containing the file content and set the header.cf field to LV_COLOR_FORMAT_RAW or LV_COLOR_FORMAT_RAW_ALPHA, and set the image source to this lv_image_dsc_t object using lv_image_set_src(icon, &my_img_dsc), and when it is time to decode, the registered decoder that recognizes the image format will be used to decode it.

    The types having the word "variable" in the "Form" column in the table above would support this approach if it was needed.

Using Custom Image Formats

If you have a file-based image type that is not in the above list, you can set up LVGL to successfully handle it by implementing a custom image decoder. You can use an external decoding library or write your own. To "connect" it to LVGL, use LVGL's Image Decoder interface.

An image decoder consists of 4 callbacks:

FieldDescription
infoGet some basic info about the image (width, height and color format).
openOpen an image: - optionally store entire decoded image; - set dsc->decoded to NULL to indicate the image can be decoded incrementally; - return LV_RESULT_OK if decoder can decode the given image, LV_RESULT_INVALID otherwise. (This is normally done by reading the image header from the file and determining compatibility by reading the header content.)
get_areaIf open didn't fully open an image this function should decode the indicated area of the image into the draw buffer.
closeClose an opened image, and free the allocated resources.

You can add any number of image decoders. When an image needs to be drawn, the library will try all the registered image decoders until it finds one which can open the image, i.e. one which knows that format.

The built-in decoder understands all the formats in Color Formats minus the RAW formats.

Custom Image Formats

The easiest way to create a custom image is to use the online image converter and select RAW or RAW_WITH_ALPHA format. It will just take every byte of the binary file you uploaded and write it as an image "bitmap". You then need to attach an image decoder that will parse that bitmap and generate the real, render-able bitmap.

header.cf will be LV_COLOR_FORMAT_RAW, LV_COLOR_FORMAT_RAW_ALPHA accordingly. Use the format according to your needs: a fully opaque image, or one using an alpha channel.

The decoded format of a RAW image depends on the decoder. Example: JPG images are decoded to RGB888 and PNG images are decoded to ARGB8888. See Color Formats for more details.

Registering an Image Decoder

Here's an example of getting LVGL to work with a custom format using the PNG decoder as an example. In lv_libpng.c, see the following functions as examples to follow.

ActionFunction
Create and register image decoderlv_libpng_init()
De-initialize image decoderlv_libpng_deinit()
Gather basic information about the image and store it in header.decoder_info()
Open a image and generate decoded image 1decoder_open()
Free any allocated resourcesdecoder_close()
Partially decode based on specified area (Optional: use if decoder_open() does not decode whole image.)decoder_get_area() 2

Manually Using an Image Decoder

LVGL will use registered image decoders automatically if you try and draw a raw image (i.e. using the lv_image Widget) but you can use them manually as well. Create an lv_image_decoder_dsc_t variable to describe the decoding session and call lv_image_decoder_open.

 
lv_result_t res;
lv_image_decoder_dsc_t dsc;
lv_image_decoder_args_t args = { 0 }; /* Custom decoder behavior via args */
res = lv_image_decoder_open(&dsc, &my_img_dsc, &args);

if(res == LV_RESULT_OK) {
    /* Do something with `dsc->decoded`. You can copy out the decoded image by `lv_draw_buf_dup(dsc.decoded)`*/
    lv_image_decoder_close(&dsc);

}

You would need to set LV_USE_PRIVATE_API to 1 in lv_conf.h in order to do this since the definition of the lv_image_decoder_dsc_t and _lv_image_decoder_args_t structs are both in a private header file.

Image Post-Processing

Considering that some hardware has special requirements for image formats, such as alpha premultiplication and stride alignment, most image decoders (such as PNG decoders) may not directly output image data that meets hardware requirements.

For this reason, LVGL provides a method for implementing custom image post-processing to address unpredicted future GPU requirements (over and above the premultiplication and stride alignment provided by lv_image_decoder_post_process):

  • In your custom GPU draw unit, call a custom post-processing function after lv_image_decoder_open to adjust the data in the image cache, and
  • then mark the processing status in cache_entry->process_state (to avoid repeated post-processing).

The below code example assumes the image was opened using the decoder interface (e.g. using libpng Decoder and thus has already called lv_image_decoder_post_process to perform stride alignment and/or premultiplication via the decoder descriptor's args field).

Example (requires LV_USE_PRIVATE_API to 1 in lv_conf.h):

 
/* Define post-processing state */
typedef enum {
    MY_IMAGE_PROCESS_STATE_NONE = 0,
    MY_IMAGE_PROCESS_STATE_BIT_SHIFTED = 1 << 4,
} image_process_state_t;

lv_result_t my_image_post_process(lv_image_decoder_dsc_t * dsc)
{
    lv_color_format_t color_format = dsc->header.cf;
    lv_result_t res = LV_RESULT_OK;

    if (color_format == LV_COLOR_FORMAT_ARGB8888) {
        lv_cache_t * cache_p = dsc->cache;
        lv_cache_entry_t * entry = dsc->cache_entry;
        lv_mutex_lock(&cache_p->lock);

        if (!(entry->flags & MY_IMAGE_PROCESS_STATE_BIT_SHIFTED)) {
            lv_draw_buf_t * shifted_buf = NULL; /* Insert allocation call here. */
            if (shifted_buf == NULL) {
                LV_LOG_ERROR("No memory for bit-shifting adjustment.");
                res = LV_RESULT_INVALID;
                goto alloc_failed;
            }

            /* Handle additional GPU requirement here. */

            lv_free(dsc->decoded);
            dsc->decoded = shifted_buf;
            entry->flags |= MY_IMAGE_PROCESS_STATE_BIT_SHIFTED;
            LV_LOG_USER("Bit shifting completed.");
        }

    alloc_failed:
        lv_mutex_unlock(&cache_p->lock);
    }

    return res;
}

/* GPU draw unit */

void gpu_draw_image(lv_draw_unit_t * draw_unit, const lv_draw_image_dsc_t * draw_dsc, const lv_area_t * coords)
{
    /* ... */
    lv_image_decoder_dsc_t decoder_dsc;
    lv_image_decoder_args_t args;
    lv_memzero(&args, sizeof(args));
    args.premultiply = true;
    args.stride_align = true;
    lv_result_t res = lv_image_decoder_open(&decoder_dsc, draw_dsc->src, &args);

    if (res != LV_RESULT_OK) {
        LV_LOG_ERROR("Failed to open image");
        return;
    }

    /* Pre-multiplication and stride alignment are now done from the call to
     * lv_image_decoder_open() above.  But our additional GPU requirement (which we
     * are calling bit-shifting above) isn't handled yet, so we do it below. */
    res = my_image_post_process(&decoder_dsc);
    if (res != LV_RESULT_OK) {
        LV_LOG_ERROR("Failed to post-process image");
        return;
    }
    /* ... */
}

API

Footnotes

  1. In decoder_open(), you should try to open the image source pointed by dsc->src. Its type is already in dsc->src_type == LV_IMG_SRC_FILE/VARIABLE. If this format/type is not supported by the decoder, return LV_RESULT_INVALID. However, if you can open the image, a pointer to the decoded image should be set in dsc->decoded. If the format is known, but you don't want to decode the entire image (e.g. no memory for it), set dsc->decoded = NULL and use decoder_get_area() to get the image area pixels.

  2. lv_bmp.c has an example of decoder_get_area().

How is this guide?

Last updated on

On this page