How to display image?Example1 and example2 got some error

Hi,
When try example with m5stack based on eso32 I got some error.

example1:
My code:

with open('img/blue_flower_32.bin','rb') as f:
  img_data = f.read()

# Pixel format: Fix 0xFF: 8 bit, Red: 8 bit, Green: 8 bit, Blue: 8 bit

# Create a screen with a draggable image

scr = lv.obj()
img = lv.img(scr)
img.align(scr, lv.ALIGN.CENTER, 0, 0)
img_dsc = lv.img_dsc_t(
    {
        "header": {"always_zero": 0, "w": 100, "h": 75, "cf": lv.img.CF.TRUE_COLOR},
        "data_size": len(img_data),
        "data": img_data,
    }
)

img.set_src(img_dsc)
img.set_drag(False)

# Load the screen and display image

lv.scr_load(scr)

The result:

example2
my code:

def get_png_info(decoder, src, header):
    # Only handle variable image types

    if lv.img.src_get_type(src) != lv.img.SRC.VARIABLE:
        return lv.RES.INV

    png_header = bytes(lv.img_dsc_t.cast(src).data.__dereference__(24))

    if png_header.startswith(b'\211PNG\r\n\032\n') and png_header[12:16] == b'IHDR':
        try:
            width, height = struct.unpack(">LL", png_header[16:24])
        except struct.error:
            return lv.RES.INV
    
    # Maybe this is for an older PNG version.
    
    elif png_header.startswith(b'\211PNG\r\n\032\n'):
        # Check to see if we have the right content type
        try:
            width, height = struct.unpack(">LL", png_header[8:16])
        except struct.error:
            return lv.RES.INV
    else:
        return lv.RES.INV

    header.always_zero = 0
    header.w = width
    header.h = height
    header.cf = lv.img.CF.TRUE_COLOR_ALPHA

    # print("width=%d, height=%d" % (header.w, header.h))

    return lv.RES.OK


# Read and parse PNG file

def open_png(decoder, dsc):
    img_dsc = lv.img_dsc_t.cast(dsc.src)
    png_data = img_dsc.data
    png_size = img_dsc.data_size
    png_decoded = png.C_Pointer()
    png_width = png.C_Pointer()
    png_height = png.C_Pointer()
    error = png.decode32(png_decoded, png_width, png_height, png_data, png_size);
    if error:
        return None # LV_IMG_DECODER_OPEN_FAIL
    img_size = png_width.int_val * png_height.int_val * lv.color_t.SIZE
    img_data = png_decoded.ptr_val

    # Convert png RGBA-big-endian format to lvgl ARGB-little-endian
    # Eventually - this means swapping red and blue.
    # More info on https://forumtest.littlevgl.com/t/png-decoding-why-red-and-blue-are-swapped/72

    img_view = img_data.__dereference__(img_size)
    for i in range(0, img_size, lv.color_t.SIZE):
        ch = lv.color_t.cast(img_view[i:i]).ch
        ch.red, ch.blue = ch.blue, ch.red

    dsc.img_data = img_data
    return lv.RES.OK

# Register new image decoder

decoder = lv.img.decoder_create()
decoder.info_cb = get_png_info
decoder.open_cb = open_png

# Create a screen with a draggable image

with open('img/png_decoder_test.png','rb') as f:
  png_data = f.read()

png_img_dsc = lv.img_dsc_t({
    'data_size': len(png_data),
    'data': png_data 
})

scr = lv.obj()

# Create an image on the left using the decoder

lv.img.cache_set_size(2)
img1 = lv.img(scr)
img1.align(scr, lv.ALIGN.IN_LEFT_MID, -50, 0)
img1.set_src(png_img_dsc)
img1.set_drag(True)

# Create an image on the right directly without the decoder

img2 = lv.img(scr)
img2.align(scr, lv.ALIGN.IN_RIGHT_MID, 0, 0)
raw_dsc = lv.img_dsc_t()
get_png_info(None, png_img_dsc, raw_dsc.header)
dsc = lv.img_decoder_dsc_t({'src': png_img_dsc})
if open_png(None, dsc) == lv.RES.OK:
    raw_dsc.data = dsc.img_data
    raw_dsc.data_size = raw_dsc.header.w * raw_dsc.header.h * lv.color_t.SIZE
    img2.set_src(raw_dsc)
    img2.set_drag(True)

