SDL_image: Made image save functions consistent

From f3926ce9ab6ba2252eca53d3e19c7b47ee02b0e8 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 20 Oct 2025 08:17:41 -0700
Subject: [PATCH] Made image save functions consistent

---
 src/IMG_avif.c   | 59 +++++++++++++++++++++++------------
 src/IMG_bmp.c    |  6 +++-
 src/IMG_gif.c    |  6 +++-
 src/IMG_jpg.c    | 54 +++++++++++++++++++++++---------
 src/IMG_libpng.c | 36 ++++++++++++---------
 src/IMG_tga.c    | 67 +++++++++++++++++----------------------
 src/IMG_webp.c   | 81 +++++++++++++++++++++++-------------------------
 7 files changed, 178 insertions(+), 131 deletions(-)

diff --git a/src/IMG_avif.c b/src/IMG_avif.c
index 1face428..36a4cde9 100644
--- a/src/IMG_avif.c
+++ b/src/IMG_avif.c
@@ -731,40 +731,61 @@ SDL_Surface *IMG_LoadAVIF_IO(SDL_IOStream *src)
 
 #endif /* LOAD_AVIF */
 
-bool IMG_SaveAVIF(SDL_Surface *surface, const char *file, int quality)
-{
-    SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
-    if (dst) {
-        return IMG_SaveAVIF_IO(surface, dst, 1, quality);
-    } else {
-        return false;
-    }
-}
+#if SAVE_AVIF
 
 bool IMG_SaveAVIF_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int quality)
 {
     bool result = false;
 
+    if (!surface) {
+        SDL_InvalidParamError("surface");
+        goto done;
+    }
     if (!dst) {
-        return SDL_SetError("Passed NULL dst");
+        SDL_InvalidParamError("dst");
+        goto done;
     }
 
-#ifdef SAVE_AVIF
-    if (!result) {
-        result = IMG_SaveAVIF_IO_libavif(surface, dst, quality);
-    }
-#else
-    (void) surface;
-    (void) quality;
-    result = SDL_SetError("SDL_image built without AVIF save support");
-#endif
+    result = IMG_SaveAVIF_IO_libavif(surface, dst, quality);
 
+done:
     if (closeio) {
         SDL_CloseIO(dst);
     }
     return result;
 }
 
+bool IMG_SaveAVIF(SDL_Surface *surface, const char *file, int quality)
+{
+    SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
+    if (dst) {
+        return IMG_SaveAVIF_IO(surface, dst, true, quality);
+    } else {
+        return false;
+    }
+}
+
+#else
+
+bool IMG_SaveAVIF_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int quality)
+{
+    (void)surface;
+    (void)dst;
+    (void)closeio;
+    (void)quality;
+    return SDL_SetError("SDL_image built without AVIF save support");
+}
+
+bool IMG_SaveAVIF(SDL_Surface *surface, const char *file, int quality)
+{
+    (void)surface;
+    (void)file;
+    (void)quality;
+    return SDL_SetError("SDL_image built without AVIF save support");
+}
+
+#endif // SAVE_AVIF
+
 #ifdef LOAD_AVIF
 
 static void SetHDRProperties(SDL_Surface *surface, const avifImage *image)
diff --git a/src/IMG_bmp.c b/src/IMG_bmp.c
index 1436d90a..6fecc069 100644
--- a/src/IMG_bmp.c
+++ b/src/IMG_bmp.c
@@ -733,7 +733,11 @@ bool IMG_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
 bool IMG_SaveBMP(SDL_Surface *surface, const char *file)
 {
     SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
-    return IMG_SaveBMP_IO(surface, dst, true);
+    if (dst) {
+        return IMG_SaveBMP_IO(surface, dst, true);
+    } else {
+        return false;
+    }
 }
 
 #else // !SAVE_BMP
diff --git a/src/IMG_gif.c b/src/IMG_gif.c
index c786ae82..469ac8b5 100644
--- a/src/IMG_gif.c
+++ b/src/IMG_gif.c
@@ -2816,7 +2816,11 @@ bool IMG_SaveGIF_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
 bool IMG_SaveGIF(SDL_Surface *surface, const char *file)
 {
     SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
-    return IMG_SaveGIF_IO(surface, dst, true);
+    if (dst) {
+        return IMG_SaveGIF_IO(surface, dst, true);
+    } else {
+        return false;
+    }
 }
 
 #else
