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.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':

             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
            width, height = struct.unpack(">LL", png_header[8:16])
        except struct.error:
            return lv.RES.INV
        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



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()
      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

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)

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

This worked perfectly, thanks so much!!