# Load the screen and display image

lv.scr_load(scr)

The result:

  • lv.img.cache_set_size(2)
    TypeError: argument has wrong type

  • get_png_info
    TypeError: argument has wrong type

I check the C code:

/*
 * lvgl extension definition for:
 * void lv_img_cache_set_size(uint16_t new_slot_num)
 */
 
STATIC mp_obj_t mp_lv_img_cache_set_size(size_t mp_n_args, const mp_obj_t *mp_args)
{
    uint16_t new_slot_num = (uint16_t)mp_obj_get_int(mp_args[0]);
    lv_img_cache_set_size(new_slot_num);
    return mp_const_none;
}

STATIC MP_DEFINE_CONST_LV_FUN_OBJ_VAR(mp_lv_img_cache_set_size_obj, 1, mp_lv_img_cache_set_size, lv_img_cache_set_size);

It looks like this is correct, but why is it wrong?

Share some pictures~




Both examples work well on the online simulator:

But both examples are assuming 32bit color format, which is incompatible with ili9341 of the m5stack.

There is a better way - using the imagetools library.
The advantages are:

  • Takes care of PNG header and data decoding in several color formats
  • Fast - it uses the viper code emitter for processing the image data.

Here is a usage example:

from imagetools import get_png_info, open_png

decoder = lv.img.decoder_create()
decoder.info_cb = get_png_info
decoder.open_cb = open_png

with open('img/png_decoder_test.png' ,'rb') as f:
      png_data = f.read()

png_img_dsc = lv.img_dsc_t({
    'data_size': len(png_data),
    'data': png_data})

scr = lv.scr_act()
img1 = lv.img(scr)
img1.align(scr, lv.ALIGN.IN_TOP_LEFT, 0, 0)
img1.set_src(png_img_dsc)

Still not work…
Display No data


and img1.set_src(png_img_dsc) this line got error.
image

image

:sob:

More detail:

In @amirgon’s original example he added import lodepng as png at the top. I don’t see png being referenced anywhere in your code sample but the import statement may have other side effects. You could try adding that.

Hi @embeddedt,
In fact, this is the complete code, but the result is the same. I now turn on LV_LOG_LEVEL_INFO.

import espidf as esp
import lvgl as lv
import lvesp32
from ili9341 import ili9341
import lodepng as png
from imagetools import get_png_info, open_png

lv.init()

decoder = lv.img.decoder_create()
decoder.info_cb = get_png_info
decoder.open_cb = open_png

disp = ili9341()

with open('flash/img/png_decoder_test.png', 'rb') as f:
      png_data = f.read()

png_img_dsc = lv.img_dsc_t({
    'data_size': len(png_data),
    'data': png_data})

scr = lv.scr_act()
img1 = lv.img(scr)
img1.align(scr, lv.ALIGN.IN_TOP_LEFT, 0, 0)
img1.set_src(png_img_dsc)

What I can be sure of is that the image file reading was successful.
Read data len:
image
Image size in bytes:
image

I temporarily found a way to display pictures, but this is not what I want.
Step 1
Convert the image file to a binary file (this will increase the file size by three times)

image
Step 2
Use the code, need set the image width and height.

with open('flash/img/png_decoder_test.bin','rb') as f:
    img_data = f.read()

scr = lv.obj()
img = lv.img(scr)
img.align(scr, lv.ALIGN.IN_TOP_LEFT, 0, 0)

img_dsc = lv.img_dsc_t(
    {
        "header": {"always_zero": 0, "w": 200, "h": 150, "cf": lv.img.CF.TRUE_COLOR},
        "data_size": len(img_data),
        "data": img_data,
    }
)

img.set_src(img_dsc)

Step 3
The image can be displayed.
image

Result

  1. Using this method will lose the transparency of the image.(or it can be set :slight_smile: )
  2. Image files need to be processed in advance.
  3. The display effect is not very good (noisy?).

