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.
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
waveshare_esp32_s3_touch_lcd_2)lv_conf.hsettings:LV_COLOR_DEPTH 16(viaMICROPY_COLOR_DEPTH = 16→ RGB565)LV_USE_LODEPNG 1LV_USE_LIBPNG 0(commented aslv_libpng.c:14:10: fatal error: png.h: No such file or directory)Reproduction
Minimal Python in any Activity:
The exact same code with an indexed-palette PNG renders correctly:
Reproducer PNGs
file(1)reportsM:builtin/res/mipmap-mdpi/MicroPythonOS-logo-white-long-w296.png4-bit colormap, non-interlaced(color type 3)hero_lightningpiggy.png(80×100)8-bit/color RGBA, non-interlaced(color type 6)hero_lightningpenguin.png(78×100)8-bit/color RGBA, non-interlaced(color type 6)Image.quantize(method=2)→ color type 38-bit colormap, non-interlacedFiles 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 onlylv.image.set_src()that produces no output.Diagnostics performed
open()from Python on the device reads the file fine via theM:driver pathlv.lodepng_init()is exposed and callable; calling it from apponCreatedoesn't change behaviour (suggests it's already inited, or it's not the missing piece)hero_image.get_width() / get_height()return 0 afterset_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).bg_coloron the same widget instead — that renders correctly, ruling out z-order / overlay / clip issuesImage.quantize(colors=255, method=2, dither=FLOYDSTEINBERG)) — those then render correctly on the same device, without code changesExpected 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:
Lightning Piggy added a
scripts/check_png_format.pyvalidator that walksres/and fails CI on any RGBA PNG, plus adocs/assets.mdwrite-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 optionaltRNStransparency) 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 rightLV_DRAW_BUF_*/ colour-conversion flags for RGBA-on-RGB565.