From a82b6e68201c8b14b33206014d08ecff7b875fef Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 6 Jan 2026 11:45:13 -0800
Subject: [PATCH] Cleaned up PNG loading logic
If libpng is enabled and available, use that for PNG loading. Otherwise, use WIC and ImageIO if they've been enabled, otherwise use SDL's STB implementation.
Note that we always fall back to SDL's implementation so that if PNG support is enabled we'll always have an available decoder.
---
CMakeLists.txt | 110 ++++++++++++++++++++++++----------------------
src/IMG_ImageIO.h | 11 +++++
src/IMG_ImageIO.m | 11 +----
src/IMG_WIC.c | 37 ++--------------
src/IMG_WIC.h | 24 ++++++++++
src/IMG_libpng.c | 72 ++----------------------------
src/IMG_libpng.h | 4 ++
src/IMG_png.c | 33 +++++++++++---
8 files changed, 132 insertions(+), 170 deletions(-)
create mode 100644 src/IMG_ImageIO.h
create mode 100644 src/IMG_WIC.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5a85211e..c8e833cb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -106,7 +106,7 @@ cmake_dependent_option(SDLIMAGE_SAMPLES_INSTALL "Install the SDL3_image sample p
option(SDLIMAGE_TESTS "Build unit tests?" OFF)
option(SDLIMAGE_TESTS_INSTALL "Install unit tests?" OFF)
-option(SDLIMAGE_BACKEND_STB "Use stb_image for loading JPEG and PNG files" ON)
+option(SDLIMAGE_BACKEND_STB "Use stb_image for loading JPEG files" ON)
cmake_dependent_option(SDLIMAGE_BACKEND_WIC "Add WIC backend (Windows Imaging Component)" OFF WIN32 OFF)
cmake_dependent_option(SDLIMAGE_BACKEND_IMAGEIO "Use native Mac OS X frameworks for loading images" ON APPLE OFF)
@@ -822,54 +822,66 @@ if(SDLIMAGE_PCX)
endif()
list(APPEND SDLIMAGE_BACKENDS PNG)
+list(APPEND SDLIMAGE_BACKENDS APNG)
set(SDLIMAGE_PNG_ENABLED FALSE)
-if(SDLIMAGE_PNG_LIBPNG)
- if(SDLIMAGE_PNG_VENDORED)
- set(PNG_SHARED "${SDLIMAGE_PNG_SHARED}")
- set(SDLIMAGE_PNG_ENABLED TRUE)
- message(STATUS "${PROJECT_NAME}: Using vendored libpng")
- set(PNG_TESTS OFF CACHE BOOL "Build PNG Tests" FORCE)
- set(SKIP_INSTALL_EXPORT TRUE)
- sdl_check_project_in_subfolder(external/libpng libpng SDLIMAGE_VENDORED)
- add_subdirectory(external/libpng external/libpng-build EXCLUDE_FROM_ALL)
- register_license(libpng external/libpng/LICENSE)
- if(SDLIMAGE_PNG_SHARED)
- set(PNG_LIBRARY png_shared)
- else()
- set(PNG_LIBRARY png_static)
- endif()
- add_library(PNG::PNG ALIAS ${PNG_LIBRARY})
- set_property(TARGET ${PNG_LIBRARY} PROPERTY DEBUG_POSTFIX "")
- target_include_directories(${sdl3_image_target_name} PRIVATE external/libpng)
- if(SDLIMAGE_PNG_SHARED OR NOT SDLIMAGE_BUILD_SHARED_LIBS)
- list(APPEND INSTALL_EXTRA_TARGETS ${PNG_LIBRARY})
- endif()
- set_target_properties(${PNG_LIBRARY} PROPERTIES EXPORT_NAME external_libpng)
- add_library(SDL3_image::external_libpng ALIAS ${PNG_LIBRARY})
- if(NOT SDLIMAGE_PNG_SHARED)
- list(APPEND PC_LIBS -l$<TARGET_FILE_BASE_NAME:${PNG_LIBRARY}>)
- if(SDLIMAGE_ZLIB_VENDORED)
- list(APPEND PC_LIBS -l$<TARGET_FILE_BASE_NAME:${ZLIB_LIBRARY}>)
+if(SDLIMAGE_PNG)
+ set(SDLIMAGE_APNG_ENABLED FALSE)
+ set(SDLIMAGE_PNG_ENABLED TRUE)
+ set(SDLIMAGE_LIBPNG_ENABLED FALSE)
+
+ if(SDLIMAGE_PNG_LIBPNG)
+ if(SDLIMAGE_PNG_VENDORED)
+ set(PNG_SHARED "${SDLIMAGE_PNG_SHARED}")
+ set(SDLIMAGE_LIBPNG_ENABLED TRUE)
+ message(STATUS "${PROJECT_NAME}: Using vendored libpng")
+ set(PNG_TESTS OFF CACHE BOOL "Build PNG Tests" FORCE)
+ set(SKIP_INSTALL_EXPORT TRUE)
+ sdl_check_project_in_subfolder(external/libpng libpng SDLIMAGE_VENDORED)
+ add_subdirectory(external/libpng external/libpng-build EXCLUDE_FROM_ALL)
+ register_license(libpng external/libpng/LICENSE)
+ if(SDLIMAGE_PNG_SHARED)
+ set(PNG_LIBRARY png_shared)
else()
- list(APPEND PC_REQUIRES zlib)
+ set(PNG_LIBRARY png_static)
endif()
- endif()
- elseif(SDLIMAGE_PNG_SHARED AND DEFINED SDLIMAGE_DYNAMIC_PNG AND EXISTS "${SDLIMAGE_DYNAMIC_PNG}")
- message(STATUS "${PROJECT_NAME}: Using libpng from CMake variable")
- set(SDLIMAGE_PNG_ENABLED TRUE)
- else()
- find_package(PNG ${required})
- if(PNG_FOUND)
- message(STATUS "${PROJECT_NAME}: Using system libpng")
- set(SDLIMAGE_PNG_ENABLED TRUE)
+ add_library(PNG::PNG ALIAS ${PNG_LIBRARY})
+ set_property(TARGET ${PNG_LIBRARY} PROPERTY DEBUG_POSTFIX "")
+ target_include_directories(${sdl3_image_target_name} PRIVATE external/libpng)
+ if(SDLIMAGE_PNG_SHARED OR NOT SDLIMAGE_BUILD_SHARED_LIBS)
+ list(APPEND INSTALL_EXTRA_TARGETS ${PNG_LIBRARY})
+ endif()
+ set_target_properties(${PNG_LIBRARY} PROPERTIES EXPORT_NAME external_libpng)
+ add_library(SDL3_image::external_libpng ALIAS ${PNG_LIBRARY})
if(NOT SDLIMAGE_PNG_SHARED)
- list(APPEND PC_REQUIRES libpng)
+ list(APPEND PC_LIBS -l$<TARGET_FILE_BASE_NAME:${PNG_LIBRARY}>)
+ if(SDLIMAGE_ZLIB_VENDORED)
+ list(APPEND PC_LIBS -l$<TARGET_FILE_BASE_NAME:${ZLIB_LIBRARY}>)
+ else()
+ list(APPEND PC_REQUIRES zlib)
+ endif()
endif()
+ elseif(SDLIMAGE_PNG_SHARED AND DEFINED SDLIMAGE_DYNAMIC_PNG AND EXISTS "${SDLIMAGE_DYNAMIC_PNG}")
+ message(STATUS "${PROJECT_NAME}: Using libpng from CMake variable")
+ set(SDLIMAGE_LIBPNG_ENABLED TRUE)
else()
- message(${FATAL_ERROR} "libpng NOT found")
+ find_package(PNG ${required})
+ if(PNG_FOUND)
+ message(STATUS "${PROJECT_NAME}: Using system libpng")
+ set(SDLIMAGE_LIBPNG_ENABLED TRUE)
+ if(NOT SDLIMAGE_PNG_SHARED)
+ list(APPEND PC_REQUIRES libpng)
+ endif()
+ else()
+ message(${FATAL_ERROR} "libpng NOT found")
+ endif()
endif()
endif()
- if(SDLIMAGE_PNG_ENABLED)
+
+ if(SDLIMAGE_LIBPNG_ENABLED)
+ set(SDLIMAGE_APNG_ENABLED TRUE)
+ target_compile_definitions(${sdl3_image_target_name} PRIVATE
+ SDL_IMAGE_LIBPNG
+ )
if(SDLIMAGE_PNG_SHARED)
if(NOT DEFINED SDLIMAGE_DYNAMIC_PNG)
target_include_directories(${sdl3_image_target_name} PRIVATE
@@ -887,21 +899,15 @@ if(SDLIMAGE_PNG_LIBPNG)
else()
target_link_libraries(${sdl3_image_target_name} PRIVATE PNG::PNG)
endif()
- endif()
- if(SDLIMAGE_PNG_ENABLED)
- target_compile_definitions(${sdl3_image_target_name} PRIVATE
- LOAD_PNG
- SAVE_PNG=$<BOOL:${SDLIMAGE_PNG_SAVE}>
- )
- if(NOT(SDLIMAGE_BACKEND_STB OR SDLIMAGE_BACKEND_WIC OR SDLIMAGE_BACKEND_IMAGEIO))
- target_compile_definitions(${sdl3_image_target_name} PRIVATE
- SDL_IMAGE_LIBPNG
- )
- endif()
else()
# Variable is used by test suite
set(SDLIMAGE_PNG_SAVE OFF)
endif()
+
+ target_compile_definitions(${sdl3_image_target_name} PRIVATE
+ LOAD_PNG
+ SAVE_PNG=$<BOOL:${SDLIMAGE_PNG_SAVE}>
+ )
endif()
list(APPEND SDLIMAGE_BACKENDS PNM)
diff --git a/src/IMG_ImageIO.h b/src/IMG_ImageIO.h
new file mode 100644
index 00000000..1ec86385
--- /dev/null
+++ b/src/IMG_ImageIO.h
@@ -0,0 +1,11 @@
+/*
+ * IMG_ImageIO.c
+ * SDL_image
+ *
+ * Created by Eric Wing on 1/1/09.
+ * Copyright 2009 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+extern SDL_Surface *IMG_LoadPNG_ImageIO(SDL_IOStream *src);
+
diff --git a/src/IMG_ImageIO.m b/src/IMG_ImageIO.m
index b9a4a667..aa889fc3 100644
--- a/src/IMG_ImageIO.m
+++ b/src/IMG_ImageIO.m
@@ -450,15 +450,6 @@ bool IMG_isJPG(SDL_IOStream *src)
#endif /* JPG_USES_IMAGEIO */
-#ifdef PNG_USES_IMAGEIO
-
-bool IMG_isPNG(SDL_IOStream *src)
-{
- return Internal_isType(src, kUTTypePNG);
-}
-
-#endif /* PNG_USES_IMAGEIO */
-
// This isn't a public API function. Apple seems to be able to identify tga's.
bool IMG_isTGA(SDL_IOStream *src)
{
@@ -551,7 +542,7 @@ bool IMG_isTIF(SDL_IOStream *src)
#ifdef PNG_USES_IMAGEIO
-SDL_Surface* IMG_LoadPNG_IO (SDL_IOStream *src)
+SDL_Surface* IMG_LoadPNG_ImageIO (SDL_IOStream *src)
{
return LoadImageFromIOStream (src, kUTTypePNG);
}
diff --git a/src/IMG_WIC.c b/src/IMG_WIC.c
index 9f5eaf92..7084859b 100644
--- a/src/IMG_WIC.c
+++ b/src/IMG_WIC.c
@@ -28,7 +28,7 @@
static IWICImagingFactory* wicFactory = NULL;
-static bool WIC_Init(void)
+bool WIC_Init(void)
{
if (wicFactory == NULL) {
HRESULT hr = CoCreateInstance(
@@ -55,30 +55,6 @@ static void WIC_Quit(void)
}
#endif // 0
-bool IMG_isPNG(SDL_IOStream *src)
-{
- Sint64 start;
- bool is_PNG;
- Uint8 magic[4];
-
- if (!src) {
- return false;
- }
-
- start = SDL_TellIO(src);
- is_PNG = false;
- if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic) ) {
- if ( magic[0] == 0x89 &&
- magic[1] == 'P' &&
- magic[2] == 'N' &&
- magic[3] == 'G' ) {
- is_PNG = true;
- }
- }
- SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
- return is_PNG;
-}
-
bool IMG_isJPG(SDL_IOStream *src)
{
Sint64 start;
@@ -175,7 +151,7 @@ bool IMG_isTIF(SDL_IOStream * src)
return is_TIF;
}
-static SDL_Surface* WIC_LoadImage(SDL_IOStream *src)
+SDL_Surface *WIC_LoadImage(SDL_IOStream *src)
{
SDL_Surface* surface = NULL;
@@ -243,14 +219,9 @@ static SDL_Surface* WIC_LoadImage(SDL_IOStream *src)
IWICStream_Release(stream);
}
- SDL_free(memoryBuffer);
+ SDL_free(memoryBuffer);
- return surface;
-}
-
-SDL_Surface *IMG_LoadPNG_IO(SDL_IOStream *src)
-{
- return WIC_LoadImage(src);
+ return surface;
}
SDL_Surface *IMG_LoadJPG_IO(SDL_IOStream *src)
diff --git a/src/IMG_WIC.h b/src/IMG_WIC.h
new file mode 100644
index 00000000..93856c95
--- /dev/null
+++ b/src/IMG_WIC.h
@@ -0,0 +1,24 @@
+/*
+ SDL_image: An example image loading library for use with SDL
+ Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+extern bool WIC_Init(void);
+extern SDL_Surface *WIC_LoadImage(SDL_IOStream *src);
+
diff --git a/src/IMG_libpng.c b/src/IMG_libpng.c
index 07f3f87a..c77e632f 100644
--- a/src/IMG_libpng.c
+++ b/src/IMG_libpng.c
@@ -206,7 +206,7 @@ static struct
/* Need to turn off optimizations so weak framework load check works */
__attribute__((optnone))
#endif
-static bool IMG_InitPNG(void)
+bool IMG_InitPNG(void)
{
if (lib.loaded == 0) {
/* Uncomment this if you want to use zlib with libpng to decompress / compress manually if you'd prefer that.
@@ -323,42 +323,6 @@ static void png_flush_data(png_structp png_ptr)
lib.png_write_flush(png_ptr);
}
-bool IMG_isPNG(SDL_IOStream *stream)
-{
- if (!stream) {
- return false;
- }
-
- if (!IMG_InitPNG()) {
- return false;
- }
-
- png_byte header[sizeof(png_sig)];
- Sint64 initial_offset;
- bool is_png = false;
-
- // Get the current read position of the stream
- initial_offset = SDL_TellIO(stream);
- if (initial_offset < 0) {
- return false;
- }
-
- // Read the first 8 bytes (PNG signature)
- if (SDL_ReadIO(stream, header, sizeof(png_sig)) != sizeof(png_sig)) {
- goto cleanup;
- }
-
- if (lib.png_sig_cmp(header, 0, sizeof(png_sig)) == 0) {
- is_png = true;
- }
-
-cleanup:
- // Reset the stream's read position to its initial offset
- SDL_SeekIO(stream, initial_offset, SDL_IO_SEEK_SET);
-
- return is_png;
-}
-
struct png_load_vars
{
const char *error;
@@ -530,7 +494,7 @@ static bool LIBPNG_LoadPNG_IO_Internal(SDL_IOStream *src, struct png_load_vars *
return true;
}
-SDL_Surface *IMG_LoadPNG_IO(SDL_IOStream *src)
+SDL_Surface *IMG_LoadPNG_LIBPNG(SDL_IOStream *src)
{
Sint64 start_pos;
bool success = false;
@@ -540,10 +504,6 @@ SDL_Surface *IMG_LoadPNG_IO(SDL_IOStream *src)
return NULL;
}
- if (!IMG_InitPNG()) {
- return NULL;
- }
-
start_pos = SDL_TellIO(src);
struct png_load_vars vars;
@@ -686,17 +646,13 @@ static bool LIBPNG_SavePNG_IO_Internal(struct png_save_vars *vars, SDL_Surface *
return true;
}
-bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
+bool IMG_SavePNG_LIBPNG(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
if (!surface || !dst) {
SDL_SetError("Surface or SDL_IOStream is NULL");
return false;
}
- if (!IMG_InitPNG()) {
- return false;
- }
-
struct png_save_vars vars;
bool result = false;
@@ -729,28 +685,6 @@ bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
return result;
}
-bool IMG_SavePNG(SDL_Surface *surface, const char *file)
-{
- SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
- if (dst) {
- return IMG_SavePNG_IO(surface, dst, true);
- } else {
- return false;
- }
-}
-
-#else // !SAVE_PNG
-
-bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
-{
- return SDL_SetError("SDL_image built without PNG save support");
-}
-
-bool IMG_SavePNG(SDL_Surface *surface, const char *file)
-{
- return SDL_SetError("SDL_image built without PNG save support");
-}
-
#endif // SAVE_PNG
typedef struct
diff --git a/src/IMG_libpng.h b/src/IMG_libpng.h
index 752fef98..1b224e18 100644
--- a/src/IMG_libpng.h
+++ b/src/IMG_libpng.h
@@ -19,5 +19,9 @@
3. This notice may not be removed or altered from any source distribution.
*/
+extern bool IMG_InitPNG(void);
+extern SDL_Surface *IMG_LoadPNG_LIBPNG(SDL_IOStream *src);
+extern bool IMG_SavePNG_LIBPNG(SDL_Surface *surface, SDL_IOStream *dst, bool closeio);
+
extern bool IMG_CreateAPNGAnimationEncoder(IMG_AnimationEncoder *encoder, SDL_PropertiesID props);
extern bool IMG_CreateAPNGAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_PropertiesID props);
diff --git a/src/IMG_png.c b/src/IMG_png.c
index d87ecbad..0580bbe5 100644
--- a/src/IMG_png.c
+++ b/src/IMG_png.c
@@ -23,16 +23,17 @@
#include <SDL3_image/SDL_image.h>
-#if !defined(SDL_IMAGE_LIBPNG)
+#include "IMG_ImageIO.h"
+#include "IMG_libpng.h"
+#include "IMG_WIC.h"
/* We'll have PNG save support by default */
#if !defined(SAVE_PNG)
#define SAVE_PNG 1
#endif
-#if defined(LOAD_PNG) && defined(USE_STBIMAGE)
+#if defined(LOAD_PNG)
-/* FIXME: This is a copypaste from LIBPNG! Pull that out of the ifdefs */
/* See if an image is contained in a data source */
bool IMG_isPNG(SDL_IOStream *src)
{
@@ -61,10 +62,26 @@ bool IMG_isPNG(SDL_IOStream *src)
/* Load a PNG type image from an SDL datasource */
SDL_Surface *IMG_LoadPNG_IO(SDL_IOStream *src)
{
+#ifdef SDL_IMAGE_LIBPNG
+ if (IMG_InitPNG()) {
+ return IMG_LoadPNG_LIBPNG(src);
+ }
+#endif
+
+#if defined(SDL_IMAGE_USE_WIC_BACKEND)
+ if (WIC_Init()) {
+ return WIC_LoadImage(src);
+ }
+#endif
+
+#ifdef PNG_USES_IMAGEIO
+ return IMG_LoadPNG_ImageIO(src);
+#else
return SDL_LoadPNG_IO(src, false);
+#endif
}
-#elif !defined(PNG_USES_IMAGEIO)
+#else
/* See if an image is contained in a data source */
bool IMG_isPNG(SDL_IOStream *src)
@@ -85,6 +102,12 @@ SDL_Surface *IMG_LoadPNG_IO(SDL_IOStream *src)
bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
+#ifdef SDL_IMAGE_LIBPNG
+ if (IMG_InitPNG()) {
+ return IMG_SavePNG_LIBPNG(surface, dst, closeio);
+ }
+#endif
+
return SDL_SavePNG_IO(surface, dst, closeio);
}
@@ -111,5 +134,3 @@ bool IMG_SavePNG(SDL_Surface *surface, const char *file)
}
#endif // SAVE_PNG
-
-#endif /* SDL_IMAGE_LIBPNG */