Puppet 2.6.0 — Binary (1-bit) BMP output silently drops content loaded via external <img> tags

Hi all,

I’ve been building an e-ink dashboard (ESP32 + Waveshare 7.5") using Puppet 2.6.0 to render a Home Assistant Lovelace markdown card, and I ran into a reproducible bug that I wanted to document here since issue creation is currently restricted on the GitHub repo.

Setup: A markdown card renders weather forecast text plus condition icons loaded via <img src="/local/icons/rainy.svg" width="50" height="50"> (referencing local files served from /config/www/).

Symptom: The icons are completely missing from the final image — but only in one specific output configuration.

What I ruled out along the way:

  • Not a network/loading issue — confirmed via the live browser’s DevTools Network tab: all icon requests return 200 OK, zero console errors.
  • Not a sanitizer issue — the <img> tags render fine in the actual Lovelace dashboard.
  • Not a theme/contrast issue — tested with Dark Mode on/off and Invert Colors on/off; icons stayed absent in every combination.
  • Not a dithering setting — tested None, Floyd-Steinberg, Atkinson, and Jarvis-Judice-Ninke; no difference.

What actually isolates it: Comparing output formats for the identical page/URL:

  • format=png → icons render correctly :white_check_mark:
  • format=jpeg → icons render correctly :white_check_mark:
  • format=bmp&bmp_mode=binary → icons are completely absent :cross_mark: (confirmed by downloading and inspecting the raw .bmp file directly, not just the web UI preview panel)

So the bug appears to be isolated specifically to the 1-bit binary BMP conversion/quantization step — everything upstream of that (page render, sanitizer, network) works correctly.

Why I haven’t switched to PNG/JPEG as a workaround: ESPHome’s online_image component decodes the image at runtime on the ESP32 itself, and for a 480×800 display the final 1-bit framebuffer needs a single ~48KB contiguous memory allocation. On a plain ESP32-WROOM-32 (no PSRAM), PNG/JPEG decoding fragments the heap enough that this allocation intermittently fails (Failed to allocate 48000 bytes), whereas raw BMP 1-bit requires no decompression and copies almost directly into the framebuffer. So for memory-constrained boards like mine, the binary BMP path is the only one that reliably works — which makes this bug particularly relevant for exactly the e-ink use case Puppet is designed for.

Has anyone else run into this, or found a workaround? Happy to share the raw BMP/PNG files or full HA card config if it helps someone dig into it.

Thanks!

I assume by this you mean the Puppet app GitHub repo?

I have the Waveshare 7 inch ESP32Touch-S3 and I have been downloading monochrome images as PNG mainly because the readability was never good enough as BMP. I have a couple of full screen images in the code (plus a whole bunch of other images) and 2000 odd lines of yaml and so far memory is not an issue.

What chip are you using?

Thanks for the reply! I’m on a plain ESP32-WROOM-32 (no PSRAM) — that’s likely the key difference. On this chip I’m hitting Failed to allocate 48000 bytes (heap fragmentation) specifically when decoding PNG/JPEG at runtime via online_image, since the final 1-bit framebuffer for a 480×800 display needs a single ~48KB contiguous block in internal RAM, and PNG/JPEG decoding puts extra pressure on that before the binary output is even produced.

I suspect your S3 Touch board has PSRAM onboard, which would explain why memory isn’t an issue for you even with a lot of images — that extra external RAM absorbs exactly this kind of allocation pressure that a plain WROOM can’t.

Given that, it sounds like my two options are: (1) find/report the Puppet binary-BMP bug that’s dropping <img>-loaded content in my case, since raw BMP is the only format light enough for a PSRAM-less chip, or (2) consider swapping to an ESP32-S3 board with PSRAM if I want to reliably use PNG going forward. Appreciate the data point either way!