SDL_image: Reduce pixel format conversion with libpng

From 9f914cf94db14194435b437b287b48e800aafcc0 Mon Sep 17 00:00:00 2001
From: Cameron Cawley <[EMAIL REDACTED]>
Date: Mon, 6 Oct 2025 20:07:20 +0100
Subject: [PATCH] Reduce pixel format conversion with libpng

---
 src/IMG_libpng.c | 96 +++++++++++++++++++++++++-----------------------
 1 file changed, 50 insertions(+), 46 deletions(-)

diff --git a/src/IMG_libpng.c b/src/IMG_libpng.c
index b882d60a..25e6d71d 100644
--- a/src/IMG_libpng.c
+++ b/src/IMG_libpng.c
@@ -122,7 +122,6 @@ static struct
     void (*png_read_update_info)(png_structrp png_ptr, png_inforp info_ptr);
     void (*png_set_expand)(png_structrp png_ptr);
     void (*png_set_gray_to_rgb)(png_structrp png_ptr);
-    void (*png_set_packing)(png_structrp png_ptr);
     void (*png_set_read_fn)(png_structrp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn);
     void (*png_set_strip_16)(png_structrp png_ptr);
     int (*png_set_interlace_handling)(png_structrp png_ptr);
@@ -139,8 +138,6 @@ static struct
     void (*png_set_sig_bytes)(png_structrp png_ptr, int num_bytes);
     void (*png_set_compression_level)(png_structrp png_ptr, int level);
 
-    void (*png_set_bgr)(png_structrp png_ptr);
-    void (*png_set_swap_alpha)(png_structrp png_ptr);
     void (*png_set_filter)(png_structrp png_ptr, int method, int filters);
 
     png_structp (*png_create_write_struct)(png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn);
@@ -161,7 +158,6 @@ static struct
     png_byte (*png_get_color_type)(png_const_structrp png_ptr, png_const_inforp info_ptr);
     png_uint_32 (*png_get_image_width)(png_const_structrp png_ptr, png_const_inforp info_ptr);
     png_uint_32 (*png_get_image_height)(png_const_structrp png_ptr, png_const_inforp info_ptr);
-    void (*png_set_expand_gray_1_2_4_to_8)(png_structrp png_ptr);
 
     void (*png_write_flush)(png_structrp png_ptr);
 } lib;
@@ -226,7 +222,6 @@ static bool IMG_InitPNG(void)
         FUNCTION_LOADER_LIBPNG(png_read_update_info, void (*)(png_structrp png_ptr, png_inforp info_ptr))
         FUNCTION_LOADER_LIBPNG(png_set_expand, void (*)(png_structrp png_ptr))
         FUNCTION_LOADER_LIBPNG(png_set_gray_to_rgb, void (*)(png_structrp png_ptr))
-        FUNCTION_LOADER_LIBPNG(png_set_packing, void (*)(png_structrp png_ptr))
         FUNCTION_LOADER_LIBPNG(png_set_read_fn, void (*)(png_structrp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn))
         FUNCTION_LOADER_LIBPNG(png_set_strip_16, void (*)(png_structrp png_ptr))
         FUNCTION_LOADER_LIBPNG(png_set_interlace_handling, int (*)(png_structrp png_ptr))
@@ -243,8 +238,6 @@ static bool IMG_InitPNG(void)
         FUNCTION_LOADER_LIBPNG(png_set_sig_bytes, void (*)(png_structrp png_ptr, int num_bytes))
         FUNCTION_LOADER_LIBPNG(png_set_compression_level, void (*)(png_structrp png_ptr, int level))
 
-        FUNCTION_LOADER_LIBPNG(png_set_bgr, void (*)(png_structrp png_ptr))
-        FUNCTION_LOADER_LIBPNG(png_set_swap_alpha, void (*)(png_structrp png_ptr))
         FUNCTION_LOADER_LIBPNG(png_set_filter, void (*)(png_structrp png_ptr, int method, int filters))
 
         FUNCTION_LOADER_LIBPNG(png_create_write_struct, png_structp(*)(png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warn_fn))
