SDL: Fix dib-to-bmp logic in SDL_windowsclipboard.c

From cf439d5c63d80fa3672bdfb468ca8e64116aa6fe Mon Sep 17 00:00:00 2001
From: Ramez Ragaa <[EMAIL REDACTED]>
Date: Sat, 28 Dec 2024 18:02:52 +0200
Subject: [PATCH] Fix dib-to-bmp logic in SDL_windowsclipboard.c

This addresses the issue #11762 by reading the biCompression field to
determine the correct size of the color table, and consequently the
correct bih_size value.
---
 src/video/windows/SDL_windowsclipboard.c | 72 ++++++++----------------
 1 file changed, 24 insertions(+), 48 deletions(-)

diff --git a/src/video/windows/SDL_windowsclipboard.c b/src/video/windows/SDL_windowsclipboard.c
index e62936bb9a4e2..dcb46bab597ba 100644
--- a/src/video/windows/SDL_windowsclipboard.c
+++ b/src/video/windows/SDL_windowsclipboard.c
@@ -41,51 +41,6 @@
 // Assume we can directly read and write BMP fields without byte swapping
 SDL_COMPILE_TIME_ASSERT(verify_byte_order, SDL_BYTEORDER == SDL_LIL_ENDIAN);
 
-static int WIN_GetPixelDataOffset(BITMAPINFOHEADER bih)
-{
-    int offset = 0;
-    // biSize Specifies the number of bytes required by the structure
-    // We expect to always be 40 because it should be packed
-    if (40 == bih.biSize && 40 == sizeof(BITMAPINFOHEADER))
-    {
-        //
-        // biBitCount Specifies the number of bits per pixel.
-        // Might exist some bit masks *after* the header and *before* the pixel offset
-        // we're looking, but only if we have more than
-        // 8 bits per pixel, so we need to ajust for that
-        //
-        if (bih.biBitCount > 8)
-        {
-            // If bih.biCompression is RBG we should NOT offset more
-
-            if (bih.biCompression == BI_BITFIELDS)
-            {
-                offset += 3 * sizeof(RGBQUAD);
-            } else if (bih.biCompression == 6 /* BI_ALPHABITFIELDS */) {
-                // Not common, but still right
-                offset += 4 * sizeof(RGBQUAD);
-            }
-        }
-    }
-
-    //
-    // biClrUsed Specifies the number of color indices in the color table that are actually used by the bitmap.
-    // If this value is zero, the bitmap uses the maximum number of colors
-    // corresponding to the value of the biBitCount member for the compression mode specified by biCompression.
-    // If biClrUsed is nonzero and the biBitCount member is less than 16
-    // the biClrUsed member specifies the actual number of colors
-    //
-    if (bih.biClrUsed > 0) {
-        offset += bih.biClrUsed * sizeof(RGBQUAD);
-    } else {
-        if (bih.biBitCount < 16) {
-            offset = offset + (sizeof(RGBQUAD) << bih.biBitCount);
-        }
-    }
-    return bih.biSize + offset;
-}
-
-
 static BOOL WIN_OpenClipboard(SDL_VideoDevice *_this)
 {
     // Retry to open the clipboard in case another application has it open
@@ -157,9 +112,30 @@ static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size)
         LPVOID dib = GlobalLock(hMem);
         if (dib) {
             BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)dib;
-            size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);
+
+            // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#color-tables
+            size_t color_table_size;
+            switch (pbih->biCompression) {
+            case BI_RGB:
+                if (pbih->biBitCount <= 8) {
+                    color_table_size = sizeof(RGBQUAD) * (pbih->biClrUsed == 0 ? 1 << pbih->biBitCount : pbih->biClrUsed);
+                } else {
+                    color_table_size = 0;
+                }
+                break;
+            case BI_BITFIELDS:
+                color_table_size = 3 * sizeof(DWORD);
+                break;
+            case 6 /* BI_ALPHABITFIELDS */:
+                // https://learn.microsoft.com/en-us/previous-versions/windows/embedded/aa452885(v=msdn.10)
+                color_table_size = 4 * sizeof(DWORD);
+                break;
+            default: // FOURCC
+                color_table_size = sizeof(RGBQUAD) * pbih->biClrUsed;
+            }
+
+            size_t bih_size = pbih->biSize + color_table_size;
             size_t dib_size = bih_size + pbih->biSizeImage;
-            int pixel_offset = WIN_GetPixelDataOffset(*pbih);
             if (dib_size <= mem_size) {
                 size_t bmp_size = sizeof(BITMAPFILEHEADER) + mem_size;
                 bmp = SDL_malloc(bmp_size);
@@ -169,7 +145,7 @@ static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size)
                     pbfh->bfSize = (DWORD)bmp_size;
                     pbfh->bfReserved1 = 0;
                     pbfh->bfReserved2 = 0;
-                    pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + pixel_offset);
+                    pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + color_table_size);
                     SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, dib_size);
                     *size = bmp_size;
                 }