SDL: Added SDL_FlipSurface() to flip a surface vertically or horizontally

From 308906ba2542df132e424d4fcee2995578989cec Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 20 Jan 2024 06:31:37 -0800
Subject: [PATCH] Added SDL_FlipSurface() to flip a surface vertically or
 horizontally

Fixes https://github.com/libsdl-org/SDL/issues/8857
---
 WhatsNew.txt                        |  1 +
 docs/README-migration.md            |  1 +
 include/SDL3/SDL_render.h           | 14 +----
 include/SDL3/SDL_surface.h          | 21 +++++++
 src/dynapi/SDL_dynapi.sym           |  1 +
 src/dynapi/SDL_dynapi_overrides.h   |  1 +
 src/dynapi/SDL_dynapi_procs.h       |  3 +-
 src/render/SDL_render.c             |  4 +-
 src/render/SDL_sysrender.h          |  2 +-
 src/render/psp/SDL_render_psp.c     |  2 +-
 src/render/software/SDL_render_sw.c |  6 +-
 src/video/SDL_surface.c             | 86 +++++++++++++++++++++++++++++
 test/testautomation_surface.c       | 63 ++++++++++++++++++++-
 13 files changed, 184 insertions(+), 21 deletions(-)

diff --git a/WhatsNew.txt b/WhatsNew.txt
index 1b84c49b9b06..3090648bb380 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -17,6 +17,7 @@ General:
 * Added SDL_GetPrimaryDisplay() to get the instance ID of the primary display
 * Added SDL_GetWindowParent() to get the parent of popup windows
 * Added SDL_CreateSurface() and SDL_CreateSurfaceFrom() which replace SDL_CreateRGBSurface*(), and can also be used to create YUV surfaces
+* Added SDL_FlipSurface() to flip a surface vertically or horizontally
 * Added SDL_GetJoysticks(), SDL_GetJoystickInstanceName(), SDL_GetJoystickInstancePath(), SDL_GetJoystickInstancePlayerIndex(), SDL_GetJoystickInstanceGUID(), SDL_GetJoystickInstanceVendor(), SDL_GetJoystickInstanceProduct(), SDL_GetJoystickInstanceProductVersion(), and SDL_GetJoystickInstanceType() to directly query the list of available joysticks
 * Added SDL_GetGamepads(), SDL_GetGamepadInstanceName(), SDL_GetGamepadInstancePath(), SDL_GetGamepadInstancePlayerIndex(), SDL_GetGamepadInstanceGUID(), SDL_GetGamepadInstanceVendor(), SDL_GetGamepadInstanceProduct(), SDL_GetGamepadInstanceProductVersion(), and SDL_GetGamepadInstanceType() to directly query the list of available gamepads
 * Added SDL_GetSensors(), SDL_GetSensorInstanceName(), SDL_GetSensorInstanceType(), and SDL_GetSensorInstanceNonPortableType() to directly query the list of available sensors
diff --git a/docs/README-migration.md b/docs/README-migration.md
index 8f6043de2ab1..6b3b3b85a66c 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -1037,6 +1037,7 @@ The following functions have been removed:
 * SDL_SetTextureUserData() - use SDL_GetTextureProperties() instead
 
 The following symbols have been renamed:
+* SDL_RendererFlip => SDL_FlipMode
 * SDL_ScaleModeBest => SDL_SCALEMODE_BEST
 * SDL_ScaleModeLinear => SDL_SCALEMODE_LINEAR
 * SDL_ScaleModeNearest => SDL_SCALEMODE_NEAREST
diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 2a5958dfe756..e6d762dfb603 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -105,16 +105,6 @@ typedef enum
     SDL_TEXTUREACCESS_TARGET     /**< Texture can be used as a render target */
 } SDL_TextureAccess;
 
-/**
- * Flip constants for SDL_RenderTextureRotated
- */
-typedef enum
-{
-    SDL_FLIP_NONE = 0x00000000,     /**< Do not flip */
-    SDL_FLIP_HORIZONTAL = 0x00000001,    /**< flip horizontally */
-    SDL_FLIP_VERTICAL = 0x00000002     /**< flip vertically */
-} SDL_RendererFlip;
-
 /**
  * How the logical size is mapped to the output
  */
