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");