Hi @imliubo
The problem you are seeing is a result of a bug in the Micropython binding, that started after integrating a newer version of Micropython.
It is related to static member functions of objects.

I’ve fixed it on a branch: https://github.com/lvgl/lv_binding_micropython/tree/bugfix/static_members
Please try to integrate this fix into your lv_binding_micropython.
If it works correctly on your side, I’ll merge this into master.

Hi @amirgon,
Thanks for help me again! It works! I’m so excited. :hugs: :hugs: :hugs:

LOOK
image
It’s very good!

But I got a question,We registered the image decoding callback function through the following code,and it just for PNG format file.What should I do if I want to display pictures in multiple formats? Such as BMP, JPG.

decoder = lv.img.decoder_create()
decoder.info_cb = get_png_info
decoder.open_cb = open_png

It seems impossible to register a callback function for each format separately.

Maybe it is possible to process images of different formats in imagetools?

That’s great!
I’ll integrate this fix into master soon.

lvgl supports multiple decoders. It’s possible to create another decoder and register another image format.

However, today with Micropython we only support raw images and PNG format.
lvgl has support for BMP decoder and GIF decoder, but these were not integrated into Micropython.

Is it really necessary? Are there any advantages of BMP or GIF vs. PNG?
You can always convert your image to PNG and use it in Micropython with the PNG decoder.
PNG is a lossless compression format and considered a better choice than BMP or GIF.

There is also an ongoing work on JPG decoder. JPG is ubiquitous and efficient (lossy compression) so when that’s ready, I’m definitely planning to integrate it into Micropython.

I know this, lvgl provides an interface to do this.

It’s not so necessary. It’s great to support PNG at the moment, and a better-looking interface can already be made.

Waiting for it. :grinning:

But I got new question :joy:
When display a png image, it will show No Data at frist, only after call the set_src function the image just display success.
But read the PNG file and decoder the image will take some time, can we don’t show the No data text?

with open('flash/img/start_bg.png','rb') as f:
    img_data = f.read()

scr = lv.obj()
img = lv.img(scr) # it will show No Data 
img.align(scr, lv.ALIGN.IN_TOP_LEFT, 0, 0)

img_dsc = lv.img_dsc_t(
    {
        "data_size": len(img_data),
        "data": img_data,
    }
)

img.set_src(img_dsc)

It seem can’t upload mp4 file,so I convert the video to gif for upload.
core2-png-no-data

Original video

Perhaps you could wait to create the image when img_data is read? Is that the part that it’s waiting for? I’m not sure if with statements in Python execute asynchronously or not; I’ve never had to use them before.

The “No data” message is displayed because image source is NULL:

The file is completely read before creating the image,.
However, PNG decoding happens after the image was already created (when calling img.set_src) and apparently takes a little time until it completes. During that time image source remains NULL and the “No data” message is displayed.

If you don’t like the “No data” message, I can suggest the following options:

  • Create the image on a separate screen and call lv.scr_load to make it active only after setting image source by img.set_src(img_dsc)
  • Prepare some small raw image (even blank image) and set the source to the raw image first. Only afterwards call img.set_src(img_dsc)
  • Improve the way NULL image source is handled in the C code. Drawing a spinner or something like that could be very nice. Then contribute it back to LVGL :wink:

I thought the file is already read,It waiting for decoder the PNG image(the waste time part) :grin:

Thank you for explain it. :smile:
I thought your suggestion 2 it will be work,but I am not try it yet.

Maybe we can draw this symbol image (lv.SYMBOL.IMAGE) instead the No data text. :blush:

Thanks you @amirgon and you @embeddedt for help me in these days. :heart:
Finally I can display the image, It was so excited! :hugs:

Hello, I don’t wanna open new topic for this trivial question, but what is the quickest way to integrate this fix to lv_binding_micropython?(for Newbie)

This fix should be in the latest versions of lv_micropython and lv_binding_micropython.

I confirm that the fix was merged to master branch.

Update:.
BMP, GIF, JPG, SJPG are now supported also in Micropython.