@@ -1494,7 +1484,7 @@ extern DECLSPEC int SDLCALL SDL_RenderTexture(SDL_Renderer *renderer, SDL_Textur
  * \param center A pointer to a point indicating the point around which
  *               dstrect will be rotated (if NULL, rotation will be done
  *               around dstrect.w/2, dstrect.h/2).
- * \param flip An SDL_RendererFlip value stating which flipping actions should
+ * \param flip An SDL_FlipMode value stating which flipping actions should
  *             be performed on the texture
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
@@ -1504,7 +1494,7 @@ extern DECLSPEC int SDLCALL SDL_RenderTexture(SDL_Renderer *renderer, SDL_Textur
 extern DECLSPEC int SDLCALL SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture,
                                                      const SDL_FRect *srcrect, const SDL_FRect *dstrect,
                                                      const double angle, const SDL_FPoint *center,
-                                                     const SDL_RendererFlip flip);
+                                                     const SDL_FlipMode flip);
 
 /**
  * Render a list of triangles, optionally using a texture and indices into the
diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h
index 63706aa5282c..3f378a7d8437 100644
--- a/include/SDL3/SDL_surface.h
+++ b/include/SDL3/SDL_surface.h
@@ -75,6 +75,15 @@ typedef enum
     SDL_SCALEMODE_BEST     /**< anisotropic filtering */
 } SDL_ScaleMode;
 
