MicroPython Images

Displaying Images using MicroPython

Hi! I have been using LittlevGL for a project and it has been so easy to use, however I am having a bit of difficulty in displaying images on my device. I am using an ESP32 with ILI9341 display, and simply want to be able to display png images on the device. I tried following lv_binding_micropython/examples/example2.py and using it to display a QR code saved as a png, however when I do so I get the following on the display:

Clearly the QR code is somewhere in here, but I don’t quite understand what is going wrong. Also, I’m not sure if you can make out but the image comes out blue instead of just black and white for some reason. (I have attached my code below)

I also tried using the lvgl image converter to generate a binary and then use this as the data source of the image but couldn’t get this to work either.

What is the easiest way to display small png images on the device using lvgl? Thanks in advance for any help!

# init
import ustruct as struct
from lib.screen.display import Display
import lodepng as png
import lvgl as lv

lv.init()
lv.log_register_print_cb(lambda  level, path, line, msg: print('%s(%d): %s' % (path, line, msg)))

disp = Display() # Handles setting up of display externally

# Parse PNG file header
# Taken from https://github.com/shibukawa/imagesize_py/blob/ffef30c1a4715c5acf90e8945ceb77f4a2ed2d45/imagesize.py#L63-L85

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

    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
    
    dsc.img_data = img_data
    return lv.RES.OK
 
decoder = lv.img.decoder_create()
decoder.info_cb = get_png_info
decoder.open_cb = open_png

with  open('image.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()

img2 = lv.img(scr)
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)

lv.scr_load(scr)

Hi,

The code below can load converted binary image with color format LV_IMG_CF_TRUE_COLOR_ALPHA.
Hope this helps.

def load_image_src(path):
      f = open(path, 'rb')
      src = lv.img_dsc_t()
      h = f.read(4)
      lv.img.decoder_get_info(h, src.header)
      src.data_size = src.header.w * src.header.h * 4
      src.data = f.read()
      f.close()
      return src

Hi @Ash27!

The recommended way to load PNG images on ESP32+ILi9341 is by using the imagetools library.

You can use it like this:


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

lv.init()
disp = ili9341(dc=32, cs=33, power=-1, backlight=-1)

decoder = lv.img.decoder_create()
decoder.info_cb = get_png_info
decoder.open_cb = open_png
with open('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)

Also, please try other PNG files, maybe the problem is specific to the PNG file you are using.
Try this one for example:
image

This worked perfectly, thanks so much!!

Hi, the link you provided (“imagetools library”) is not available anymore. I’ve flashed my esp32 with micropython+lvgl (lv_micropython) but lodepng and imagetools are not present, and i cannot install them using the package manager on Thonny, so i was thinking of adding the necessary python modules directly i.e. copying them as .py files on the esp32 flash. Thank you for the attention.

LodePNG is available, you have to turn it on in the lv_conf.h file.

Thanks a lot for your answer. I’m looking for that file but I’m not able to find it in lv_micropython folder

Is there a way to add imagetools and lodepng without re-flashing a new version of lvgl+micropython inside the esp32? I couldn’t find anywhere those modules.

no there is not. You have to recompile the firmware and flash the ESP32 with the new firmware.

could you please please be more precise and tell me the define i have to modify to turn it on? I’ve searcged a lot but nod found anything useful