Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
640f356754 |
@@ -1,3 +1,4 @@
|
||||
LvScreenshot KEYWORD1
|
||||
lvScreenshotTake KEYWORD2
|
||||
lvScreenshotAttachHttp KEYWORD2
|
||||
lvScreenshotAttachHttpRgb KEYWORD2
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "LvScreenshot",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Board-independent LVGL 9 screen capture (lv_snapshot_take -> tight RGB565) plus an optional AsyncWebServer /screenshot endpoint and a PC tool that saves a PNG.",
|
||||
"keywords": "lvgl, screenshot, snapshot, capture, png, esp32",
|
||||
"frameworks": "arduino",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
name=LvScreenshot
|
||||
version=1.0.0
|
||||
version=1.1.0
|
||||
author=xPablo.cz
|
||||
maintainer=xPablo.cz <https://git.xpablo.cz/xPablo.cz>
|
||||
sentence=Board-independent LVGL 9 screen capture with an optional /screenshot web endpoint.
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include "LvScreenshot.hpp"
|
||||
#include <cstring>
|
||||
#include <esp_lcd_panel_rgb.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
namespace {
|
||||
// One shared capture buffer: the async response streams from it over ~1 s, so it must outlive the
|
||||
@@ -57,7 +59,64 @@ void lvScreenshotAttachHttp(AsyncWebServer * www, LvScreenshotLockFn lock,
|
||||
req->send(resp);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- RGB-panel path: read the scan-out framebuffer directly (no lv_snapshot re-render) ----
|
||||
namespace {
|
||||
esp_lcd_panel_handle_t g_rgbPanel = nullptr;
|
||||
uint8_t * g_rgbSnap = nullptr; // reused PSRAM copy; streamed until the next request
|
||||
size_t g_rgbLen = 0;
|
||||
int g_rgbW = 0;
|
||||
int g_rgbH = 0;
|
||||
LvScreenshotLockFn g_rgbLock = nullptr;
|
||||
LvScreenshotUnlockFn g_rgbUnlock = nullptr;
|
||||
}
|
||||
|
||||
void lvScreenshotAttachHttpRgb(AsyncWebServer * www, esp_lcd_panel_handle_t panelHandle,
|
||||
int width, int height, LvScreenshotLockFn lock,
|
||||
LvScreenshotUnlockFn unlock, const char * path) {
|
||||
if (!www) return;
|
||||
g_rgbPanel = panelHandle;
|
||||
g_rgbW = width;
|
||||
g_rgbH = height;
|
||||
g_rgbLen = (size_t) width * (size_t) height * 2; // RGB565
|
||||
g_rgbLock = lock;
|
||||
g_rgbUnlock = unlock;
|
||||
www->on(path ? path : "/screenshot", HTTP_GET, [](AsyncWebServerRequest * req) {
|
||||
void * fb = nullptr;
|
||||
const esp_err_t err = g_rgbPanel ? esp_lcd_rgb_panel_get_frame_buffer(g_rgbPanel, 1, &fb) : ESP_FAIL;
|
||||
if (err != ESP_OK || !fb) { req->send(500, "text/plain", "screenshot: no framebuffer"); return; }
|
||||
|
||||
if (!g_rgbSnap) {
|
||||
g_rgbSnap = static_cast<uint8_t *>(heap_caps_malloc(g_rgbLen, MALLOC_CAP_SPIRAM));
|
||||
if (!g_rgbSnap) g_rgbSnap = static_cast<uint8_t *>(heap_caps_malloc(g_rgbLen, MALLOC_CAP_8BIT));
|
||||
}
|
||||
if (!g_rgbSnap) { req->send(500, "text/plain", "screenshot: alloc failed"); return; }
|
||||
|
||||
// Copy a single consistent frame under the LVGL lock (don't capture mid-flush).
|
||||
if (g_rgbLock && g_rgbLock(-1)) {
|
||||
std::memcpy(g_rgbSnap, fb, g_rgbLen);
|
||||
if (g_rgbUnlock) g_rgbUnlock();
|
||||
} else {
|
||||
std::memcpy(g_rgbSnap, fb, g_rgbLen); // lock unavailable: best-effort
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * resp = req->beginResponse(
|
||||
"application/octet-stream", g_rgbLen,
|
||||
[](uint8_t * out, size_t maxLen, size_t index) -> size_t {
|
||||
const size_t remaining = g_rgbLen - index;
|
||||
const size_t n = remaining < maxLen ? remaining : maxLen;
|
||||
std::memcpy(out, g_rgbSnap + index, n);
|
||||
return n;
|
||||
});
|
||||
resp->addHeader("X-Width", String(g_rgbW));
|
||||
resp->addHeader("X-Height", String(g_rgbH));
|
||||
resp->addHeader("X-Format", "RGB565");
|
||||
req->send(resp);
|
||||
});
|
||||
}
|
||||
#else
|
||||
// ESPAsyncWebServer not available in this project: no-op so the core LvScreenshot still builds.
|
||||
// ESPAsyncWebServer not available in this project: no-ops so the core LvScreenshot still builds.
|
||||
void lvScreenshotAttachHttp(AsyncWebServer *, LvScreenshotLockFn, LvScreenshotUnlockFn, const char *) {}
|
||||
void lvScreenshotAttachHttpRgb(AsyncWebServer *, esp_lcd_panel_handle_t, int, int,
|
||||
LvScreenshotLockFn, LvScreenshotUnlockFn, const char *) {}
|
||||
#endif
|
||||
|
||||
@@ -25,3 +25,26 @@ typedef bool (*LvScreenshotUnlockFn)(void);
|
||||
*/
|
||||
void lvScreenshotAttachHttp(AsyncWebServer * www, LvScreenshotLockFn lock,
|
||||
LvScreenshotUnlockFn unlock, const char * path = "/screenshot");
|
||||
|
||||
// esp_lcd panel handle (matches the ESP-IDF typedef in <esp_lcd_types.h>). Forward-declared so this
|
||||
// header stays lightweight; the .cpp includes the real ESP-IDF headers.
|
||||
typedef struct esp_lcd_panel_t * esp_lcd_panel_handle_t;
|
||||
|
||||
/**
|
||||
* @brief RGB-panel variant of the /screenshot route. Instead of re-rendering the UI via lv_snapshot
|
||||
* (which allocates a fresh width*height*2 buffer), this reads the RGB panel's scan-out
|
||||
* framebuffer DIRECTLY — the pixels already on screen with every LVGL layer composited. On
|
||||
* ESP32-S3 RGB panels this is both lighter on PSRAM (no extra render buffer; the framebuffer
|
||||
* already exists) and captures exactly what is displayed, including overlays on lv_layer_top().
|
||||
* Prefer this over lvScreenshotAttachHttp() whenever the display is an RGB panel.
|
||||
* @param www the AsyncWebServer to attach to.
|
||||
* @param panelHandle the RGB LCD panel handle (e.g. board->getLCD()->getRefreshPanelHandle()).
|
||||
* @param width,height panel dimensions in pixels.
|
||||
* @param lock/unlock LVGL-lock hooks; the framebuffer is copied while locked (not mid-flush).
|
||||
* @param path route path (default "/screenshot").
|
||||
*
|
||||
* No-op if ESPAsyncWebServer is not available.
|
||||
*/
|
||||
void lvScreenshotAttachHttpRgb(AsyncWebServer * www, esp_lcd_panel_handle_t panelHandle,
|
||||
int width, int height, LvScreenshotLockFn lock,
|
||||
LvScreenshotUnlockFn unlock, const char * path = "/screenshot");
|
||||
|
||||
Reference in New Issue
Block a user