From 0a54c70db319d920544c601c98c1807c7dce0363 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 19 Oct 2025 23:14:00 -0700
Subject: [PATCH] Added support for loading ANI animated cursors
---
Android.mk | 5 +-
CMakeLists.txt | 16 +
VisualC/SDL_image.vcxproj | 10 +-
VisualC/SDL_image.vcxproj.filters | 6 +
include/SDL3_image/SDL_image.h | 139 +++++++--
src/IMG.c | 1 +
src/IMG_ani.c | 482 ++++++++++++++++++++++++++++++
src/IMG_ani.h | 23 ++
src/IMG_anim_decoder.c | 5 +-
9 files changed, 661 insertions(+), 26 deletions(-)
create mode 100644 src/IMG_ani.c
create mode 100644 src/IMG_ani.h
diff --git a/Android.mk b/Android.mk
index 35164da8..9b9f1ada 100644
--- a/Android.mk
+++ b/Android.mk
@@ -73,6 +73,7 @@ LOCAL_MODULE := SDL3_image
LOCAL_SRC_FILES := \
src/IMG.c \
+ src/IMG_ani.c \
src/IMG_anim_encoder.c \
src/IMG_anim_decoder.c \
src/IMG_avif.c \
@@ -99,8 +100,8 @@ LOCAL_SRC_FILES := \
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -DLOAD_BMP -DSAVE_BMP -DLOAD_GIF -DSAVE_GIF -DLOAD_LBM \
- -DLOAD_PCX -DLOAD_PNM -DLOAD_SVG -DLOAD_TGA -DSAVE_TGA \
+LOCAL_CFLAGS := -DLOAD_ANI -DLOAD_BMP -DLOAD_GIF -DLOAD_LBM \
+ -DLOAD_PCX -DLOAD_PNM -DLOAD_SVG -DLOAD_TGA \
-DLOAD_XCF -DLOAD_XPM -DLOAD_XV -DLOAD_QOI
LOCAL_LDLIBS :=
LOCAL_LDFLAGS := -Wl,--no-undefined -Wl,--version-script=$(LOCAL_PATH)/src/SDL_image.sym
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c888178f..ccf05a97 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -110,6 +110,7 @@ option(SDLIMAGE_BACKEND_STB "Use stb_image for loading JPEG and PNG 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)
+option(SDLIMAGE_ANI "Support loading ANI animations" ON)
option(SDLIMAGE_AVIF "Support loading AVIF images" ON)
option(SDLIMAGE_BMP "Support loading BMP images" ON)
option(SDLIMAGE_GIF "Support loading GIF images" ON)
@@ -260,6 +261,7 @@ set(BUILD_SHARED_LIBS ${SDLIMAGE_BUILD_SHARED_LIBS})
add_library(${sdl3_image_target_name}
src/IMG.c
src/IMG_WIC.c
+ src/IMG_ani.c
src/IMG_anim_encoder.c
src/IMG_anim_decoder.c
src/IMG_avif.c
@@ -594,6 +596,20 @@ if(SDLIMAGE_AVIF)
endif()
endif()
+list(APPEND SDLIMAGE_BACKENDS ANI)
+set(SDLIMAGE_ANI_ENABLED FALSE)
+if(SDLIMAGE_ANI)
+ set(SDLIMAGE_ANI_ENABLED TRUE)
+ if(SDLIMAGE_ANI_ENABLED)
+ target_compile_definitions(${sdl3_image_target_name} PRIVATE
+ LOAD_ANI
+ )
+ else()
+ # Variable is used by test suite
+ set(SDLIMAGE_ANI_SAVE OFF)
+ endif()
+endif()
+
list(APPEND SDLIMAGE_BACKENDS BMP)
set(SDLIMAGE_BMP_ENABLED FALSE)
if(SDLIMAGE_BMP)
diff --git a/VisualC/SDL_image.vcxproj b/VisualC/SDL_image.vcxproj
index e0cc754c..629db25b 100644
--- a/VisualC/SDL_image.vcxproj
+++ b/VisualC/SDL_image.vcxproj
@@ -100,7 +100,7 @@
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>external\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions>DLL_EXPORT;_DEBUG;WIN32;_WINDOWS;USE_STBIMAGE;LOAD_AVIF;LOAD_AVIF_DYNAMIC="libavif-16.dll";LOAD_BMP;LOAD_GIF;LOAD_JPG;LOAD_LBM;LOAD_PCX;LOAD_PNG;LOAD_PNM;LOAD_QOI;LOAD_SVG;LOAD_TGA;LOAD_TIF;LOAD_TIF_DYNAMIC="libtiff-6.dll";LOAD_WEBP;LOAD_WEBP_DYNAMIC="libwebp-7.dll";LOAD_WEBPDEMUX_DYNAMIC="libwebpdemux-2.dll";LOAD_WEBPMUX_DYNAMIC="libwebpmux-3.dll";LOAD_XCF;LOAD_XPM;LOAD_XV;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>DLL_EXPORT;_DEBUG;WIN32;_WINDOWS;USE_STBIMAGE;LOAD_ANI;LOAD_AVIF;LOAD_AVIF_DYNAMIC="libavif-16.dll";LOAD_BMP;LOAD_GIF;LOAD_JPG;LOAD_LBM;LOAD_PCX;LOAD_PNG;LOAD_PNM;LOAD_QOI;LOAD_SVG;LOAD_TGA;LOAD_TIF;LOAD_TIF_DYNAMIC="libtiff-6.dll";LOAD_WEBP;LOAD_WEBP_DYNAMIC="libwebp-7.dll";LOAD_WEBPDEMUX_DYNAMIC="libwebpdemux-2.dll";LOAD_WEBPMUX_DYNAMIC="libwebpmux-3.dll";LOAD_XCF;LOAD_XPM;LOAD_XV;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>OldStyle</DebugInformationFormat>
@@ -127,7 +127,7 @@
</Midl>
<ClCompile>
<AdditionalIncludeDirectories>external\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions>DLL_EXPORT;_DEBUG;WIN32;_WINDOWS;USE_STBIMAGE;LOAD_AVIF;LOAD_AVIF_DYNAMIC="libavif-16.dll";LOAD_BMP;LOAD_GIF;LOAD_JPG;LOAD_LBM;LOAD_PCX;LOAD_PNG;LOAD_PNM;LOAD_QOI;LOAD_SVG;LOAD_TGA;LOAD_TIF;LOAD_TIF_DYNAMIC="libtiff-6.dll";LOAD_WEBP;LOAD_WEBP_DYNAMIC="libwebp-7.dll";LOAD_WEBPDEMUX_DYNAMIC="libwebpdemux-2.dll";LOAD_WEBPMUX_DYNAMIC="libwebpmux-3.dll";LOAD_LIBPNG_DYNAMIC="libpng16-16.dll";SDL_IMAGE_LIBPNG;LOAD_XCF;LOAD_XPM;LOAD_XV;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>DLL_EXPORT;_DEBUG;WIN32;_WINDOWS;USE_STBIMAGE;LOAD_ANI;LOAD_AVIF;LOAD_AVIF_DYNAMIC="libavif-16.dll";LOAD_BMP;LOAD_GIF;LOAD_JPG;LOAD_LBM;LOAD_PCX;LOAD_PNG;LOAD_PNM;LOAD_QOI;LOAD_SVG;LOAD_TGA;LOAD_TIF;LOAD_TIF_DYNAMIC="libtiff-6.dll";LOAD_WEBP;LOAD_WEBP_DYNAMIC="libwebp-7.dll";LOAD_WEBPDEMUX_DYNAMIC="libwebpdemux-2.dll";LOAD_WEBPMUX_DYNAMIC="libwebpmux-3.dll";LOAD_LIBPNG_DYNAMIC="libpng16-16.dll";SDL_IMAGE_LIBPNG;LOAD_XCF;LOAD_XPM;LOAD_XV;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
@@ -154,7 +154,7 @@
</Midl>
<ClCompile>
<AdditionalIncludeDirectories>external\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions>DLL_EXPORT;NDEBUG;WIN32;_WINDOWS;USE_STBIMAGE;LOAD_AVIF;LOAD_AVIF_DYNAMIC="libavif-16.dll";LOAD_BMP;LOAD_GIF;LOAD_JPG;LOAD_LBM;LOAD_PCX;LOAD_PNG;LOAD_PNM;LOAD_QOI;LOAD_SVG;LOAD_TGA;LOAD_TIF;LOAD_TIF_DYNAMIC="libtiff-6.dll";LOAD_WEBP;LOAD_WEBP_DYNAMIC="libwebp-7.dll";LOAD_WEBPDEMUX_DYNAMIC="libwebpdemux-2.dll";LOAD_XCF;LOAD_XPM;LOAD_XV;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>DLL_EXPORT;NDEBUG;WIN32;_WINDOWS;USE_STBIMAGE;LOAD_ANI;LOAD_AVIF;LOAD_AVIF_DYNAMIC="libavif-16.dll";LOAD_BMP;LOAD_GIF;LOAD_JPG;LOAD_LBM;LOAD_PCX;LOAD_PNG;LOAD_PNM;LOAD_QOI;LOAD_SVG;LOAD_TGA;LOAD_TIF;LOAD_TIF_DYNAMIC="libtiff-6.dll";LOAD_WEBP;LOAD_WEBP_DYNAMIC="libwebp-7.dll";LOAD_WEBPDEMUX_DYNAMIC="libwebpdemux-2.dll";LOAD_XCF;LOAD_XPM;LOAD_XV;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
@@ -181,7 +181,7 @@
</Midl>
<ClCompile>
<AdditionalIncludeDirectories>external\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions>DLL_EXPORT;NDEBUG;WIN32;_WINDOWS;USE_STBIMAGE;LOAD_AVIF;LOAD_AVIF_DYNAMIC="libavif-16.dll";LOAD_BMP;LOAD_GIF;LOAD_JPG;LOAD_LBM;LOAD_PCX;LOAD_PNG;LOAD_PNM;LOAD_QOI;LOAD_SVG;LOAD_TGA;LOAD_TIF;LOAD_TIF_DYNAMIC="libtiff-6.dll";LOAD_WEBP;LOAD_WEBP_DYNAMIC="libwebp-7.dll";LOAD_WEBPDEMUX_DYNAMIC="libwebpdemux-2.dll";LOAD_WEBPMUX_DYNAMIC="libwebpmux-3.dll";LOAD_LIBPNG_DYNAMIC="libpng16-16.dll";SDL_IMAGE_LIBPNG;LOAD_XCF;LOAD_XPM;LOAD_XV;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>DLL_EXPORT;NDEBUG;WIN32;_WINDOWS;USE_STBIMAGE;LOAD_ANI;LOAD_AVIF;LOAD_AVIF_DYNAMIC="libavif-16.dll";LOAD_BMP;LOAD_GIF;LOAD_JPG;LOAD_LBM;LOAD_PCX;LOAD_PNG;LOAD_PNM;LOAD_QOI;LOAD_SVG;LOAD_TGA;LOAD_TIF;LOAD_TIF_DYNAMIC="libtiff-6.dll";LOAD_WEBP;LOAD_WEBP_DYNAMIC="libwebp-7.dll";LOAD_WEBPDEMUX_DYNAMIC="libwebpdemux-2.dll";LOAD_WEBPMUX_DYNAMIC="libwebpmux-3.dll";LOAD_LIBPNG_DYNAMIC="libpng16-16.dll";SDL_IMAGE_LIBPNG;LOAD_XCF;LOAD_XPM;LOAD_XV;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
@@ -197,6 +197,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\src\IMG.c" />
+ <ClCompile Include="..\src\IMG_ani.c" />
<ClCompile Include="..\src\IMG_anim_decoder.c" />
<ClCompile Include="..\src\IMG_anim_encoder.c" />
<ClCompile Include="..\src\IMG_avif.c" />
@@ -226,6 +227,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\SDL3_image\SDL_image.h" />
+ <ClInclude Include="..\src\IMG_ani.h" />
<ClInclude Include="..\src\IMG_anim_decoder.h" />
<ClInclude Include="..\src\IMG_anim_encoder.h" />
<ClInclude Include="..\src\IMG_libpng.h" />
diff --git a/VisualC/SDL_image.vcxproj.filters b/VisualC/SDL_image.vcxproj.filters
index 5ac3ea57..0fdfe327 100644
--- a/VisualC/SDL_image.vcxproj.filters
+++ b/VisualC/SDL_image.vcxproj.filters
@@ -73,6 +73,9 @@
<ClCompile Include="..\src\xmlman.c">
<Filter>Sources</Filter>
</ClCompile>
+ <ClCompile Include="..\src\IMG_ani.c">
+ <Filter>Sources</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Sources">
@@ -110,6 +113,9 @@
<ClInclude Include="..\src\xmlman.h">
<Filter>Sources</Filter>
</ClInclude>
+ <ClInclude Include="..\src\IMG_ani.h">
+ <Filter>Sources</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\src\version.rc">
diff --git a/include/SDL3_image/SDL_image.h b/include/SDL3_image/SDL_image.h
index f3a8d1b2..4f95311a 100644
--- a/include/SDL3_image/SDL_image.h
+++ b/include/SDL3_image/SDL_image.h
@@ -376,7 +376,7 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTextureTyped_IO(SDL_Renderer *
extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_GetClipboardImage(void);
/**
- * Detect AVIF image data on a readable/seekable SDL_IOStream.
+ * Detect ANI animated cursor data on a readable/seekable SDL_IOStream.
*
* This function attempts to determine if a file is a given filetype, reading
* the least amount possible from the SDL_IOStream (usually a few bytes).
@@ -394,7 +394,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_GetClipboardImage(void);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is AVIF data, zero otherwise.
+ * \returns true if this is ANI animated cursor data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
@@ -417,6 +417,50 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_GetClipboardImage(void);
* \sa IMG_isXV
* \sa IMG_isWEBP
*/
+extern SDL_DECLSPEC bool SDLCALL IMG_isANI(SDL_IOStream *src);
+
+/**
+ * Detect AVIF image data on a readable/seekable SDL_IOStream.
+ *
+ * This function attempts to determine if a file is a given filetype, reading
+ * the least amount possible from the SDL_IOStream (usually a few bytes).
+ *
+ * There is no distinction made between "not the filetype in question" and
+ * basic i/o errors.
+ *
+ * This function will always attempt to seek `src` back to where it started
+ * when this function was called, but it will not report any errors in doing
+ * so, but assuming seeking works, this means you can immediately use this
+ * with a different IMG_isTYPE function, or load the image without further
+ * seeking.
+ *
+ * You do not need to call this function to load data; SDL_image can work to
+ * determine file type in many cases in its standard load functions.
+ *
+ * \param src a seekable/readable SDL_IOStream to provide image data.
+ * \returns true if this is AVIF data, false otherwise.
+ *
+ * \since This function is available since SDL_image 3.0.0.
+ *
+ * \sa IMG_isANI
+ * \sa IMG_isICO
+ * \sa IMG_isCUR
+ * \sa IMG_isBMP
+ * \sa IMG_isGIF
+ * \sa IMG_isJPG
+ * \sa IMG_isJXL
+ * \sa IMG_isLBM
+ * \sa IMG_isPCX
+ * \sa IMG_isPNG
+ * \sa IMG_isPNM
+ * \sa IMG_isSVG
+ * \sa IMG_isQOI
+ * \sa IMG_isTIF
+ * \sa IMG_isXCF
+ * \sa IMG_isXPM
+ * \sa IMG_isXV
+ * \sa IMG_isWEBP
+ */
extern SDL_DECLSPEC bool SDLCALL IMG_isAVIF(SDL_IOStream *src);
/**
@@ -438,10 +482,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isAVIF(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is ICO data, zero otherwise.
+ * \returns true if this is ICO data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isCUR
* \sa IMG_isBMP
@@ -481,10 +526,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isICO(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is CUR data, zero otherwise.
+ * \returns true if this is CUR data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isBMP
@@ -524,10 +570,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isCUR(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is BMP data, zero otherwise.
+ * \returns true if this is BMP data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -567,10 +614,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isBMP(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is GIF data, zero otherwise.
+ * \returns true if this is GIF data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -610,10 +658,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isGIF(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is JPG data, zero otherwise.
+ * \returns true if this is JPG data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -653,10 +702,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isJPG(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is JXL data, zero otherwise.
+ * \returns true if this is JXL data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -696,10 +746,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isJXL(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is LBM data, zero otherwise.
+ * \returns true if this is LBM data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -739,10 +790,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isLBM(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is PCX data, zero otherwise.
+ * \returns true if this is PCX data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -782,10 +834,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isPCX(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is PNG data, zero otherwise.
+ * \returns true if this is PNG data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -825,10 +878,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isPNG(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is PNM data, zero otherwise.
+ * \returns true if this is PNM data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -868,10 +922,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isPNM(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is SVG data, zero otherwise.
+ * \returns true if this is SVG data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -911,10 +966,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isSVG(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is QOI data, zero otherwise.
+ * \returns true if this is QOI data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -954,10 +1010,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isQOI(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is TIFF data, zero otherwise.
+ * \returns true if this is TIFF data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -997,10 +1054,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isTIF(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is XCF data, zero otherwise.
+ * \returns true if this is XCF data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -1040,10 +1098,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isXCF(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is XPM data, zero otherwise.
+ * \returns true if this is XPM data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -1083,10 +1142,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isXPM(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is XV data, zero otherwise.
+ * \returns true if this is XV data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -1126,10 +1186,11 @@ extern SDL_DECLSPEC bool SDLCALL IMG_isXV(SDL_IOStream *src);
* determine file type in many cases in its standard load functions.
*
* \param src a seekable/readable SDL_IOStream to provide image data.
- * \returns non-zero if this is WEBP data, zero otherwise.
+ * \returns true if this is WEBP data, false otherwise.
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isANI
* \sa IMG_isAVIF
* \sa IMG_isICO
* \sa IMG_isCUR
@@ -2225,6 +2286,7 @@ typedef struct IMG_Animation
* \sa IMG_CreateAnimatedCursor
* \sa IMG_LoadAnimation_IO
* \sa IMG_LoadAnimationTyped_IO
+ * \sa IMG_LoadANIAnimation_IO
* \sa IMG_LoadAPNGAnimation_IO
* \sa IMG_LoadAVIFAnimation_IO
* \sa IMG_LoadGIFAnimation_IO
@@ -2253,6 +2315,7 @@ extern SDL_DECLSPEC IMG_Animation * SDLCALL IMG_LoadAnimation(const char *file);
* \sa IMG_CreateAnimatedCursor
* \sa IMG_LoadAnimation
* \sa IMG_LoadAnimationTyped_IO
+ * \sa IMG_LoadANIAnimation_IO
* \sa IMG_LoadAPNGAnimation_IO
* \sa IMG_LoadAVIFAnimation_IO
* \sa IMG_LoadGIFAnimation_IO
@@ -2288,6 +2351,7 @@ extern SDL_DECLSPEC IMG_Animation * SDLCALL IMG_LoadAnimation_IO(SDL_IOStream *s
* \sa IMG_CreateAnimatedCursor
* \sa IMG_LoadAnimation
* \sa IMG_LoadAnimation_IO
+ * \sa IMG_LoadANIAnimation_IO
* \sa IMG_LoadAPNGAnimation_IO
* \sa IMG_LoadAVIFAnimation_IO
* \sa IMG_LoadGIFAnimation_IO
@@ -2308,6 +2372,7 @@ extern SDL_DECLSPEC IMG_Animation * SDLCALL IMG_LoadAnimationTyped_IO(SDL_IOStre
* \sa IMG_LoadAnimation
* \sa IMG_LoadAnimation_IO
* \sa IMG_LoadAnimationTyped_IO
+ * \sa IMG_LoadANIAnimation_IO
* \sa IMG_LoadAPNGAnimation_IO
* \sa IMG_LoadAVIFAnimation_IO
* \sa IMG_LoadGIFAnimation_IO
@@ -2315,6 +2380,34 @@ extern SDL_DECLSPEC IMG_Animation * SDLCALL IMG_LoadAnimationTyped_IO(SDL_IOStre
*/
extern SDL_DECLSPEC void SDLCALL IMG_FreeAnimation(IMG_Animation *anim);
+/**
+ * Load an ANI animation directly from an SDL_IOStream.
+ *
+ * If you know you definitely have an ANI image, you can call this function,
+ * which will skip SDL_image's file format detection routines. Generally, it's
+ * better to use the abstract interfaces; also, there is only an SDL_IOStream
+ * interface available here.
+ *
+ * When done with the returned animation, the app should dispose of it with a
+ * call to IMG_FreeAnimation().
+ *
+ * \param src an SDL_IOStream from which data will be read.
+ * \returns a new IMG_Animation, or NULL on error.
+ *
+ * \since This function is available since SDL_image 3.4.0.
+ *
+ * \sa IMG_isANI
+ * \sa IMG_LoadAnimation
+ * \sa IMG_LoadAnimation_IO
+ * \sa IMG_LoadAnimationTyped_IO
+ * \sa IMG_LoadAPNGAnimation_IO
+ * \sa IMG_LoadAVIFAnimation_IO
+ * \sa IMG_LoadGIFAnimation_IO
+ * \sa IMG_LoadWEBPAnimation_IO
+ * \sa IMG_FreeAnimation
+ */
+extern SDL_DECLSPEC IMG_Animation *SDLCALL IMG_LoadANIAnimation_IO(SDL_IOStream *src);
+
/**
* Load an APNG animation directly from an SDL_IOStream.
*
@@ -2331,9 +2424,11 @@ extern SDL_DECLSPEC void SDLCALL IMG_FreeAnimation(IMG_Animation *anim);
*
* \since This function is available since SDL_image 3.4.0.
*
+ * \sa IMG_isPNG
* \sa IMG_LoadAnimation
* \sa IMG_LoadAnimation_IO
* \sa IMG_LoadAnimationTyped_IO
+ * \sa IMG_LoadANIAnimation_IO
* \sa IMG_LoadAVIFAnimation_IO
* \sa IMG_LoadGIFAnimation_IO
* \sa IMG_LoadWEBPAnimation_IO
@@ -2357,9 +2452,11 @@ extern SDL_DECLSPEC IMG_Animation *SDLCALL IMG_LoadAPNGAnimation_IO(SDL_IOStream
*
* \since This function is available since SDL_image 3.4.0.
*
+ * \sa IMG_isAVIF
* \sa IMG_LoadAnimation
* \sa IMG_LoadAnimation_IO
* \sa IMG_LoadAnimationTyped_IO
+ * \sa IMG_LoadANIAnimation_IO
* \sa IMG_LoadAPNGAnimation_IO
* \sa IMG_LoadGIFAnimation_IO
* \sa IMG_LoadWEBPAnimation_IO
@@ -2380,9 +2477,11 @@ extern SDL_DECLSPEC IMG_Animation *SDLCALL IMG_LoadAVIFAnimation_IO(SDL_IOStream
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isGIF
* \sa IMG_LoadAnimation
* \sa IMG_LoadAnimation_IO
* \sa IMG_LoadAnimationTyped_IO
+ * \sa IMG_LoadANIAnimation_IO
* \sa IMG_LoadAPNGAnimation_IO
* \sa IMG_LoadAVIFAnimation_IO
* \sa IMG_LoadWEBPAnimation_IO
@@ -2403,9 +2502,11 @@ extern SDL_DECLSPEC IMG_Animation * SDLCALL IMG_LoadGIFAnimation_IO(SDL_IOStream
*
* \since This function is available since SDL_image 3.0.0.
*
+ * \sa IMG_isWEBP
* \sa IMG_LoadAnimation
* \sa IMG_LoadAnimation_IO
* \sa IMG_LoadAnimationTyped_IO
+ * \sa IMG_LoadANIAnimation_IO
* \sa IMG_LoadAPNGAnimation_IO
* \sa IMG_LoadAVIFAnimation_IO
* \sa IMG_LoadGIFAnimation_IO
diff --git a/src/IMG.c b/src/IMG.c
index 8809de7b..95117162 100644
--- a/src/IMG.c
+++ b/src/IMG.c
@@ -83,6 +83,7 @@ static struct {
{ "WEBP", IMG_isWEBP, IMG_LoadWEBPAnimation_IO },
{ "APNG", IMG_isPNG, IMG_LoadAPNGAnimation_IO },
{ "AVIFS", IMG_isAVIF, IMG_LoadAVIFAnimation_IO },
+ { "ANI", IMG_isANI, IMG_LoadANIAnimation_IO },
};
int IMG_Version(void)
diff --git a/src/IMG_ani.c b/src/IMG_ani.c
new file mode 100644
index 00000000..476b36ab
--- /dev/null
+++ b/src/IMG_ani.c
@@ -0,0 +1,482 @@
+/*
+ SDL_image: An example image loading library for use with SDL
+ Copyright (C) 1997-2025 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.
+*/
+
+#include <SDL3_image/SDL_image.h>
+
+#include "IMG_ani.h"
+#include "IMG_anim_decoder.h"
+
+#ifdef LOAD_ANI
+
+#define RIFF_FOURCC(c0, c1, c2, c3) \
+ ((Uint32)(Uint8)(c0) | ((Uint32)(Uint8)(c1) << 8) | \
+ ((Uint32)(Uint8)(c2) << 16) | ((Uint32)(Uint8)(c3) << 24))
+
+#define ANI_FLAG_ICON 0x1
+#define ANI_FLAG_SEQUENCE 0x2
+
+typedef struct
+{
+ Uint32 riffID;
+ Uint32 cbSize;
+ Uint32 chunkID;
+} RIFFHEADER;
+
+typedef struct {
+ Uint32 cbSizeof; // sizeof(ANIHEADER) = 36 bytes.
+ Uint32 frames; // Number of frames in the frame list.
+ Uint32 steps; // Number of steps in the animation loop.
+ Uint32 width; // Width
+ Uint32 height; // Height
+ Uint32 bpp; // bpp
+ Uint32 planes; // Not used
+ Uint32 jifRate; // Default display rate, in jiffies (1/60s)
+ Uint32 fl; // AF_ICON should be set. AF_SEQUENCE is optional
+} ANIHEADER;
+
+
+bool IMG_isANI(SDL_IOStream* src)
+{
+ Sint64 start;
+ bool is_ANI;
+ RIFFHEADER header;
+
+ if (!src) {
+ return false;
+ }
+
+ start = SDL_TellIO(src);
+ is_ANI = false;
+ if (SDL_ReadU32LE(src, &header.riffID) &&
+ SDL_ReadU32LE(src, &header.cbSize) &&
+ SDL_ReadU32LE(src, &header.chunkID) &&
+ header.riffID == RIFF_FOURCC('R', 'I', 'F', 'F') &&
+ header.chunkID == RIFF_FOURCC('A', 'C', 'O', 'N')) {
+ is_ANI = true;
+ }
+ SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
+ return is_ANI;
+}
+
+IMG_Animation *IMG_LoadANIAnimation_IO(SDL_IOStream *src)
+{
+ return IMG_DecodeAsAnimation(src, "ani", 0);
+}
+
+struct IMG_AnimationDecoderContext
+{
+ Uint32 frame_index;
+ Uint32 frame_count;
+ Sint64 *frame_offsets;
+ Uint32 *frame_durations;
+ Uint32 *frame_sequence;
+};
+
+typedef struct
+{
+ IMG_AnimationDecoderContext *ctx;
+ SDL_IOStream *src;
+ bool has_anih;
+ ANIHEADER anih;
+ char *author;
+ char *title;
+} IMG_AnimationParseContext;
+
+static bool IMG_AnimationDecoderReset_Internal(IMG_AnimationDecoder* decoder)
+{
+ IMG_AnimationDecoderContext* ctx = decoder->ctx;
+
+ ctx->frame_index = 0;
+
+ return true;
+}
+
+static bool IMG_AnimationDecoderGetNextFrame_Internal(IMG_AnimationDecoder *decoder, SDL_Surface **frame, Uint64 *duration)
+{
+ IMG_AnimationDecoderContext *ctx = decoder->ctx;
+
+ if (ctx->frame_index == ctx->frame_count) {
+ decoder->status = IMG_DECODER_STATUS_COMPLETE;
+ return false;
+ }
+
+ *duration = IMG_GetDecoderDuration(decoder, ctx->frame_durations[ctx->frame_index], 60);
+
+ Sint64 offset = ctx->frame_offsets[ctx->frame_sequence[ctx->frame_index]];
+ if (SDL_SeekIO(decoder->src, offset, SDL_IO_SEEK_SET) < 0) {
+ return SDL_SetError("Failed to seek to frame offset");
+ }
+ if (IMG_isCUR(decoder->src)) {
+ *frame = IMG_LoadCUR_IO(decoder->src);
+ } else if (IMG_isICO(decoder->src)) {
+ *frame = IMG_LoadICO_IO(decoder->src);
+ } else {
+ SDL_SetError("Unrecognized frame type");
+ *frame = NULL;
+ }
+ ++ctx->frame_index;
+
+ return (*frame != NULL);
+}
+
+static bool IMG_AnimationDecoderClose_Internal(IMG_AnimationDecoder *decoder)
+{
+ IMG_AnimationDecoderContext *ctx = decoder->ctx;
+
+ SDL_free(ctx->frame_offsets);
+ SDL_free(ctx->frame_durations);
+ SDL_free(ctx->frame_sequence);
+ SDL_free(ctx);
+ decoder->ctx = NULL;
+ return true;
+}
+
+static bool ParseANIHeader(IMG_AnimationParseContext *parse, Uint32 size)
+{
+ SDL_IOStream *src = parse->src;
+ IMG_AnimationDecoderContext *ctx = parse->ctx;
+ ANIHEADER *anih = &parse->anih;
+
+ if (size != sizeof(*anih)) {
+ return SDL_SetError("Invalid ANI header");
+ }
+
+ if (parse->has_anih) {
+ // Ignore duplicate 'anih' chunk
+ return true;
+ }
+
+ if (!SDL_ReadU32LE(src, &anih->cbSizeof) ||
+ !SDL_ReadU32LE(src, &anih->frames) ||
+ !SDL_ReadU32LE(src, &anih->steps) ||
+ !SDL_ReadU32LE(src, &anih->width) ||
+ !SDL_ReadU32LE(src, &anih->height) ||
+ !SDL_ReadU32LE(src, &anih->bpp) ||
+ !SDL_ReadU32LE(src, &anih->planes) ||
+ !SDL_ReadU32LE(src, &anih->jifRate) ||
+ !SDL_ReadU32LE(src, &anih->fl)) {
+ return SDL_SetError("Couldn't read ANI header");
+ }
+ parse->has_anih = true;
+
+ if (anih->cbSizeof != sizeof(*anih) ||
+ anih->frames == 0 ||
+ anih->steps == 0) {
+ return SDL_SetError("Invalid ANI header");
+ }
+
+ // We could support raw frames if we get an example of this
+ if (!(anih->fl & ANI_FLAG_ICON)) {
+ return SDL_SetError("Raw ANI frames are unsupported");
+ }
+
+ ctx->frame_count = anih->steps;
+ ctx->frame_offsets = (Sint64 *)SDL_calloc(anih->frames, sizeof(*ctx->frame_offsets));
+ ctx->frame_durations = (Uint32 *)SDL_calloc(ctx->frame_count, sizeof(*ctx->frame_durations));
+ ctx->frame_sequence = (Uint32 *)SDL_calloc(ctx->frame_count, sizeof(*ctx->frame_durations));
+ if (!ctx->frame_offsets || !ctx->frame_durations || !ctx->frame_sequence) {
+ return false;
+ }
+
+ for (Uint32 i = 0; i < ctx->frame_count; ++i) {
+ ctx->frame_sequence[i] = i;
+
(Patch may be truncated, please check the link at the top of this post.)