From d2b90bd83ef1d1faa7136fdb6a5db3f4556936b2 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Mon, 16 Mar 2026 18:10:10 -0500 Subject: [PATCH] Screenshot API added. --- dvx/dvxApp.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ dvx/dvxApp.h | 6 ++++ 2 files changed, 103 insertions(+) diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index 7ee1df3..e1bfa32 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -12,6 +12,8 @@ #include #include +#include "thirdparty/stb_image_write.h" + #define DBLCLICK_THRESHOLD (CLOCKS_PER_SEC / 2) #define ICON_REFRESH_INTERVAL 8 #define KB_MOVE_STEP 8 @@ -24,6 +26,7 @@ // Prototypes // ============================================================ +static uint8_t *bufferToRgb(const DisplayT *d, const uint8_t *buf, int32_t w, int32_t h, int32_t pitch); static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw, int32_t *ph); static bool checkAccelTable(AppContextT *ctx, WindowT *win, int32_t key, int32_t modifiers); static void clickMenuCheckRadio(MenuT *menu, int32_t itemIdx); @@ -78,6 +81,52 @@ static const char sAltScanToAscii[256] = { // calcPopupSize — compute popup width and height for a menu // ============================================================ +static uint8_t *bufferToRgb(const DisplayT *d, const uint8_t *buf, int32_t w, int32_t h, int32_t pitch) { + uint8_t *rgb = (uint8_t *)malloc((size_t)w * h * 3); + + if (!rgb) { + return NULL; + } + + int32_t bpp = d->format.bytesPerPixel; + uint8_t *dst = rgb; + + for (int32_t y = 0; y < h; y++) { + const uint8_t *row = buf + y * pitch; + + for (int32_t x = 0; x < w; x++) { + uint32_t pixel; + + if (bpp == 1) { + pixel = row[x]; + } else if (bpp == 2) { + pixel = ((const uint16_t *)row)[x]; + } else { + pixel = ((const uint32_t *)row)[x]; + } + + if (d->format.bitsPerPixel == 8) { + int32_t idx = pixel & 0xFF; + dst[0] = d->palette[idx * 3 + 0]; + dst[1] = d->palette[idx * 3 + 1]; + dst[2] = d->palette[idx * 3 + 2]; + } else { + uint32_t rv = (pixel >> d->format.redShift) & ((1u << d->format.redBits) - 1); + uint32_t gv = (pixel >> d->format.greenShift) & ((1u << d->format.greenBits) - 1); + uint32_t bv = (pixel >> d->format.blueShift) & ((1u << d->format.blueBits) - 1); + dst[0] = (uint8_t)(rv << (8 - d->format.redBits)); + dst[1] = (uint8_t)(gv << (8 - d->format.greenBits)); + dst[2] = (uint8_t)(bv << (8 - d->format.blueBits)); + } + + dst += 3; + } + } + + return rgb; +} + + static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw, int32_t *ph) { int32_t maxW = 0; bool hasSub = false; @@ -1389,6 +1438,28 @@ bool dvxUpdate(AppContextT *ctx) { } +// ============================================================ +// dvxScreenshot +// ============================================================ +// +// Save the entire screen (backbuffer) to a PNG file. + +int32_t dvxScreenshot(AppContextT *ctx, const char *path) { + DisplayT *d = &ctx->display; + uint8_t *rgb = bufferToRgb(d, d->backBuf, d->width, d->height, d->pitch); + + if (!rgb) { + return -1; + } + + int32_t result = stbi_write_png(path, d->width, d->height, 3, rgb, d->width * 3) ? 0 : -1; + + free(rgb); + + return result; +} + + // ============================================================ // dvxShutdown // ============================================================ @@ -1421,6 +1492,32 @@ int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path) { } +// ============================================================ +// dvxWindowScreenshot +// ============================================================ +// +// Save a window's content buffer to a PNG file. This captures the +// full content area regardless of whether the window is occluded. + +int32_t dvxWindowScreenshot(AppContextT *ctx, WindowT *win, const char *path) { + if (!win || !win->contentBuf) { + return -1; + } + + uint8_t *rgb = bufferToRgb(&ctx->display, win->contentBuf, win->contentW, win->contentH, win->contentPitch); + + if (!rgb) { + return -1; + } + + int32_t result = stbi_write_png(path, win->contentW, win->contentH, 3, rgb, win->contentW * 3) ? 0 : -1; + + free(rgb); + + return result; +} + + // ============================================================ // dvxTileWindows // ============================================================ diff --git a/dvx/dvxApp.h b/dvx/dvxApp.h index c2b1158..43a6bd5 100644 --- a/dvx/dvxApp.h +++ b/dvx/dvxApp.h @@ -91,6 +91,9 @@ void dvxMaximizeWindow(AppContextT *ctx, WindowT *win); // Request exit from main loop void dvxQuit(AppContextT *ctx); +// Save the entire screen to a PNG file (returns 0 on success, -1 on failure) +int32_t dvxScreenshot(AppContextT *ctx, const char *path); + // Set window title void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title); @@ -109,6 +112,9 @@ const BlitOpsT *dvxGetBlitOps(const AppContextT *ctx); // Load an icon for a window from an image file int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path); +// Save a window's content to a PNG file (returns 0 on success, -1 on failure) +int32_t dvxWindowScreenshot(AppContextT *ctx, WindowT *win, const char *path); + // Create an accelerator table (caller must free with dvxFreeAccelTable) AccelTableT *dvxCreateAccelTable(void);