feat: RGB-panel framebuffer-read path (lvScreenshotAttachHttpRgb) v1.1.0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
LvScreenshot KEYWORD1
|
LvScreenshot KEYWORD1
|
||||||
lvScreenshotTake KEYWORD2
|
lvScreenshotTake KEYWORD2
|
||||||
lvScreenshotAttachHttp KEYWORD2
|
lvScreenshotAttachHttp KEYWORD2
|
||||||
|
lvScreenshotAttachHttpRgb KEYWORD2
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "LvScreenshot",
|
"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.",
|
"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",
|
"keywords": "lvgl, screenshot, snapshot, capture, png, esp32",
|
||||||
"frameworks": "arduino",
|
"frameworks": "arduino",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name=LvScreenshot
|
name=LvScreenshot
|
||||||
version=1.0.0
|
version=1.1.0
|
||||||
author=xPablo.cz
|
author=xPablo.cz
|
||||||
maintainer=xPablo.cz <https://git.xpablo.cz/xPablo.cz>
|
maintainer=xPablo.cz <https://git.xpablo.cz/xPablo.cz>
|
||||||
sentence=Board-independent LVGL 9 screen capture with an optional /screenshot web endpoint.
|
sentence=Board-independent LVGL 9 screen capture with an optional /screenshot web endpoint.
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include "LvScreenshot.hpp"
|
#include "LvScreenshot.hpp"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <esp_lcd_panel_rgb.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// One shared capture buffer: the async response streams from it over ~1 s, so it must outlive the
|
// 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);
|
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
|
#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 lvScreenshotAttachHttp(AsyncWebServer *, LvScreenshotLockFn, LvScreenshotUnlockFn, const char *) {}
|
||||||
|
void lvScreenshotAttachHttpRgb(AsyncWebServer *, esp_lcd_panel_handle_t, int, int,
|
||||||
|
LvScreenshotLockFn, LvScreenshotUnlockFn, const char *) {}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -25,3 +25,26 @@ typedef bool (*LvScreenshotUnlockFn)(void);
|
|||||||
*/
|
*/
|
||||||
void lvScreenshotAttachHttp(AsyncWebServer * www, LvScreenshotLockFn lock,
|
void lvScreenshotAttachHttp(AsyncWebServer * www, LvScreenshotLockFn lock,
|
||||||
LvScreenshotUnlockFn unlock, const char * path = "/screenshot");
|
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