+/**
+ * The flip mode
+ */
+typedef enum
+{
+    SDL_FLIP_NONE,          /**< Do not flip */
+    SDL_FLIP_HORIZONTAL,    /**< flip horizontally */
+    SDL_FLIP_VERTICAL       /**< flip vertically */
+} SDL_FlipMode;
 
 /**
  * A collection of pixels used in software blitting.
@@ -602,6 +611,18 @@ extern DECLSPEC SDL_bool SDLCALL SDL_SetSurfaceClipRect(SDL_Surface *surface,
 extern DECLSPEC int SDLCALL SDL_GetSurfaceClipRect(SDL_Surface *surface,
                                              SDL_Rect *rect);
 
+/*
+ * Flip a surface vertically or horizontally.
+ *
+ * \param surface the surface to flip
+ * \param flip the direction to flip
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip);
+
 /*
  * Creates a new surface identical to the existing surface.
  *
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index a2ee7d22de92..43825dd5d070 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -964,6 +964,7 @@ SDL3_0.0.0 {
     SDL_GetHapticInstanceID;
     SDL_GetHapticName;
     SDL_ReadSurfacePixel;
+    SDL_FlipSurface;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index c1ff1b86642b..5c1160c78e5f 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -989,3 +989,4 @@
 #define SDL_GetHapticInstanceID SDL_GetHapticInstanceID_REAL
 #define SDL_GetHapticName SDL_GetHapticName_REAL
 #define SDL_ReadSurfacePixel SDL_ReadSurfacePixel_REAL
+#define SDL_FlipSurface SDL_FlipSurface_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index dc9291c45250..39aa35774ab3 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -580,7 +580,7 @@ SDL_DYNAPI_PROC(int,SDL_RenderReadPixels,(SDL_Renderer *a, const SDL_Rect *b, Ui
 SDL_DYNAPI_PROC(int,SDL_RenderRect,(SDL_Renderer *a, const SDL_FRect *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_RenderRects,(SDL_Renderer *a, const SDL_FRect *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_RenderTexture,(SDL_Renderer *a, SDL_Texture *b, const SDL_FRect *c, const SDL_FRect *d),(a,b,c,d),return)
-SDL_DYNAPI_PROC(int,SDL_RenderTextureRotated,(SDL_Renderer *a, SDL_Texture *b, const SDL_FRect *c, const SDL_FRect *d, const double e, const SDL_FPoint *f, const SDL_RendererFlip g),(a,b,c,d,e,f,g),return)
+SDL_DYNAPI_PROC(int,SDL_RenderTextureRotated,(SDL_Renderer *a, SDL_Texture *b, const SDL_FRect *c, const SDL_FRect *d, const double e, const SDL_FPoint *f, const SDL_FlipMode g),(a,b,c,d,e,f,g),return)
 SDL_DYNAPI_PROC(SDL_AssertState,SDL_ReportAssertion,(SDL_AssertData *a, const char *b, const char *c, int d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(void,SDL_ResetAssertionReport,(void),(),)
 SDL_DYNAPI_PROC(SDL_bool,SDL_ResetHint,(const char *a),(a),return)
@@ -1014,3 +1014,4 @@ SDL_DYNAPI_PROC(SDL_Haptic*,SDL_GetHapticFromInstanceID,(SDL_HapticID a),(a),ret
 SDL_DYNAPI_PROC(SDL_HapticID,SDL_GetHapticInstanceID,(SDL_Haptic *a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetHapticName,(SDL_Haptic *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_ReadSurfacePixel,(SDL_Surface *a, int b, int c, Uint8 *d, Uint8 *e, Uint8 *f, Uint8 *g),(a,b,c,d,e,f,g),return)
+SDL_DYNAPI_PROC(int,SDL_FlipSurface,(SDL_Surface *a, SDL_FlipMode b),(a,b),return)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 06da0b920fbe..0643c3e16e22 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -611,7 +611,7 @@ static int QueueCmdCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_
 
 static int QueueCmdCopyEx(SDL_Renderer *renderer, SDL_Texture *texture,
                           const SDL_FRect *srcquad, const SDL_FRect *dstrect,
-                          const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip, float scale_x, float scale_y)
+                          const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y)
 {
     SDL_RenderCommand *cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_COPY_EX, texture);
     int retval = -1;
@@ -3296,7 +3296,7 @@ int SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FR
 
 int SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture,
                       const SDL_FRect *srcrect, const SDL_FRect *dstrect,
-                      const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
+                      const double angle, const SDL_FPoint *center, const SDL_FlipMode flip)
 {
     SDL_FRect real_srcrect;
     SDL_FRect real_dstrect;
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index ec4b34ab0bac..85a76e4ddc7c 100644
--- a/src/render/SDL_sysrender.h
+++ b/src/render/SDL_sysrender.h
@@ -173,7 +173,7 @@ struct SDL_Renderer
                      const SDL_FRect *srcrect, const SDL_FRect *dstrect);
     int (*QueueCopyEx)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
                        const SDL_FRect *srcquad, const SDL_FRect *dstrect,
-                       const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip, float scale_x, float scale_y);
+                       const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y);
     int (*QueueGeometry)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
                          const float *xy, int xy_stride, const SDL_Color *color, int color_stride, const float *uv, int uv_stride,
                          int num_vertices, const void *indices, int num_indices, int size_indices,
diff --git a/src/render/psp/SDL_render_psp.c b/src/render/psp/SDL_render_psp.c
index aa6434fd174f..c2115caf7d0d 100644
--- a/src/render/psp/SDL_render_psp.c
+++ b/src/render/psp/SDL_render_psp.c
@@ -842,7 +842,7 @@ static int PSP_QueueCopy(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Tex
 
 static int PSP_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
                            const SDL_FRect *srcrect, const SDL_FRect *dstrect,
-                           const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip, float scale_x, float scale_y)
+                           const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y)
 {
     VertTV *verts = (VertTV *)SDL_AllocateRenderVertices(renderer, 4 * sizeof(VertTV), 4, &cmd->data.draw.first);
     const float centerx = center->x;
diff --git a/src/render/software/SDL_render_sw.c b/src/render/software/SDL_render_sw.c
index 18c8b36e8443..29651aa79473 100644
--- a/src/render/software/SDL_render_sw.c
+++ b/src/render/software/SDL_render_sw.c
@@ -257,14 +257,14 @@ typedef struct CopyExData
     SDL_Rect dstrect;
     double angle;
     SDL_FPoint center;
-    SDL_RendererFlip flip;
+    SDL_FlipMode flip;
     float scale_x;
     float scale_y;
 } CopyExData;
 
 static int SW_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
                           const SDL_FRect *srcrect, const SDL_FRect *dstrect,
-                          const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip, float scale_x, float scale_y)
+                          const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y)
 {
     CopyExData *verts = (CopyExData *)SDL_AllocateRenderVertices(renderer, sizeof(CopyExData), 0, &cmd->data.draw.first);
 
@@ -311,7 +311,7 @@ static int Blit_to_Screen(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *surf
 
 static int SW_RenderCopyEx(SDL_Renderer *renderer, SDL_Surface *surface, SDL_Texture *texture,
                            const SDL_Rect *srcrect, const SDL_Rect *final_rect,
-                           const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip, float scale_x, float scale_y)
+                           const double angle, const SDL_FPoint *center, const SDL_FlipMode flip, float scale_x, float scale_y)
 {
     SDL_Surface *src = (SDL_Surface *)texture->driverdata;
     SDL_Rect tmp_rect;
diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c
index ffe9df476b68..9a52b13fb975 100644
--- a/src/video/SDL_surface.c
+++ b/src/video/SDL_surface.c
@@ -1078,11 +1078,97 @@ void SDL_UnlockSurface(SDL_Surface *surface)
 #endif
 }
 
+static int SDL_FlipSurfaceHorizontal(SDL_Surface *surface)
+{
+    SDL_bool isstack;
+    Uint8 *row, *a, *b, *tmp;
+    int i, j, bpp;
+
+    if (surface->format->BitsPerPixel < 8) {
+        /* We could implement this if needed, but we'd have to flip sets of bits within a byte */
+        return SDL_Unsupported();
+    }
+
+    if (surface->h <= 0) {
+        return 0;
+    }
+
+    if (surface->w <= 1) {
+        return 0;
+    }
+
+    bpp = surface->format->BytesPerPixel;
+    row = (Uint8 *)surface->pixels;
+    tmp = SDL_small_alloc(Uint8, surface->pitch, &isstack);
+    for (i = surface->h; i--; ) {
+        a = row;
+        b = a + (surface->w - 1) * bpp;
+        for (j = surface->w / 2; j--; ) {
+            SDL_memcpy(tmp, a, bpp);
+            SDL_memcpy(a, b, bpp);
+            SDL_memcpy(b, tmp, bpp);
+            a += bpp;
+            b -= bpp;
+        }
+        row += surface->pitch;
+    }
+    SDL_small_free(tmp, isstack);
+    return 0;
+}
+
+static int SDL_FlipSurfaceVertical(SDL_Surface *surface)
+{
+    SDL_bool isstack;
+    Uint8 *a, *b, *tmp;
+    int i;
+
+    if (surface->h <= 1) {
+        return 0;
+    }
+
+    a = (Uint8 *)surface->pixels;
+    b = a + (surface->h - 1) * surface->pitch;
+    tmp = SDL_small_alloc(Uint8, surface->pitch, &isstack);
+    for (i = surface->h / 2; i--; ) {
+        SDL_memcpy(tmp, a, surface->pitch);
+        SDL_memcpy(a, b, surface->pitch);
+        SDL_memcpy(b, tmp, surface->pitch);
+        a += surface->pitch;
+        b -= surface->pitch;
+    }
+    SDL_small_free(tmp, isstack);
+    return 0;
+}
+
+int SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip)
+{
+    if (!surface || !surface->format) {
+        return SDL_InvalidParamError("surface");
+    }
+    if (!surface->pixels) {
+        return 0;
+    }
+
+    switch (flip) {
+    case SDL_FLIP_HORIZONTAL:
+        return SDL_FlipSurfaceHorizontal(surface);
+    case SDL_FLIP_VERTICAL:
+        return SDL_FlipSurfaceVertical(surface);
+    default:
+        return SDL_InvalidParamError("flip");
+    }
+}
+
 /*
  * Creates a new surface identical to the existing surface
  */
 SDL_Surface *SDL_DuplicateSurface(SDL_Surface *surface)
 {
+    if (!surface) {
+        SDL_InvalidParamError("surface");
+        return NULL;
+    }
+
     return SDL_ConvertSurface(surface, surface->format);
 }
 
