sdl2-compat: Add shaped window based on SDL_WINDOW_TRANSPARENT

From 03631a857cbb8182987eb28c9cc5c88433b28ffe Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Thu, 23 Nov 2023 05:23:50 +0300
Subject: [PATCH] Add shaped window based on SDL_WINDOW_TRANSPARENT

---
 src/sdl2_compat.c          | 231 ++++++++++++++++++++++++++++++++++++-
 src/sdl2_compat.h          |  23 ++++
 src/sdl3_include_wrapper.h |  20 ----
 src/sdl3_syms.h            |   4 -
 4 files changed, 253 insertions(+), 25 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index 14b3ad2..d2a534b 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -5568,16 +5568,116 @@ SDL_CreateWindowFrom(const void *data)
     return window;
 }
 
+static SDL_Window *g_shaped_window = NULL;
+static SDL_WindowShapeMode g_shape_mode;
+static Uint8 *g_bitmap = NULL;
+static int g_bitmap_w = 0, g_bitmap_h = 0;
+static SDL_Surface *g_shape_surface = NULL;
+static SDL_Texture *g_shape_texture = NULL;
+
+static void shaped_window_cleanup(void)
+{
+    g_shaped_window = NULL;
+    SDL3_zero(g_shape_mode);
+    if (g_bitmap) {
+        SDL3_free(g_bitmap);
+        g_bitmap = NULL;
+    }
+    g_bitmap_w = 0;
+    g_bitmap_h = 0;
+
+    if (g_shape_surface) {
+        SDL3_DestroySurface(g_shape_surface);
+        g_shape_surface = NULL;
+    }
+
+    if (g_shape_texture) {
+        SDL3_DestroyTexture(g_shape_texture);
+        g_shape_texture = NULL;
+    }
+}
+
+/* REQUIRES that bitmap point to a w-by-h bitmap with ppb pixels-per-byte. */
+static void SDL_CalculateShapeBitmap(SDL_WindowShapeMode mode, SDL_Surface *shape, Uint8 *bitmap, Uint8 ppb)
+{
+    int x = 0;
+    int y = 0;
+    Uint8 r = 0, g = 0, b = 0, alpha = 0;
+    Uint8 *pixel = NULL;
+    Uint32 pixel_value = 0, mask_value = 0;
+    size_t bytes_per_scanline = (size_t)(shape->w + (ppb - 1)) / ppb;
+    Uint8 *bitmap_scanline;
+    SDL_Color key;
+
+    if (SDL_MUSTLOCK(shape)) {
+        SDL_LockSurface(shape);
+    }
+
+    SDL_memset(bitmap, 0, shape->h * bytes_per_scanline);
+
+    for (y = 0; y < shape->h; y++) {
+        bitmap_scanline = bitmap + y * bytes_per_scanline;
+        for (x = 0; x < shape->w; x++) {
+            alpha = 0;
+            pixel_value = 0;
+            pixel = (Uint8 *)(shape->pixels) + (y * shape->pitch) + (x * shape->format->BytesPerPixel);
+            switch (shape->format->BytesPerPixel) {
+            case (1):
+                pixel_value = *pixel;
+                break;
+            case (2):
+                pixel_value = *(Uint16 *)pixel;
+                break;
+            case (3):
+                pixel_value = *(Uint32 *)pixel & (~shape->format->Amask);
+                break;
+            case (4):
+                pixel_value = *(Uint32 *)pixel;
+                break;
+            }
+            SDL_GetRGBA(pixel_value, shape->format, &r, &g, &b, &alpha);
+            switch (mode.mode) {
+            case (ShapeModeDefault):
+                mask_value = (alpha >= 1 ? 1 : 0);
+                break;
+            case (ShapeModeBinarizeAlpha):
+                mask_value = (alpha >= mode.parameters.binarizationCutoff ? 1 : 0);
+                break;
+            case (ShapeModeReverseBinarizeAlpha):
+                mask_value = (alpha <= mode.parameters.binarizationCutoff ? 1 : 0);
+                break;
+            case (ShapeModeColorKey):
+                key = mode.parameters.colorKey;
+                mask_value = ((key.r != r || key.g != g || key.b != b) ? 1 : 0);
+                break;
+            }
+            bitmap_scanline[x / ppb] |= mask_value << (x % ppb);
+        }
+    }
+
+    if (SDL_MUSTLOCK(shape)) {
+        SDL_UnlockSurface(shape);
+    }
+}
+
+
+
 DECLSPEC SDL_Window * SDLCALL
 SDL_CreateShapedWindow(const char *title, unsigned int x, unsigned int y, unsigned int w, unsigned int h, Uint32 flags)
 {
     SDL_Window *window;
     int hidden = flags & SDL_WINDOW_HIDDEN;
 
+    if (g_shaped_window != NULL) {
+        SDL3_SetError("only 1 shaped window");
+        return NULL;
+    }
+
     flags &= ~SDL2_WINDOW_SHOWN;
     flags |= SDL_WINDOW_HIDDEN;
+    flags |= SDL_WINDOW_TRANSPARENT;
 
-    window = SDL3_CreateShapedWindow(title, (int)w, (int)h, flags);
+    window = SDL3_CreateWindow(title, (int)w, (int)h, flags);
     if (window) {
         if (!SDL_WINDOWPOS_ISUNDEFINED(x) || !SDL_WINDOWPOS_ISUNDEFINED(y)) {
             SDL3_SetWindowPosition(window, (int)x, (int)y);
@@ -5586,9 +5686,95 @@ SDL_CreateShapedWindow(const char *title, unsigned int x, unsigned int y, unsign
             SDL3_ShowWindow(window);
         }
     }
+
+    shaped_window_cleanup();
+    g_shaped_window = window;
+
     return window;
 }
 
+DECLSPEC SDL_bool SDLCALL
+SDL_IsShapedWindow(const SDL_Window *window)
+{
+    if (window == NULL) {
+        return SDL_FALSE;
+    }
+    if (window == g_shaped_window) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
+DECLSPEC int SDLCALL
+SDL_SetWindowShape(SDL_Window *window,SDL_Surface *shape, SDL_WindowShapeMode *shape_mode)
+{
+    if (window == NULL) {
+        return SDL_NONSHAPEABLE_WINDOW;
+    }
+
+    if (window != g_shaped_window) {
+        return SDL_NONSHAPEABLE_WINDOW;
+    }
+
+    if (shape == NULL) {
+        return SDL_INVALID_SHAPE_ARGUMENT;
+    }
+
+    if (shape_mode == NULL) {
+        return SDL_INVALID_SHAPE_ARGUMENT;
+    }
+
+    shaped_window_cleanup();
+    g_shaped_window = window;
+    g_shape_mode = *shape_mode;
+
+    g_bitmap_w = shape->w;
+    g_bitmap_h = shape->h;
+    g_bitmap = (Uint8*) SDL_malloc(shape->w * shape->h);
+    if (g_bitmap == NULL) {
+        shaped_window_cleanup();
+        g_shaped_window = window;
+        return SDL3_OutOfMemory();
+    }
+
+    SDL_CalculateShapeBitmap(*shape_mode, shape, g_bitmap, 1);
+
+    g_shape_surface = SDL3_CreateSurface(g_bitmap_w, g_bitmap_h, SDL_PIXELFORMAT_ABGR8888);
+    if (g_shape_surface) {
+        int x, y, i = 0;
+        Uint32 *ptr = (Uint32 *)g_shape_surface->pixels;
+        for (y = 0; y < g_bitmap_h; y++) {
+            for (x = 0; x < g_bitmap_w; x++) {
+                Uint8 val = g_bitmap[i++];
+                if (val == 0) {
+                    ptr[x] = 0;
+                } else {
+                    ptr[x] = 0xffffffff;
+                }
+            }
+            ptr = (Uint32 *)((Uint8 *)ptr + g_shape_surface->pitch);
+        }
+    }
+
+    return 0;
+}
+
+DECLSPEC int SDLCALL
+SDL_GetShapedWindowMode(SDL_Window *window, SDL_WindowShapeMode *shape_mode)
+{
+    if (window == NULL) {
+        return SDL_NONSHAPEABLE_WINDOW;
+    }
+    if (window != g_shaped_window) {
+        return SDL_NONSHAPEABLE_WINDOW;
+    }
+
+    if (shape_mode) {
+        *shape_mode = g_shape_mode;
+    }
+    return 0;
+}
+
 DECLSPEC int SDLCALL
 SDL_SetWindowFullscreen(SDL_Window *window, Uint32 flags)
 {
@@ -5707,6 +5893,44 @@ SDL_UnionFRect(const SDL_FRect *A, const SDL_FRect *B, SDL_FRect *result)
 DECLSPEC void SDLCALL
 SDL_RenderPresent(SDL_Renderer *renderer)
 {
+    /* Apply the shape */
+    if (g_shape_surface && g_shaped_window == SDL3_GetRenderWindow(renderer)) {
+        SDL_RendererInfo info;
+        SDL3_GetRendererInfo(renderer, &info);
+
+        if (info.flags & SDL_RENDERER_SOFTWARE) {
+            if (g_bitmap) {
+                int x, y, i = 0;
+                Uint8 r, g, b, a;
+                SDL3_GetRenderDrawColor(renderer, &r, &g, &b, &a);
+                SDL3_SetRenderDrawColor(renderer, 0, 0, 0, 0);
+                for (y = 0; y < g_bitmap_h; y++) {
+                    for (x = 0; x < g_bitmap_w; x++) {
+                        Uint8 val = g_bitmap[i++];
+                        if (val == 0) {
+                            SDL3_RenderPoint(renderer, (float)x, (float)y);
+                        }
+                    }
+                }
+                SDL3_SetRenderDrawColor(renderer, r, g, b, a);
+            }
+        } else {
+            if (g_shape_texture == NULL) {
+                SDL_BlendMode bm;
+
+                g_shape_texture = SDL3_CreateTextureFromSurface(renderer, g_shape_surface);
+
+                /* if Alpha is 0, set all to 0, else leave unchanged. */
+                bm = SDL3_ComposeCustomBlendMode(
+                        SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD,
+                        SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_ADD);
+
+                SDL3_SetTextureBlendMode(g_shape_texture, bm);
+            }
+            SDL3_RenderTexture(renderer, g_shape_texture, NULL, NULL);
+        }
+    }
+
     SDL3_RenderPresent(renderer);
 }
 
@@ -5876,6 +6100,11 @@ SDL_SetWindowMouseGrab(SDL_Window *window, SDL_bool grabbed)
 DECLSPEC void SDLCALL
 SDL_DestroyWindow(SDL_Window *window)
 {
+    if (window == g_shaped_window) {
+        shaped_window_cleanup();
+        g_shaped_window = NULL;
+    }
+
     SDL3_DestroyWindow(window);
 }
 
diff --git a/src/sdl2_compat.h b/src/sdl2_compat.h
index 69f5bf1..3536d87 100644
--- a/src/sdl2_compat.h
+++ b/src/sdl2_compat.h
@@ -298,4 +298,27 @@ struct SDL_SysWMinfo
 
 typedef struct SDL_SysWMinfo SDL_SysWMinfo;
 
+
+#define SDL_NONSHAPEABLE_WINDOW -1
+#define SDL_INVALID_SHAPE_ARGUMENT -2
+#define SDL_WINDOW_LACKS_SHAPE -3
+
+typedef enum {
+    ShapeModeDefault,
+    ShapeModeBinarizeAlpha,
+    ShapeModeReverseBinarizeAlpha,
+    ShapeModeColorKey
+} WindowShapeMode;
+
+#define SDL_SHAPEMODEALPHA(mode) (mode == ShapeModeDefault || mode == ShapeModeBinarizeAlpha || mode == ShapeModeReverseBinarizeAlpha)
+typedef union {
+    Uint8 binarizationCutoff;
+    SDL_Color colorKey;
+} SDL_WindowShapeParams;
+
+typedef struct SDL_WindowShapeMode {
+    WindowShapeMode mode;
+    SDL_WindowShapeParams parameters;
+} SDL_WindowShapeMode;
+
 #endif /* sdl2_compat_h */
diff --git a/src/sdl3_include_wrapper.h b/src/sdl3_include_wrapper.h
index cf25bb7..0bff999 100644
--- a/src/sdl3_include_wrapper.h
+++ b/src/sdl3_include_wrapper.h
@@ -83,7 +83,6 @@
 #define SDL_CreateRWLock IGNORE_THIS_VERSION_OF_SDL_CreateRWLock
 #define SDL_CreateRenderer IGNORE_THIS_VERSION_OF_SDL_CreateRenderer
 #define SDL_CreateSemaphore IGNORE_THIS_VERSION_OF_SDL_CreateSemaphore
-#define SDL_CreateShapedWindow IGNORE_THIS_VERSION_OF_SDL_CreateShapedWindow
 #define SDL_CreateSoftwareRenderer IGNORE_THIS_VERSION_OF_SDL_CreateSoftwareRenderer
 #define SDL_CreateSurface IGNORE_THIS_VERSION_OF_SDL_CreateSurface
 #define SDL_CreateSurfaceFrom IGNORE_THIS_VERSION_OF_SDL_CreateSurfaceFrom
@@ -340,7 +339,6 @@
 #define SDL_GetSensorNonPortableType IGNORE_THIS_VERSION_OF_SDL_GetSensorNonPortableType
 #define SDL_GetSensorType IGNORE_THIS_VERSION_OF_SDL_GetSensorType
 #define SDL_GetSensors IGNORE_THIS_VERSION_OF_SDL_GetSensors
-#define SDL_GetShapedWindowMode IGNORE_THIS_VERSION_OF_SDL_GetShapedWindowMode
 #define SDL_GetSurfaceAlphaMod IGNORE_THIS_VERSION_OF_SDL_GetSurfaceAlphaMod
 #define SDL_GetSurfaceBlendMode IGNORE_THIS_VERSION_OF_SDL_GetSurfaceBlendMode
 #define SDL_GetSurfaceClipRect IGNORE_THIS_VERSION_OF_SDL_GetSurfaceClipRect
@@ -446,7 +444,6 @@
 #define SDL_IsDeXMode IGNORE_THIS_VERSION_OF_SDL_IsDeXMode
 #define SDL_IsGamepad IGNORE_THIS_VERSION_OF_SDL_IsGamepad
 #define SDL_IsJoystickVirtual IGNORE_THIS_VERSION_OF_SDL_IsJoystickVirtual
-#define SDL_IsShapedWindow IGNORE_THIS_VERSION_OF_SDL_IsShapedWindow
 #define SDL_IsTablet IGNORE_THIS_VERSION_OF_SDL_IsTablet
 #define SDL_JoystickConnected IGNORE_THIS_VERSION_OF_SDL_JoystickConnected
 #define SDL_JoystickEventsEnabled IGNORE_THIS_VERSION_OF_SDL_JoystickEventsEnabled
@@ -637,7 +634,6 @@
 #define SDL_SetWindowOpacity IGNORE_THIS_VERSION_OF_SDL_SetWindowOpacity
 #define SDL_SetWindowPosition IGNORE_THIS_VERSION_OF_SDL_SetWindowPosition
 #define SDL_SetWindowResizable IGNORE_THIS_VERSION_OF_SDL_SetWindowResizable
-#define SDL_SetWindowShape IGNORE_THIS_VERSION_OF_SDL_SetWindowShape
 #define SDL_SetWindowSize IGNORE_THIS_VERSION_OF_SDL_SetWindowSize
 #define SDL_SetWindowTitle IGNORE_THIS_VERSION_OF_SDL_SetWindowTitle
 #define SDL_SetWindowsMessageHook IGNORE_THIS_VERSION_OF_SDL_SetWindowsMessageHook
@@ -1240,10 +1236,6 @@
 #undef SDL_CreateSemaphore
 #endif
 
-#ifdef SDL_CreateShapedWindow
-#undef SDL_CreateShapedWindow
-#endif
-
 #ifdef SDL_CreateSoftwareRenderer
 #undef SDL_CreateSoftwareRenderer
 #endif
@@ -2268,10 +2260,6 @@
 #undef SDL_GetSensors
 #endif
 
-#ifdef SDL_GetShapedWindowMode
-#undef SDL_GetShapedWindowMode
-#endif
-
 #ifdef SDL_GetSurfaceAlphaMod
 #undef SDL_GetSurfaceAlphaMod
 #endif
@@ -2692,10 +2680,6 @@
 #undef SDL_IsJoystickVirtual
 #endif
 
-#ifdef SDL_IsShapedWindow
-#undef SDL_IsShapedWindow
-#endif
-
 #ifdef SDL_IsTablet
 #undef SDL_IsTablet
 #endif
@@ -3456,10 +3440,6 @@
 #undef SDL_SetWindowResizable
 #endif
 
-#ifdef SDL_SetWindowShape
-#undef SDL_SetWindowShape
-#endif
-
 #ifdef SDL_SetWindowSize
 #undef SDL_SetWindowSize
 #endif
diff --git a/src/sdl3_syms.h b/src/sdl3_syms.h
index a235357..7269dac 100644
--- a/src/sdl3_syms.h
+++ b/src/sdl3_syms.h
@@ -351,10 +351,6 @@ SDL3_SYM(SDL_RWops*,RWFromMem,(void *a, size_t b),(a,b),return)
 SDL3_SYM(SDL_RWops*,RWFromConstMem,(const void *a, size_t b),(a,b),return)
 SDL3_SYM(SDL_RWops*,CreateRW,(void),(),return)
 SDL3_SYM(void,DestroyRW,(SDL_RWops *a),(a),)
-SDL3_SYM(SDL_Window*,CreateShapedWindow,(const char *a, int b, int c, Uint32 d),(a,b,c,d),return)
-SDL3_SYM_PASSTHROUGH(SDL_bool,IsShapedWindow,(const SDL_Window *a),(a),return)
-SDL3_SYM_PASSTHROUGH(int,SetWindowShape,(SDL_Window *a, SDL_Surface *b, SDL_WindowShapeMode *c),(a,b,c),return)
-SDL3_SYM_PASSTHROUGH(int,GetShapedWindowMode,(SDL_Window *a, SDL_WindowShapeMode *b),(a,b),return)
 SDL3_SYM_PASSTHROUGH(void*,malloc,(size_t a),(a),return)
 SDL3_SYM_PASSTHROUGH(void*,calloc,(size_t a, size_t b),(a,b),return)
 SDL3_SYM_PASSTHROUGH(void*,realloc,(void *a, size_t b),(a,b),return)