SDL_image: Optimize GIF decoder with better transparency handling

From cc27338a0a0b6dfd68c674eee588054755cfe16d Mon Sep 17 00:00:00 2001
From: Xen <[EMAIL REDACTED]>
Date: Sat, 18 Oct 2025 04:48:12 -0700
Subject: [PATCH] Optimize GIF decoder with better transparency handling

---
 src/IMG_gif.c | 91 +++++++++++++++++++++++++++------------------------
 1 file changed, 49 insertions(+), 42 deletions(-)

diff --git a/src/IMG_gif.c b/src/IMG_gif.c
index 44046ebe..60d1f4a7 100644
--- a/src/IMG_gif.c
+++ b/src/IMG_gif.c
@@ -147,9 +147,9 @@ static int DoExtension(SDL_IOStream * src, int label, State_t * state);
 static int GetDataBlock(SDL_IOStream * src, unsigned char *buf, State_t * state);
 static int GetCode(SDL_IOStream * src, int code_size, int flag, State_t * state);
 static int LWZReadByte(SDL_IOStream * src, int flag, int input_code_size, State_t * state);
-static Image *ReadImage(SDL_IOStream * src, int len, int height, int,
-			unsigned char cmap[3][MAXCOLORMAPSIZE],
-			int gray, int interlace, int ignore, State_t * state);
+static Image *ReadImage(SDL_IOStream *src, int len, int height, int cmapSize,
+          unsigned char cmap[3][MAXCOLORMAPSIZE],
+          int gray, int interlace, int ignore, State_t *state);
 
 static int
 ReadColorMap(SDL_IOStream *src, int number,
@@ -403,16 +403,16 @@ ReadImage(SDL_IOStream * src, int len, int height, int cmapSize,
           int gray, int interlace, int ignore, State_t * state)
 {
     Image *image;
-    SDL_Palette *palette;
     unsigned char c;
-    int i, v;
+    int v;
     int xpos = 0, ypos = 0, pass = 0;
-
+    int transparent_index;
+    Uint8 *dst;
+    int pixel_count = 0;
+    int total_pixels = len * height;
+    
     (void) gray; /* unused */
-
-    /*
-    **  Initialize the compression routines
-     */
+    
     if (!ReadOK(src, &c, 1)) {
         RWSetMsg("EOF / read error on image data");
         return NULL;
@@ -421,33 +421,45 @@ ReadImage(SDL_IOStream * src, int len, int height, int cmapSize,
         RWSetMsg("error reading image");
         return NULL;
     }
-    /*
-    **  If this is an "uninteresting picture" ignore it.
-     */
+    
     if (ignore) {
         while (LWZReadByte(src, FALSE, c, state) >= 0)
             ;
         return NULL;
     }
-    image = ImageNewCmap(len, height, cmapSize);
+    
+    /* Create RGBA32 surface with frame dimensions */
+    image = SDL_CreateSurface(len, height, SDL_PIXELFORMAT_RGBA32);
     if (!image) {
         return NULL;
     }
-
-    palette = SDL_CreateSurfacePalette(image);
-    if (!palette) {
-        return NULL;
-    }
-    if (cmapSize > palette->ncolors) {
-        cmapSize = palette->ncolors;
-    }
-    palette->ncolors = cmapSize;
-    for (i = 0; i < cmapSize; i++) {
-        ImageSetCmap(image, i, cmap[CM_RED][i], cmap[CM_GREEN][i], cmap[CM_BLUE][i]);
-    }
-
-    while ((v = LWZReadByte(src, FALSE, c, state)) >= 0) {
-        ((Uint8 *)image->pixels)[xpos + ypos * image->pitch] = (Uint8)v;
+    
+    /* Initialize entire surface to transparent */
+    SDL_memset(image->pixels, 0, image->pitch * height);
+    
+    transparent_index = state->Gif89.transparent;
+    
+    /* Decode and convert to RGBA */
+    while ((v = LWZReadByte(src, FALSE, c, state)) >= 0 && pixel_count < total_pixels) {
+        dst = (Uint8 *)image->pixels + (ypos * image->pitch) + (xpos * 4);
+        
+        /* Only write non-transparent pixels */
+        if (transparent_index < 0 || v != transparent_index) {
+            if (v < cmapSize) {
+                dst[0] = cmap[CM_RED][v];
+                dst[1] = cmap[CM_GREEN][v];
+                dst[2] = cmap[CM_BLUE][v];
+                dst[3] = 255;  /* Opaque */
+            } else {
+                /* Invalid color index */
+                dst[0] = 255;
+                dst[1] = 0;
+                dst[2] = 255;
+                dst[3] = 255;
+            }
+        }
+        
+        pixel_count++;
         ++xpos;
         if (xpos == len) {
             xpos = 0;
@@ -464,7 +476,6 @@ ReadImage(SDL_IOStream * src, int len, int height, int cmapSize,
                     ypos += 2;
                     break;
                 }
-
                 if (ypos >= height) {
                     ++pass;
                     switch (pass) {
@@ -488,9 +499,8 @@ ReadImage(SDL_IOStream * src, int len, int height, int cmapSize,
         if (ypos >= height)
             break;
     }
-
-  fini:
-
+    
+fini:
     return image;
 }
 
@@ -882,16 +892,13 @@ static bool IMG_AnimationDecoderGetNextFrame_Internal(IMG_AnimationDecoder *deco
         }
 
         if (!image) {
-            // TODO:
-            // Error reading image, but we'll continue with what we have, or should we??
-            continue;
-        }
+            // Incorrect animation is harder to detect than a direct failure,
+            // so it's better to fail than try to animate a GIF without a,
+            // full set of frames it has in the file.
 
-        /* Apply transparency if needed */
-        if (ctx->state.Gif89.transparent >= 0) {
-            if (!SDL_SetSurfaceColorKey(image, true, ctx->state.Gif89.transparent)) {
-                SDL_DestroySurface(image);
-                return SDL_SetError("Failed to set transparency on frame");
+            // Only set the error if ReadImage did not do it.
+            if (SDL_GetError()[0] == '\0') {
+                return SDL_SetError("Failed to decode frame.");
             }
         }