From bff841cd470132f854e97aee413e6e47c3edf5c7 Mon Sep 17 00:00:00 2001
From: CrackedPixel <5776225+CrackedPixel@users.noreply.github.com>
Date: Sat, 22 Feb 2025 00:44:49 -0600
Subject: [PATCH] implemented avif support for testing

---
 src/Makefile    | 33 +++++++++++++++++
 src/config.h    |  1 +
 src/rtextures.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 133 insertions(+)

diff --git a/src/Makefile b/src/Makefile
index c29eedc6b..221a1a708 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -121,6 +121,15 @@ SDL_INCLUDE_PATH      ?= $(RAYLIB_SRC_PATH)/external/SDL2/include
 SDL_LIBRARY_PATH      ?= $(RAYLIB_SRC_PATH)/external/SDL2/lib
 SDL_LIBRARIES         ?= -lSDL2 -lSDL2main
 
+# AVIF support: It requires libavif to be provided externally
+# WARNING: Library is not included in raylib, it MUST be configured by users
+LIBAVIF_ENABLE          ?= TRUE
+LIBAVIF_ROOT_PATH       ?= $(RAYLIB_SRC_PATH)/external/libavif
+LIBAVIF_INCLUDE_PATH    ?= $(LIBAVIF_ROOT_PATH)/include
+LIBAVIF_SRC_PATH        ?= $(LIBAVIF_ROOT_PATH)/src
+LIBAVIF_LIBRARY_PATH    ?= $(LIBAVIF_ROOT_PATH)/lib
+LIBAVIF_LIBRARIES       ?= -lavif
+
 # Determine if the file has root access (only required to install raylib)
 # "whoami" prints the name of the user that calls him (so, if it is the root user, "whoami" prints "root")
 ROOT = $(shell whoami)
@@ -508,6 +517,11 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID)
     endif
 endif
 
+# AVIF
+ifeq ($(LIBAVIF_ENABLE),TRUE)
+    INCLUDE_PATHS += -I$(LIBAVIF_INCLUDE_PATH)
+endif
+
 # Define library paths containing required libs: LDFLAGS
 # NOTE: This is only required for dynamic library generation
 #------------------------------------------------------------------------------------------------
@@ -555,6 +569,11 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID)
     LDFLAGS += -Wl,-undefined,dynamic_lookup
 endif
 
+# AVIF
+ifeq ($(LIBAVIF_ENABLE),TRUE)
+    LDFLAGS += -L$(LIBAVIF_LIBRARY_PATH)
+endif
+
 # Define libraries required on linking: LDLIBS
 # NOTE: This is only required for dynamic library generation
 #------------------------------------------------------------------------------------------------
@@ -632,6 +651,11 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID)
     LDLIBS = -llog -landroid -lEGL -lGLESv2 -lOpenSLES -lc -lm
 endif
 
+# AVIF
+ifeq ($(LIBAVIF_ENABLE),TRUE)
+    LDLIBS += $(LIBAVIF_LIBRARIES)
+endif
+
 # Define source code object files required
 #------------------------------------------------------------------------------------------------
 OBJS = rcore.o \
@@ -785,6 +809,15 @@ endif
 android_native_app_glue.o : $(NATIVE_APP_GLUE)/android_native_app_glue.c
 	$(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS)
 
+# AVIF
+libavif.o : $(LIBAVIF_SRC_PATH)/avif.c
+ifeq ($(LIBAVIF_ENABLE),TRUE)
+    cmake -S $(LIBAVIF_ROOT_PATH) -B $(LIBAVIF_LIBRARY_PATH)
+    make -C $(LIBAVIF_LIBRARY_PATH)
+else
+    @echo "AVIF support is disabled, skipping AVIF library build."
+endif
+
 # Install generated and needed files to desired directories.
 # On GNU/Linux and BSDs, there are some standard directories that contain extra
 # libraries and header files. These directories (often /usr/local/lib and
diff --git a/src/config.h b/src/config.h
index f7b015305..924f4c17e 100644
--- a/src/config.h
+++ b/src/config.h
@@ -204,6 +204,7 @@
 //#define SUPPORT_FILEFORMAT_ASTC     1
 //#define SUPPORT_FILEFORMAT_PKM      1
 //#define SUPPORT_FILEFORMAT_PVR      1
+//#define SUPPORT_FILEFORMAT_AVIF     1
 
 // Support image export functionality (.png, .bmp, .tga, .jpg, .qoi)
 #define SUPPORT_IMAGE_EXPORT            1
diff --git a/src/rtextures.c b/src/rtextures.c
index ee116b0e5..f6fea31b7 100644
--- a/src/rtextures.c
+++ b/src/rtextures.c
@@ -21,6 +21,7 @@
 *       #define SUPPORT_FILEFORMAT_KTX
 *       #define SUPPORT_FILEFORMAT_PVR
 *       #define SUPPORT_FILEFORMAT_ASTC
