From 5527130145ab8f527ee1be5a27a8ef7a716feca1 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Mon, 23 Feb 2026 18:55:57 -0600 Subject: [PATCH] Add wdrvScreenshot, auto-demo mode, palette fixes, and DAC width detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add wdrvScreenshot() to capture the screen to PNG via stb_image_write.h, reading the framebuffer (or DDI bitmap fallback) and VGA DAC palette. Convert demo.c to non-interactive mode with automatic screenshots after each demo (DEMO01-15.PNG) and no keypress waits, plus per-driver DOSBox-X configs for automated testing. Set a standard Windows 3.1 256-color palette (8R x 8G x 4B color cube with 20 static system colors) to ensure consistent output across drivers. Fix wdrvSetPalette to also program the VGA DAC directly, since VBESVGA's SetPalette DDI updates its internal color table but not the hardware. Detect DAC width via VBE 4F08 (S3TRIO=6-bit, VBESVGA=8-bit) and use correct shift in both DAC writes and reads — fixes dark display on VBESVGA where 6-bit values in 8-bit DAC produced 1/4 brightness. Fix S3 dispYOffset: extend PDEVICE deHeight by the offset so the driver's internal clipping allows the full 600-row logical screen, rather than incorrectly reducing dpVertRes to 590. Co-Authored-By: Claude Opus 4.6 --- .gitignore | 3 + CLAUDE.md | 66 +- demo.c | 885 +++++++++++++++++- dosbox-et4000.conf | 37 + dosbox-s3trio.conf | 37 + dosbox-vbesvga.conf | 37 + dosbox-vga.conf | 37 + dosbox-x.conf | 8 +- win31drv/stb_image_write.h | 1724 +++++++++++++++++++++++++++++++++++ win31drv/windrv.c | 1747 +++++++++++++++++++++++++++++++++--- win31drv/windrv.h | 112 +++ 11 files changed, 4541 insertions(+), 152 deletions(-) create mode 100644 dosbox-et4000.conf create mode 100644 dosbox-s3trio.conf create mode 100644 dosbox-vbesvga.conf create mode 100644 dosbox-vga.conf create mode 100644 win31drv/stb_image_write.h diff --git a/.gitignore b/.gitignore index abaf4bb..ce86f8d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ bin/ # Runtime logs OUTPUT.LOG +# Screenshots +screenshots/ + # Editor backups *~ *.swp diff --git a/CLAUDE.md b/CLAUDE.md index 5cd6cb0..c3149a7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,14 +50,37 @@ windriver/ AND driver is not VGA-class (1bpp/4planes). - **Pattern scratch artifact**: S3 driver writes 8x8 dithered brush pattern to VRAM at fixed position (~(144,1)-(151,8)) during accelerated pattern fills. Fixed by shifting CRTC display - start down 10 scanlines (`dispYOffset`) so the scratch area is off-screen. + start down 10 scanlines (`dispYOffset`) so the scratch area is off-screen. All drawing Y + coordinates are offset by dispYOffset. The full dpVertRes (600) is reported and usable — + the shift just consumes slightly more VRAM. +- **S3TRIO BitBlt source corruption**: S3TRIO's accelerated BitBlt corrupts source VRAM + during source-dependent ROP operations (SRCINVERT, NOTSRCCOPY, SRCAND, SRCPAINT). + In Windows 3.1, GDI uses intermediate off-screen bitmaps. Our direct DDI calls must + work around this by redrawing source rects after the ROP, or using separate source areas. + Off-screen VRAM (y >= screenH) is NOT usable — the driver clips to screen dimensions. - **`-fno-gcse` required for windrv.c**: With -O2 GCSE, stack layout causes issues during 16-bit driver calls. Only windrv.c needs this. See `WINDRV_CFLAGS` in win31drv/Makefile. - Output DDI (polylines/rectangles) requires a **physical pen** from RealizeObject, not a raw LogPen16T. The pen must be in DGROUP (same as brush, drawMode, PDEVICE). +- **Curve primitives removed**: Ellipses, polygons, roundrects, arcs, and pies were removed + because DIB engine drivers (VBESVGA/ET4000) hang (expect GDI curve decomposition callback) + and S3TRIO only partially renders them. Only polyline is reliable via Output DDI. +- **OS_RECTANGLE crashes DIB engine drivers**: VBESVGA/ET4000 Output(OS_RECTANGLE) crashes + like curve primitives. `wdrvRectangleEx` draws rectangles as two 3-point polylines instead. +- **Output DDI lpClipRect**: DIB engine drivers (VBESVGA) dereference lpClipRect + unconditionally in polyline paths too. Always pass a valid clip rect (0,0,0x7FFF,0x7FFF). - `wdrvUnloadDriver` does NOT auto-call Disable — caller must handle text mode restore - `sleep()` hangs under DOSBox-X because BIOS timer ticks don't advance without I/O - Debug output: `-d` flag enables verbose logging in neload, winstub, thunk, and windrv +- **SetPalette DDI vs VGA DAC**: VBESVGA's SetPalette DDI updates the driver's internal + color table (used by ColorInfo for RGB→index matching) but does NOT program the VGA + DAC hardware. In real Windows 3.1, GDI programs the DAC separately. `wdrvSetPalette` + works around this by also writing DAC registers directly (ports 0x3C8/0x3C9) after + the DDI call. This is idempotent on drivers like S3TRIO that already program the DAC. +- **DAC width**: S3TRIO uses 6-bit DAC (values 0-63), VBESVGA uses 8-bit DAC (values + 0-255). Detected at Enable via VBE 4F08 subfunc 01. Both `wdrvSetPalette` (port write) + and `readDacPalette` (screenshot) use the detected width for correct shift amounts. + Wrong shift causes dark display (6-bit values in 8-bit DAC = 1/4 brightness). - Known issue: mode mismatch HW=800x600 vs GDIINFO=640x480 ## DGROUP Stack Management @@ -111,6 +134,11 @@ windriver/ - DOSBox-X machine type: `svga_et4000` for ET4000 hardware emulation - ET4000 is 640x480 8bpp, software-rendered (no accelerator engine in DOSBox-X) - CR30=0x00 on ET4000 → isS3=false → no S3 engine wait, no display start shift +- **GetPixel breaks ScanLR on ET4000**: The Pixel DDI with color=-1 (get mode) leaves + VGA hardware state (likely GR5 read mode) that causes ScanLR to not match the pixel + just read. GetPixel itself returns correct values; only the ScanLR interaction is + broken. Flood fill software path avoids GetPixel entirely, using ScanLR for all + pixel-color queries. ## Font Loading Notes - .FON files are NE containers with RT_FONT (type 8) resources; each resource is raw .FNT data @@ -133,6 +161,42 @@ windriver/ - Demo 4: Screen-to-screen blit (BitBlt SRCCOPY) — works - Demo 5: ExtTextOut text rendering — works (VBESVGA.DRV) - Demo 7: TrueType font rendering at multiple sizes — works +- Demo 8: Color text showcase (fg/bg colors, opaque/transparent, palette grid) — works +- Demo 9: ROP3 operations (DSTINVERT, SRCINVERT, NOTSRCCOPY, SRCAND, SRCPAINT) — works +- Demo 10: ScanLR + Flood Fill (FB or software fallback) — works on all drivers +- Demo 11: Text measurement (GetCharWidth DDI, wdrvMeasureText) — works +- Demo 12: Styled pen lines (software Bresenham, all drivers) — works +- Demo 13: Pixel buffer blit (FB or software fallback) — works on all drivers +- Demo 14: Hardware cursor (arrow, crosshair, ibeam, hand; animated circle) — works +- Demo 15: Screen save/restore (screen-to-screen BitBlt stash/restore) — works - VGA.DRV: 640x480 4-plane 16-color mode; limited color palette but functional - ET4000.DRV: 640x480 8bpp on svga_et4000; software-only, no hw acceleration - Drivers stored in `drivers/` directory, copied to `bin/` during build + +## Software Rendering Fallbacks +- **Styled pens**: Always software-rendered via Bresenham + Pixel DDI (PS_SOLID uses HW Output DDI). + S3TRIO silently accepts styled pens but doesn't render them; software path gives identical output everywhere. +- **Flood fill**: Uses direct FB when available, falls back to ScanLR DDI + FillRect. + Cannot use GetPixel — ET4000 DIB engine's Pixel DDI (color=-1) corrupts VRAM, + causing subsequent ScanLR to not match the read pixel. Seed color is determined by + probing ScanLR with each palette index until a match is found. +- **Pixel blit**: Uses direct FB memcpy when available, falls back to per-pixel SetPixel with PALETTEINDEX. +- All four drivers (S3TRIO, VBESVGA, ET4000, VGA) now have identical feature sets. + +## New API Functions (added with demos 9-15) +- `wdrvScanLR(handle, x, y, color, style)` — ScanLR DDI wrapper (ordinal 12) +- `wdrvFloodFill(handle, x, y, fillColor)` — scanline flood fill (FB or software) +- `wdrvGetCharWidths(handle, font, firstChar, lastChar, widths)` — GetCharWidth DDI +- `wdrvMeasureText(handle, font, text, length)` — sum char widths for string +- `wdrvPolylineEx(handle, points, count, color, penStyle)` — polyline with pen style +- `wdrvRectangleEx(handle, x, y, w, h, color, penStyle)` — rectangle with pen style +- `wdrvBlitPixels(handle, x, y, w, h, pixels, srcPitch)` — pixel blit (FB or software) +- `wdrvBlitBmp(handle, x, y, bmpPath, setPalette)` — load+display 8bpp BMP +- `wdrvSetCursor(handle, shape)` — built-in cursor shapes (arrow/crosshair/ibeam/hand/none) +- `wdrvSetCursorCustom(handle, hotX, hotY, andMask, xorMask)` — custom 32x32 mono cursor +- `wdrvMoveCursor(handle, x, y)` — move hardware cursor +- `wdrvCreateBitmap(handle, width, height)` — CreateBitmap DDI +- `wdrvDeleteBitmap(handle, bitmap)` — DeleteBitmap DDI +- `wdrvBitmapSetPixels/GetPixels(handle, bitmap, data, size)` — BitmapBits DDI +- `wdrvBitBltFromBitmap/ToBitmap(handle, bitmap, ...)` — BitBlt with bitmap PDEVICE +- `wdrvScreenshot(handle, filename)` — capture screen to PNG (FB or DDI fallback) diff --git a/demo.c b/demo.c index 9fdf053..638ad9a 100644 --- a/demo.c +++ b/demo.c @@ -102,13 +102,7 @@ int main(int argc, char *argv[]) // Run drawing demos demoDrawing(drv); - logMsg("Drawing complete. Press any key...\n"); - - // Wait for a keypress so we can see the output - while (!kbhit()) { - // Busy-wait; kbhit() does keyboard I/O which keeps DOSBox-X responsive - } - (void)getkey(); // consume the key + logMsg("Drawing complete.\n"); // Disable the driver (restore text mode) logMsg("Calling wdrvDisable...\n"); @@ -162,6 +156,10 @@ static void printDriverInfo(WdrvHandleT drv) logMsg(" ExtTextOut: %s\n", info.hasExtTextOut ? "yes" : "no"); logMsg(" SetPalette: %s\n", info.hasSetPalette ? "yes" : "no"); logMsg(" SetCursor: %s\n", info.hasSetCursor ? "yes" : "no"); + logMsg(" MoveCursor: %s\n", info.hasMoveCursor ? "yes" : "no"); + logMsg(" ScanLR: %s\n", info.hasScanLR ? "yes" : "no"); + logMsg(" GetCharWidth:%s\n", info.hasGetCharWidth ? "yes" : "no"); + logMsg(" CreateBitmap:%s\n", info.hasCreateBitmap ? "yes" : "no"); } @@ -225,6 +223,7 @@ static void demoDrawing(WdrvHandleT drv) } logMsg(" Drew %d colored rectangles\n", 16); + wdrvScreenshot(drv, "DEMO01.PNG"); } // Demo 2: Draw pixel patterns @@ -241,6 +240,7 @@ static void demoDrawing(WdrvHandleT drv) } } logMsg(" Drew %d pixels\n", pixCount); + wdrvScreenshot(drv, "DEMO02.PNG"); } // Demo 3: Draw lines using Output (polyline with realized pen) @@ -290,6 +290,7 @@ static void demoDrawing(WdrvHandleT drv) lineCount++; } logMsg(" Drew %d lines\n", lineCount); + wdrvScreenshot(drv, "DEMO03.PNG"); } // Demo 4: Screen-to-screen blit test @@ -307,6 +308,7 @@ static void demoDrawing(WdrvHandleT drv) bp.rop3 = SRCCOPY; wdrvBitBlt(drv, &bp); logMsg(" Screen blit done\n"); + wdrvScreenshot(drv, "DEMO04.PNG"); } // Demo 5: Text output using ExtTextOut @@ -340,6 +342,7 @@ static void demoDrawing(WdrvHandleT drv) logMsg(" msg4 ret=%" PRId32 "\n", ret); logMsg(" Text output done\n"); + wdrvScreenshot(drv, "DEMO05.PNG"); } // Demo 6: Multiple loaded fonts @@ -394,6 +397,7 @@ static void demoDrawing(WdrvHandleT drv) wdrvUnloadFont(sansFont); wdrvUnloadFont(sysFont); logMsg(" Font demo done\n"); + wdrvScreenshot(drv, "DEMO06.PNG"); } // Demo 7: TrueType font rendering @@ -440,17 +444,872 @@ static void demoDrawing(WdrvHandleT drv) wdrvUnloadFont(ttfMedium); wdrvUnloadFont(ttfLarge); logMsg(" TTF demo done\n"); + wdrvScreenshot(drv, "DEMO07.PNG"); + } + + // Demo 8: Color text showcase + if (info.hasBitBlt && info.hasExtTextOut) { + logMsg("Demo 8: Color text showcase\n"); + int32_t ret; + + // Clear screen to black + WdrvBitBltParamsT bp; + memset(&bp, 0, sizeof(bp)); + bp.width = screenW; + bp.height = screenH; + bp.rop3 = BLACKNESS; + wdrvBitBlt(drv, &bp); + + // Load fonts for this demo + WdrvFontT ttfSans20 = wdrvLoadFontTtf("LIBSANS.TTF", 20); + WdrvFontT ttfSans16 = wdrvLoadFontTtf("LIBSANS.TTF", 16); + WdrvFontT courFont = wdrvLoadFontFon("COURE.FON", 1); + WdrvFontT sansFont = wdrvLoadFontFon("SSERIFE.FON", 0); + WdrvFontT ttfSerif16 = wdrvLoadFontTtf("LIBSERIF.TTF", 16); + + // --- Title row (y=10) --- + if (ttfSans20) { + const char *title = "Color Text Demo"; + ret = wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, ttfSans20); + logMsg(" title ret=%" PRId32 "\n", ret); + } + + // --- Foreground color row (y=40) --- + if (ttfSans16) { + static const struct { const char *text; uint32_t color; } fgItems[] = { + { "Red", MAKE_RGB(255, 0, 0) }, + { "Green", MAKE_RGB( 0, 255, 0) }, + { "Blue", MAKE_RGB( 0, 128, 255) }, + { "Yellow", MAKE_RGB(255, 255, 0) }, + { "Cyan", MAKE_RGB( 0, 255, 255) }, + { "Magenta", MAKE_RGB(255, 0, 255) }, + { "White", MAKE_RGB(255, 255, 255) }, + }; + int16_t fx = 10; + for (int i = 0; i < 7; i++) { + ret = wdrvExtTextOut(drv, fx, 40, fgItems[i].text, (int16_t)strlen(fgItems[i].text), + fgItems[i].color, MAKE_RGB(0, 0, 0), false, ttfSans16); + logMsg(" fg[%d] ret=%" PRId32 "\n", i, ret); + fx += (int16_t)(strlen(fgItems[i].text) * 10 + 16); + } + } + + // --- Opaque background row (y=65) --- + if (ttfSans16) { + static const struct { const char *text; uint32_t fg; uint32_t bg; } bgItems[] = { + { "White on Blue", MAKE_RGB(255, 255, 255), MAKE_RGB( 0, 0, 200) }, + { "Black on Yellow", MAKE_RGB( 0, 0, 0), MAKE_RGB(255, 255, 0) }, + { "White on Red", MAKE_RGB(255, 255, 255), MAKE_RGB(200, 0, 0) }, + { "Black on Green", MAKE_RGB( 0, 0, 0), MAKE_RGB( 0, 200, 0) }, + { "Yellow on Magenta", MAKE_RGB(255, 255, 0), MAKE_RGB(180, 0, 180) }, + { "Black on Cyan", MAKE_RGB( 0, 0, 0), MAKE_RGB( 0, 200, 200) }, + }; + int16_t bx = 10; + for (int i = 0; i < 6; i++) { + ret = wdrvExtTextOut(drv, bx, 65, bgItems[i].text, (int16_t)strlen(bgItems[i].text), + bgItems[i].fg, bgItems[i].bg, true, ttfSans16); + logMsg(" bg[%d] ret=%" PRId32 "\n", i, ret); + bx += (int16_t)(strlen(bgItems[i].text) * 9 + 12); + } + } + + // --- Transparent over colored rectangles (y=95) --- + // Dark blue strip + wdrvFillRect(drv, 10, 95, 600, 25, MAKE_RGB(0, 0, 128)); + if (ttfSans16) { + const char *msg = "Transparent text over dark blue rectangle"; + ret = wdrvExtTextOut(drv, 20, 98, msg, (int16_t)strlen(msg), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, ttfSans16); + logMsg(" trans1 ret=%" PRId32 "\n", ret); + } + + // Dark green strip + wdrvFillRect(drv, 10, 125, 600, 25, MAKE_RGB(0, 128, 0)); + if (ttfSans16) { + const char *msg = "Yellow text over dark green rectangle"; + ret = wdrvExtTextOut(drv, 20, 128, msg, (int16_t)strlen(msg), + MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, ttfSans16); + logMsg(" trans2 ret=%" PRId32 "\n", ret); + } + + // --- Multiple fonts in color (y=160) --- + { + int16_t fy = 160; + + const char *fontMsg = "The quick brown fox jumps over the lazy dog"; + int16_t fontLen = (int16_t)strlen(fontMsg); + + // Built-in: white on dark red + ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen, + MAKE_RGB(255, 255, 255), MAKE_RGB(170, 0, 0), true, NULL); + logMsg(" font-builtin ret=%" PRId32 "\n", ret); + fy += 20; + + // Courier .FON: cyan on black + if (courFont) { + ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen, + MAKE_RGB(0, 255, 255), MAKE_RGB(0, 0, 0), true, courFont); + logMsg(" font-cour ret=%" PRId32 "\n", ret); + fy += 20; + } + + // Sans .FON: yellow on dark blue + if (sansFont) { + ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen, + MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 128), true, sansFont); + logMsg(" font-sans ret=%" PRId32 "\n", ret); + fy += 20; + } + + // TTF Sans: green on dark gray + if (ttfSans16) { + ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen, + MAKE_RGB(0, 255, 0), MAKE_RGB(64, 64, 64), true, ttfSans16); + logMsg(" font-ttfsans ret=%" PRId32 "\n", ret); + fy += 20; + } + + // TTF Serif: magenta on dark green + if (ttfSerif16) { + ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen, + MAKE_RGB(255, 0, 255), MAKE_RGB(0, 100, 0), true, ttfSerif16); + logMsg(" font-ttfserif ret=%" PRId32 "\n", ret); + } + } + + // --- Color palette grid (y=280) --- + { + static const struct { const char *name; uint32_t color; } fgPal[] = { + { "Bk", MAKE_RGB( 0, 0, 0) }, + { "Rd", MAKE_RGB(255, 0, 0) }, + { "Gn", MAKE_RGB( 0, 255, 0) }, + { "Bl", MAKE_RGB( 0, 128, 255) }, + { "Yw", MAKE_RGB(255, 255, 0) }, + { "Cn", MAKE_RGB( 0, 255, 255) }, + { "Mg", MAKE_RGB(255, 0, 255) }, + { "Wh", MAKE_RGB(255, 255, 255) }, + }; + static const struct { const char *name; uint32_t color; } bgPal[] = { + { "Black", MAKE_RGB( 0, 0, 0) }, + { "Blue", MAKE_RGB( 0, 0, 200) }, + { "Red", MAKE_RGB(200, 0, 0) }, + { "White", MAKE_RGB(255, 255, 255) }, + }; + + int16_t gy = 280; + + // Column headers (background names) + int16_t hdrX = 10 + 30; // offset past row labels + for (int bg = 0; bg < 4; bg++) { + ret = wdrvExtTextOut(drv, (int16_t)(hdrX + bg * 75), gy, + bgPal[bg].name, (int16_t)strlen(bgPal[bg].name), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + gy += 18; + + // Grid cells + for (int fg = 0; fg < 8; fg++) { + // Row label + ret = wdrvExtTextOut(drv, 10, gy, + fgPal[fg].name, (int16_t)strlen(fgPal[fg].name), + MAKE_RGB(170, 170, 170), MAKE_RGB(0, 0, 0), false, NULL); + + for (int bg = 0; bg < 4; bg++) { + ret = wdrvExtTextOut(drv, (int16_t)(40 + bg * 75), gy, + "Aa", 2, + fgPal[fg].color, bgPal[bg].color, true, NULL); + } + gy += 18; + } + logMsg(" palette grid done\n"); + } + + wdrvUnloadFont(ttfSans20); + wdrvUnloadFont(ttfSans16); + wdrvUnloadFont(courFont); + wdrvUnloadFont(sansFont); + wdrvUnloadFont(ttfSerif16); + logMsg(" Color text demo done\n"); + wdrvScreenshot(drv, "DEMO08.PNG"); + } + + // Demo 9: ROP3 Operations + if (info.hasBitBlt) { + logMsg("Demo 9: ROP3 Operations\n"); + int32_t ret; + WdrvBitBltParamsT bp; + + // Clear screen + memset(&bp, 0, sizeof(bp)); + bp.width = screenW; + bp.height = screenH; + bp.rop3 = BLACKNESS; + wdrvBitBlt(drv, &bp); + + // Title + if (info.hasExtTextOut) { + const char *title = "Demo 9: ROP3 Operations"; + wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Layout: 5 columns, 3 rows + // Row 1 (r1): source color A (also used as BitBlt source) + // Row 2 (r2): operand color B + // Row 3 (r3): ROP result + // S3TRIO's accelerated BitBlt corrupts source VRAM during source- + // dependent ROPs, so each source rect is redrawn after the ROP. + int16_t bx = 20; + int16_t by = 40; + int16_t bw = 100; + int16_t bh = 50; + int16_t gap = 5; + int16_t col = bw + 20; + int16_t r1 = by; + int16_t r2 = by + bh + gap; + int16_t r3 = by + 2 * (bh + gap); + + // Column 0: DSTINVERT (no source needed) + wdrvFillRect(drv, bx, r1, bw, bh, MAKE_RGB(255, 0, 0)); + memset(&bp, 0, sizeof(bp)); + bp.dstX = bx + 10; + bp.dstY = r1 + 10; + bp.width = bw - 20; + bp.height = bh - 20; + bp.rop3 = DSTINVERT; + ret = wdrvBitBlt(drv, &bp); + logMsg(" DSTINVERT ret=%" PRId32 "\n", ret); + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, bx, r3 + bh + 5, "DSTINVERT", 9, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Column 1: SRCINVERT (green XOR yellow) + // Fill source (r1) with green, copy to result (r3) via SRCCOPY + wdrvFillRect(drv, bx + col, r1, bw, bh, MAKE_RGB(0, 255, 0)); + memset(&bp, 0, sizeof(bp)); + bp.srcX = bx + col; + bp.srcY = r1; + bp.dstX = bx + col; + bp.dstY = r3; + bp.width = bw; + bp.height = bh; + bp.rop3 = SRCCOPY; + wdrvBitBlt(drv, &bp); + wdrvFillRect(drv, bx + col, r1, bw, bh, MAKE_RGB(0, 255, 0)); + // Fill source (r1) with yellow, SRCINVERT into result (r3) + wdrvFillRect(drv, bx + col, r1, bw, bh, MAKE_RGB(255, 255, 0)); + memset(&bp, 0, sizeof(bp)); + bp.srcX = bx + col; + bp.srcY = r1; + bp.dstX = bx + col; + bp.dstY = r3; + bp.width = bw; + bp.height = bh; + bp.rop3 = SRCINVERT; + ret = wdrvBitBlt(drv, &bp); + logMsg(" SRCINVERT ret=%" PRId32 "\n", ret); + // Redraw display rows (source may be corrupted by S3TRIO) + wdrvFillRect(drv, bx + col, r1, bw, bh, MAKE_RGB(0, 255, 0)); + wdrvFillRect(drv, bx + col, r2, bw, bh, MAKE_RGB(255, 255, 0)); + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, bx + col, r3 + bh + 5, "SRCINVERT", 9, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Column 2: NOTSRCCOPY (NOT blue) + wdrvFillRect(drv, bx + 2 * col, r1, bw, bh, MAKE_RGB(0, 0, 255)); + memset(&bp, 0, sizeof(bp)); + bp.srcX = bx + 2 * col; + bp.srcY = r1; + bp.dstX = bx + 2 * col; + bp.dstY = r3; + bp.width = bw; + bp.height = bh; + bp.rop3 = NOTSRCCOPY; + ret = wdrvBitBlt(drv, &bp); + logMsg(" NOTSRCCOPY ret=%" PRId32 "\n", ret); + // Redraw source display + wdrvFillRect(drv, bx + 2 * col, r1, bw, bh, MAKE_RGB(0, 0, 255)); + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, bx + 2 * col, r3 + bh + 5, "NOTSRCCOPY", 10, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Column 3: SRCAND (yellow AND cyan) + // Fill result (r3) with cyan, fill source (r1) with yellow, SRCAND + wdrvFillRect(drv, bx + 3 * col, r3, bw, bh, MAKE_RGB(0, 255, 255)); + wdrvFillRect(drv, bx + 3 * col, r1, bw, bh, MAKE_RGB(255, 255, 0)); + memset(&bp, 0, sizeof(bp)); + bp.srcX = bx + 3 * col; + bp.srcY = r1; + bp.dstX = bx + 3 * col; + bp.dstY = r3; + bp.width = bw; + bp.height = bh; + bp.rop3 = SRCAND; + ret = wdrvBitBlt(drv, &bp); + logMsg(" SRCAND ret=%" PRId32 "\n", ret); + // Redraw display rows + wdrvFillRect(drv, bx + 3 * col, r1, bw, bh, MAKE_RGB(255, 255, 0)); + wdrvFillRect(drv, bx + 3 * col, r2, bw, bh, MAKE_RGB(0, 255, 255)); + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, bx + 3 * col, r3 + bh + 5, "SRCAND", 6, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Column 4: SRCPAINT (red OR blue) + // Fill result (r3) with blue, fill source (r1) with red, SRCPAINT + wdrvFillRect(drv, bx + 4 * col, r3, bw, bh, MAKE_RGB(0, 0, 255)); + wdrvFillRect(drv, bx + 4 * col, r1, bw, bh, MAKE_RGB(255, 0, 0)); + memset(&bp, 0, sizeof(bp)); + bp.srcX = bx + 4 * col; + bp.srcY = r1; + bp.dstX = bx + 4 * col; + bp.dstY = r3; + bp.width = bw; + bp.height = bh; + bp.rop3 = SRCPAINT; + ret = wdrvBitBlt(drv, &bp); + logMsg(" SRCPAINT ret=%" PRId32 "\n", ret); + // Redraw display rows + wdrvFillRect(drv, bx + 4 * col, r1, bw, bh, MAKE_RGB(255, 0, 0)); + wdrvFillRect(drv, bx + 4 * col, r2, bw, bh, MAKE_RGB(0, 0, 255)); + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, bx + 4 * col, r3 + bh + 5, "SRCPAINT", 8, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + logMsg(" ROP3 demo done\n"); + wdrvScreenshot(drv, "DEMO09.PNG"); + } + + // Demo 10: ScanLR + Flood Fill + if (info.hasBitBlt) { + logMsg("Demo 10: ScanLR + Flood Fill\n"); + WdrvBitBltParamsT bp; + + // Clear screen + memset(&bp, 0, sizeof(bp)); + bp.width = screenW; + bp.height = screenH; + bp.rop3 = BLACKNESS; + wdrvBitBlt(drv, &bp); + + if (info.hasExtTextOut) { + const char *title = "Demo 10: ScanLR + Flood Fill"; + wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Draw outlined shapes using wdrvRectangle + if (info.hasOutput) { + wdrvRectangle(drv, 50, 50, 150, 100, MAKE_RGB(255, 255, 255)); + wdrvRectangle(drv, 250, 50, 150, 100, MAKE_RGB(255, 255, 0)); + wdrvRectangle(drv, 450, 50, 150, 100, MAKE_RGB(0, 255, 255)); + } + + // Try flood fill (FB-based, skips multi-plane VGA) + int32_t ret = wdrvFloodFill(drv, 125, 100, MAKE_RGB(255, 0, 0)); + logMsg(" FloodFill rect1 ret=%" PRId32 "\n", ret); + + ret = wdrvFloodFill(drv, 325, 100, MAKE_RGB(0, 255, 0)); + logMsg(" FloodFill rect2 ret=%" PRId32 "\n", ret); + + ret = wdrvFloodFill(drv, 525, 100, MAKE_RGB(0, 0, 255)); + logMsg(" FloodFill rect3 ret=%" PRId32 "\n", ret); + + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, 80, 155, "Red Fill", 8, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + wdrvExtTextOut(drv, 280, 155, "Green Fill", 10, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + wdrvExtTextOut(drv, 480, 155, "Blue Fill", 9, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Test ScanLR if available + if (info.hasScanLR) { + int16_t leftX = wdrvScanLR(drv, 125, 100, MAKE_RGB(255, 0, 0), WDRV_SCAN_LEFT); + int16_t rightX = wdrvScanLR(drv, 125, 100, MAKE_RGB(255, 0, 0), WDRV_SCAN_RIGHT); + logMsg(" ScanLR at (125,100): left=%d right=%d\n", leftX, rightX); + + if (info.hasExtTextOut) { + char buf[64]; + int len = sprintf(buf, "ScanLR: L=%d R=%d", leftX, rightX); + wdrvExtTextOut(drv, 10, 200, buf, (int16_t)len, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + } + + logMsg(" Flood fill demo done\n"); + wdrvScreenshot(drv, "DEMO10.PNG"); + } + + // Demo 11: Text Measurement + if (info.hasExtTextOut && info.hasGetCharWidth) { + logMsg("Demo 11: Text Measurement\n"); + WdrvBitBltParamsT bp; + + // Clear screen + memset(&bp, 0, sizeof(bp)); + bp.width = screenW; + bp.height = screenH; + bp.rop3 = BLACKNESS; + wdrvBitBlt(drv, &bp); + + if (info.hasExtTextOut) { + const char *title = "Demo 11: Text Measurement"; + wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + int16_t ty = 40; + + // Measure built-in font + const char *testStr = "Hello, World!"; + int16_t testLen = (int16_t)strlen(testStr); + int32_t measuredW = wdrvMeasureText(drv, NULL, testStr, testLen); + logMsg(" Built-in '%s' width=%" PRId32 "\n", testStr, measuredW); + + wdrvExtTextOut(drv, 10, ty, testStr, testLen, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 128), true, NULL); + // Draw underline at measured width + if (info.hasOutput) { + Point16T pts[2]; + pts[0].x = 10; + pts[0].y = ty + 18; + pts[1].x = 10 + (int16_t)measuredW; + pts[1].y = ty + 18; + wdrvPolyline(drv, pts, 2, MAKE_RGB(255, 0, 0)); + } + + char buf[80]; + int len = sprintf(buf, "Width = %" PRId32 " pixels", measuredW); + wdrvExtTextOut(drv, 10 + (int16_t)measuredW + 10, ty, + buf, (int16_t)len, + MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, NULL); + ty += 30; + + // Measure TTF font + WdrvFontT ttfFont = wdrvLoadFontTtf("LIBSANS.TTF", 16); + if (ttfFont) { + const char *ttfStr = "TrueType measurement test"; + int16_t ttfLen = (int16_t)strlen(ttfStr); + int32_t ttfW = wdrvMeasureText(drv, ttfFont, ttfStr, ttfLen); + logMsg(" TTF '%s' width=%" PRId32 "\n", ttfStr, ttfW); + + wdrvExtTextOut(drv, 10, ty, ttfStr, ttfLen, + MAKE_RGB(0, 255, 255), MAKE_RGB(0, 64, 64), true, ttfFont); + if (info.hasOutput) { + Point16T pts[2]; + pts[0].x = 10; + pts[0].y = ty + 20; + pts[1].x = 10 + (int16_t)ttfW; + pts[1].y = ty + 20; + wdrvPolyline(drv, pts, 2, MAKE_RGB(255, 0, 0)); + } + + len = sprintf(buf, "Width = %" PRId32 " pixels", ttfW); + wdrvExtTextOut(drv, 10 + (int16_t)ttfW + 10, ty, + buf, (int16_t)len, + MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, NULL); + ty += 30; + + wdrvUnloadFont(ttfFont); + } + + // Show individual char widths + int16_t charWidths[26]; + int32_t ret = wdrvGetCharWidths(drv, NULL, 'A', 'Z', charWidths); + if (ret == WDRV_OK) { + int16_t cx = 10; + for (int ci = 0; ci < 26; ci++) { + char ch[2] = { (char)('A' + ci), 0 }; + wdrvExtTextOut(drv, cx, ty, ch, 1, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + cx += charWidths[ci]; + } + ty += 20; + + len = sprintf(buf, "Char widths A-Z from GetCharWidth DDI"); + wdrvExtTextOut(drv, 10, ty, buf, (int16_t)len, + MAKE_RGB(170, 170, 170), MAKE_RGB(0, 0, 0), false, NULL); + } + + logMsg(" Text measurement demo done\n"); + wdrvScreenshot(drv, "DEMO11.PNG"); + } + + // Demo 12: Styled Pen Lines + if (info.hasOutput) { + logMsg("Demo 12: Styled Pen Lines\n"); + WdrvBitBltParamsT bp; + + // Clear screen + memset(&bp, 0, sizeof(bp)); + bp.width = screenW; + bp.height = screenH; + bp.rop3 = BLACKNESS; + wdrvBitBlt(drv, &bp); + + if (info.hasExtTextOut) { + const char *title = "Demo 12: Styled Pen Lines"; + wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + static const struct { const char *name; int16_t style; } penStyles[] = { + { "SOLID", WDRV_PEN_SOLID }, + { "DASH", WDRV_PEN_DASH }, + { "DOT", WDRV_PEN_DOT }, + { "DASHDOT", WDRV_PEN_DASHDOT }, + { "DASHDOTDOT", WDRV_PEN_DASHDOTDOT }, + }; + + int16_t ly = 40; + for (int si = 0; si < 5; si++) { + // Draw horizontal line + Point16T pts[2]; + pts[0].x = 120; + pts[0].y = ly + 8; + pts[1].x = screenW - 20; + pts[1].y = ly + 8; + int32_t ret = wdrvPolylineEx(drv, pts, 2, MAKE_RGB(255, 255, 255), penStyles[si].style); + logMsg(" %s ret=%" PRId32 "\n", penStyles[si].name, ret); + + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, 10, ly, penStyles[si].name, (int16_t)strlen(penStyles[si].name), + MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, NULL); + } + ly += 25; + } + + // Draw styled rectangles + ly += 10; + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, 10, ly, "Styled Rectangles:", 18, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + ly += 20; + + int16_t rx = 20; + for (int si = 0; si < 5; si++) { + wdrvRectangleEx(drv, rx, ly, 100, 60, MAKE_RGB(0, 255, 255), penStyles[si].style); + rx += 120; + } + + logMsg(" Styled pen demo done\n"); + wdrvScreenshot(drv, "DEMO12.PNG"); + } + + // Demo 13: Pixel Buffer Blit + if (info.hasBitBlt) { + logMsg("Demo 13: Pixel Buffer Blit\n"); + WdrvBitBltParamsT bp; + + // Clear screen + memset(&bp, 0, sizeof(bp)); + bp.width = screenW; + bp.height = screenH; + bp.rop3 = BLACKNESS; + wdrvBitBlt(drv, &bp); + + if (info.hasExtTextOut) { + const char *title = "Demo 13: Pixel Buffer Blit"; + wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Generate a 256-color gradient pattern + int16_t patW = 256; + int16_t patH = 200; + uint8_t *pattern = (uint8_t *)malloc(patW * patH); + + if (pattern) { + for (int16_t py = 0; py < patH; py++) { + for (int16_t px = 0; px < patW; px++) { + pattern[py * patW + px] = (uint8_t)((px + py) & 0xFF); + } + } + + int32_t ret = wdrvBlitPixels(drv, 20, 40, patW, patH, pattern, patW); + logMsg(" BlitPixels gradient ret=%" PRId32 "\n", ret); + free(pattern); + } + + // Generate a color bars pattern + int16_t barW = 256; + int16_t barH = 100; + uint8_t *bars = (uint8_t *)malloc(barW * barH); + + if (bars) { + for (int16_t py = 0; py < barH; py++) { + for (int16_t px = 0; px < barW; px++) { + bars[py * barW + px] = (uint8_t)(px); + } + } + + int32_t ret = wdrvBlitPixels(drv, 300, 40, barW, barH, bars, barW); + logMsg(" BlitPixels bars ret=%" PRId32 "\n", ret); + free(bars); + } + + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, 20, 250, "Gradient pattern", 16, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + wdrvExtTextOut(drv, 300, 250, "Color bars", 10, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + logMsg(" Pixel blit demo done\n"); + wdrvScreenshot(drv, "DEMO13.PNG"); + } + + // Demo 14: Hardware Cursor + if (info.hasSetCursor && info.hasMoveCursor) { + logMsg("Demo 14: Hardware Cursor\n"); + WdrvBitBltParamsT bp; + + // Clear screen + memset(&bp, 0, sizeof(bp)); + bp.width = screenW; + bp.height = screenH; + bp.rop3 = BLACKNESS; + wdrvBitBlt(drv, &bp); + + if (info.hasExtTextOut) { + const char *title = "Demo 14: Hardware Cursor"; + wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + wdrvExtTextOut(drv, 10, 30, "Cursor shapes cycling automatically.", 36, + MAKE_RGB(170, 170, 170), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Draw some background content + wdrvFillRect(drv, 100, 100, 200, 200, MAKE_RGB(0, 0, 128)); + wdrvFillRect(drv, 350, 100, 200, 200, MAKE_RGB(0, 128, 0)); + + static const WdrvCursorShapeE shapes[] = { + WDRV_CURSOR_ARROW, + WDRV_CURSOR_CROSSHAIR, + WDRV_CURSOR_IBEAM, + WDRV_CURSOR_HAND, + }; + static const char *shapeNames[] = { + "Arrow", "Crosshair", "I-Beam", "Hand", + }; + + for (int si = 0; si < 4; si++) { + int32_t ret = wdrvSetCursor(drv, shapes[si]); + logMsg(" SetCursor(%s) ret=%" PRId32 "\n", shapeNames[si], ret); + + if (info.hasExtTextOut) { + // Clear label area + wdrvFillRect(drv, 10, 50, 400, 20, MAKE_RGB(0, 0, 0)); + char buf[64]; + int len = sprintf(buf, "Cursor: %s", shapeNames[si]); + wdrvExtTextOut(drv, 10, 50, buf, (int16_t)len, + MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Animate cursor in a circle + int16_t cx = screenW / 2; + int16_t cy = screenH / 2; + int16_t radius = 120; + + for (int angle = 0; angle < 360; angle += 5) { + int a = angle % 360; + int qa = a % 90; + int32_t s = (int32_t)qa * radius / 90; + int32_t c = (int32_t)(90 - qa) * radius / 90; + int16_t mx = cx; + int16_t my = cy; + + if (a < 90) { + mx = (int16_t)(cx + s); + my = (int16_t)(cy - c); + } else if (a < 180) { + mx = (int16_t)(cx + c); + my = (int16_t)(cy + s); + } else if (a < 270) { + mx = (int16_t)(cx - s); + my = (int16_t)(cy + c); + } else { + mx = (int16_t)(cx - c); + my = (int16_t)(cy - s); + } + + wdrvMoveCursor(drv, mx, my); + + // Small delay via port I/O + for (int d = 0; d < 500; d++) { + (void)inportb(0x80); + } + } + } + + // Hide cursor + wdrvSetCursor(drv, WDRV_CURSOR_NONE); + logMsg(" Cursor demo done\n"); + wdrvScreenshot(drv, "DEMO14.PNG"); + } + + // Demo 15: Screen Save/Restore (via screen-to-screen BitBlt) + if (info.hasBitBlt) { + logMsg("Demo 15: Screen Save/Restore\n"); + WdrvBitBltParamsT bp; + + // Clear screen + memset(&bp, 0, sizeof(bp)); + bp.width = screenW; + bp.height = screenH; + bp.rop3 = BLACKNESS; + wdrvBitBlt(drv, &bp); + + if (info.hasExtTextOut) { + const char *title = "Demo 15: Screen Save/Restore"; + wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title), + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Draw source content at (20,40) + int16_t saveX = 20; + int16_t saveY = 40; + int16_t saveW = 100; + int16_t saveH = 80; + + wdrvFillRect(drv, saveX, saveY, saveW, saveH, MAKE_RGB(255, 0, 0)); + wdrvFillRect(drv, saveX + 20, saveY + 10, 60, 60, MAKE_RGB(0, 255, 0)); + wdrvFillRect(drv, saveX + 30, saveY + 20, 40, 30, MAKE_RGB(0, 0, 255)); + + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, saveX, saveY + saveH + 5, "Source", 6, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + // Save: blit source region to a hidden screen area (bottom of screen) + int16_t stashY = screenH - saveH - 1; + memset(&bp, 0, sizeof(bp)); + bp.srcX = saveX; + bp.srcY = saveY; + bp.dstX = 0; + bp.dstY = stashY; + bp.width = saveW; + bp.height = saveH; + bp.rop3 = SRCCOPY; + int32_t ret = wdrvBitBlt(drv, &bp); + logMsg(" Save to stash(%d,%d) ret=%" PRId32 "\n", 0, stashY, ret); + + // Overwrite the source area + wdrvFillRect(drv, saveX, saveY, saveW, saveH, MAKE_RGB(64, 64, 64)); + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, saveX + 5, saveY + 35, "Cleared", 7, + MAKE_RGB(255, 255, 255), MAKE_RGB(64, 64, 64), true, NULL); + } + + // Restore: blit from stash to two different positions + memset(&bp, 0, sizeof(bp)); + bp.srcX = 0; + bp.srcY = stashY; + bp.dstX = 200; + bp.dstY = saveY; + bp.width = saveW; + bp.height = saveH; + bp.rop3 = SRCCOPY; + ret = wdrvBitBlt(drv, &bp); + logMsg(" Restore copy1 ret=%" PRId32 "\n", ret); + + memset(&bp, 0, sizeof(bp)); + bp.srcX = 0; + bp.srcY = stashY; + bp.dstX = 350; + bp.dstY = saveY; + bp.width = saveW; + bp.height = saveH; + bp.rop3 = SRCCOPY; + ret = wdrvBitBlt(drv, &bp); + logMsg(" Restore copy2 ret=%" PRId32 "\n", ret); + + // Clear the stash area + wdrvFillRect(drv, 0, stashY, saveW, saveH, MAKE_RGB(0, 0, 0)); + + if (info.hasExtTextOut) { + wdrvExtTextOut(drv, 200, saveY + saveH + 5, "Copy 1", 6, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + wdrvExtTextOut(drv, 350, saveY + saveH + 5, "Copy 2", 6, + MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL); + } + + logMsg(" Screen save/restore done\n"); + wdrvScreenshot(drv, "DEMO15.PNG"); } } static void setupPalette(WdrvHandleT drv) { - // The driver sets up its own palette during Enable (via VBE 4F09) - // and stores an internal color table that RealizeObject uses for - // color matching. We leave the palette as-is so the DAC and the - // internal table stay in sync. RealizeObject will find the best - // matching index, and the DAC will display the correct color. - (void)drv; + // Set the standard Windows 3.1 default 256-color palette. + // This is an 8R x 8G x 4B color cube with the 20 static system + // colors overwriting indices 0-9 and 246-255. + // Using a consistent palette ensures identical output across all + // drivers regardless of their built-in default palettes. + uint8_t pal[1024]; // 256 entries x 4 bytes (R, G, B, flags) + + // Generate 8R x 8G x 4B color cube + for (int32_t b = 0; b < 4; b++) { + for (int32_t g = 0; g < 8; g++) { + for (int32_t r = 0; r < 8; r++) { + int32_t idx = b * 64 + g * 8 + r; + pal[idx * 4 + 0] = (uint8_t)(r * 0x20); + pal[idx * 4 + 1] = (uint8_t)(g * 0x20); + pal[idx * 4 + 2] = (uint8_t)(b * 0x40); + pal[idx * 4 + 3] = 0; + } + } + } + + // Overwrite first 10 entries with static system colors + static const uint8_t sysFirst[10][3] = { + { 0x00, 0x00, 0x00 }, // 0: Black + { 0x80, 0x00, 0x00 }, // 1: Dark Red + { 0x00, 0x80, 0x00 }, // 2: Dark Green + { 0x80, 0x80, 0x00 }, // 3: Dark Yellow + { 0x00, 0x00, 0x80 }, // 4: Dark Blue + { 0x80, 0x00, 0x80 }, // 5: Dark Magenta + { 0x00, 0x80, 0x80 }, // 6: Dark Cyan + { 0xC0, 0xC0, 0xC0 }, // 7: Light Gray + { 0xC0, 0xDC, 0xC0 }, // 8: Money Green + { 0xA6, 0xCA, 0xF0 }, // 9: Sky Blue + }; + for (int32_t i = 0; i < 10; i++) { + pal[i * 4 + 0] = sysFirst[i][0]; + pal[i * 4 + 1] = sysFirst[i][1]; + pal[i * 4 + 2] = sysFirst[i][2]; + pal[i * 4 + 3] = 0; + } + + // Overwrite last 10 entries (246-255) with static system colors + static const uint8_t sysLast[10][3] = { + { 0xFF, 0xFB, 0xF0 }, // 246: Cream + { 0xA0, 0xA0, 0xA4 }, // 247: Medium Gray + { 0x80, 0x80, 0x80 }, // 248: Dark Gray + { 0xFF, 0x00, 0x00 }, // 249: Red + { 0x00, 0xFF, 0x00 }, // 250: Green + { 0xFF, 0xFF, 0x00 }, // 251: Yellow + { 0x00, 0x00, 0xFF }, // 252: Blue + { 0xFF, 0x00, 0xFF }, // 253: Magenta + { 0x00, 0xFF, 0xFF }, // 254: Cyan + { 0xFF, 0xFF, 0xFF }, // 255: White + }; + for (int32_t i = 0; i < 10; i++) { + pal[(246 + i) * 4 + 0] = sysLast[i][0]; + pal[(246 + i) * 4 + 1] = sysLast[i][1]; + pal[(246 + i) * 4 + 2] = sysLast[i][2]; + pal[(246 + i) * 4 + 3] = 0; + } + + wdrvSetPalette(drv, 0, 256, pal); } diff --git a/dosbox-et4000.conf b/dosbox-et4000.conf new file mode 100644 index 0000000..0595c93 --- /dev/null +++ b/dosbox-et4000.conf @@ -0,0 +1,37 @@ +# DOSBox-X config for ET4000.DRV testing +[sdl] +output = surface +windowresolution = 1024x768 + +[dosbox] +machine = svga_et4000 +memsize = 64 +quit warning = false + +[cpu] +core = normal +cputype = pentium +cycles = max + +[render] +aspect = true +scaler = none + +[video] +vmemsize = 8 +vmemsizekb = 0 +vesa oldvbe = false +vesa oldvbe10 = false + +[dos] +umb = true +xms = true +ems = true + +[autoexec] +@echo off +mount c /home/scott/claude/windriver +c: +cd bin +DEMO.EXE ET4000.DRV +exit diff --git a/dosbox-s3trio.conf b/dosbox-s3trio.conf new file mode 100644 index 0000000..256faab --- /dev/null +++ b/dosbox-s3trio.conf @@ -0,0 +1,37 @@ +# DOSBox-X config for S3TRIO.DRV testing +[sdl] +output = surface +windowresolution = 1024x768 + +[dosbox] +machine = svga_s3trio64 +memsize = 64 +quit warning = false + +[cpu] +core = normal +cputype = pentium +cycles = max + +[render] +aspect = true +scaler = none + +[video] +vmemsize = 8 +vmemsizekb = 0 +vesa oldvbe = false +vesa oldvbe10 = false + +[dos] +umb = true +xms = true +ems = true + +[autoexec] +@echo off +mount c /home/scott/claude/windriver +c: +cd bin +DEMO.EXE S3TRIO.DRV +exit diff --git a/dosbox-vbesvga.conf b/dosbox-vbesvga.conf new file mode 100644 index 0000000..aa38158 --- /dev/null +++ b/dosbox-vbesvga.conf @@ -0,0 +1,37 @@ +# DOSBox-X config for VBESVGA.DRV testing +[sdl] +output = surface +windowresolution = 1024x768 + +[dosbox] +machine = svga_s3trio64 +memsize = 64 +quit warning = false + +[cpu] +core = normal +cputype = pentium +cycles = max + +[render] +aspect = true +scaler = none + +[video] +vmemsize = 8 +vmemsizekb = 0 +vesa oldvbe = false +vesa oldvbe10 = false + +[dos] +umb = true +xms = true +ems = true + +[autoexec] +@echo off +mount c /home/scott/claude/windriver +c: +cd bin +DEMO.EXE VBESVGA.DRV +exit diff --git a/dosbox-vga.conf b/dosbox-vga.conf new file mode 100644 index 0000000..fc44fb2 --- /dev/null +++ b/dosbox-vga.conf @@ -0,0 +1,37 @@ +# DOSBox-X config for VGA.DRV testing +[sdl] +output = surface +windowresolution = 1024x768 + +[dosbox] +machine = svga_s3trio64 +memsize = 64 +quit warning = false + +[cpu] +core = normal +cputype = pentium +cycles = max + +[render] +aspect = true +scaler = none + +[video] +vmemsize = 8 +vmemsizekb = 0 +vesa oldvbe = false +vesa oldvbe10 = false + +[dos] +umb = true +xms = true +ems = true + +[autoexec] +@echo off +mount c /home/scott/claude/windriver +c: +cd bin +DEMO.EXE VGA.DRV +exit diff --git a/dosbox-x.conf b/dosbox-x.conf index 83719eb..acc1651 100644 --- a/dosbox-x.conf +++ b/dosbox-x.conf @@ -2,7 +2,7 @@ # S3 Trio64 SVGA with VESA support [sdl] -output = opengl +output = surface windowresolution = 1024x768 [dosbox] @@ -31,10 +31,4 @@ xms = true ems = true [autoexec] -@echo off -mount c /home/scott/claude/windriver -c: -cd bin -DEMO.EXE S3TRIO.DRV -rem exit diff --git a/win31drv/stb_image_write.h b/win31drv/stb_image_write.h new file mode 100644 index 0000000..e4b32ed --- /dev/null +++ b/win31drv/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/win31drv/windrv.c b/win31drv/windrv.c index 91cbd87..4020e35 100644 --- a/win31drv/windrv.c +++ b/win31drv/windrv.c @@ -49,6 +49,8 @@ #include #define STB_TRUETYPE_IMPLEMENTATION #include "stb_truetype.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" // ============================================================================ // Driver instance structure (opaque handle) @@ -101,6 +103,7 @@ struct WdrvDriverS { uint16_t penOff; uint32_t penLinear; uint32_t penRealizedColor; + int16_t penRealizedStyle; bool penRealized; // Physical color (within DGROUP, output of ColorInfo) @@ -128,6 +131,12 @@ struct WdrvDriverS { // scratch area is off-screen, and add the offset to all Y coordinates. int16_t dispYOffset; bool isS3; + uint8_t dacWidth; // DAC bits per channel: 6 (standard VGA) or 8 + + // Hardware cursor (persistent allocation — some drivers keep a pointer) + uint16_t cursorSel; + uint32_t cursorLinear; + uint32_t cursorAllocSize; }; // ============================================================================ @@ -167,7 +176,7 @@ static bool allocDrawMode(struct WdrvDriverS *drv); static bool allocBrushBuffers(struct WdrvDriverS *drv); static bool allocPenBuffers(struct WdrvDriverS *drv); static bool realizeBrush(struct WdrvDriverS *drv, uint32_t color); -static bool realizePen(struct WdrvDriverS *drv, uint32_t color); +static bool realizeStyledPen(struct WdrvDriverS *drv, uint32_t color, int16_t style); static uint32_t colorToPhys(struct WdrvDriverS *drv, uint32_t colorRef); static void setDisplayStart(struct WdrvDriverS *drv, uint32_t byteOffset); @@ -195,6 +204,18 @@ static void removeInt2FhHandler(void); static bool installExceptionCapture(void); static void removeExceptionCapture(void); +static void drawStyledLine(struct WdrvDriverS *drv, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint32_t color, const int16_t *pattern, int16_t patLen, int16_t *patIdx, int16_t *patRemain); +static int32_t drawStyledPolyline(struct WdrvDriverS *drv, Point16T *points, int16_t count, uint32_t color, int16_t penStyle); +static int32_t floodFillSoftware(struct WdrvDriverS *drv, int16_t x, int16_t y, uint32_t fillColor); +static int32_t blitPixelsSoftware(struct WdrvDriverS *drv, int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, int32_t srcPitch); +static void readDacPalette(uint8_t rgb[768], uint8_t dacWidth); + +// Pen dash patterns: even indices = draw, odd indices = skip (pixel counts) +static const int16_t gPatDash[] = {18, 6}; +static const int16_t gPatDot[] = {3, 3}; +static const int16_t gPatDashDot[] = {9, 6, 3, 6}; +static const int16_t gPatDashDotDot[] = {9, 3, 3, 3, 3, 3}; + // ============================================================================ // INT 10h (Video BIOS) reflector // @@ -1239,6 +1260,14 @@ void wdrvUnloadDriver(WdrvHandleT handle) return; } + // Free cursor allocation if present + if (handle->cursorSel) { + free16BitBlock(handle->cursorSel, handle->cursorLinear); + handle->cursorSel = 0; + handle->cursorLinear = 0; + handle->cursorAllocSize = 0; + } + freeDrawObjects(handle); // PDEVICE and other objects are in DGROUP - freed by neUnloadModule neUnloadModule(&handle->neMod); @@ -1266,13 +1295,17 @@ int32_t wdrvGetInfo(WdrvHandleT handle, WdrvInfoT *info) info->rasterCaps = handle->gdiInfo.dpRaster; } - info->hasBitBlt = handle->ddiEntry[DDI_ORD_BITBLT].present; - info->hasOutput = handle->ddiEntry[DDI_ORD_OUTPUT].present; - info->hasPixel = handle->ddiEntry[DDI_ORD_PIXEL].present; - info->hasStretchBlt = handle->ddiEntry[DDI_ORD_STRETCHBLT].present; - info->hasExtTextOut = handle->ddiEntry[DDI_ORD_EXTTEXTOUT].present; - info->hasSetPalette = handle->ddiEntry[DDI_ORD_SETPALETTE].present; - info->hasSetCursor = handle->ddiEntry[DDI_ORD_SETCURSOR].present; + info->hasBitBlt = handle->ddiEntry[DDI_ORD_BITBLT].present; + info->hasOutput = handle->ddiEntry[DDI_ORD_OUTPUT].present; + info->hasPixel = handle->ddiEntry[DDI_ORD_PIXEL].present; + info->hasStretchBlt = handle->ddiEntry[DDI_ORD_STRETCHBLT].present; + info->hasExtTextOut = handle->ddiEntry[DDI_ORD_EXTTEXTOUT].present; + info->hasSetPalette = handle->ddiEntry[DDI_ORD_SETPALETTE].present; + info->hasSetCursor = handle->ddiEntry[DDI_ORD_SETCURSOR].present; + info->hasMoveCursor = handle->ddiEntry[DDI_ORD_MOVECURSOR].present; + info->hasScanLR = handle->ddiEntry[DDI_ORD_SCANLR].present; + info->hasGetCharWidth = handle->ddiEntry[DDI_ORD_GETCHARWIDTH].present; + info->hasCreateBitmap = handle->ddiEntry[DDI_ORD_CREATEBITMAP].present; return WDRV_OK; } @@ -1475,6 +1508,21 @@ int32_t wdrvEnable(WdrvHandleT handle, int32_t width, int32_t height, int32_t bp } } + // Query DAC width via VBE 4F08 subfunc 01 (Get DAC Palette Width). + // Standard VGA = 6 bits, some SVGA cards support 8 bits. + handle->dacWidth = 6; // default + { + __dpmi_regs vr; + memset(&vr, 0, sizeof(vr)); + vr.x.ax = 0x4F08; + vr.h.bl = 0x01; // Get DAC palette width + __dpmi_int(0x10, &vr); + if ((vr.x.ax & 0xFF) == 0x4F) { + handle->dacWidth = vr.h.bh; + } + logErr("windrv: DAC width: %u bits\n", handle->dacWidth); + } + // Query current VBE mode for diagnostics { __dpmi_regs vr; @@ -1645,11 +1693,16 @@ int32_t wdrvEnable(WdrvHandleT handle, int32_t width, int32_t height, int32_t bp // Shift the visible display down by 10 scanlines so the S3 driver's // pattern scratch area at VRAM (144,1)-(151,8) is off-screen. // All drawing Y coordinates are offset by dispYOffset to compensate. + // This just uses slightly more VRAM — the full dpVertRes is usable. handle->dispYOffset = 10; setDisplayStart(handle, (uint32_t)handle->dispYOffset * handle->pitch); + + // Extend the PDEVICE height by dispYOffset so the driver's internal + // clipping allows the full logical screen. Logical y=0..599 maps + // to VRAM y=10..609, so the driver needs to accept up to y=609. + DibPDevice16T *pd = (DibPDevice16T *)handle->pdevLinear; + pd->deHeight += handle->dispYOffset; } else { - // Non-S3 hardware, VGA-class, or software/DIB driver: no S3 - // scratch area, no display start shift. handle->dispYOffset = 0; } @@ -1814,60 +1867,6 @@ int32_t wdrvFillRect(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_ return wdrvBitBlt(handle, &bp); } - // Fall back to Output with rectangle - if (handle->ddiEntry[DDI_ORD_OUTPUT].present) { - // Allocate 16-bit memory for the point array and pen - // Output(lpDstDev, style, count, lpPoints, lpPen, lpBrush, lpDrawMode, lpClipRect) - // For rectangle: style=OS_RECTANGLE, count=2 (top-left, bottom-right) - - // Build 2-point rectangle (offset Y into hidden-scanline region) - Point16T pts[2]; - pts[0].x = x; - pts[0].y = y + handle->dispYOffset; - pts[1].x = x + w; - pts[1].y = y + h + handle->dispYOffset; - - uint32_t ptsLinear; - uint16_t ptsSel = alloc16BitBlock(sizeof(pts), &ptsLinear); - if (ptsSel == 0) { - return WDRV_ERR_NO_MEMORY; - } - memcpy((void *)ptsLinear, pts, sizeof(pts)); - - // Output params (Pascal order): - // lpDstDev(2w), style(1w), count(1w), lpPoints(2w), - // lpPen(2w), lpBrush(2w), lpDrawMode(2w), lpClipRect(2w) - // Total: 14 words - uint16_t dgSel = handle->neMod.autoDataSel; - uint16_t params[14]; - int i = 0; - params[i++] = dgSel; // lpDstDev seg - params[i++] = handle->pdevOff; // lpDstDev off - params[i++] = OS_RECTANGLE; // style - params[i++] = 2; // count - params[i++] = ptsSel; // lpPoints seg - params[i++] = 0; // lpPoints off - params[i++] = 0; // lpPen seg (NULL) - params[i++] = 0; // lpPen off - params[i++] = dgSel; // lpBrush seg - params[i++] = handle->brushOff; // lpBrush off - params[i++] = dgSel; // lpDrawMode seg - params[i++] = handle->drawModeOff; // lpDrawMode off - params[i++] = 0; // lpClipRect seg (NULL = no clip) - params[i++] = 0; // lpClipRect off - - waitForEngine(); - - uint32_t result = thunkCall16(&gThunkCtx, - handle->ddiEntry[DDI_ORD_OUTPUT].sel, - handle->ddiEntry[DDI_ORD_OUTPUT].off, - params, i); - - free16BitBlock(ptsSel, ptsLinear); - - return ((int16_t)(result & 0xFFFF)) ? WDRV_OK : WDRV_ERR_UNSUPPORTED; - } - return WDRV_ERR_UNSUPPORTED; } @@ -1951,26 +1950,123 @@ uint32_t wdrvGetPixel(WdrvHandleT handle, int16_t x, int16_t y) } +static void drawStyledLine(struct WdrvDriverS *drv, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint32_t color, const int16_t *pattern, int16_t patLen, int16_t *patIdx, int16_t *patRemain) +{ + int16_t dx = (x1 > x0) ? (x1 - x0) : (x0 - x1); + int16_t dy = (y1 > y0) ? (y1 - y0) : (y0 - y1); + int16_t sx = (x0 < x1) ? 1 : -1; + int16_t sy = (y0 < y1) ? 1 : -1; + int16_t err = dx - dy; + + int16_t cx = x0; + int16_t cy = y0; + + for (;;) { + // Even index = draw, odd index = skip + if ((*patIdx & 1) == 0) { + wdrvSetPixel((WdrvHandleT)drv, cx, cy, color); + } + + // Advance pattern state + (*patRemain)--; + if (*patRemain <= 0) { + *patIdx = (*patIdx + 1) % patLen; + *patRemain = pattern[*patIdx]; + } + + if (cx == x1 && cy == y1) { + break; + } + + int16_t e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + cx += sx; + } + if (e2 < dx) { + err += dx; + cy += sy; + } + } +} + + +static int32_t drawStyledPolyline(struct WdrvDriverS *drv, Point16T *points, int16_t count, uint32_t color, int16_t penStyle) +{ + const int16_t *pattern; + int16_t patLen; + + switch (penStyle) { + case PS_DASH: + pattern = gPatDash; + patLen = 2; + break; + case PS_DOT: + pattern = gPatDot; + patLen = 2; + break; + case PS_DASHDOT: + pattern = gPatDashDot; + patLen = 4; + break; + case PS_DASHDOTDOT: + pattern = gPatDashDotDot; + patLen = 6; + break; + default: + return WDRV_ERR_UNSUPPORTED; + } + + int16_t patIdx = 0; + int16_t patRemain = pattern[0]; + + for (int16_t i = 0; i < count - 1; i++) { + drawStyledLine(drv, points[i].x, points[i].y, points[i + 1].x, points[i + 1].y, color, pattern, patLen, &patIdx, &patRemain); + } + + return WDRV_OK; +} + + int32_t wdrvPolyline(WdrvHandleT handle, Point16T *points, int16_t count, uint32_t color) +{ + return wdrvPolylineEx(handle, points, count, color, PS_SOLID); +} + + +int32_t wdrvPolylineEx(WdrvHandleT handle, Point16T *points, int16_t count, uint32_t color, int16_t penStyle) { if (!handle || !handle->enabled) { return WDRV_ERR_NOT_ENABLED; } + // Non-SOLID pen styles always use software rendering. + // S3TRIO silently accepts styled pens but doesn't render them; + // software Bresenham gives identical output on all drivers. + if (penStyle != PS_SOLID) { + if (!handle->ddiEntry[DDI_ORD_PIXEL].present) { + return WDRV_ERR_UNSUPPORTED; + } + return drawStyledPolyline(handle, points, count, color, penStyle); + } + if (!handle->ddiEntry[DDI_ORD_OUTPUT].present) { return WDRV_ERR_UNSUPPORTED; } // Realize a physical pen (driver expects RealizeObject output, not a logical pen) - if (!handle->penRealized || handle->penRealizedColor != color) { - if (!realizePen(handle, color)) { + if (!handle->penRealized || handle->penRealizedColor != color || handle->penRealizedStyle != penStyle) { + if (!realizeStyledPen(handle, color, penStyle)) { return WDRV_ERR_UNSUPPORTED; } } - // Allocate 16-bit memory for the point array, offsetting Y coordinates + // Allocate 16-bit memory for points + clip rect, offsetting Y coordinates. + // DIB engine drivers (VBESVGA) dereference lpClipRect unconditionally. uint32_t ptsSize = count * sizeof(Point16T); + uint16_t clipOff = (uint16_t)((ptsSize + 1) & ~1); // word-align + uint32_t blockSize = (uint32_t)clipOff + 8; // + RECT (4 WORDs) uint32_t ptsLinear; - uint16_t ptsSel = alloc16BitBlock(ptsSize, &ptsLinear); + uint16_t ptsSel = alloc16BitBlock(blockSize, &ptsLinear); if (ptsSel == 0) { return WDRV_ERR_NO_MEMORY; } @@ -1981,6 +2077,11 @@ int32_t wdrvPolyline(WdrvHandleT handle, Point16T *points, int16_t count, uint32 dst[pi].y += handle->dispYOffset; } } + int16_t *clipRect = (int16_t *)(ptsLinear + clipOff); + clipRect[0] = 0; + clipRect[1] = 0; + clipRect[2] = 0x7FFF; + clipRect[3] = 0x7FFF; // Output(lpDstDev, style, count, lpPoints, lpPen, lpBrush, lpDrawMode, lpClipRect) uint16_t dgSel = handle->neMod.autoDataSel; @@ -1998,8 +2099,8 @@ int32_t wdrvPolyline(WdrvHandleT handle, Point16T *points, int16_t count, uint32 params[i++] = 0; params[i++] = dgSel; params[i++] = handle->drawModeOff; - params[i++] = 0; // lpClipRect = NULL - params[i++] = 0; + params[i++] = ptsSel; // lpClipRect seg + params[i++] = clipOff; // lpClipRect off waitForEngine(); @@ -2018,64 +2119,38 @@ int32_t wdrvPolyline(WdrvHandleT handle, Point16T *points, int16_t count, uint32 int32_t wdrvRectangle(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color) { - // Use Output with OS_RECTANGLE for outlined rectangle - if (!handle || !handle->enabled) { - return WDRV_ERR_NOT_ENABLED; - } - if (!handle->ddiEntry[DDI_ORD_OUTPUT].present) { - return WDRV_ERR_UNSUPPORTED; + return wdrvRectangleEx(handle, x, y, w, h, color, PS_SOLID); +} + + +int32_t wdrvRectangleEx(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color, int16_t penStyle) +{ + // Draw a rectangle outline using two polylines (left+top, right+bottom). + // OS_RECTANGLE crashes on DIB engine drivers (VBESVGA, ET4000) because + // they expect GDI-level curve decomposition callbacks. Polylines are + // universally supported. + Point16T side1[3]; + side1[0].x = x; + side1[0].y = y + h; + side1[1].x = x; + side1[1].y = y; + side1[2].x = x + w; + side1[2].y = y; + + int32_t ret = wdrvPolylineEx(handle, side1, 3, color, penStyle); + if (ret != WDRV_OK) { + return ret; } - // Realize a physical pen (driver expects RealizeObject output, not a logical pen) - if (!handle->penRealized || handle->penRealizedColor != color) { - if (!realizePen(handle, color)) { - return WDRV_ERR_UNSUPPORTED; - } - } + Point16T side2[3]; + side2[0].x = x + w; + side2[0].y = y; + side2[1].x = x + w; + side2[1].y = y + h; + side2[2].x = x; + side2[2].y = y + h; - Point16T pts[2]; - pts[0].x = x; - pts[0].y = y + handle->dispYOffset; - pts[1].x = x + w; - pts[1].y = y + h + handle->dispYOffset; - - uint32_t ptsLinear; - uint16_t ptsSel = alloc16BitBlock(sizeof(pts), &ptsLinear); - if (ptsSel == 0) { - return WDRV_ERR_NO_MEMORY; - } - memcpy((void *)ptsLinear, pts, sizeof(pts)); - - uint16_t dgSel = handle->neMod.autoDataSel; - uint16_t params[14]; - int i = 0; - params[i++] = dgSel; - params[i++] = handle->pdevOff; - params[i++] = OS_RECTANGLE; - params[i++] = 2; - params[i++] = ptsSel; - params[i++] = 0; - params[i++] = dgSel; // lpPen in DGROUP (physical pen) - params[i++] = handle->penOff; - params[i++] = dgSel; - params[i++] = handle->brushOff; - params[i++] = dgSel; - params[i++] = handle->drawModeOff; - params[i++] = 0; // lpClipRect = NULL - params[i++] = 0; - - waitForEngine(); - - uint32_t result = thunkCall16(&gThunkCtx, - handle->ddiEntry[DDI_ORD_OUTPUT].sel, - handle->ddiEntry[DDI_ORD_OUTPUT].off, - params, i); - - waitForEngine(); - - free16BitBlock(ptsSel, ptsLinear); - - return ((int16_t)(result & 0xFFFF)) ? WDRV_OK : WDRV_ERR_UNSUPPORTED; + return wdrvPolylineEx(handle, side2, 3, color, penStyle); } @@ -2117,6 +2192,20 @@ int32_t wdrvSetPalette(WdrvHandleT handle, int32_t startIndex, int32_t count, co params, 4); free16BitBlock(palSel, palLinear); + + // Also program the VGA DAC directly. Some drivers (e.g. VBESVGA) + // update their internal color table via SetPalette but do not + // program the DAC hardware — in real Windows 3.1, GDI does that + // separately. Writing the DAC is idempotent, so this is harmless + // on drivers (like S3TRIO) that already programmed it. + uint8_t dacShift = 8 - handle->dacWidth; // 2 for 6-bit, 0 for 8-bit + outportb(0x3C8, (uint8_t)startIndex); + for (int32_t i = 0; i < count; i++) { + outportb(0x3C9, colors[i * 4 + 0] >> dacShift); + outportb(0x3C9, colors[i * 4 + 1] >> dacShift); + outportb(0x3C9, colors[i * 4 + 2] >> dacShift); + } + return WDRV_OK; } @@ -2248,6 +2337,1260 @@ int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y, } +// ============================================================================ +// ScanLR and Flood Fill +// ============================================================================ + +int16_t wdrvScanLR(WdrvHandleT handle, int16_t x, int16_t y, uint32_t color, int16_t style) +{ + if (!handle || !handle->enabled) { + return -1; + } + if (!handle->ddiEntry[DDI_ORD_SCANLR].present) { + return -1; + } + + uint32_t physColor = colorToPhys(handle, color); + + // WORD ScanLR(LPDEVICE, WORD x, WORD y, DWORD color, WORD style) + // Pascal push order: lpDevice(2w), x(1w), y(1w), color(2w), style(1w) + // Total: 7 words + uint16_t dgSel = handle->neMod.autoDataSel; + uint16_t params[7]; + int i = 0; + params[i++] = dgSel; + params[i++] = handle->pdevOff; + params[i++] = (uint16_t)x; + params[i++] = (uint16_t)(y + handle->dispYOffset); + params[i++] = (uint16_t)(physColor >> 16); + params[i++] = (uint16_t)(physColor); + params[i++] = (uint16_t)style; + + waitForEngine(); + + uint32_t result = thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_SCANLR].sel, + handle->ddiEntry[DDI_ORD_SCANLR].off, + params, i); + + waitForEngine(); + + return (int16_t)(result & 0xFFFF); +} + + +// ScanLR with a pre-converted physical color (no colorToPhys round-trip). +// Used by floodFillSoftware where the seed color comes from GetPixel DX:AX, +// which is already in the physical format that ScanLR expects. + +static int32_t floodFillSoftware(struct WdrvDriverS *drv, int16_t x, int16_t y, uint32_t fillColor) +{ + WdrvHandleT handle = (WdrvHandleT)drv; + int16_t screenW = drv->gdiInfo.dpHorzRes; + int16_t screenH = drv->gdiInfo.dpVertRes; + + if (x < 0 || x >= screenW || y < 0 || y >= screenH) { + return WDRV_OK; + } + + // Determine seed color for ScanLR by probing palette indices. + // We CANNOT use GetPixel — the ET4000 DIB engine's Pixel DDI with color=-1 + // has a side effect that corrupts the pixel's VRAM representation, causing + // subsequent ScanLR calls to not match that pixel. + // Instead, probe with ScanLR(SCAN_RIGHT) at (x,y) for each palette index + // until we find one that matches (returns > x). + if (!drv->ddiEntry[DDI_ORD_SCANLR].present) { + return WDRV_ERR_UNSUPPORTED; + } + + uint16_t dgSel = drv->neMod.autoDataSel; + int32_t seedIdx = -1; + + for (int32_t idx = 0; idx < 256; idx++) { + uint32_t color = PALETTEINDEX(idx); + uint32_t phys = colorToPhys(handle, color); + uint16_t params[7]; + int pi = 0; + params[pi++] = dgSel; + params[pi++] = drv->pdevOff; + params[pi++] = (uint16_t)x; + params[pi++] = (uint16_t)(y + drv->dispYOffset); + params[pi++] = (uint16_t)(phys >> 16); + params[pi++] = (uint16_t)(phys); + params[pi++] = (uint16_t)WDRV_SCAN_RIGHT; + int16_t r = (int16_t)(thunkCall16(&gThunkCtx, drv->ddiEntry[DDI_ORD_SCANLR].sel, drv->ddiEntry[DDI_ORD_SCANLR].off, params, pi) & 0xFFFF); + if (r > x) { + seedIdx = idx; + break; + } + } + + if (seedIdx < 0) { + return WDRV_OK; // Cannot determine seed color + } + + // Convert seed and fill to physical colors for ScanLR + uint32_t seedPhys = colorToPhys(handle, PALETTEINDEX(seedIdx)); + uint32_t fillPhys = colorToPhys(handle, fillColor); + + // If seed and fill are the same physical color, nothing to do + if (seedPhys == fillPhys) { + return WDRV_OK; + } + + // Scanline stack flood fill using ScanLR + FillRect only + typedef struct { + int16_t x; + int16_t y; + } SeedT; + + #define FLOOD_STACK_SIZE 4096 + static SeedT stack[FLOOD_STACK_SIZE]; + int32_t sp = 0; + + stack[sp].x = x; + stack[sp].y = y; + sp++; + + while (sp > 0) { + sp--; + int16_t cx = stack[sp].x; + int16_t cy = stack[sp].y; + + if (cx < 0 || cx >= screenW || cy < 0 || cy >= screenH) { + continue; + } + + // Check if pixel at (cx,cy) still matches seed color + { + uint16_t params[7]; + int pi = 0; + params[pi++] = dgSel; + params[pi++] = drv->pdevOff; + params[pi++] = (uint16_t)cx; + params[pi++] = (uint16_t)(cy + drv->dispYOffset); + params[pi++] = (uint16_t)(seedPhys >> 16); + params[pi++] = (uint16_t)(seedPhys); + params[pi++] = (uint16_t)WDRV_SCAN_RIGHT; + int16_t r = (int16_t)(thunkCall16(&gThunkCtx, drv->ddiEntry[DDI_ORD_SCANLR].sel, drv->ddiEntry[DDI_ORD_SCANLR].off, params, pi) & 0xFFFF); + if (r <= cx) { + continue; // Pixel no longer matches seed + } + } + + // Find left boundary: scan left from cx + int16_t left; + { + uint16_t params[7]; + int pi = 0; + params[pi++] = dgSel; + params[pi++] = drv->pdevOff; + params[pi++] = (uint16_t)cx; + params[pi++] = (uint16_t)(cy + drv->dispYOffset); + params[pi++] = (uint16_t)(seedPhys >> 16); + params[pi++] = (uint16_t)(seedPhys); + params[pi++] = (uint16_t)WDRV_SCAN_LEFT; // scan left, find not-matching + left = (int16_t)(thunkCall16(&gThunkCtx, drv->ddiEntry[DDI_ORD_SCANLR].sel, drv->ddiEntry[DDI_ORD_SCANLR].off, params, pi) & 0xFFFF); + left++; // ScanLR returns the non-matching pixel; span starts one pixel right + } + + // Find right boundary: scan right from cx + int16_t right; + { + uint16_t params[7]; + int pi = 0; + params[pi++] = dgSel; + params[pi++] = drv->pdevOff; + params[pi++] = (uint16_t)cx; + params[pi++] = (uint16_t)(cy + drv->dispYOffset); + params[pi++] = (uint16_t)(seedPhys >> 16); + params[pi++] = (uint16_t)(seedPhys); + params[pi++] = (uint16_t)WDRV_SCAN_RIGHT; // scan right, find not-matching + right = (int16_t)(thunkCall16(&gThunkCtx, drv->ddiEntry[DDI_ORD_SCANLR].sel, drv->ddiEntry[DDI_ORD_SCANLR].off, params, pi) & 0xFFFF); + right--; // ScanLR returns the non-matching pixel; span ends one pixel left + } + + if (left > right) { + continue; + } + + // Fill the span + wdrvFillRect(handle, left, cy, right - left + 1, 1, fillColor); + + // Seed adjacent rows using ScanLR to find spans of seed color + for (int16_t dir = -1; dir <= 1; dir += 2) { + int16_t ny = cy + dir; + if (ny < 0 || ny >= screenH) { + continue; + } + + int16_t sx = left; + while (sx <= right) { + // Find next seed-colored pixel in adjacent row + uint16_t params[7]; + int pi = 0; + params[pi++] = dgSel; + params[pi++] = drv->pdevOff; + params[pi++] = (uint16_t)sx; + params[pi++] = (uint16_t)(ny + drv->dispYOffset); + params[pi++] = (uint16_t)(seedPhys >> 16); + params[pi++] = (uint16_t)(seedPhys); + params[pi++] = (uint16_t)WDRV_SCAN_RIGHT_MATCH; + int16_t matchStart = (int16_t)(thunkCall16(&gThunkCtx, drv->ddiEntry[DDI_ORD_SCANLR].sel, drv->ddiEntry[DDI_ORD_SCANLR].off, params, pi) & 0xFFFF); + + if (matchStart > right || matchStart < sx) { + break; // No more seed-colored pixels in this row within span + } + + // Push one seed point for this sub-span + if (sp < FLOOD_STACK_SIZE) { + stack[sp].x = matchStart; + stack[sp].y = ny; + sp++; + } + + // Skip past this seed-colored run to find the next gap + params[2] = (uint16_t)matchStart; + params[6] = (uint16_t)WDRV_SCAN_RIGHT; + int16_t matchEnd = (int16_t)(thunkCall16(&gThunkCtx, drv->ddiEntry[DDI_ORD_SCANLR].sel, drv->ddiEntry[DDI_ORD_SCANLR].off, params, pi) & 0xFFFF); + + sx = matchEnd; // Continue from where the non-match was found + if (matchEnd <= matchStart) { + break; // Safety: avoid infinite loop + } + } + } + } + + #undef FLOOD_STACK_SIZE + return WDRV_OK; +} + + +int32_t wdrvFloodFill(WdrvHandleT handle, int16_t x, int16_t y, uint32_t fillColor) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + + // Check if we can use the fast framebuffer path + bool useFb = (handle->vramPtr != NULL); + if (handle->gdiInfoValid && handle->gdiInfo.dpPlanes > 1) { + useFb = false; + } + + if (!useFb) { + // Fall back to software using ScanLR + FillRect (no GetPixel — it + // corrupts pixel VRAM on ET4000 DIB engine, breaking ScanLR matching) + if (!handle->ddiEntry[DDI_ORD_SCANLR].present) { + return WDRV_ERR_UNSUPPORTED; + } + return floodFillSoftware(handle, x, y, fillColor); + } + + // Fast path: direct framebuffer access + uint8_t *fb = (uint8_t *)handle->vramPtr; + + int16_t screenW = handle->gdiInfo.dpHorzRes; + int16_t screenH = handle->gdiInfo.dpVertRes; + int32_t pitch = handle->pitch; + + if (x < 0 || x >= screenW || y < 0 || y >= screenH) { + return WDRV_OK; + } + + // Read seed pixel from framebuffer + waitForEngine(); + uint8_t seedColor = fb[(y + handle->dispYOffset) * pitch + x]; + + // If seed is same as fill, nothing to do + uint32_t fillPhys = colorToPhys(handle, fillColor); + uint8_t fillIdx = (uint8_t)(fillPhys & 0xFF); + if (seedColor == fillIdx) { + return WDRV_OK; + } + + // Scanline stack flood fill + typedef struct { + int16_t x; + int16_t y; + } SeedT; + + #define FLOOD_STACK_SIZE 4096 + static SeedT stack[FLOOD_STACK_SIZE]; + int32_t sp = 0; + + stack[sp].x = x; + stack[sp].y = y; + sp++; + + while (sp > 0) { + sp--; + int16_t cx = stack[sp].x; + int16_t cy = stack[sp].y; + + if (cx < 0 || cx >= screenW || cy < 0 || cy >= screenH) { + continue; + } + + waitForEngine(); + uint8_t *row = fb + (cy + handle->dispYOffset) * pitch; + if (row[cx] != seedColor) { + continue; + } + + // Scan left + int16_t left = cx; + while (left > 0 && row[left - 1] == seedColor) { + left--; + } + + // Scan right + int16_t right = cx; + while (right < screenW - 1 && row[right + 1] == seedColor) { + right++; + } + + // Fill the span + wdrvFillRect(handle, left, cy, right - left + 1, 1, fillColor); + + // Resync framebuffer after fill + waitForEngine(); + + // Push seeds for rows above and below + for (int16_t dir = -1; dir <= 1; dir += 2) { + int16_t ny = cy + dir; + if (ny < 0 || ny >= screenH) { + continue; + } + uint8_t *nrow = fb + (ny + handle->dispYOffset) * pitch; + bool inSpan = false; + for (int16_t sx = left; sx <= right; sx++) { + if (nrow[sx] == seedColor) { + if (!inSpan) { + if (sp < FLOOD_STACK_SIZE) { + stack[sp].x = sx; + stack[sp].y = ny; + sp++; + } + inSpan = true; + } + } else { + inSpan = false; + } + } + } + } + + #undef FLOOD_STACK_SIZE + return WDRV_OK; +} + + +// ============================================================================ +// Text measurement +// ============================================================================ + +int32_t wdrvGetCharWidths(WdrvHandleT handle, WdrvFontT font, uint8_t firstChar, uint8_t lastChar, int16_t *widths) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!handle->ddiEntry[DDI_ORD_GETCHARWIDTH].present) { + return WDRV_ERR_UNSUPPORTED; + } + + // Use built-in font if none specified + if (!font) { + font = wdrvLoadFontBuiltin(); + if (!font) { + return WDRV_ERR_NO_MEMORY; + } + } + + uint16_t count = (uint16_t)(lastChar - firstChar + 1); + + // Allocate 16-bit buffer for results + uint32_t bufSize = count * 2; // array of WORDs + uint32_t bufLinear; + uint16_t bufSel = alloc16BitBlock(bufSize, &bufLinear); + if (bufSel == 0) { + return WDRV_ERR_NO_MEMORY; + } + memset((void *)bufLinear, 0, bufSize); + + // Set up DrawMode for GetCharWidth + DrawMode16T *dm = (DrawMode16T *)handle->drawModeLinear; + dm->rop2 = R2_COPYPEN; + dm->bkMode = BM_TRANSPARENT; + + // BOOL GetCharWidth(LPDEVICE, LPINT lpBuffer, WORD firstChar, WORD lastChar, + // LPFONTINFO, LPDRAWMODE, LPTEXTXFORM) + // Pascal push order: left to right + // Total: 12 words + uint16_t dgSel = handle->neMod.autoDataSel; + uint16_t params[12]; + int i = 0; + params[i++] = dgSel; // lpDevice seg + params[i++] = handle->pdevOff; // lpDevice off + params[i++] = bufSel; // lpBuffer seg + params[i++] = 0; // lpBuffer off + params[i++] = (uint16_t)firstChar; // firstChar + params[i++] = (uint16_t)lastChar; // lastChar + params[i++] = font->fontSel; // lpFont seg + params[i++] = 0x0042; // lpFont off (fsType) + params[i++] = dgSel; // lpDrawMode seg + params[i++] = handle->drawModeOff; // lpDrawMode off + params[i++] = 0; // lpTextXForm seg (NULL) + params[i++] = 0; // lpTextXForm off + + waitForEngine(); + + uint32_t result = thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_GETCHARWIDTH].sel, + handle->ddiEntry[DDI_ORD_GETCHARWIDTH].off, + params, i); + + waitForEngine(); + + dbg("windrv: GetCharWidth(%d..%d) returned %d\n", + (int)firstChar, (int)lastChar, (int16_t)(result & 0xFFFF)); + + // Copy results back + int16_t *src = (int16_t *)bufLinear; + for (uint16_t ci = 0; ci < count; ci++) { + widths[ci] = src[ci]; + } + + free16BitBlock(bufSel, bufLinear); + + return ((int16_t)(result & 0xFFFF)) ? WDRV_OK : WDRV_ERR_UNSUPPORTED; +} + + +int32_t wdrvMeasureText(WdrvHandleT handle, WdrvFontT font, const char *text, int16_t length) +{ + if (!handle || !handle->enabled) { + return 0; + } + + // Use built-in font if none specified + if (!font) { + font = wdrvLoadFontBuiltin(); + if (!font) { + return 0; + } + } + + // Get widths for full printable range + int16_t widths[256]; + memset(widths, 0, sizeof(widths)); + + int32_t ret = wdrvGetCharWidths(handle, font, 0, 255, widths); + if (ret != WDRV_OK) { + // Fallback: use font's average width + return (int32_t)font->pixWidth * length; + } + + // Sum character widths + int32_t totalWidth = 0; + for (int16_t ci = 0; ci < length; ci++) { + uint8_t ch = (uint8_t)text[ci]; + totalWidth += widths[ch]; + } + + return totalWidth; +} + + +// ============================================================================ +// Pixel buffer blitting +// ============================================================================ + +static int32_t blitPixelsSoftware(struct WdrvDriverS *drv, int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, int32_t srcPitch) +{ + int16_t screenW = drv->gdiInfo.dpHorzRes; + int16_t screenH = drv->gdiInfo.dpVertRes; + + // Clip to screen + int16_t sx = 0; + int16_t sy = 0; + int16_t dw = w; + int16_t dh = h; + + if (x < 0) { + sx = -x; + dw += x; + x = 0; + } + if (y < 0) { + sy = -y; + dh += y; + y = 0; + } + if (x + dw > screenW) { + dw = screenW - x; + } + if (y + dh > screenH) { + dh = screenH - y; + } + if (dw <= 0 || dh <= 0) { + return WDRV_OK; + } + + for (int16_t row = 0; row < dh; row++) { + const uint8_t *src = pixels + (sy + row) * srcPitch + sx; + for (int16_t col = 0; col < dw; col++) { + wdrvSetPixel((WdrvHandleT)drv, x + col, y + row, PALETTEINDEX(src[col])); + } + } + + return WDRV_OK; +} + + +int32_t wdrvBlitPixels(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, int32_t srcPitch) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + + // Check if we can use the fast framebuffer path + bool useFb = (handle->vramPtr != NULL); + if (handle->gdiInfoValid && handle->gdiInfo.dpPlanes > 1) { + useFb = false; + } + + if (!useFb) { + // Fall back to software using Pixel DDI + if (!handle->ddiEntry[DDI_ORD_PIXEL].present) { + return WDRV_ERR_UNSUPPORTED; + } + return blitPixelsSoftware(handle, x, y, w, h, pixels, srcPitch); + } + + // Fast path: direct framebuffer access + uint8_t *fb = (uint8_t *)handle->vramPtr; + + int16_t screenW = handle->gdiInfo.dpHorzRes; + int16_t screenH = handle->gdiInfo.dpVertRes; + int32_t fbPitch = handle->pitch; + + // Clip to screen + int16_t sx = 0; + int16_t sy = 0; + int16_t dw = w; + int16_t dh = h; + + if (x < 0) { + sx = -x; + dw += x; + x = 0; + } + if (y < 0) { + sy = -y; + dh += y; + y = 0; + } + if (x + dw > screenW) { + dw = screenW - x; + } + if (y + dh > screenH) { + dh = screenH - y; + } + if (dw <= 0 || dh <= 0) { + return WDRV_OK; + } + + waitForEngine(); + + for (int16_t row = 0; row < dh; row++) { + uint8_t *dst = fb + (y + row + handle->dispYOffset) * fbPitch + x; + const uint8_t *src = pixels + (sy + row) * srcPitch + sx; + memcpy(dst, src, dw); + } + + return WDRV_OK; +} + + +int32_t wdrvBlitBmp(WdrvHandleT handle, int16_t x, int16_t y, const char *bmpPath, bool setPaletteFlag) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + + FILE *f = fopen(bmpPath, "rb"); + if (!f) { + return WDRV_ERR_FILE_NOT_FOUND; + } + + // Read BMP file header (14 bytes) + uint8_t fileHdr[14]; + if (fread(fileHdr, 1, 14, f) != 14) { + fclose(f); + return WDRV_ERR_BAD_FORMAT; + } + if (fileHdr[0] != 'B' || fileHdr[1] != 'M') { + fclose(f); + return WDRV_ERR_BAD_FORMAT; + } + + uint32_t pixelOffset = *(uint32_t *)(fileHdr + 10); + + // Read BITMAPINFOHEADER (40 bytes) + uint8_t infoHdr[40]; + if (fread(infoHdr, 1, 40, f) != 40) { + fclose(f); + return WDRV_ERR_BAD_FORMAT; + } + + int32_t bmpW = *(int32_t *)(infoHdr + 4); + int32_t bmpH = *(int32_t *)(infoHdr + 8); + uint16_t bmpBpp = *(uint16_t *)(infoHdr + 14); + uint32_t bmpCompr = *(uint32_t *)(infoHdr + 16); + + if (bmpBpp != 8 || bmpCompr != 0) { + logErr("windrv: BlitBmp: only 8bpp uncompressed supported (got %dbpp compr=%lu)\n", + (int)bmpBpp, (unsigned long)bmpCompr); + fclose(f); + return WDRV_ERR_UNSUPPORTED; + } + + bool bottomUp = (bmpH > 0); + if (bmpH < 0) { + bmpH = -bmpH; + } + + // Read 256-entry palette + uint8_t palette[1024]; // 256 * 4 (BGRA) + if (fread(palette, 1, 1024, f) != 1024) { + fclose(f); + return WDRV_ERR_BAD_FORMAT; + } + + if (setPaletteFlag) { + // Convert BGRA to RGBX for wdrvSetPalette + uint8_t rgbPal[1024]; + for (int pi = 0; pi < 256; pi++) { + rgbPal[pi * 4 + 0] = palette[pi * 4 + 2]; // R + rgbPal[pi * 4 + 1] = palette[pi * 4 + 1]; // G + rgbPal[pi * 4 + 2] = palette[pi * 4 + 0]; // B + rgbPal[pi * 4 + 3] = 0; + } + wdrvSetPalette(handle, 0, 256, rgbPal); + } + + // BMP rows are padded to 4-byte alignment + int32_t bmpPitch = (bmpW + 3) & ~3; + + // Allocate pixel buffer + uint8_t *pixels = (uint8_t *)malloc(bmpPitch * bmpH); + if (!pixels) { + fclose(f); + return WDRV_ERR_NO_MEMORY; + } + + // Seek to pixel data and read + fseek(f, pixelOffset, SEEK_SET); + + if (bottomUp) { + // BMP is bottom-up: read rows in reverse + for (int32_t row = bmpH - 1; row >= 0; row--) { + fread(pixels + row * bmpPitch, 1, bmpPitch, f); + } + } else { + fread(pixels, 1, bmpPitch * bmpH, f); + } + + fclose(f); + + int32_t ret = wdrvBlitPixels(handle, x, y, (int16_t)bmpW, (int16_t)bmpH, pixels, bmpPitch); + free(pixels); + return ret; +} + + +// ============================================================================ +// Hardware cursor +// ============================================================================ + +// Built-in 32x32 cursor AND masks (128 bytes each) and XOR masks (128 bytes each). +// AND mask: 1=transparent, 0=use XOR value. XOR mask: 1=white/invert, 0=black. + +// Arrow cursor (top-left pointing) +static const uint8_t gCursorArrowAnd[128] = { + 0x3F,0xFF,0xFF,0xFF, 0x1F,0xFF,0xFF,0xFF, 0x0F,0xFF,0xFF,0xFF, 0x07,0xFF,0xFF,0xFF, + 0x03,0xFF,0xFF,0xFF, 0x01,0xFF,0xFF,0xFF, 0x00,0xFF,0xFF,0xFF, 0x00,0x7F,0xFF,0xFF, + 0x00,0x3F,0xFF,0xFF, 0x00,0x1F,0xFF,0xFF, 0x00,0x0F,0xFF,0xFF, 0x00,0xFF,0xFF,0xFF, + 0x00,0xFF,0xFF,0xFF, 0x04,0x7F,0xFF,0xFF, 0x06,0x7F,0xFF,0xFF, 0x06,0x7F,0xFF,0xFF, + 0x0F,0x3F,0xFF,0xFF, 0x0F,0x3F,0xFF,0xFF, 0x1F,0xBF,0xFF,0xFF, 0x1F,0xFF,0xFF,0xFF, + 0x3F,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, +}; +static const uint8_t gCursorArrowXor[128] = { + 0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00, 0x60,0x00,0x00,0x00, 0x70,0x00,0x00,0x00, + 0x78,0x00,0x00,0x00, 0x7C,0x00,0x00,0x00, 0x7E,0x00,0x00,0x00, 0x7F,0x00,0x00,0x00, + 0x7F,0x80,0x00,0x00, 0x7F,0xC0,0x00,0x00, 0x7F,0xE0,0x00,0x00, 0x7E,0x00,0x00,0x00, + 0x7E,0x00,0x00,0x00, 0x73,0x00,0x00,0x00, 0x61,0x00,0x00,0x00, 0x61,0x00,0x00,0x00, + 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, +}; + +// Crosshair cursor +static const uint8_t gCursorCrosshairAnd[128] = { + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0x01,0xFF,0xFF, + 0xFF,0x01,0xFF,0xFF, 0xFF,0x01,0xFF,0xFF, 0xFF,0x01,0xFF,0xFF, 0x80,0x00,0x01,0xFF, + 0xFF,0x01,0xFF,0xFF, 0xFF,0x01,0xFF,0xFF, 0xFF,0x01,0xFF,0xFF, 0xFF,0x01,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, +}; +static const uint8_t gCursorCrosshairXor[128] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x80,0x00,0x00, + 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x3F,0xFF,0xFC,0x00, + 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, +}; + +// I-beam cursor +static const uint8_t gCursorIbeamAnd[128] = { + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xF8,0x38,0xFF,0xFF, 0xFE,0x7E,0xFF,0xFF, + 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, + 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, + 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, + 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, 0xFF,0x7E,0xFF,0xFF, 0xFE,0x7E,0xFF,0xFF, + 0xF8,0x38,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, +}; +static const uint8_t gCursorIbeamXor[128] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x03,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, + 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, + 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, + 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, + 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, 0x00,0x80,0x00,0x00, + 0x03,0x80,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, +}; + +// Hand cursor (pointing up) +static const uint8_t gCursorHandAnd[128] = { + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xF3,0xFF,0xFF,0xFF, 0xE1,0xFF,0xFF,0xFF, 0xE1,0xFF,0xFF,0xFF, 0xE1,0xFF,0xFF,0xFF, + 0xE1,0xFF,0xFF,0xFF, 0xE0,0x7F,0xFF,0xFF, 0xE0,0x07,0xFF,0xFF, 0xE0,0x03,0xFF,0xFF, + 0xA0,0x03,0xFF,0xFF, 0x80,0x03,0xFF,0xFF, 0x80,0x03,0xFF,0xFF, 0x80,0x07,0xFF,0xFF, + 0xC0,0x07,0xFF,0xFF, 0xC0,0x0F,0xFF,0xFF, 0xC0,0x0F,0xFF,0xFF, 0xE0,0x0F,0xFF,0xFF, + 0xE0,0x1F,0xFF,0xFF, 0xF0,0x1F,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, +}; +static const uint8_t gCursorHandXor[128] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x0C,0x00,0x00,0x00, 0x0C,0x00,0x00,0x00, 0x0C,0x00,0x00,0x00, 0x0C,0x00,0x00,0x00, + 0x0C,0x00,0x00,0x00, 0x0D,0x80,0x00,0x00, 0x0D,0xB0,0x00,0x00, 0x0D,0xB8,0x00,0x00, + 0x4D,0xB8,0x00,0x00, 0x6D,0xB8,0x00,0x00, 0x6F,0xB8,0x00,0x00, 0x7F,0xF0,0x00,0x00, + 0x3F,0xF0,0x00,0x00, 0x1F,0xE0,0x00,0x00, 0x1F,0xE0,0x00,0x00, 0x0F,0xE0,0x00,0x00, + 0x0F,0xC0,0x00,0x00, 0x07,0xC0,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, +}; + + +int32_t wdrvSetCursor(WdrvHandleT handle, WdrvCursorShapeE shape) +{ + if (shape == WDRV_CURSOR_NONE) { + // Hide cursor by passing NULL + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!handle->ddiEntry[DDI_ORD_SETCURSOR].present) { + return WDRV_ERR_UNSUPPORTED; + } + + uint16_t params[2]; + params[0] = 0; // NULL seg + params[1] = 0; // NULL off + + waitForEngine(); + thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_SETCURSOR].sel, + handle->ddiEntry[DDI_ORD_SETCURSOR].off, + params, 2); + waitForEngine(); + return WDRV_OK; + } + + const uint8_t *andMask = NULL; + const uint8_t *xorMask = NULL; + int16_t hotX = 0; + int16_t hotY = 0; + + switch (shape) { + case WDRV_CURSOR_ARROW: + andMask = gCursorArrowAnd; + xorMask = gCursorArrowXor; + hotX = 1; + hotY = 1; + break; + case WDRV_CURSOR_CROSSHAIR: + andMask = gCursorCrosshairAnd; + xorMask = gCursorCrosshairXor; + hotX = 15; + hotY = 15; + break; + case WDRV_CURSOR_IBEAM: + andMask = gCursorIbeamAnd; + xorMask = gCursorIbeamXor; + hotX = 8; + hotY = 15; + break; + case WDRV_CURSOR_HAND: + andMask = gCursorHandAnd; + xorMask = gCursorHandXor; + hotX = 5; + hotY = 0; + break; + default: + return WDRV_ERR_UNSUPPORTED; + } + + return wdrvSetCursorCustom(handle, hotX, hotY, andMask, xorMask); +} + + +int32_t wdrvSetCursorCustom(WdrvHandleT handle, int16_t hotX, int16_t hotY, const uint8_t *andMask, const uint8_t *xorMask) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!handle->ddiEntry[DDI_ORD_SETCURSOR].present) { + return WDRV_ERR_UNSUPPORTED; + } + + // CursorInfo16T (12 bytes) + AND mask (128 bytes) + XOR mask (128 bytes) = 268 bytes + uint32_t allocSize = sizeof(CursorInfo16T) + 128 + 128; + + // Free previous cursor allocation + if (handle->cursorSel) { + free16BitBlock(handle->cursorSel, handle->cursorLinear); + handle->cursorSel = 0; + handle->cursorLinear = 0; + handle->cursorAllocSize = 0; + } + + // Allocate new cursor block + uint32_t curLinear; + uint16_t curSel = alloc16BitBlock(allocSize, &curLinear); + if (curSel == 0) { + return WDRV_ERR_NO_MEMORY; + } + + handle->cursorSel = curSel; + handle->cursorLinear = curLinear; + handle->cursorAllocSize = allocSize; + + // Build the cursor shape structure + CursorInfo16T *ci = (CursorInfo16T *)curLinear; + ci->csHotX = hotX; + ci->csHotY = hotY; + ci->csWidth = 32; + ci->csHeight = 32; + ci->csWidthB = 4; // 32 / 8 + ci->csColor = 1; // mono: planes*bpp = 1 + + // Copy masks after the header + uint8_t *maskDst = (uint8_t *)curLinear + sizeof(CursorInfo16T); + memcpy(maskDst, andMask, 128); + memcpy(maskDst + 128, xorMask, 128); + + // SetCursor(LPCURSORSHAPE) — 2 words (far ptr) + uint16_t params[2]; + params[0] = curSel; + params[1] = 0; + + waitForEngine(); + + thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_SETCURSOR].sel, + handle->ddiEntry[DDI_ORD_SETCURSOR].off, + params, 2); + + waitForEngine(); + + dbg("windrv: SetCursor(%dx%d hot=%d,%d) done\n", 32, 32, hotX, hotY); + return WDRV_OK; +} + + +int32_t wdrvMoveCursor(WdrvHandleT handle, int16_t x, int16_t y) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!handle->ddiEntry[DDI_ORD_MOVECURSOR].present) { + return WDRV_ERR_UNSUPPORTED; + } + + // MoveCursor(WORD absX, WORD absY) — 2 words + uint16_t params[2]; + params[0] = (uint16_t)x; + params[1] = (uint16_t)(y + handle->dispYOffset); + + thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_MOVECURSOR].sel, + handle->ddiEntry[DDI_ORD_MOVECURSOR].off, + params, 2); + + return WDRV_OK; +} + + +// ============================================================================ +// Off-screen bitmaps +// ============================================================================ + +struct WdrvBitmapS { + uint16_t pdevSel; + uint32_t pdevLinear; + uint32_t pdevSize; + int16_t width; + int16_t height; + uint8_t bpp; +}; + + +WdrvBitmapT wdrvCreateBitmap(WdrvHandleT handle, int16_t width, int16_t height) +{ + if (!handle || !handle->enabled) { + return NULL; + } + if (!handle->ddiEntry[DDI_ORD_CREATEBITMAP].present) { + return NULL; + } + + uint8_t bpp = (uint8_t)(handle->gdiInfo.dpBitsPixel * handle->gdiInfo.dpPlanes); + + // Allocate PDEVICE-sized block for the bitmap descriptor + uint32_t pdevLinear; + uint16_t pdevSel = alloc16BitBlock(PDEVICE_MAX_SIZE, &pdevLinear); + if (pdevSel == 0) { + return NULL; + } + memset((void *)pdevLinear, 0, PDEVICE_MAX_SIZE); + + // Init the Bitmap16T header + Bitmap16T *bm = (Bitmap16T *)pdevLinear; + bm->bmType = 0; // memory bitmap + bm->bmWidth = width; + bm->bmHeight = height; + bm->bmWidthBytes = (int16_t)(((width * bpp + 15) / 16) * 2); + bm->bmPlanes = (uint8_t)handle->gdiInfo.dpPlanes; + bm->bmBitsPixel = (uint8_t)handle->gdiInfo.dpBitsPixel; + bm->bmBits = 0; // driver will manage bits + + // DDK: short FAR PASCAL CreateBitmap(LPPDEVICE lpDevice, short style, + // LPBITMAP lpBitmap, LPSTR lpBits) + // style > 0 = create, style < 0 = delete + // Total: lpDevice(2w) + style(1w) + lpBitmap(2w) + lpBits(2w) = 7 words + uint16_t dgSel = handle->neMod.autoDataSel; + uint16_t params[7]; + int i = 0; + params[i++] = dgSel; // lpDevice seg + params[i++] = handle->pdevOff; // lpDevice off + params[i++] = 1; // style = create + params[i++] = pdevSel; // lpBitmap seg + params[i++] = 0; // lpBitmap off + params[i++] = 0; // lpBits seg (NULL = don't init) + params[i++] = 0; // lpBits off + + waitForEngine(); + + uint32_t result = thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_CREATEBITMAP].sel, + handle->ddiEntry[DDI_ORD_CREATEBITMAP].off, + params, i); + + waitForEngine(); + + dbg("windrv: CreateBitmap(%dx%d %dbpp) returned %d\n", + width, height, (int)bpp, (int16_t)(result & 0xFFFF)); + + if ((int16_t)(result & 0xFFFF) <= 0) { + free16BitBlock(pdevSel, pdevLinear); + return NULL; + } + + struct WdrvBitmapS *bitmap = (struct WdrvBitmapS *)calloc(1, sizeof(struct WdrvBitmapS)); + if (!bitmap) { + free16BitBlock(pdevSel, pdevLinear); + return NULL; + } + + bitmap->pdevSel = pdevSel; + bitmap->pdevLinear = pdevLinear; + bitmap->pdevSize = PDEVICE_MAX_SIZE; + bitmap->width = width; + bitmap->height = height; + bitmap->bpp = bpp; + + return bitmap; +} + + +void wdrvDeleteBitmap(WdrvHandleT handle, WdrvBitmapT bitmap) +{ + if (!bitmap) { + return; + } + + if (handle && handle->enabled && handle->ddiEntry[DDI_ORD_DELETEBITMAP].present) { + // DeleteBitmap(LPDEVICE lpDevice, LPBITMAP lpBitmap) + // Pascal push order: lpDevice(2w) + lpBitmap(2w) = 4 words... but ordinal says 2 words + // Actually DeleteBitmap is just: void DeleteBitmap(LPBITMAP) = 2 words + uint16_t params[2]; + params[0] = bitmap->pdevSel; + params[1] = 0; + + waitForEngine(); + thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_DELETEBITMAP].sel, + handle->ddiEntry[DDI_ORD_DELETEBITMAP].off, + params, 2); + waitForEngine(); + } + + free16BitBlock(bitmap->pdevSel, bitmap->pdevLinear); + free(bitmap); +} + + +int32_t wdrvBitmapSetPixels(WdrvHandleT handle, WdrvBitmapT bitmap, const uint8_t *data, uint32_t dataSize) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!bitmap) { + return WDRV_ERR_UNSUPPORTED; + } + if (!handle->ddiEntry[DDI_ORD_BITMAPBITS].present) { + return WDRV_ERR_UNSUPPORTED; + } + + // Allocate 16-bit buffer for the pixel data + uint32_t bufLinear; + uint16_t bufSel = alloc16BitBlock(dataSize, &bufLinear); + if (bufSel == 0) { + return WDRV_ERR_NO_MEMORY; + } + memcpy((void *)bufLinear, data, dataSize); + + // BitmapBits(LPDEVICE lpDevice, DWORD fFlags, DWORD dwCount, LPSTR lpBits) + // Pascal push order: lpDevice(2w), fFlags(2w), dwCount(2w), lpBits(2w) = 8 words + // fFlags=0 means SET bits, fFlags!=0 means GET bits + // Actually: long BitmapBits(LPPDEVICE, fFlags, dwCount, lpBits) + uint16_t params[8]; + int i = 0; + params[i++] = bitmap->pdevSel; // lpDevice seg + params[i++] = 0; // lpDevice off + params[i++] = 0; // fFlags high (0 = set) + params[i++] = 0; // fFlags low + params[i++] = (uint16_t)(dataSize >> 16); // dwCount high + params[i++] = (uint16_t)(dataSize); // dwCount low + params[i++] = bufSel; // lpBits seg + params[i++] = 0; // lpBits off + + waitForEngine(); + + uint32_t result = thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_BITMAPBITS].sel, + handle->ddiEntry[DDI_ORD_BITMAPBITS].off, + params, i); + + waitForEngine(); + + free16BitBlock(bufSel, bufLinear); + + dbg("windrv: BitmapBits(SET, %lu bytes) returned %ld\n", + (unsigned long)dataSize, (long)(int32_t)result); + + return WDRV_OK; +} + + +int32_t wdrvBitmapGetPixels(WdrvHandleT handle, WdrvBitmapT bitmap, uint8_t *data, uint32_t dataSize) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!bitmap) { + return WDRV_ERR_UNSUPPORTED; + } + if (!handle->ddiEntry[DDI_ORD_BITMAPBITS].present) { + return WDRV_ERR_UNSUPPORTED; + } + + // Allocate 16-bit buffer for receiving the pixel data + uint32_t bufLinear; + uint16_t bufSel = alloc16BitBlock(dataSize, &bufLinear); + if (bufSel == 0) { + return WDRV_ERR_NO_MEMORY; + } + memset((void *)bufLinear, 0, dataSize); + + // fFlags=1 means GET bits + uint16_t params[8]; + int i = 0; + params[i++] = bitmap->pdevSel; + params[i++] = 0; + params[i++] = 0; // fFlags high + params[i++] = 1; // fFlags low (1 = get) + params[i++] = (uint16_t)(dataSize >> 16); + params[i++] = (uint16_t)(dataSize); + params[i++] = bufSel; + params[i++] = 0; + + waitForEngine(); + + uint32_t result = thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_BITMAPBITS].sel, + handle->ddiEntry[DDI_ORD_BITMAPBITS].off, + params, i); + + waitForEngine(); + + memcpy(data, (void *)bufLinear, dataSize); + free16BitBlock(bufSel, bufLinear); + + dbg("windrv: BitmapBits(GET, %lu bytes) returned %ld\n", + (unsigned long)dataSize, (long)(int32_t)result); + + return WDRV_OK; +} + + +int32_t wdrvBitBltFromBitmap(WdrvHandleT handle, WdrvBitmapT srcBitmap, int16_t srcX, int16_t srcY, int16_t dstX, int16_t dstY, int16_t w, int16_t h, uint32_t rop3) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!srcBitmap) { + return WDRV_ERR_UNSUPPORTED; + } + if (!handle->ddiEntry[DDI_ORD_BITBLT].present) { + return WDRV_ERR_UNSUPPORTED; + } + + uint16_t dgSel = handle->neMod.autoDataSel; + uint16_t params[16]; + int i = 0; + + // lpDstDev = screen PDEVICE + params[i++] = dgSel; + params[i++] = handle->pdevOff; + // DstX, DstY + params[i++] = (uint16_t)dstX; + params[i++] = (uint16_t)(dstY + handle->dispYOffset); + // lpSrcDev = bitmap PDEVICE + params[i++] = srcBitmap->pdevSel; + params[i++] = 0; + // SrcX, SrcY + params[i++] = (uint16_t)srcX; + params[i++] = (uint16_t)srcY; + // xExt, yExt + params[i++] = (uint16_t)w; + params[i++] = (uint16_t)h; + // Rop3 + params[i++] = (uint16_t)(rop3 >> 16); + params[i++] = (uint16_t)(rop3 & 0xFFFF); + // lpBrush + params[i++] = dgSel; + params[i++] = handle->brushOff; + // lpDrawMode + params[i++] = dgSel; + params[i++] = handle->drawModeOff; + + waitForEngine(); + + uint32_t result = thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_BITBLT].sel, + handle->ddiEntry[DDI_ORD_BITBLT].off, + params, i); + + waitForEngine(); + + dbg("windrv: BitBltFromBitmap returned %lu\n", (unsigned long)(result & 0xFFFF)); + + return ((int16_t)(result & 0xFFFF)) ? WDRV_OK : WDRV_ERR_UNSUPPORTED; +} + + +int32_t wdrvBitBltToBitmap(WdrvHandleT handle, WdrvBitmapT dstBitmap, int16_t srcX, int16_t srcY, int16_t dstX, int16_t dstY, int16_t w, int16_t h, uint32_t rop3) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!dstBitmap) { + return WDRV_ERR_UNSUPPORTED; + } + if (!handle->ddiEntry[DDI_ORD_BITBLT].present) { + return WDRV_ERR_UNSUPPORTED; + } + + uint16_t dgSel = handle->neMod.autoDataSel; + uint16_t params[16]; + int i = 0; + + // lpDstDev = bitmap PDEVICE + params[i++] = dstBitmap->pdevSel; + params[i++] = 0; + // DstX, DstY + params[i++] = (uint16_t)dstX; + params[i++] = (uint16_t)dstY; + // lpSrcDev = screen PDEVICE + params[i++] = dgSel; + params[i++] = handle->pdevOff; + // SrcX, SrcY + params[i++] = (uint16_t)srcX; + params[i++] = (uint16_t)(srcY + handle->dispYOffset); + // xExt, yExt + params[i++] = (uint16_t)w; + params[i++] = (uint16_t)h; + // Rop3 + params[i++] = (uint16_t)(rop3 >> 16); + params[i++] = (uint16_t)(rop3 & 0xFFFF); + // lpBrush + params[i++] = dgSel; + params[i++] = handle->brushOff; + // lpDrawMode + params[i++] = dgSel; + params[i++] = handle->drawModeOff; + + waitForEngine(); + + uint32_t result = thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_BITBLT].sel, + handle->ddiEntry[DDI_ORD_BITBLT].off, + params, i); + + waitForEngine(); + + dbg("windrv: BitBltToBitmap returned %lu\n", (unsigned long)(result & 0xFFFF)); + + return ((int16_t)(result & 0xFFFF)) ? WDRV_OK : WDRV_ERR_UNSUPPORTED; +} + + // ============================================================================ // Framebuffer access // ============================================================================ @@ -2270,6 +3613,146 @@ int32_t wdrvGetPitch(WdrvHandleT handle) } +static void readDacPalette(uint8_t rgb[768], uint8_t dacWidth) +{ + uint8_t dacShift = 8 - dacWidth; // 2 for 6-bit, 0 for 8-bit + outportb(0x3C7, 0); + for (int32_t i = 0; i < 768; i++) { + rgb[i] = (uint8_t)(inportb(0x3C9) << dacShift); + } +} + + +int32_t wdrvScreenshot(WdrvHandleT handle, const char *filename) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!filename) { + return WDRV_ERR_UNSUPPORTED; + } + + int16_t screenW = handle->gdiInfo.dpHorzRes; + int16_t screenH = handle->gdiInfo.dpVertRes; + + // Read the VGA DAC palette (256 entries x 3 bytes = 768 bytes) + uint8_t palette[768]; + readDacPalette(palette, handle->dacWidth); + + // Allocate RGB output buffer + uint8_t *rgbBuf = (uint8_t *)malloc((uint32_t)screenW * screenH * 3); + if (!rgbBuf) { + setError(WDRV_ERR_NO_MEMORY); + return WDRV_ERR_NO_MEMORY; + } + + // Determine which read path to use: + // 1. Linear FB: vramPtr != NULL, single plane, and VRAM large enough + // for the full screen (excludes banked 64KB VGA aperture). + // 2. DDI bitmap: BitBltToBitmap + BitmapGetPixels in strips. + // 3. GetPixel fallback: per-pixel Pixel DDI (slow but universal). + uint32_t screenBytes = (uint32_t)screenW * screenH; + bool useLinearFb = (handle->vramPtr != NULL) && + (handle->gdiInfo.dpPlanes == 1) && + (handle->vramSize >= screenBytes); + + if (useLinearFb) { + // Fast path: linear framebuffer read + uint8_t *fb = (uint8_t *)handle->vramPtr; + int32_t pitch = handle->pitch; + + waitForEngine(); + + for (int16_t y = 0; y < screenH; y++) { + uint8_t *src = fb + (y + handle->dispYOffset) * pitch; + uint8_t *dst = rgbBuf + y * screenW * 3; + for (int16_t x = 0; x < screenW; x++) { + uint8_t idx = src[x]; + dst[x * 3 + 0] = palette[idx * 3 + 0]; + dst[x * 3 + 1] = palette[idx * 3 + 1]; + dst[x * 3 + 2] = palette[idx * 3 + 2]; + } + } + dbg("windrv: screenshot via linear FB\n"); + } else { + // Try DDI bitmap path first + #define SCREENSHOT_STRIP_HEIGHT 32 + WdrvBitmapT bmp = wdrvCreateBitmap(handle, screenW, SCREENSHOT_STRIP_HEIGHT); + + if (bmp) { + // DDI bitmap path: read screen in strips + uint32_t stripBytes = (uint32_t)screenW * SCREENSHOT_STRIP_HEIGHT; + uint8_t *stripBuf = (uint8_t *)malloc(stripBytes); + if (!stripBuf) { + wdrvDeleteBitmap(handle, bmp); + free(rgbBuf); + setError(WDRV_ERR_NO_MEMORY); + return WDRV_ERR_NO_MEMORY; + } + + bool ok = true; + for (int16_t y = 0; y < screenH; y += SCREENSHOT_STRIP_HEIGHT) { + int16_t stripH = SCREENSHOT_STRIP_HEIGHT; + if (y + stripH > screenH) { + stripH = screenH - y; + } + + int32_t ret = wdrvBitBltToBitmap(handle, bmp, 0, y, 0, 0, screenW, stripH, 0x00CC0020); + if (ret != WDRV_OK) { + ok = false; + break; + } + + ret = wdrvBitmapGetPixels(handle, bmp, stripBuf, stripBytes); + if (ret != WDRV_OK) { + ok = false; + break; + } + + for (int16_t row = 0; row < stripH; row++) { + uint8_t *src = stripBuf + row * screenW; + uint8_t *dst = rgbBuf + (y + row) * screenW * 3; + for (int16_t x = 0; x < screenW; x++) { + uint8_t idx = src[x]; + dst[x * 3 + 0] = palette[idx * 3 + 0]; + dst[x * 3 + 1] = palette[idx * 3 + 1]; + dst[x * 3 + 2] = palette[idx * 3 + 2]; + } + } + } + + free(stripBuf); + wdrvDeleteBitmap(handle, bmp); + + if (!ok) { + free(rgbBuf); + return WDRV_ERR_UNSUPPORTED; + } + dbg("windrv: screenshot via DDI bitmap\n"); + } else { + // No viable read path (no linear FB, no CreateBitmap) + dbg("windrv: screenshot unsupported (no read path)\n"); + free(rgbBuf); + setError(WDRV_ERR_UNSUPPORTED); + return WDRV_ERR_UNSUPPORTED; + } + #undef SCREENSHOT_STRIP_HEIGHT + } + + // Write PNG + int32_t ok = stbi_write_png(filename, screenW, screenH, 3, rgbBuf, screenW * 3); + free(rgbBuf); + + if (!ok) { + setError(WDRV_ERR_UNSUPPORTED); + return WDRV_ERR_UNSUPPORTED; + } + + dbg("windrv: screenshot saved to %s (%dx%d)\n", filename, screenW, screenH); + return WDRV_OK; +} + + // ============================================================================ // Error handling // ============================================================================ @@ -2675,7 +4158,7 @@ static bool realizeBrush(struct WdrvDriverS *drv, uint32_t color) } -static bool realizePen(struct WdrvDriverS *drv, uint32_t color) +static bool realizeStyledPen(struct WdrvDriverS *drv, uint32_t color, int16_t style) { if (!drv->ddiEntry[DDI_ORD_REALIZEOBJECT].present) { return false; @@ -2685,7 +4168,7 @@ static bool realizePen(struct WdrvDriverS *drv, uint32_t color) // Set up the logical pen LogPen16T *lp = (LogPen16T *)drv->logPenLinear; - lp->lopnStyle = PS_SOLID; + lp->lopnStyle = (uint16_t)style; lp->lopnWidth.x = 1; lp->lopnWidth.y = 0; lp->lopnColor = color; @@ -2715,12 +4198,13 @@ static bool realizePen(struct WdrvDriverS *drv, uint32_t color) waitForEngine(); - dbg("windrv: RealizeObject(pen, color=0x%06lX) returned %d\n", - (unsigned long)color, (int16_t)(result & 0xFFFF)); + dbg("windrv: RealizeObject(pen, color=0x%06lX, style=%d) returned %d\n", + (unsigned long)color, (int)style, (int16_t)(result & 0xFFFF)); if ((int16_t)(result & 0xFFFF) > 0) { drv->penRealized = true; drv->penRealizedColor = color; + drv->penRealizedStyle = style; // Dump the first 16 bytes of the realized pen uint8_t *pdata = (uint8_t *)drv->penLinear; @@ -2740,8 +4224,9 @@ static bool realizePen(struct WdrvDriverS *drv, uint32_t color) static void freeDrawObjects(struct WdrvDriverS *drv) { // Objects are embedded in DGROUP - freed when module is unloaded - drv->brushRealized = false; - drv->penRealized = false; + drv->brushRealized = false; + drv->penRealized = false; + drv->penRealizedStyle = 0; } diff --git a/win31drv/windrv.h b/win31drv/windrv.h index d0d13f3..a9b54c2 100644 --- a/win31drv/windrv.h +++ b/win31drv/windrv.h @@ -73,6 +73,10 @@ typedef struct { bool hasExtTextOut; // Driver exports ExtTextOut bool hasSetPalette; // Driver exports SetPalette bool hasSetCursor; // Driver exports SetCursor + bool hasMoveCursor; // Driver exports MoveCursor + bool hasScanLR; // Driver exports ScanLR + bool hasGetCharWidth; // Driver exports GetCharWidth + bool hasCreateBitmap; // Driver exports CreateBitmap } WdrvInfoT; // ============================================================================ @@ -150,6 +154,12 @@ int32_t wdrvPolyline(WdrvHandleT handle, Point16T *points, int16_t count, uint32 // Draw a rectangle outline. int32_t wdrvRectangle(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color); +// Draw a polyline with a specific pen style. +int32_t wdrvPolylineEx(WdrvHandleT handle, Point16T *points, int16_t count, uint32_t color, int16_t penStyle); + +// Draw a rectangle outline with a specific pen style. +int32_t wdrvRectangleEx(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color, int16_t penStyle); + // Draw text using the ExtTextOut DDI function. // Pass NULL for font to use the built-in 8x16 VGA bitmap font. int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y, @@ -157,6 +167,104 @@ int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y, uint32_t fgColor, uint32_t bgColor, bool opaque, WdrvFontT font); +// ============================================================================ +// Pen styles (for wdrvPolylineEx / wdrvRectangleEx) +// ============================================================================ + +#define WDRV_PEN_SOLID 0 +#define WDRV_PEN_DASH 1 +#define WDRV_PEN_DOT 2 +#define WDRV_PEN_DASHDOT 3 +#define WDRV_PEN_DASHDOTDOT 4 + +// ============================================================================ +// ScanLR and Flood Fill +// ============================================================================ + +// ScanLR style bits +#define WDRV_SCAN_RIGHT 0x00 // Scan right, find not-matching +#define WDRV_SCAN_RIGHT_MATCH 0x01 // Scan right, find matching +#define WDRV_SCAN_LEFT 0x02 // Scan left, find not-matching +#define WDRV_SCAN_LEFT_MATCH 0x03 // Scan left, find matching + +// Scan left or right from (x,y) for color match/non-match. +// style is a bitfield: bit 0=FIND_COLOR (match), bit 1=STEP_LEFT. +// Returns X where scan stopped, or -1/screenW if not found. +int16_t wdrvScanLR(WdrvHandleT handle, int16_t x, int16_t y, uint32_t color, int16_t style); + +// Flood fill starting at (x,y) with fillColor. +// Uses framebuffer when available, falls back to ScanLR + FillRect. +int32_t wdrvFloodFill(WdrvHandleT handle, int16_t x, int16_t y, uint32_t fillColor); + +// ============================================================================ +// Text measurement +// ============================================================================ + +// Get character widths for a range of characters. +// widths array must have room for (lastChar - firstChar + 1) entries. +int32_t wdrvGetCharWidths(WdrvHandleT handle, WdrvFontT font, uint8_t firstChar, uint8_t lastChar, int16_t *widths); + +// Measure total pixel width of a text string. +int32_t wdrvMeasureText(WdrvHandleT handle, WdrvFontT font, const char *text, int16_t length); + +// ============================================================================ +// Pixel buffer blitting (direct framebuffer) +// ============================================================================ + +// Blit an 8bpp pixel buffer to the screen. +// Uses framebuffer when available, falls back to Pixel DDI per pixel. +int32_t wdrvBlitPixels(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, int32_t srcPitch); + +// Load and display a BMP file at (x,y). If setPalette is true, applies +// the BMP's palette via wdrvSetPalette. +int32_t wdrvBlitBmp(WdrvHandleT handle, int16_t x, int16_t y, const char *bmpPath, bool setPalette); + +// ============================================================================ +// Hardware cursor +// ============================================================================ + +typedef enum { + WDRV_CURSOR_ARROW, + WDRV_CURSOR_CROSSHAIR, + WDRV_CURSOR_IBEAM, + WDRV_CURSOR_HAND, + WDRV_CURSOR_NONE +} WdrvCursorShapeE; + +// Set cursor to a built-in shape. WDRV_CURSOR_NONE hides the cursor. +int32_t wdrvSetCursor(WdrvHandleT handle, WdrvCursorShapeE shape); + +// Set cursor to a custom 32x32 mono bitmap. +// andMask and xorMask are each 128 bytes (32 rows x 4 bytes/row). +int32_t wdrvSetCursorCustom(WdrvHandleT handle, int16_t hotX, int16_t hotY, const uint8_t *andMask, const uint8_t *xorMask); + +// Move the hardware cursor to screen position (x,y). +int32_t wdrvMoveCursor(WdrvHandleT handle, int16_t x, int16_t y); + +// ============================================================================ +// Off-screen bitmaps +// ============================================================================ + +typedef struct WdrvBitmapS *WdrvBitmapT; + +// Create an off-screen bitmap via the driver's CreateBitmap DDI. +WdrvBitmapT wdrvCreateBitmap(WdrvHandleT handle, int16_t width, int16_t height); + +// Delete an off-screen bitmap. +void wdrvDeleteBitmap(WdrvHandleT handle, WdrvBitmapT bitmap); + +// Set bitmap pixel data via BitmapBits DDI. +int32_t wdrvBitmapSetPixels(WdrvHandleT handle, WdrvBitmapT bitmap, const uint8_t *data, uint32_t dataSize); + +// Get bitmap pixel data via BitmapBits DDI. +int32_t wdrvBitmapGetPixels(WdrvHandleT handle, WdrvBitmapT bitmap, uint8_t *data, uint32_t dataSize); + +// BitBlt from an off-screen bitmap to the screen. +int32_t wdrvBitBltFromBitmap(WdrvHandleT handle, WdrvBitmapT srcBitmap, int16_t srcX, int16_t srcY, int16_t dstX, int16_t dstY, int16_t w, int16_t h, uint32_t rop3); + +// BitBlt from the screen to an off-screen bitmap. +int32_t wdrvBitBltToBitmap(WdrvHandleT handle, WdrvBitmapT dstBitmap, int16_t srcX, int16_t srcY, int16_t dstX, int16_t dstY, int16_t w, int16_t h, uint32_t rop3); + // ============================================================================ // Font loading // ============================================================================ @@ -201,6 +309,10 @@ void *wdrvGetFramebuffer(WdrvHandleT handle); // Get the framebuffer pitch (bytes per scanline). int32_t wdrvGetPitch(WdrvHandleT handle); +// Save the current screen to a PNG file. +// Reads the framebuffer (or uses DDI fallback) and DAC palette. +int32_t wdrvScreenshot(WdrvHandleT handle, const char *filename); + // ============================================================================ // Error reporting // ============================================================================