SDL: SDL_SoftStretch() doesn't handle large pixel formats

From cadeec9cc93b9b825120987a49d657b38d90ed1f Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 21 Dec 2024 06:16:10 -0800
Subject: [PATCH] SDL_SoftStretch() doesn't handle large pixel formats

Fixes https://github.com/libsdl-org/SDL/issues/11534
---
 src/video/SDL_surface.c       |  3 +-
 test/testautomation_surface.c | 63 +++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c
index 71fa9ceb07cc2..f6e4f5e252a8f 100644
--- a/src/video/SDL_surface.c
+++ b/src/video/SDL_surface.c
@@ -1254,7 +1254,8 @@ bool SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect, S
     if (scaleMode == SDL_SCALEMODE_NEAREST) {
         if (!(src->map.info.flags & complex_copy_flags) &&
             src->format == dst->format &&
-            !SDL_ISPIXELFORMAT_INDEXED(src->format)) {
+            !SDL_ISPIXELFORMAT_INDEXED(src->format) &&
+            SDL_BYTESPERPIXEL(src->format) <= 4) {
             return SDL_SoftStretch(src, srcrect, dst, dstrect, SDL_SCALEMODE_NEAREST);
         } else if (SDL_BITSPERPIXEL(src->format) < 8) {
             // Scaling bitmap not yet supported, convert to RGBA for blit
diff --git a/test/testautomation_surface.c b/test/testautomation_surface.c
index 7ba11caac49b7..c1d641eef6331 100644
--- a/test/testautomation_surface.c
+++ b/test/testautomation_surface.c
@@ -1479,6 +1479,64 @@ static int SDLCALL surface_testPremultiplyAlpha(void *arg)
 }
 
 
+static int SDLCALL surface_testScale(void *arg)
+{
+    SDL_PixelFormat formats[] = {
+        SDL_PIXELFORMAT_ARGB8888, SDL_PIXELFORMAT_RGBA8888,
+        SDL_PIXELFORMAT_ARGB2101010, SDL_PIXELFORMAT_ABGR2101010,
+        SDL_PIXELFORMAT_ARGB64, SDL_PIXELFORMAT_RGBA64,
+        SDL_PIXELFORMAT_ARGB128_FLOAT, SDL_PIXELFORMAT_RGBA128_FLOAT,
+    };
+    SDL_ScaleMode modes[] = {
+        SDL_SCALEMODE_NEAREST, SDL_SCALEMODE_LINEAR
+    };
+    SDL_Surface *surface, *result;
+    SDL_PixelFormat format;
+    SDL_ScaleMode mode;
+    const float MAXIMUM_ERROR = 0.0001f;
+    float srcR = 10 / 255.0f, srcG = 128 / 255.0f, srcB = 240 / 255.0f, srcA = 170 / 255.0f;
+    float actualR, actualG, actualB, actualA;
+    float deltaR, deltaG, deltaB, deltaA;
+    int i, j, ret;
+
+    for (i = 0; i < SDL_arraysize(formats); ++i) {
+        for (j = 0; j < SDL_arraysize(modes); ++j) {
+            format = formats[i];
+            mode = modes[j];
+
+            surface = SDL_CreateSurface(1, 1, format);
+            SDLTest_AssertCheck(surface != NULL, "SDL_CreateSurface()");
+            ret = SDL_SetSurfaceColorspace(surface, SDL_COLORSPACE_SRGB);
+            SDLTest_AssertCheck(ret == true, "SDL_SetSurfaceColorspace()");
+            ret = SDL_ClearSurface(surface, srcR, srcG, srcB, srcA);
+            SDLTest_AssertCheck(ret == true, "SDL_ClearSurface()");
+            result = SDL_ScaleSurface(surface, 2, 2, mode);
+            SDLTest_AssertCheck(ret == true, "SDL_PremultiplySurfaceAlpha()");
+            ret = SDL_ReadSurfacePixelFloat(result, 1, 1, &actualR, &actualG, &actualB, &actualA);
+            SDLTest_AssertCheck(ret == true, "SDL_ReadSurfacePixelFloat()");
+            deltaR = SDL_fabsf(actualR - srcR);
+            deltaG = SDL_fabsf(actualG - srcG);
+            deltaB = SDL_fabsf(actualB - srcB);
+            deltaA = SDL_fabsf(actualA - srcA);
+            SDLTest_AssertCheck(
+                deltaR <= MAXIMUM_ERROR &&
+                deltaG <= MAXIMUM_ERROR &&
+                deltaB <= MAXIMUM_ERROR &&
+                deltaA <= MAXIMUM_ERROR,
+                "Checking %s %s scaling results, expected %.4f,%.4f,%.4f,%.4f got %.4f,%.4f,%.4f,%.4f",
+                SDL_GetPixelFormatName(format),
+                mode == SDL_SCALEMODE_NEAREST ? "nearest" : "linear",
+                srcR, srcG, srcB, srcA, actualR, actualG, actualB, actualA);
+
+            SDL_DestroySurface(surface);
+            SDL_DestroySurface(result);
+        }
+    }
+
+    return TEST_COMPLETED;
+}
+
+
 /* ================= Test References ================== */
 
 /* Surface test cases */
@@ -1574,6 +1632,10 @@ static const SDLTest_TestCaseReference surfaceTestPremultiplyAlpha = {
     surface_testPremultiplyAlpha, "surface_testPremultiplyAlpha", "Test alpha premultiply operations.", TEST_ENABLED
 };
 
+static const SDLTest_TestCaseReference surfaceTestScale = {
+    surface_testScale, "surface_testScale", "Test scaling operations.", TEST_ENABLED
+};
+
 /* Sequence of Surface test cases */
 static const SDLTest_TestCaseReference *surfaceTests[] = {
     &surfaceTestSaveLoadBitmap,
@@ -1599,6 +1661,7 @@ static const SDLTest_TestCaseReference *surfaceTests[] = {
     &surfaceTestPalettization,
     &surfaceTestClearSurface,
     &surfaceTestPremultiplyAlpha,
+    &surfaceTestScale,
     NULL
 };