SDL: windows: fix BITMAPINFOHEADER values when getting bitmap from Clipboard

From 8a0f649363e349817c88bd5d9fd940dffaca2d7c Mon Sep 17 00:00:00 2001
From: evertonse <[EMAIL REDACTED]>
Date: Mon, 4 Nov 2024 15:31:45 -0300
Subject: [PATCH] windows: fix BITMAPINFOHEADER values when getting bitmap from
 Clipboard

---
 src/video/windows/SDL_windowsclipboard.c | 50 +++++++++++++++++++++++-
 1 file changed, 48 insertions(+), 2 deletions(-)

diff --git a/src/video/windows/SDL_windowsclipboard.c b/src/video/windows/SDL_windowsclipboard.c
index 6a0cc35c74d51..e62936bb9a4e2 100644
--- a/src/video/windows/SDL_windowsclipboard.c
+++ b/src/video/windows/SDL_windowsclipboard.c
@@ -41,6 +41,51 @@
 // 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
@@ -114,8 +159,9 @@ static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size)
             BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)dib;
             size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);
             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) + dib_size;
+                size_t bmp_size = sizeof(BITMAPFILEHEADER) + mem_size;
                 bmp = SDL_malloc(bmp_size);
                 if (bmp) {
                     BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp;
@@ -123,7 +169,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) + bih_size);
+                    pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + pixel_offset);
                     SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, dib_size);
                     *size = bmp_size;
                 }