SDL_image: Optimized APNG decoding

From 87394474a2d0f00172c94ad4914eeb8dd4cd83b3 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 8 Jan 2026 09:55:46 -0800
Subject: [PATCH] Optimized APNG decoding

We save the PLTE and tRNG chunks as-is and create a single palette up front to apply to each frame.

Also removed extraneous SDL_SetError() calls which mask the underlying read/write error.
---
 src/IMG_libpng.c | 377 +++++++++++++----------------------------------
 1 file changed, 100 insertions(+), 277 deletions(-)

diff --git a/src/IMG_libpng.c b/src/IMG_libpng.c
index a5526958..55b7fc82 100644
--- a/src/IMG_libpng.c
+++ b/src/IMG_libpng.c
@@ -732,7 +732,7 @@ typedef struct
 } DecompressionContext;
 
 static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png_bytep compressed_data, png_size_t compressed_size,
-                                              int width, int height, int png_color_type, int bit_depth, SDL_Color *palette_colors, int palette_count)
+                                              int width, int height, int png_color_type, int bit_depth, png_bytep chunk_PLTE, Uint32 size_PLTE, png_bytep chunk_tRNS, Uint32 size_tRNS)
 {
     /*
      * Usually you'd directly decompress zlib but then we have to do defiltering and deinterlacing ourselves.
@@ -745,13 +745,11 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
     // Create a memory stream to hold our synthetic PNG
     context->mem_stream = SDL_IOFromDynamicMem();
     if (!context->mem_stream) {
-        SDL_SetError("Failed to create memory stream");
         goto error;
     }
 
     // Write PNG signature
     if (SDL_WriteIO(context->mem_stream, png_sig, 8) != 8) {
-        SDL_SetError("Failed to write PNG signature");
         goto error;
     }
 
@@ -762,7 +760,6 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
 
         // Write IHDR length and type
         if (SDL_WriteIO(context->mem_stream, ihdr_header, 8) != 8) {
-            SDL_SetError("Failed to write IHDR header");
             goto error;
         }
 
@@ -776,7 +773,6 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
         ihdr_data[12] = PNG_FILTER_TYPE_DEFAULT;
 
         if (SDL_WriteIO(context->mem_stream, ihdr_data, 13) != 13) {
-            SDL_SetError("Failed to write IHDR data");
             goto error;
         }
 
@@ -786,106 +782,22 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
         png_byte crc_bytes[4];
         custom_png_save_uint_32(crc_bytes, crc);
         if (SDL_WriteIO(context->mem_stream, crc_bytes, 4) != 4) {
-            SDL_SetError("Failed to write IHDR CRC");
             goto error;
         }
     }
 
-    // Write PLTE chunk if this is a palette-based image
-    if (png_color_type == PNG_COLOR_TYPE_PALETTE && palette_colors && palette_count > 0) {
-        // Calculate PLTE chunk size (3 bytes per color)
-        png_uint_32 plte_size = palette_count * 3;
-
-        // PLTE header
-        png_byte plte_header[8] = { 0, 0, 0, 0, 'P', 'L', 'T', 'E' };
-        custom_png_save_uint_32(plte_header, plte_size);
-
-        if (SDL_WriteIO(context->mem_stream, plte_header, 8) != 8) {
-            SDL_SetError("Failed to write PLTE header");
-            goto error;
-        }
-
-        // Write palette data
-        png_byte *plte_data = (png_byte *)SDL_malloc(plte_size);
-        if (!plte_data) {
-            SDL_SetError("Out of memory for PLTE data");
-            goto error;
-        }
-
-        for (int i = 0; i < palette_count; i++) {
-            plte_data[i * 3 + 0] = palette_colors[i].r;
-            plte_data[i * 3 + 1] = palette_colors[i].g;
-            plte_data[i * 3 + 2] = palette_colors[i].b;
-        }
-
-        if (SDL_WriteIO(context->mem_stream, plte_data, plte_size) != plte_size) {
-            SDL_free(plte_data);
-            SDL_SetError("Failed to write PLTE data");
+    // Write PLTE chunk
+    if (chunk_PLTE) {
+        if (SDL_WriteIO(context->mem_stream, chunk_PLTE, size_PLTE) != size_PLTE) {
             goto error;
         }
+    }
 
-        // Calculate and write PLTE CRC
-        png_uint_32 crc = SDL_crc32(0, (Uint8 *)"PLTE", 4);
-        crc = SDL_crc32(crc, plte_data, plte_size);
-        png_byte crc_bytes[4];
-        custom_png_save_uint_32(crc_bytes, crc);
-        SDL_free(plte_data);
-
-        if (SDL_WriteIO(context->mem_stream, crc_bytes, 4) != 4) {
-            SDL_SetError("Failed to write PLTE CRC");
+    // Write tRNS chunk
+    if (chunk_tRNS) {
+        if (SDL_WriteIO(context->mem_stream, chunk_tRNS, size_tRNS) != size_tRNS) {
             goto error;
         }
-
-        // Write tRNS chunk
-        if (palette_colors[0].a != 255) {
-            // Calculate tRNS chunk size
-            png_uint_32 trns_size = 0;
-
-            for (int i = 0; i < palette_count; ++i) {
-                if (palette_colors[i].a == 255) {
-                    break;
-                }
-                ++trns_size;
-            }
-
-            // tRNS header
-            png_byte trns_header[8] = { 0, 0, 0, 0, 't', 'R', 'N', 'S' };
-            custom_png_save_uint_32(trns_header, trns_size);
-
-            if (SDL_WriteIO(context->mem_stream, trns_header, 8) != 8) {
-                SDL_SetError("Failed to write tRNS header");
-                goto error;
-            }
-
-            // Write transparency data
-            png_byte *trns_data = (png_byte *)SDL_malloc(trns_size);
-            if (!trns_data) {
-                SDL_SetError("Out of memory for tRNS data");
-                goto error;
-            }
-
-            for (png_uint_32 i = 0; i < trns_size; i++) {
-                trns_data[i] = palette_colors[i].a;
-            }
-
-            if (SDL_WriteIO(context->mem_stream, trns_data, trns_size) != trns_size) {
-                SDL_free(trns_data);
-                SDL_SetError("Failed to write tRNS data");
-                goto error;
-            }
-
-            // Calculate and write tRNS CRC
-            png_uint_32 crc = SDL_crc32(0, (Uint8 *)"tRNS", 4);
-            crc = SDL_crc32(crc, trns_data, trns_size);
-            png_byte crc_bytes[4];
-            custom_png_save_uint_32(crc_bytes, crc);
-            SDL_free(trns_data);
-
-            if (SDL_WriteIO(context->mem_stream, crc_bytes, 4) != 4) {
-                SDL_SetError("Failed to write tRNS CRC");
-                goto error;
-            }
-        }
     }
 
     // Write IDAT chunk
@@ -895,13 +807,11 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
 
         // Write IDAT length and type
         if (SDL_WriteIO(context->mem_stream, idat_header, 8) != 8) {
-            SDL_SetError("Failed to write IDAT header");
             goto error;
         }
 
         // Write compressed data
         if (SDL_WriteIO(context->mem_stream, compressed_data, compressed_size) != compressed_size) {
-            SDL_SetError("Failed to write IDAT data");
             goto error;
         }
 
@@ -911,7 +821,6 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
         png_byte crc_bytes[4];
         custom_png_save_uint_32(crc_bytes, crc);
         if (SDL_WriteIO(context->mem_stream, crc_bytes, 4) != 4) {
-            SDL_SetError("Failed to write IDAT CRC");
             goto error;
         }
     }
@@ -925,7 +834,6 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
         };
 
         if (SDL_WriteIO(context->mem_stream, iend_chunk, 12) != 12) {
-            SDL_SetError("Failed to write IEND chunk");
             goto error;
         }
     }
@@ -941,25 +849,21 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
     void *buffer = NULL;
 
     if (SDL_SeekIO(context->mem_stream, 0, SDL_IO_SEEK_SET) < 0) {
-        SDL_SetError("Failed to rewind memory stream");
         goto error;
     }
 
     buffer = SDL_malloc(data_size);
     if (!buffer) {
-        SDL_SetError("Out of memory for PNG data buffer");
         goto error;
     }
 
     if (SDL_ReadIO(context->mem_stream, buffer, data_size) != (size_t)data_size) {
-        SDL_SetError("Failed to read from memory stream");
         SDL_free(buffer);
         goto error;
     }
 
     context->read_stream = SDL_IOFromConstMem(buffer, data_size);
     if (!context->read_stream) {
-        SDL_SetError("Failed to create read stream");
         SDL_free(buffer);
         goto error;
     }
@@ -967,14 +871,12 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
     // Now we have a proper PNG file in memory, use libpng to read it
     context->png_ptr = lib.png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
     if (!context->png_ptr) {
-        SDL_SetError("Failed to create PNG read struct");
         SDL_free(buffer);
         goto error;
     }
 
     context->info_ptr = lib.png_create_info_struct(context->png_ptr);
     if (!context->info_ptr) {
-        SDL_SetError("Failed to create PNG info struct");
         SDL_free(buffer);
         goto error;
     }
@@ -1053,54 +955,45 @@ static SDL_Surface *decompress_png_frame_data(DecompressionContext *context, png
     return NULL;
 }
 
-static bool read_png_chunk(SDL_IOStream *stream, char *type, png_bytep *data, png_size_t *length)
+static bool read_png_chunk(SDL_IOStream *stream, png_bytep *chunk, Uint32 *chunk_size, char *chunk_type, png_bytep *data, Uint32 *data_length)
 {
-    Uint32 chunk_length;
-    char chunk_type[5] = { 0 };
-    Uint32 crc;
+    Uint8 header[8];
 
-    // Read chunk length (4 bytes, big-endian)
-    if (SDL_ReadIO(stream, &chunk_length, 4) != 4) {
-        SDL_SetError("Couldn't read chunk length");
+    // Read chunk header (8 bytes)
+    if (SDL_ReadIO(stream, &header, sizeof(header)) != sizeof(header)) {
         return false;
     }
-    chunk_length = SDL_Swap32BE(chunk_length);
 
-    // Read chunk type (4 bytes)
-    if (SDL_ReadIO(stream, chunk_type, 4) != 4) {
-        SDL_SetError("Couldn't read chunk type");
-        return false;
-    }
+    // Get data length (4 bytes, big-endian)
+    SDL_memcpy(data_length, header, 4);
+    *data_length = SDL_Swap32BE(*data_length);
+
+    // Get chunk type (4 bytes)
+    SDL_memcpy(chunk_type, header+4, 4);
 
-    // Allocate memory for chunk data
-    *data = (png_bytep)SDL_malloc(chunk_length);
-    if (!*data && chunk_length > 0) {
-        SDL_SetError("Out of memory for chunk data");
+    // Allocate memory for chunk
+    *chunk_size = sizeof(header) + *data_length + 4;
+    *chunk = (png_bytep)SDL_malloc(*chunk_size);
+    if (!*chunk) {
         return false;
     }
+    SDL_memcpy(*chunk, header, sizeof(header));
+    *data = *chunk + sizeof(header);
 
     // Read chunk data
-    if (chunk_length > 0) {
-        if (SDL_ReadIO(stream, *data, chunk_length) != chunk_length) {
-            SDL_free(*data);
-            *data = NULL;
-            SDL_SetError("Couldn't read chunk data");
+    if (*data_length > 0) {
+        if (SDL_ReadIO(stream, *data, *data_length) != *data_length) {
+            SDL_free(*chunk);
             return false;
         }
     }
 
     // Read CRC (4 bytes, big-endian)
-    if (SDL_ReadIO(stream, &crc, 4) != 4) {
-        SDL_free(*data);
-        *data = NULL;
-        SDL_SetError("Couldn't read chunk CRC");
+    if (SDL_ReadIO(stream, *data + *data_length, 4) != 4) {
+        SDL_free(*chunk);
         return false;
     }
 
-    // Store the chunk type and length
-    SDL_memcpy(type, chunk_type, 4);
-    *length = chunk_length;
-
     return true;
 }
 
@@ -1120,10 +1013,11 @@ struct IMG_AnimationDecoderContext
     int bit_depth;
     int png_color_type;
 
-    SDL_Color *palette_colors;
-    int palette_count;
-    png_bytep trans_alpha;
-    int trans_count;
+    SDL_Palette *palette;
+    png_bytep chunk_PLTE;
+    Uint32 size_PLTE;
+    png_bytep chunk_tRNS;
+    Uint32 size_tRNS;
 };
 
 static bool IMG_AnimationDecoderReset_Internal(IMG_AnimationDecoder *decoder)
@@ -1227,28 +1121,17 @@ static bool IMG_AnimationDecoderGetNextFrame_Internal(IMG_AnimationDecoder *deco
         fctl->height,
         ctx->png_color_type,
         ctx->bit_depth,
-        ctx->palette_colors,
-        ctx->palette_count);
+        ctx->chunk_PLTE,
+        ctx->size_PLTE,
+        ctx->chunk_tRNS,
+        ctx->size_tRNS);
 
     if (!temp_frame) {
         return SDL_SetError("Failed to decompress PNG frame data: %s", SDL_GetError());
     }
 
     if (temp_frame->format == SDL_PIXELFORMAT_INDEX8) {
-        if (ctx->palette_colors && ctx->palette_count > 0) {
-            SDL_Palette *frame_palette = SDL_CreatePalette(ctx->palette_count);
-            if (frame_palette) {
-                if (!SDL_SetPaletteColors(frame_palette, ctx->palette_colors, 0, ctx->palette_count)) {
-                    SDL_DestroySurface(temp_frame);
-                    return SDL_SetError("Failed to set palette colors for frame: %s", SDL_GetError());
-                }
-                if (!SDL_SetSurfacePalette(temp_frame, frame_palette)) {
-                    SDL_DestroySurface(temp_frame);
-                    return SDL_SetError("Failed to set palette for frame: %s", SDL_GetError());
-                }
-                SDL_DestroyPalette(frame_palette);
-            }
-        }
+        SDL_SetSurfacePalette(temp_frame, ctx->palette);
     }
 
     switch (fctl->blend_op) {
@@ -1303,21 +1186,11 @@ static bool IMG_AnimationDecoderClose_Internal(IMG_AnimationDecoder *decoder)
         SDL_free(ctx->fctl_frames);
     }
 
-    if (ctx->palette_colors) {
-        SDL_free(ctx->palette_colors);
-    }
-
-    if (ctx->trans_alpha) {
-        SDL_free(ctx->trans_alpha);
-    }
-
-    if (ctx->canvas) {
-        SDL_DestroySurface(ctx->canvas);
-    }
-
-    if (ctx->prev_canvas_copy) {
-        SDL_DestroySurface(ctx->prev_canvas_copy);
-    }
+    SDL_DestroyPalette(ctx->palette);
+    SDL_free(ctx->chunk_PLTE);
+    SDL_free(ctx->chunk_tRNS);
+    SDL_DestroySurface(ctx->canvas);
+    SDL_DestroySurface(ctx->prev_canvas_copy);
 
     SDL_free(ctx);
     decoder->ctx = NULL;
@@ -1340,15 +1213,13 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
     unsigned char header[8];
     if (SDL_ReadIO(decoder->src, header, sizeof(header)) != sizeof(header)) {
         SDL_SetError("Failed to read PNG header");
-        SDL_free(ctx);
-        decoder->ctx = NULL;
+        IMG_AnimationDecoderClose_Internal(decoder);
         return false;
     }
 
     if (lib.png_sig_cmp(header, 0, 8)) {
         SDL_SetError("Not a valid PNG file signature");
-        SDL_free(ctx);
-        decoder->ctx = NULL;
+        IMG_AnimationDecoderClose_Internal(decoder);
         return false;
     }
 
@@ -1362,29 +1233,29 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
     bool found_iend = false;
     while (!found_iend) {
         char chunk_type[5] = { 0 };
+        png_bytep chunk = NULL;
+        Uint32 chunk_size;
         png_bytep chunk_data = NULL;
-        png_size_t chunk_length = 0;
+        Uint32 chunk_length = 0;
+        bool chunk_saved = false;
 
-        if (!read_png_chunk(decoder->src, chunk_type, &chunk_data, &chunk_length)) {
-            SDL_free(ctx);
-            decoder->ctx = NULL;
+        if (!read_png_chunk(decoder->src, &chunk, &chunk_size, chunk_type, &chunk_data, &chunk_length)) {
+            IMG_AnimationDecoderClose_Internal(decoder);
             return false;
         }
 
         if (chunk_length > SDL_MAX_SINT32) {
             SDL_SetError("APNG chunk too large to process");
-            SDL_free(chunk_data);
-            SDL_free(ctx);
-            decoder->ctx = NULL;
+            SDL_free(chunk);
+            IMG_AnimationDecoderClose_Internal(decoder);
             return false;
         }
 
         if (SDL_memcmp(chunk_type, "IHDR", 4) == 0) {
             if (chunk_length != 13) {
                 SDL_SetError("Invalid IHDR chunk size");
-                SDL_free(chunk_data);
-                SDL_free(ctx);
-                decoder->ctx = NULL;
+                SDL_free(chunk);
+                IMG_AnimationDecoderClose_Internal(decoder);
                 return false;
             }
 
@@ -1397,9 +1268,8 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
         } else if (SDL_memcmp(chunk_type, "acTL", 4) == 0) {
             if (chunk_length != 8) {
                 SDL_SetError("Invalid acTL chunk size");
-                SDL_free(chunk_data);
-                SDL_free(ctx);
-                decoder->ctx = NULL;
+                SDL_free(chunk);
+                IMG_AnimationDecoderClose_Internal(decoder);
                 return false;
             }
 
@@ -1410,57 +1280,46 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
         } else if (SDL_memcmp(chunk_type, "PLTE", 4) == 0) {
             int num_entries = (int)chunk_length / 3;
             if (num_entries > 0 && num_entries <= 256) {
-                if (ctx->palette_colors) {
-                    SDL_free(ctx->palette_colors);
-                }
+                SDL_DestroyPalette(ctx->palette);
 
-                ctx->palette_colors = (SDL_Color *)SDL_malloc(num_entries * sizeof(SDL_Color));
-                if (!ctx->palette_colors) {
-                    SDL_SetError("Out of memory for palette data");
-                    SDL_free(chunk_data);
-                    SDL_free(ctx);
-                    decoder->ctx = NULL;
+                ctx->palette = SDL_CreatePalette(num_entries);
+                if (!ctx->palette) {
+                    SDL_free(chunk);
+                    IMG_AnimationDecoderClose_Internal(decoder);
                     return false;
                 }
 
                 for (int i = 0; i < num_entries; i++) {
-                    ctx->palette_colors[i].r = chunk_data[i * 3];
-                    ctx->palette_colors[i].g = chunk_data[i * 3 + 1];
-                    ctx->palette_colors[i].b = chunk_data[i * 3 + 2];
-                    ctx->palette_colors[i].a = 0xFF; // Default opaque
+                    ctx->palette->colors[i].r = chunk_data[i * 3];
+                    ctx->palette->colors[i].g = chunk_data[i * 3 + 1];
+                    ctx->palette->colors[i].b = chunk_data[i * 3 + 2];
+                    ctx->palette->colors[i].a = SDL_ALPHA_OPAQUE;
                 }
-
-                ctx->palette_count = num_entries;
-            }
-        } else if (SDL_memcmp(chunk_type, "tRNS", 4) == 0) {
-            if (ctx->trans_alpha) {
-                SDL_free(ctx->trans_alpha);
-            }
-
-            ctx->trans_alpha = (png_bytep)SDL_malloc(chunk_length);
-            if (!ctx->trans_alpha) {
-                SDL_SetError("Out of memory for transparency data");
-                SDL_free(chunk_data);
-                SDL_free(ctx);
-                decoder->ctx = NULL;
-                return false;
             }
 
-            SDL_memcpy(ctx->trans_alpha, chunk_data, chunk_length);
-            ctx->trans_count = (int)chunk_length;
+            SDL_free(ctx->chunk_PLTE);
+            ctx->chunk_PLTE = chunk;
+            ctx->size_PLTE = chunk_size;
+            chunk_saved = true;
 
-            if (ctx->palette_colors && ctx->palette_count > 0) {
-                int num_trans = SDL_min(ctx->trans_count, ctx->palette_count);
+        } else if (SDL_memcmp(chunk_type, "tRNS", 4) == 0) {
+            if (ctx->palette) {
+                int num_trans = SDL_min((int)chunk_size, ctx->palette->ncolors);
                 for (int i = 0; i < num_trans; i++) {
-                    ctx->palette_colors[i].a = ctx->trans_alpha[i];
+                    ctx->palette->colors[i].a = chunk_data[i];
                 }
             }
+
+            SDL_free(ctx->chunk_tRNS);
+            ctx->chunk_tRNS = chunk;
+            ctx->size_tRNS = chunk_size;
+            chunk_saved = true;
+
         } else if (SDL_memcmp(chunk_type, "fcTL", 4) == 0) {
             if (chunk_length != 26) {
                 SDL_SetError("Invalid fcTL chunk size");
-                SDL_free(chunk_data);
-                SDL_free(ctx);
-                decoder->ctx = NULL;
+                SDL_free(chunk);
+                IMG_AnimationDecoderClose_Internal(decoder);
                 return false;
             }
 
@@ -1470,9 +1329,8 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
                                                                   sizeof(apng_fcTL_chunk) * ctx->fctl_capacity);
                 if (!ctx->fctl_frames) {
                     SDL_SetError("Out of memory for fcTL chunks");
-                    SDL_free(chunk_data);
-                    SDL_free(ctx);
-                    decoder->ctx = NULL;
+                    SDL_free(chunk);
+                    IMG_AnimationDecoderClose_Internal(decoder);
                     return false;
                 }
             }
@@ -1508,18 +1366,15 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
                 png_size_t new_size = fctl->raw_idat_size + chunk_length;
                 if (new_size < fctl->raw_idat_size) {
                     SDL_SetError("IDAT size would overflow");
-                    SDL_free(chunk_data);
-                    SDL_free(ctx);
-                    decoder->ctx = NULL;
+                    SDL_free(chunk);
+                    IMG_AnimationDecoderClose_Internal(decoder);
                     return false;
                 }
 
                 png_bytep new_buffer = (png_bytep)SDL_realloc(fctl->raw_idat_data, new_size);
                 if (!new_buffer) {
-                    SDL_SetError("Out of memory for IDAT data");
-                    SDL_free(chunk_data);
-                    SDL_free(ctx);
-                    decoder->ctx = NULL;
+                    SDL_free(chunk);
+                    IMG_AnimationDecoderClose_Internal(decoder);
                     return false;
                 }
 
@@ -1530,9 +1385,8 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
         } else if (SDL_memcmp(chunk_type, "fdAT", 4) == 0) {
             if (chunk_length < 4) {
                 SDL_SetError("Invalid fdAT chunk size");
-                SDL_free(chunk_data);
-                SDL_free(ctx);
-                decoder->ctx = NULL;
+                SDL_free(chunk);
+                IMG_AnimationDecoderClose_Internal(decoder);
                 return false;
             }
 
@@ -1553,18 +1407,15 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
 
                 if (new_size < fctl->raw_idat_size) {
                     SDL_SetError("fdAT size would overflow");
-                    SDL_free(chunk_data);
-                    SDL_free(ctx);
-                    decoder->ctx = NULL;
+                    SDL_free(chunk);
+                    IMG_AnimationDecoderClose_Internal(decoder);
                     return false;
                 }
 
                 png_bytep new_buffer = (png_bytep)SDL_realloc(fctl->raw_idat_data, new_size);
                 if (!new_buffer) {
-                    SDL_SetError("Out of memory for fdAT data");
-                    SDL_free(chunk_data);
-                    SDL_free(ctx);
-                    decoder->ctx = NULL;
+                    SDL_free(chunk);
+                    IMG_AnimationDecoderClose_Internal(decoder);
                     return false;
                 }
 
@@ -1606,49 +1457,21 @@ bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_Propertie
             }
         }
 
-        SDL_free(chunk_data);
+        if (!chunk_saved) {
+            SDL_free(chunk);
+        }
     }
 
     if (!ctx->is_apng || ctx->fctl_count == 0) {
         SDL_SetError("Not an APNG file or no frame control chunks found");
-        if (ctx->fctl_frames) {
-            for (int i = 0; i < ctx->fctl_count; i++) {
-                if (ctx->fctl_frames[i].raw_idat_data) {
-                    SDL_free(ctx->fctl_frames[i].raw_idat_data);
-                }
-            }
-            SDL_free(ctx->fctl_frames);
-        }
-        if (ctx->palette_colors) {
-            SDL_free(ctx->palette_colors);
-        }
-        if (ctx->trans_alpha) {
-            SDL_free(ctx->trans_alpha);
-        }
-        SDL_free(ctx);
-        decoder->ctx = NULL;
+        IMG_AnimationDecoderClose_Internal(decoder);
         return false;
     }
 
     // Validate what might be missing or wrong
     if (ctx->bit_depth < 1 || ctx->png_color_type < 0 || ctx->width < 1 || ctx->height < 1) {
         SDL_SetError("Received invalid APNG with either corrupt or unspecified bit depth, color type, width or height");
-        if (ctx->fctl_frames) {
-            for (int i = 0; i < ctx->fctl_count; i++) {
-                if (ctx->fctl_frames[i].raw_idat_data) {
-                    SDL_free(ctx->fctl_frames[i].raw_idat_data);
-                }
-            }
-            SDL_free(ctx->fctl_frames);
-        }
-        if (ctx->palette_colors) {
-            SDL_free(ctx->palette_colors);
-        }
-        if (ctx->trans_alpha) {
-            SDL_free(ctx->trans_alpha);
-        }
-        SDL_free(ctx);
-        decoder->ctx = NULL;
+        IMG_AnimationDecoderClose_Internal(decoder);
         return false;
     }