diff --git a/test/testautomation_surface.c b/test/testautomation_surface.c
index def9c35f0301..00a63e33c32b 100644
--- a/test/testautomation_surface.c
+++ b/test/testautomation_surface.c
@@ -22,6 +22,15 @@
 #include "testautomation_suites.h"
 #include "testautomation_images.h"
 
+
+#define CHECK_FUNC(FUNC, PARAMS)    \
+{                                   \
+    int result = FUNC PARAMS;       \
+    if (result != 0) {              \
+        SDLTest_AssertCheck(result == 0, "Validate result from %s, expected: 0, got: %i, %s", #FUNC, result, SDL_GetError()); \
+    }                               \
+}
+
 /* ================= Test Case Implementation ================== */
 
 /* Shared test surface */
@@ -791,6 +800,54 @@ static int surface_testOverflow(void *arg)
     return TEST_COMPLETED;
 }
 
+static int surface_testFlip(void *arg)
+{
+    SDL_Surface *surface;
+    Uint8 *pixels;
+    int offset;
+    const char *expectedError;
+
+    surface = SDL_CreateSurface(3, 3, SDL_PIXELFORMAT_RGB24);
+    SDLTest_AssertCheck(surface != NULL, "SDL_CreateSurface()");
+
+    SDL_ClearError();
+    expectedError = "Parameter 'surface' is invalid";
+    SDL_FlipSurface(NULL, SDL_FLIP_HORIZONTAL);
+    SDLTest_AssertCheck(SDL_strcmp(SDL_GetError(), expectedError) == 0,
+                        "Expected \"%s\", got \"%s\"", expectedError, SDL_GetError());
+
+    SDL_ClearError();
+    expectedError = "Parameter 'flip' is invalid";
+    SDL_FlipSurface(surface, SDL_FLIP_NONE);
+    SDLTest_AssertCheck(SDL_strcmp(SDL_GetError(), expectedError) == 0,
+                        "Expected \"%s\", got \"%s\"", expectedError, SDL_GetError());
+
+    pixels = (Uint8 *)surface->pixels;
+    *pixels = 0xFF;
+    offset = 0;
+
+    SDLTest_AssertPass("Call to SDL_FlipSurface(surface, SDL_FLIP_VERTICAL)");
+    CHECK_FUNC(SDL_FlipSurface, (surface, SDL_FLIP_VERTICAL));
+    SDLTest_AssertCheck(pixels[offset] == 0x00,
+                        "Expected pixels[%d] == 0x00 got 0x%.2X", offset, pixels[offset]);
+    offset = 2 * surface->pitch;
+    SDLTest_AssertCheck(pixels[offset] == 0xFF,
+                        "Expected pixels[%d] == 0xFF got 0x%.2X", offset, pixels[offset]);
+
+    SDLTest_AssertPass("Call to SDL_FlipSurface(surface, SDL_FLIP_HORIZONTAL)");
+    CHECK_FUNC(SDL_FlipSurface, (surface, SDL_FLIP_HORIZONTAL));
+    SDLTest_AssertCheck(pixels[offset] == 0x00,
+                        "Expected pixels[%d] == 0x00 got 0x%.2X", offset, pixels[offset]);
+    offset += (surface->w - 1) * surface->format->BytesPerPixel;
+    SDLTest_AssertCheck(pixels[offset] == 0xFF,
+                        "Expected pixels[%d] == 0xFF got 0x%.2X", offset, pixels[offset]);
+
+    SDL_DestroySurface(surface);
+
+    return TEST_COMPLETED;
+}
+
+
 /* ================= Test References ================== */
 
 /* Surface test cases */
@@ -849,11 +906,15 @@ static const SDLTest_TestCaseReference surfaceTestOverflow = {
     surface_testOverflow, "surface_testOverflow", "Test overflow detection.", TEST_ENABLED
 };
 
+static const SDLTest_TestCaseReference surfaceTestFlip = {
+    surface_testFlip, "surface_testFlip", "Test surface flipping.", TEST_ENABLED
+};
+
 /* Sequence of Surface test cases */
 static const SDLTest_TestCaseReference *surfaceTests[] = {
     &surfaceTest1, &surfaceTest2, &surfaceTest3, &surfaceTest4, &surfaceTest5,
     &surfaceTest6, &surfaceTest7, &surfaceTest8, &surfaceTest9, &surfaceTest10,
-    &surfaceTest11, &surfaceTest12, &surfaceTestOverflow, NULL
+    &surfaceTest11, &surfaceTest12, &surfaceTestOverflow, &surfaceTestFlip, NULL
 };
 
 /* Surface test suite (global) */