diff --git a/src/IMG_jpg.c b/src/IMG_jpg.c
index 6d29f523..69fe543b 100644
--- a/src/IMG_jpg.c
+++ b/src/IMG_jpg.c
@@ -757,15 +757,7 @@ static bool IMG_SaveJPG_IO_tinyjpeg(SDL_Surface *surface, SDL_IOStream *dst, int
 
 #endif /* SAVE_JPG && (defined(LOAD_JPG_DYNAMIC) || !defined(WANT_JPEGLIB)) */
 
-bool IMG_SaveJPG(SDL_Surface *surface, const char *file, int quality)
-{
-    SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
-    if (dst) {
-        return IMG_SaveJPG_IO(surface, dst, 1, quality);
-    } else {
-        return false;
-    }
-}
+#if SAVE_JPG
 
 bool IMG_SaveJPG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int quality)
 {
@@ -773,11 +765,15 @@ bool IMG_SaveJPG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int q
     (void)surface;
     (void)quality;
 
+    if (!surface) {
+        SDL_InvalidParamError("surface");
+        goto done;
+    }
     if (!dst) {
-        return SDL_SetError("Passed NULL dst");
+        SDL_InvalidParamError("dst");
+        goto done;
     }
 
-#if SAVE_JPG
 #ifdef USE_JPEGLIB
     if (!result) {
         result = IMG_SaveJPG_IO_jpeglib(surface, dst, quality);
@@ -790,12 +786,40 @@ bool IMG_SaveJPG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int q
     }
 #endif
 
-#else
-    result = SDL_SetError("SDL_image built without JPEG save support");
-#endif
-
+done:
     if (closeio) {
         SDL_CloseIO(dst);
     }
     return result;
 }
+
+bool IMG_SaveJPG(SDL_Surface *surface, const char *file, int quality)
+{
+    SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
+    if (dst) {
+        return IMG_SaveJPG_IO(surface, dst, true, quality);
+    } else {
+        return false;
+    }
+}
+
+#else // !SAVE_JPG
+
+bool IMG_SaveJPG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int quality)
+{
+    (void)surface;
+    (void)dst;
+    (void)closeio;
+    (void)quality;
+    return SDL_SetError("SDL_image built without JPG save support");
+}
+
+bool IMG_SaveJPG(SDL_Surface *surface, const char *file, int quality)
+{
+    (void)surface;
+    (void)file;
+    (void)quality;
+    return SDL_SetError("SDL_image built without JPG save support");
+}
+
+#endif // SAVE_JPG
diff --git a/src/IMG_libpng.c b/src/IMG_libpng.c
index 82b138fc..8253c9b8 100644
--- a/src/IMG_libpng.c
+++ b/src/IMG_libpng.c
@@ -666,13 +666,9 @@ static bool LIBPNG_SavePNG_IO_Internal(struct png_save_vars *vars, SDL_Surface *
 
     return true;
 }
