Skip to content

lv.image.set_src() silently fails on 8-bit RGBA PNGs (LV_COLOR_DEPTH=16, lodepng) #140

@bitcoin3us

Description

@bitcoin3us

Summary

lv.image.set_src() called with an 8-bit RGBA PNG (PNG color type 6) succeeds without raising and prints no error, but the image widget stays at 0×0 pixels and nothing renders on screen. The same code path works correctly with indexed-palette PNGs (color type 3). Hit this developing Lightning Piggy (com.lightningpiggy.displaywallet) on MPOS 0.10.0 — hero / icon / confetti graphics were all silently invisible until I converted them.

Environment

  • MicroPythonOS 0.10.0
  • Board: Waveshare ESP32-S3 Touch LCD 2 (waveshare_esp32_s3_touch_lcd_2)
  • LVGL: bundled lvgl-micropython
  • lv_conf.h settings:
    • LV_COLOR_DEPTH 16 (via MICROPY_COLOR_DEPTH = 16 → RGB565)
    • LV_USE_LODEPNG 1
    • LV_USE_LIBPNG 0 (commented as lv_libpng.c:14:10: fatal error: png.h: No such file or directory)

Reproduction

Minimal Python in any Activity:

img = lv.image(lv.screen_active())
img.set_src("M:path/to/some_rgba.png")  # 8-bit RGBA, color type 6
img.set_size(80, 100)
img.center()
# Result: widget is positioned correctly and not HIDDEN, but renders no pixels.
# img.get_width() / get_height() return 0 before the explicit set_size, and
# even after set_size the image content area is blank.

The exact same code with an indexed-palette PNG renders correctly:

img.set_src("M:builtin/res/mipmap-mdpi/MicroPythonOS-logo-white-long-w296.png")
# (this file is color type 3 — renders fine)

Reproducer PNGs

File file(1) reports Renders?
M:builtin/res/mipmap-mdpi/MicroPythonOS-logo-white-long-w296.png 4-bit colormap, non-interlaced (color type 3)
LP hero_lightningpiggy.png (80×100) 8-bit/color RGBA, non-interlaced (color type 6)
LP hero_lightningpenguin.png (78×100) 8-bit/color RGBA, non-interlaced (color type 6)
Same penguin/piggy after Image.quantize(method=2) → color type 3 8-bit colormap, non-interlaced

Files I had problems with are pure 80×100 RGBA cartoons, ~8 KB. The PNG itself parses fine (valid IHDR, declared dims, valid magic bytes) — both open(path, 'rb') from MicroPython AND host-side decoders read them without complaint. It's only lv.image.set_src() that produces no output.

Diagnostics performed

  • Verified the FS driver works — open() from Python on the device reads the file fine via the M: driver path
  • Verified lv.lodepng_init() is exposed and callable; calling it from app onCreate doesn't change behaviour (suggests it's already inited, or it's not the missing piece)
  • hero_image.get_width() / get_height() return 0 after set_src() on RGBA PNGs (suggests the decoder isn't producing image header dims). set_size(80,100) forces non-zero bounds but the area still draws empty (suggests decoder doesn't produce pixel data either).
  • Painted a solid bg_color on the same widget instead — that renders correctly, ruling out z-order / overlay / clip issues
  • Used the MPOS-bundled indexed PNG (the launcher logo) as the src in the LP context — that renders fine, ruling out a path-prefix or filesystem-context issue
  • Converted my RGBA PNGs to indexed-palette via Pillow (Image.quantize(colors=255, method=2, dither=FLOYDSTEINBERG)) — those then render correctly on the same device, without code changes

Expected behaviour

lv.image.set_src() with a valid 8-bit RGBA PNG should either render the image (preferred) or raise / print an error indicating the decoder rejected the format. Silent 0×0 is the worst-of-both: code looks like it succeeded.

Workaround

Convert all 8-bit RGBA PNGs to indexed-palette before shipping. PIL one-liner:

from PIL import Image
Image.open("foo.png").quantize(colors=255, method=2,
    dither=Image.Dither.FLOYDSTEINBERG).save("foo.png", optimize=True)

Lightning Piggy added a scripts/check_png_format.py validator that walks res/ and fails CI on any RGBA PNG, plus a docs/assets.md write-up of the constraint. Happy to upstream the validator if useful for other LP-style apps.

Likely root cause (speculation)

The combination LV_COLOR_DEPTH=16 (RGB565 framebuffer) + lodepng + 8-bit RGBA source is a known interaction point in lvgl — the decoder needs to convert RGBA → RGB565 and the alpha-blending path may not be wired up correctly in this build. Indexed-palette PNGs go through a simpler code path (palette lookup with optional tRNS transparency) that doesn't hit the same code, which is consistent with what we observe. Probably worth a sanity check that the lvgl-micropython build is enabling the right LV_DRAW_BUF_* / colour-conversion flags for RGBA-on-RGB565.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions