Promoted from DIYStickyNotes/lib/LvScreenshot (byte-identical src) into a reusable Gitea library. Adds LICENSE, .gitignore, library.properties, keywords.txt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
73 lines
3.5 KiB
Markdown
73 lines
3.5 KiB
Markdown
# LvScreenshot
|
||
|
||
Board-independent **LVGL 9 screen capture** as a reusable PlatformIO library: grab the current screen
|
||
into a tight **RGB565** buffer via `lv_snapshot_take()`, optionally serve it over HTTP, and save it as
|
||
a PNG on your PC. Because it re-renders the LVGL object tree (not the panel framebuffer), it works with
|
||
**any** display driver — partial-flush (LovyanGFX), full-frame (Arduino_GFX), or RGB panel.
|
||
|
||
## Requirements
|
||
|
||
- LVGL 9 with **`LV_USE_SNAPSHOT 1`** in your `lv_conf.h` (the core warns at compile time otherwise).
|
||
- **PSRAM must be enabled/available on ESP32** — see "Memory / PSRAM" below. This is essential.
|
||
- The optional HTTP helper needs `ESPAsyncWebServer`; it self-disables (no-op) when that is absent.
|
||
|
||
## Memory / PSRAM (important)
|
||
|
||
A full-frame snapshot is large — e.g. 320×480 RGB565 = **~300 KB**. LVGL's stock `lv_snapshot_take()`
|
||
allocates that from LVGL's own heap, which with **`LV_USE_STDLIB_MALLOC = LV_STDLIB_BUILTIN`** (the
|
||
default) is a small fixed pool (`LV_MEM_SIZE`). It cannot fit ~300 KB, so `lv_snapshot_take()` simply
|
||
**returns null and the capture fails** — the symptom that must be worked around whenever capture goes
|
||
through LVGL.
|
||
|
||
This library avoids that: instead of letting LVGL allocate, it allocates the draw buffer itself from
|
||
**PSRAM** (`heap_caps_malloc(MALLOC_CAP_SPIRAM)` on ESP32, plain `malloc` elsewhere) and renders into
|
||
it via `lv_snapshot_take_to_draw_buf()`. Consequences for integrators:
|
||
|
||
- **Enable PSRAM** in your build (e.g. `-DBOARD_HAS_PSRAM`, `board_build.arduino.memory_type` set to a
|
||
`*_opi`/`*_qspi` PSRAM variant). Without a working PSRAM heap the two ~300 KB allocations (draw buffer
|
||
+ tight-packed copy) will fail and capture returns `false`.
|
||
- You do **not** need to enlarge `LV_MEM_SIZE` or switch `LV_USE_STDLIB_MALLOC` — the library
|
||
deliberately bypasses LVGL's allocator for the snapshot buffer for exactly this reason.
|
||
- Peak transient usage is ~2× the frame (~600 KB) during a capture; both buffers are freed right after
|
||
(the HTTP helper keeps one until the next request so it can stream it).
|
||
|
||
## Core API (`LvScreenshot.hpp`, LVGL-only)
|
||
|
||
```cpp
|
||
LvScreenshot shot;
|
||
if (lvgl_port_lock(-1)) { // the caller must hold the LVGL lock
|
||
bool ok = lvScreenshotCaptureTop(&shot); // topmost overlay, else the active screen
|
||
lvgl_port_unlock();
|
||
if (ok) { /* shot.data = RGB565, shot.width/height/length */ lvScreenshotFree(&shot); }
|
||
}
|
||
```
|
||
`lvScreenshotCapture(obj, &shot)` captures a specific object. Buffers use PSRAM on ESP32.
|
||
|
||
## Optional HTTP endpoint (`LvScreenshotHttp.hpp`, needs ESPAsyncWebServer)
|
||
|
||
```cpp
|
||
#include <LvScreenshotHttp.hpp>
|
||
// in your web-server setup:
|
||
lvScreenshotAttachHttp(www, lvgl_port_lock, lvgl_port_unlock); // registers GET /screenshot
|
||
```
|
||
The route returns the raw RGB565 body plus `X-Width` / `X-Height` / `X-Format` headers.
|
||
|
||
## PC tool
|
||
|
||
```bash
|
||
uv run tools/screenshot.py --host diystickynotes.local --out shot.png
|
||
# --swap-rb if colours look inverted (BGR panel)
|
||
# --byteswap if the image is garbled (big-endian RGB565)
|
||
```
|
||
Converts the RGB565 response to a PNG (numpy + Pillow; deps auto-installed via uv / PEP 723).
|
||
|
||
## Gating it to dev builds (recommended)
|
||
|
||
Keep production lean: flip `LV_USE_SNAPSHOT` on only under a build flag, and only call
|
||
`lvScreenshotAttachHttp` under the same flag, so normal builds are byte-identical and expose no
|
||
endpoint. See the DIYStickyNotes `[env:screenshot_*]` envs for an example.
|
||
|
||
## License
|
||
|
||
MIT (code). Screenshots you capture are your own content.
|