-#endif // SAVE_PNG
 
 bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
 {
-#if !SAVE_PNG
-    return false;
-#else
     if (!surface || !dst) {
         SDL_SetError("Surface or SDL_IOStream is NULL");
         return false;
@@ -707,30 +703,42 @@ bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
         SDL_SetError("%s", vars.error);
     }
 
-    if (closeio && dst) {
+    if (closeio) {
         SDL_CloseIO(dst);
     }
 
     return result;
-#endif
 }
 
 bool IMG_SavePNG(SDL_Surface *surface, const char *file)
 {
-    if (!surface || !file) {
-        SDL_SetError("Surface or file name is NULL");
-        return false;
-    }
-
     SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
-    if (!dst) {
-        SDL_SetError("Failed to open file %s for writing: %s", file, SDL_GetError());
+    if (dst) {
+        return IMG_SavePNG_IO(surface, dst, true);
+    } else {
         return false;
     }
+}
+
+#else // !SAVE_PNG
 
-    return IMG_SavePNG_IO(surface, dst, true);
+bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
+{
+    (void)surface;
+    (void)dst;
+    (void)closeio;
+    return SDL_SetError("SDL_image built without PNG save support");
+}
+
+bool IMG_SavePNG(SDL_Surface *surface, const char *file)
+{
+    (void)surface;
+    (void)file;
+    return SDL_SetError("SDL_image built without PNG save support");
 }
 
+#endif // SAVE_PNG
+
 typedef struct
 {
     png_uint_32 num_frames;
diff --git a/src/IMG_tga.c b/src/IMG_tga.c
index 2ed0b9ed..a19636d9 100644
--- a/src/IMG_tga.c
+++ b/src/IMG_tga.c
@@ -358,21 +358,19 @@ SDL_Surface *IMG_LoadTGA_IO(SDL_IOStream *src)
 
 bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
 {
-    Sint64 start;
-    const char *error = NULL;
+    Sint64 start = -1;
     struct TGAheader hdr;
-    bool retval = false;
     SDL_Palette *surface_palette = NULL;
     SDL_Surface *temp_surface = NULL;
+    bool result = false;
 
-    if (!surface || !dst) {
-
-        if (closeio && dst) {
-            SDL_CloseIO(dst);
-        }
-
-        SDL_SetError("Invalid parameters to IMG_SaveTGA_IO");
-        return false;
+    if (!surface) {
+        SDL_InvalidParamError("surface");
+        goto done;
+    }
+    if (!dst) {
+        SDL_InvalidParamError("dst");
+        goto done;
     }
 
     start = SDL_TellIO(dst);
@@ -393,8 +391,7 @@ bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
 
     const SDL_PixelFormatDetails *pixelFormatDetails = SDL_GetPixelFormatDetails(surface->format);
     if (!pixelFormatDetails) {
-        error = "Failed to get SDL_PixelFormatDetails for surface format";
-        goto error;
+        goto done;
     }
 
     surface_palette = SDL_GetSurfacePalette(surface);
@@ -432,13 +429,12 @@ bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
         }
         break;
     default:
-        error = "Unsupported SDL_Surface format for TGA saving. Supported formats are INDEX8, BGR24, RGB24, BGRA32, RGBA32, XRGB8888, XRGB1555, ARGB1555.";
-        goto error;
+        SDL_SetError("Unsupported SDL_Surface format for TGA saving. Supported formats are INDEX8, BGR24, RGB24, BGRA32, RGBA32, XRGB8888, XRGB1555, ARGB1555.");
+        goto done;
     }
 
     if (SDL_WriteIO(dst, &hdr, sizeof(hdr)) != sizeof(hdr)) {
-        error = "Error writing TGA header";
-        goto error;
+        goto done;
     }
 
     if (hdr.has_cmap && surface_palette) {
@@ -452,8 +448,7 @@ bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
                 color_entry[1] = colors[i].g;
                 color_entry[2] = colors[i].r;
                 if (SDL_WriteIO(dst, color_entry, 3) != 3) {
-                    error = "Error writing TGA colormap entry (24-bit)";
-                    goto error;
+                    goto done;
                 }
                 break;
             case 32:
@@ -462,13 +457,12 @@ bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
                 color_entry[2] = colors[i].r;
                 color_entry[3] = colors[i].a;
                 if (SDL_WriteIO(dst, color_entry, 4) != 4) {
-                    error = "Error writing TGA colormap entry (32-bit)";
-                    goto error;
+                    goto done;
                 }
                 break;
             default:
-                error = "Unsupported TGA colormap bit depth for saving";
-                goto error;
+                SDL_SetError("Unsupported TGA colormap bit depth for saving");
+                goto done;
             }
         }
     }
@@ -497,8 +491,7 @@ bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
     if (target_format != SDL_PIXELFORMAT_UNKNOWN && target_format != surface->format) {
         temp_surface = SDL_ConvertSurface(surface, target_format);
         if (!temp_surface) {
-            error = "Failed to convert surface format for TGA saving";
-            goto error;
+            goto done;
         }
         pixels_to_write = (Uint8 *)temp_surface->pixels;
         pitch_to_write = temp_surface->pitch;
@@ -506,43 +499,39 @@ bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
         if (tempPixelFormatDetails) {
             bytes_per_pixel = tempPixelFormatDetails->bytes_per_pixel;
         } else {
-            error = "Failed to get SDL_PixelFormatDetails for converted surface format";
-            goto error;
+            goto done;
         }
     }
 
     for (int y = 0; y < surface->h; ++y) {
         if (SDL_WriteIO(dst, pixels_to_write + y * pitch_to_write, (size_t)surface->w * bytes_per_pixel) != (size_t)(surface->w * bytes_per_pixel)) {
-            error = "Error writing TGA pixel data";
-            goto error;
+            goto done;
         }
     }
 
-    retval = true;
+    result = true;
 
-error:
+done:
     if (temp_surface) {
         SDL_DestroySurface(temp_surface);
     }
-    if (!retval) {
+    if (!result && !closeio && start != -1) {
         SDL_SeekIO(dst, start, SDL_IO_SEEK_SET);
-        SDL_SetError("%s", error);
     }
-    if (closeio && dst) {
+    if (closeio) {
         SDL_CloseIO(dst);
     }
-    return retval;
+    return result;
 }
 
 bool IMG_SaveTGA(SDL_Surface *surface, const char *file)
 {
     SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
-    if (!dst) {
+    if (dst) {
+        return IMG_SaveTGA_IO(surface, dst, true);
+    } else {
         return false;
     }
-    bool retval = IMG_SaveTGA_IO(surface, dst, true);
-    SDL_CloseIO(dst);
-    return retval;
 }
 
 #else
diff --git a/src/IMG_webp.c b/src/IMG_webp.c
index 0ad1982e..62b0d12f 100644
--- a/src/IMG_webp.c
+++ b/src/IMG_webp.c
@@ -701,28 +701,30 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
     WebPPicture pic;
     WebPMemoryWriter writer;
     SDL_Surface *converted_surface = NULL;
-    bool ret = true;
-    const char *error = NULL;
+    bool result = false;
     bool pic_initialized = false;
     bool memorywriter_initialized = false;
     bool converted_surface_locked = false;
     Sint64 start = -1;
 
-    if (!surface || !dst) {
-        error = "Invalid input surface or destination stream.";
-        goto cleanup;
+    if (!surface) {
+        SDL_InvalidParamError("surface");
+        goto done;
+    }
+    if (!dst) {
+        SDL_InvalidParamError("dst");
+        goto done;
     }
 
     start = SDL_TellIO(dst);
 
     if (!IMG_InitWEBP()) {
-        error = SDL_GetError();
-        goto cleanup;
+        goto done;
     }
 
     if (!lib.WebPConfigInitInternal(&config, WEBP_PRESET_DEFAULT, quality, WEBP_ENCODER_ABI_VERSION)) {
-        error = "Failed to initialize WebPConfig.";
-        goto cleanup;
+        SDL_SetError("Failed to initialize WebPConfig");
+        goto done;
     }
 
     quality = SDL_clamp(quality, 0.0f, 100.0f);
@@ -734,13 +736,13 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
     config.method = 4;
 
     if (!lib.WebPValidateConfig(&config)) {
-        error = "Invalid WebP configuration.";
-        goto cleanup;
+        SDL_SetError("Invalid WebP configuration");
+        goto done;
     }
 
     if (!lib.WebPPictureInitInternal(&pic, WEBP_ENCODER_ABI_VERSION)) {
-        error = "Failed to initialize WebPPicture.";
-        goto cleanup;
+        SDL_SetError("Failed to initialize WebPPicture");
+        goto done;
     }
     pic_initialized = true;
 
@@ -750,8 +752,7 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
     if (surface->format != SDL_PIXELFORMAT_RGBA32) {
         converted_surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
         if (!converted_surface) {
-            error = SDL_GetError();
-            goto cleanup;
+            goto done;
         }
     } else {
         converted_surface = surface;
@@ -759,15 +760,14 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
 
     if (SDL_MUSTLOCK(converted_surface)) {
         if (!SDL_LockSurface(converted_surface)) {
-            error = SDL_GetError();
-            goto cleanup;
+            goto done;
         }
         converted_surface_locked = true;
     }
 
     if (!lib.WebPPictureImportRGBA(&pic, (const uint8_t *)converted_surface->pixels, converted_surface->pitch)) {
-        error = "Failed to import RGBA pixels into WebPPicture.";
-        goto cleanup;
+        SDL_SetError("Failed to import RGBA pixels into WebPPicture");
+        goto done;
     }
 
     if (converted_surface_locked) {
@@ -781,21 +781,22 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
     pic.custom_ptr = &writer;
 
     if (!lib.WebPEncode(&config, &pic)) {
-        error = GetWebPEncodingErrorStringInternal(pic.error_code);
-        goto cleanup;
+        SDL_SetError("Failed to encode WebP: %s", GetWebPEncodingErrorStringInternal(pic.error_code));
+        goto done;
     }
 
     if (writer.size > 0) {
         if (SDL_WriteIO(dst, writer.mem, writer.size) != writer.size) {
-            error = "Failed to write all WebP data to destination.";
-            goto cleanup;
+            goto done;
         }
     } else {
-        error = "No WebP data generated.";
-        goto cleanup;
+        SDL_SetError("No WebP data generated.");
+        goto done;
     }
 
-cleanup:
+    result = true;
+
+done:
     if (converted_surface_locked) {
         SDL_UnlockSurface(converted_surface);
     }
@@ -812,29 +813,25 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
         lib.WebPMemoryWriterClear(&writer);
     }
 
-    if (error) {
-        if (!closeio && start != -1) {
-            SDL_SeekIO(dst, start, SDL_IO_SEEK_SET);
-        }
-        SDL_SetError("%s", error);
-        ret = false;
+    if (!result && !closeio && start != -1) {
+        SDL_SeekIO(dst, start, SDL_IO_SEEK_SET);
     }
 
     if (closeio) {
         SDL_CloseIO(dst);
     }
 
-    return ret;
+    return result;
 }
 
 bool IMG_SaveWEBP(SDL_Surface *surface, const char *file, float quality)
 {
     SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
-    if (!dst) {
+    if (dst) {
+        return IMG_SaveWEBP_IO(surface, dst, true, quality);
+    } else {
         return false;
     }
-
-   return IMG_SaveWEBP_IO(surface, dst, true, quality);
 }
 
 struct IMG_AnimationEncoderContext
@@ -1076,14 +1073,14 @@ bool IMG_isWEBP(SDL_IOStream *src)
 SDL_Surface *IMG_LoadWEBP_IO(SDL_IOStream *src)
 {
     (void)src;
-    SDL_SetError("SDL_image was not built with WEBP support");
+    SDL_SetError("SDL_image built without WEBP load support");
     return NULL;
 }
 
 IMG_Animation *IMG_LoadWEBPAnimation_IO(SDL_IOStream *src)
 {
     (void)src;
-    SDL_SetError("SDL_image was not built with WEBP support");
+    SDL_SetError("SDL_image built without WEBP load support");
     return NULL;
 }
 
@@ -1091,7 +1088,7 @@ bool IMG_CreateWEBPAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
 {
     (void)decoder;
     (void)props;
-    return SDL_SetError("SDL_image was not built with WEBP load support");
+    return SDL_SetError("SDL_image built without WEBP load support");
 }
 
 #endif // !LOAD_WEBP
@@ -1104,7 +1101,7 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
     (void)dst;
     (void)closeio;
     (void)quality;
-    return SDL_SetError("SDL_image was not built with WEBP save support");
+    return SDL_SetError("SDL_image built without WEBP save support");
 }
 
 bool IMG_SaveWEBP(SDL_Surface *surface, const char *file, float quality)
@@ -1112,14 +1109,14 @@ bool IMG_SaveWEBP(SDL_Surface *surface, const char *file, float quality)
     (void)surface;
     (void)file;
     (void)quality;
-    return SDL_SetError("SDL_image was not built with WEBP save support");
+    return SDL_SetError("SDL_image built without WEBP save support");
 }
 
 bool IMG_CreateWEBPAnimationEncoder(IMG_AnimationEncoder *encoder, SDL_PropertiesID props)
 {
     (void)encoder;
     (void)props;
-    return SDL_SetError("SDL_image was not built with WEBP save support");
+    return SDL_SetError("SDL_image built without WEBP save support");
 }
 
 #endif // !SAVE_WEBP