@@ -265,7 +258,6 @@ static bool IMG_InitPNG(void)
         FUNCTION_LOADER_LIBPNG(png_get_color_type, png_byte(*)(png_const_structrp png_ptr, png_const_inforp info_ptr))
         FUNCTION_LOADER_LIBPNG(png_get_image_width, png_uint_32(*)(png_const_structrp png_ptr, png_const_inforp info_ptr))
         FUNCTION_LOADER_LIBPNG(png_get_image_height, png_uint_32(*)(png_const_structrp png_ptr, png_const_inforp info_ptr))
-        FUNCTION_LOADER_LIBPNG(png_set_expand_gray_1_2_4_to_8, void (*)(png_structrp png_ptr))
 
         FUNCTION_LOADER_LIBPNG(png_write_flush, void (*)(png_structrp png_ptr))
     }
@@ -407,46 +399,50 @@ static bool LIBPNG_LoadPNG_IO_Internal(SDL_IOStream *src, struct png_load_vars *
     lib.png_get_IHDR(vars->png_ptr, vars->info_ptr, &vars->width, &vars->height, &vars->bit_depth,
                      &vars->color_type, &vars->interlace_type, NULL, NULL);
 
-    // For grayscale with bit depth < 8, expand to 8 bits
-    if (vars->color_type == PNG_COLOR_TYPE_GRAY && vars->bit_depth < 8) {
-        lib.png_set_expand_gray_1_2_4_to_8(vars->png_ptr);
-    }
-
     // Only convert non-palette formats to RGB/RGBA
+    // TODO: Convert this to a colour key - the specs say that there's
+    // only one transparent colour for non-palette images
     if (vars->color_type != PNG_COLOR_TYPE_PALETTE) {
-        if (vars->color_type == PNG_COLOR_TYPE_GRAY || vars->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
-            lib.png_set_gray_to_rgb(vars->png_ptr);
-        }
-
         if (lib.png_get_valid(vars->png_ptr, vars->info_ptr, PNG_INFO_tRNS)) {
             lib.png_set_tRNS_to_alpha(vars->png_ptr);
-        } else if (!(vars->color_type & PNG_COLOR_MASK_ALPHA)) {
-            lib.png_set_filler(vars->png_ptr, 0xFF, PNG_FILLER_AFTER);
+            vars->color_type |= PNG_COLOR_MASK_ALPHA;
         }
     }
 
-    lib.png_set_packing(vars->png_ptr);
-
-    lib.png_read_update_info(vars->png_ptr, vars->info_ptr);
-    lib.png_get_IHDR(vars->png_ptr, vars->info_ptr, &vars->width, &vars->height, &vars->bit_depth,
-                     &vars->color_type, &vars->interlace_type, NULL, NULL);
+    /* SDL doesn't currently support this format, so we convert to RGB for now. */
+    if (vars->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        lib.png_set_gray_to_rgb(vars->png_ptr);
+    }
 
-    if (vars->color_type == PNG_COLOR_TYPE_PALETTE) {
-        vars->format = SDL_PIXELFORMAT_INDEX8;
+    if (vars->color_type == PNG_COLOR_TYPE_PALETTE ||
+        vars->color_type == PNG_COLOR_TYPE_GRAY) {
+        if (vars->bit_depth == 1) {
+            vars->format = SDL_PIXELFORMAT_INDEX1MSB;
+        } else if (vars->bit_depth == 2) {
+            vars->format = SDL_PIXELFORMAT_INDEX2MSB;
+        } else if (vars->bit_depth == 4) {
+            vars->format = SDL_PIXELFORMAT_INDEX4MSB;
+        } else /* if (vars->bit_depth == 8) */ {
+            vars->format = SDL_PIXELFORMAT_INDEX8;
+        }
     } else if (vars->bit_depth == 16) {
-        vars->format = SDL_PIXELFORMAT_RGBA64;
-        // Fallback to 8-bit RGBA if 16-bit not supported
-        int bpp;
-        Uint32 rmask, gmask, bmask, amask;
-        if (!SDL_GetMasksForPixelFormat(vars->format, &bpp, &rmask, &gmask, &bmask, &amask)) {
-            lib.png_set_strip_16(vars->png_ptr);
-            lib.png_read_update_info(vars->png_ptr, vars->info_ptr);
-            vars->format = SDL_PIXELFORMAT_RGBA32;
+        if (vars->color_type & PNG_COLOR_MASK_ALPHA) {
+            vars->format = SDL_PIXELFORMAT_RGBA64;
+        } else {
+            vars->format = SDL_PIXELFORMAT_RGB48;
         }
     } else {
-        vars->format = SDL_PIXELFORMAT_RGBA32;
+        if (vars->color_type & PNG_COLOR_MASK_ALPHA) {
+            vars->format = SDL_PIXELFORMAT_RGBA32;
+        } else {
+            vars->format = SDL_PIXELFORMAT_RGB24;
+        }
     }
 
+    lib.png_read_update_info(vars->png_ptr, vars->info_ptr);
+    lib.png_get_IHDR(vars->png_ptr, vars->info_ptr, &vars->width, &vars->height, &vars->bit_depth,
+                     &vars->color_type, &vars->interlace_type, NULL, NULL);
+
     vars->surface = SDL_CreateSurface(vars->width, vars->height, vars->format);
     if (vars->surface == NULL) {
         vars->error = SDL_GetError();
@@ -456,30 +452,38 @@ static bool LIBPNG_LoadPNG_IO_Internal(SDL_IOStream *src, struct png_load_vars *
     // For palette images, set up the palette
     if (vars->color_type == PNG_COLOR_TYPE_PALETTE) {
         if (lib.png_get_PLTE(vars->png_ptr, vars->info_ptr, &vars->png_palette, &vars->num_palette)) {
-            SDL_Palette *palette = SDL_CreatePalette(vars->num_palette);
+            SDL_Palette *palette = SDL_CreateSurfacePalette(vars->surface);
             if (!palette) {
                 vars->error = "Failed to create palette for PNG image";
                 return false;
             }
 
-            for (int i = 0; i < vars->num_palette; i++) {
-                SDL_Color color;
-                color.r = vars->png_palette[i].red;
-                color.g = vars->png_palette[i].green;
-                color.b = vars->png_palette[i].blue;
-                color.a = 255;
-                SDL_SetPaletteColors(palette, &color, i, 1);
+            for (int i = 0; i < palette->ncolors; i++) {
+                palette->colors[i].r = vars->png_palette[i].red;
+                palette->colors[i].g = vars->png_palette[i].green;
+                palette->colors[i].b = vars->png_palette[i].blue;
+                palette->colors[i].a = 255;
             }
 
             if (lib.png_get_tRNS(vars->png_ptr, vars->info_ptr, &vars->trans, &vars->num_trans, &vars->trans_values)) {
-                for (int i = 0; i < vars->num_trans && i < vars->num_palette; i++) {
+                for (int i = 0; i < vars->num_trans && i < palette->ncolors; i++) {
                     palette->colors[i].a = vars->trans[i];
                 }
                 SDL_SetSurfaceBlendMode(vars->surface, SDL_BLENDMODE_BLEND);
             }
+        }
+    } else if (vars->color_type == PNG_COLOR_TYPE_GRAY) {
+        SDL_Palette *palette = SDL_CreateSurfacePalette(vars->surface);
+        if (!palette) {
+            vars->error = "Failed to create palette for PNG image";
+            return false;
+        }
 
-            SDL_SetSurfacePalette(vars->surface, palette);
-            SDL_DestroyPalette(palette);
+        for (int i = 0; i < palette->ncolors; i++) {
+            palette->colors[i].r = (i * 255) / palette->ncolors;
+            palette->colors[i].g = (i * 255) / palette->ncolors;
+            palette->colors[i].b = (i * 255) / palette->ncolors;
+            palette->colors[i].a = 255;
         }
     }