SDL: Add support for monochrome cursors with inverted pixels under Windows.

From 627d134b9ee2ae9fdc256bad66995014db991cdf Mon Sep 17 00:00:00 2001
From: Dimitriy Ryazantcev <[EMAIL REDACTED]>
Date: Thu, 14 Dec 2023 16:11:25 +0200
Subject: [PATCH] Add support for monochrome cursors with inverted pixels under
 Windows.

Fixes #8157
---
 src/events/SDL_mouse.c               |  8 ++-
 src/video/windows/SDL_windowsmouse.c | 78 +++++++++++++++++++++-------
 2 files changed, 67 insertions(+), 19 deletions(-)

diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index 97510469794c..57357ad938cc 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -1238,6 +1238,12 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h,
     const Uint32 black = 0xFF000000;
     const Uint32 white = 0xFFFFFFFF;
     const Uint32 transparent = 0x00000000;
+#if defined(__WIN32__)
+    /* Only Windows backend supports inverted pixels in mono cursors. */
+    const Uint32 inverted = 0x00FFFFFF;
+#else
+    const Uint32 inverted = 0xFF000000;
+#endif /* defined(__WIN32__) */
 
     /* Make sure the width is a multiple of 8 */
     w = ((w + 7) & ~7);
@@ -1257,7 +1263,7 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h,
             if (maskb & 0x80) {
                 *pixel++ = (datab & 0x80) ? black : white;
             } else {
-                *pixel++ = (datab & 0x80) ? black : transparent;
+                *pixel++ = (datab & 0x80) ? inverted : transparent;
             }
             datab <<= 1;
             maskb <<= 1;
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index 95d2e01cfde6..a037407c925f 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -25,6 +25,7 @@
 #include "SDL_windowsvideo.h"
 
 #include "../../events/SDL_mouse_c.h"
+#include "../SDL_video_c.h"
 
 DWORD SDL_last_warp_time = 0;
 HCURSOR SDL_cursor = NULL;
@@ -80,6 +81,32 @@ static SDL_Cursor *WIN_CreateDefaultCursor()
     return cursor;
 }
 
+static SDL_bool IsMonochromeSurface(SDL_Surface *surface)
+{
+    int x, y;
+    Uint8 r, g, b, a;
+
+    SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
+
+    for (y = 0; y < surface->h; y++) {
+        for (x = 0; x < surface->w; x++) {
+            SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a);
+
+            /* Black or white pixel. */
+            if (!((r == 0x00 && g == 0x00 && b == 0x00) || (r == 0xff && g == 0xff && b == 0xff))) {
+                return SDL_FALSE;
+            }
+
+            /* Transparent or opaque pixel. */
+            if (!(a == 0x00 || a == 0xff)) {
+                return SDL_FALSE;
+            }
+        }
+    }
+
+    return SDL_TRUE;
+}
+
 static HBITMAP CreateColorBitmap(SDL_Surface *surface)
 {
     HBITMAP bitmap;
@@ -107,43 +134,55 @@ static HBITMAP CreateColorBitmap(SDL_Surface *surface)
     return bitmap;
 }
 
-static HBITMAP CreateMaskBitmap(SDL_Surface *surface)
+/* Generate bitmap with a mask and optional monochrome image data.
+ *
+ * For info on the expected mask format see:
+ * https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
+ */
+static HBITMAP CreateMaskBitmap(SDL_Surface *surface, SDL_bool is_monochrome)
 {
     HBITMAP bitmap;
     SDL_bool isstack;
     void *pixels;
     int x, y;
-    Uint8 *src, *dst;
+    Uint8 r, g, b, a;
+    Uint8 *dst;
     const int pitch = ((surface->w + 15) & ~15) / 8;
+    const int size = pitch * surface->h;
     static const unsigned char masks[] = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
 
     SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
 
-    pixels = SDL_small_alloc(Uint8, pitch * surface->h, &isstack);
+    pixels = SDL_small_alloc(Uint8, size * (is_monochrome ? 2 : 1), &isstack);
     if (!pixels) {
         return NULL;
     }
 
-    /* Make the entire mask completely transparent. */
-    SDL_memset(pixels, 0xff, pitch * surface->h);
+    dst = pixels;
 
-    SDL_LockSurface(surface);
+    /* Make the mask completely transparent. */
+    SDL_memset(dst, 0xff, size);
+    if (is_monochrome) {
+        SDL_memset(dst + size, 0x00, size);
+    }
 
-    src = surface->pixels;
-    dst = pixels;
-    for (y = 0; y < surface->h; y++, src += surface->pitch, dst += pitch) {
+    for (y = 0; y < surface->h; y++, dst += pitch) {
         for (x = 0; x < surface->w; x++) {
-            Uint8 alpha = src[x * 4 + 3];
-            if (alpha != 0) {
+            SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a);
+
+            if (a != 0) {
                 /* Reset bit of an opaque pixel. */
                 dst[x >> 3] &= ~masks[x & 7];
             }
+
+            if (is_monochrome && !(r == 0x00 && g == 0x00 && b == 0x00)) {
+                /* Set bit of white or inverted pixel. */
+                dst[size + (x >> 3)] |= masks[x & 7];
+            }
         }
     }
 
-    SDL_UnlockSurface(surface);
-
-    bitmap = CreateBitmap(surface->w, surface->h, 1, 1, pixels);
+    bitmap = CreateBitmap(surface->w, surface->h * (is_monochrome ? 2 : 1), 1, 1, pixels);
     SDL_small_free(pixels, isstack);
     if (!bitmap) {
         WIN_SetError("CreateBitmap()");
@@ -158,22 +197,25 @@ static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
     HCURSOR hcursor;
     SDL_Cursor *cursor;
     ICONINFO ii;
+    SDL_bool is_monochrome = IsMonochromeSurface(surface);
 
     SDL_zero(ii);
     ii.fIcon = FALSE;
     ii.xHotspot = (DWORD)hot_x;
     ii.yHotspot = (DWORD)hot_y;
-    ii.hbmColor = CreateColorBitmap(surface);
-    ii.hbmMask = CreateMaskBitmap(surface);
+    ii.hbmMask = CreateMaskBitmap(surface, is_monochrome);
+    ii.hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface);
 
-    if (!ii.hbmColor || !ii.hbmMask) {
+    if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) {
         return NULL;
     }
 
     hcursor = CreateIconIndirect(&ii);
 
-    DeleteObject(ii.hbmColor);
     DeleteObject(ii.hbmMask);
+    if (ii.hbmColor) {
+        DeleteObject(ii.hbmColor);
+    }
 
     if (!hcursor) {
         WIN_SetError("CreateIconIndirect()");