+*       #define SUPPORT_FILEFORMAT_AVIF
 *           Select desired fileformats to be supported for image data loading. Some of those formats are
 *           supported by default, to remove support, just comment unrequired #define in this module
 *
@@ -210,6 +211,11 @@
     #include "external/stb_perlin.h"        // Required for: stb_perlin_fbm_noise3
 #endif
 
+// AVIF
+#if defined(SUPPORT_FILEFORMAT_AVIF)
+    #include "external/libavif/include/avif/avif.h"
+#endif
+
 #define STBIR_MALLOC(size,c) ((void)(c), RL_MALLOC(size))
 #define STBIR_FREE(ptr,c) ((void)(c), RL_FREE(ptr))
 
@@ -546,6 +552,99 @@ Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, i
     {
         image.data = rl_load_astc_from_memory(fileData, dataSize, &image.width, &image.height, &image.format, &image.mipmaps);
     }
+#endif
+#if defined(SUPPORT_FILEFORMAT_AVIF)
+    else if ((strcmp(fileType, ".avif") == 0) || (strcmp(fileType, ".AVIF") == 0))
+    {
+        avifRGBImage rgb;
+        avifDecoder* decoder = avifDecoderCreate();
+        if (!decoder)
+        {
+            TRACELOG(LOG_WARNING, "IMAGE: Failed to create AVIF decoder");
+            return image;
+        }
+
+        avifResult result = avifDecoderSetIOMemory(decoder, fileData, dataSize);
+        if (result != AVIF_RESULT_OK)
+        {
+            TRACELOG(LOG_WARNING, "IMAGE: Cannot set IO on avifDecoder");
+            avifRGBImageFreePixels(&rgb);
+            avifDecoderDestroy(decoder);
+            return image;
+        }
+
+        result = avifDecoderParse(decoder);
+        if (result != AVIF_RESULT_OK)
+        {
+            TRACELOG(LOG_WARNING, "IMAGE: Failed to parse AVIF data");
+            avifRGBImageFreePixels(&rgb);
+            avifDecoderDestroy(decoder);
+            return image;
+        }
+
+        if (avifDecoderNextImage(decoder) == AVIF_RESULT_OK)
+        {
+            avifRGBImageSetDefaults(&rgb, decoder->image);
+
+            result = avifRGBImageAllocatePixels(&rgb);
+            if (result != AVIF_RESULT_OK)
+            {
+                TRACELOG(LOG_WARNING, "IMAGE: Allocation of RGB samples failed");
+                avifRGBImageFreePixels(&rgb);
+                avifDecoderDestroy(decoder);
+                return image;
+            }
+
+            result = avifImageYUVToRGB(decoder->image, &rgb);
+            if (result != AVIF_RESULT_OK)
+            {
+                TRACELOG(LOG_WARNING, "IMAGE: Conversion from YUV failed");
+                avifRGBImageFreePixels(&rgb);
+                avifDecoderDestroy(decoder);
+                return image;
+            }
+
+            image.mipmaps = 1;
+            image.width = rgb.width;
+            image.height = rgb.height;
+
+            size_t pixelCount = rgb.width * rgb.height * 4;
+            if (rgb.depth > 8)
+            {
+                image.data = RL_MALLOC(pixelCount * sizeof(uint16_t));
+                if (!image.data)
+                {
+                    TRACELOG(LOG_WARNING, "IMAGE: Failed to allocate memory for 16-bit image data");
+                    avifRGBImageFreePixels(&rgb);
+                    avifDecoderDestroy(decoder);
+                    return image;
+                }
+
+                uint16_t *firstPixel = (uint16_t *)rgb.pixels;
+                memcpy(image.data, firstPixel, pixelCount * sizeof(uint16_t));
+                image.format = PIXELFORMAT_UNCOMPRESSED_R16G16B16A16;
+            } 
+            else
+            {
+                image.data = RL_MALLOC(pixelCount * sizeof(uint8_t));
+                if (!image.data)
+                {
+                    TRACELOG(LOG_WARNING, "IMAGE: Failed to allocate memory for 8-bit image data");
+                    avifRGBImageFreePixels(&rgb);
+                    avifDecoderDestroy(decoder);
+                    return image;
+                }
+
+                uint8_t *firstPixel = rgb.pixels;
+                memcpy(image.data, firstPixel, pixelCount * sizeof(uint8_t));
+                image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
+            }
+
+            avifRGBImageFreePixels(&rgb);
+            avifDecoderDestroy(decoder);
+        } 
+        else TRACELOG(LOG_WARNING, "IMAGE: Failed to decode AVIF data");
+    }
 #endif
     else TRACELOG(LOG_WARNING, "IMAGE: Data format not supported");