lvgl_screenshot

Example of the component espressif/esp_lcd_screenshot v0.1.0
# LVGL Screenshot: Render, Capture, and Verify Without an LCD

This example demonstrates how to use the `esp_lcd_screenshot` component with LVGL. It renders a static UI to a virtual `esp_lcd` panel, captures the rendered pixels, saves the frame as a PNG file, and streams the same frame as base64 for automated image verification.

No LCD is connected to the board—the screenshot panel receives the same `draw_bitmap()` calls that a physical LCD panel would normally receive.

## What the example does

1. Mounts a LittleFS partition at `/littlefs`.
2. Creates a virtual 240 × 240 RGB888 screenshot panel using `ESP_COLOR_FOURCC_BGR24`.
3. Registers the panel as an LVGL display and renders a static screen containing a title, chart, and button.
4. Saves the latest captured frame as `/littlefs/screenshot.png`.
5. Dumps the raw framebuffer over the serial console as base64.
6. Uses a pytest script to decode the framebuffer and compare it with `golden_result.ppm`.

The screen is rendered once without an LVGL task or tick timer. Keeping the UI static makes the output deterministic and suitable for golden-image testing.

## Hardware requirements

- An ESP32-series development board with **PSRAM**
- A USB connection for flashing and serial output
- No LCD or other external hardware

This example is **not compatible with every ESP32 development board**. PSRAM is required because the example and its dependencies allocate framebuffer and image-processing memory externally. The example enables PSRAM through `CONFIG_SPIRAM=y` in [`sdkconfig.defaults`](sdkconfig.defaults). Make sure the selected board actually has PSRAM and that its flash/PSRAM configuration is supported by the chosen ESP-IDF target.

## Build and run

From this directory, select the target that matches your board, then build, flash, and open the serial monitor:

```bash
idf.py set-target esp32
idf.py build flash monitor
```

Replace `esp32` with the appropriate ESP-IDF target when necessary. Exit the monitor before running commands that need exclusive access to the serial port.

The serial output contains messages similar to:

```text
I (350) example: Install virtual screenshot LCD panel driver
I (360) lcd_screenshot: Screenshot panel created (240x240, BGR3, virtual only)
I (600) example: Save the captured frame as a PNG file
I (650) lcd_screenshot: Saved 240x240 PNG image (4321 bytes) to '/littlefs/screenshot.png'
I (660) example: PNG file size: 4321 bytes
I (670) example: Dump the captured frame over the serial console
FRAMEBUFFER_BEGIN 240 240 BGR3
FB_BASE64 3Hj9/+H...
FB_BASE64 ...
FRAMEBUFFER_END
I (1200) example: LVGL screenshot example done.
```

The framebuffer is RGB888 in B, G, R byte order (`BGR3`). The base64 payload is enclosed by `FRAMEBUFFER_BEGIN` and `FRAMEBUFFER_END` markers and contains `240 × 240 × 3` bytes.

## Copy the PNG from LittleFS

The example stores the PNG in the `storage` partition. To copy that partition from a connected board, first exit the serial monitor and run:

```bash
parttool.py --port PORT read_partition --partition-name=storage --output storage.bin
```

Replace `PORT` with the serial port used by your board, for example `/dev/ttyUSB0` or `COM3`.

`storage.bin` is a LittleFS image. Extract it on the host with [`littlefs-python`](https://pypi.org/project/littlefs-python/):

```bash
pip install littlefs-python
littlefs-python extract storage.bin out/ --block-size=4096
```

The captured image will be available at:

```text
out/screenshot.png
```

## Automated verification

The pytest script captures the serial framebuffer, decodes the base64 payload, converts it to an RGB888 PPM image, and compares it with [`golden_result.ppm`](golden_result.ppm) using a SHA-256 digest.

Run it from this directory:

```bash
pytest pytest_lvgl_screenshot.py --target esp32 --port PORT
```

Replace `PORT` with your board's serial port. The test requires a connected board with PSRAM and will flash/run the example as part of the pytest-embedded workflow.

The decoded image is also saved as `lvgl_screenshot_result.ppm` in the pytest-embedded log directory, typically below `/tmp/pytest-embedded/`, which is useful when inspecting a test failure.

### Capturing a frame in an application

The generic `esp_lcd` interface has no concept of a complete GUI frame: a frame may consist of multiple partial flushes. Call an export function only after the refresh you want is finished (for example after a synchronous `lv_refr_now()`, as in this example). The screenshot driver does not pause drawing, so do not export from another task while the GUI is still flushing that frame.

To create a project from this example, run:

idf.py create-project-from-example "espressif/esp_lcd_screenshot=0.1.0:lvgl_screenshot"

or download archive (~12.47 KB)