summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt8
-rw-r--r--CONTRIBUTORS1
-rw-r--r--README.md3
-rw-r--r--configure.ac8
-rw-r--r--docs/README.CMAKE1
-rw-r--r--docs/naturaldocs/project/Menu.txt1
-rw-r--r--examples/avif2jpeg.c55
-rw-r--r--examples/jpeg2avif.c57
-rw-r--r--examples/jpeg2avifex.c98
-rw-r--r--examples/png2avif.c58
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/Makefile.am1
-rw-r--r--src/config.h.cmake9
-rw-r--r--src/gd.h9
-rw-r--r--src/gd_avif.c680
-rw-r--r--src/gd_filename.c4
-rw-r--r--src/gd_webp.c4
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/avif/.gitignore5
-rw-r--r--tests/avif/CMakeLists.txt17
-rw-r--r--tests/avif/Makemodule.am16
-rw-r--r--tests/avif/avif_im2im.c67
-rw-r--r--tests/avif/avif_null.c23
-rw-r--r--tests/avif/avif_ptr_double_free.c34
-rw-r--r--tests/avif/baboon.avifbin0 -> 16521 bytes
-rw-r--r--tests/avif/baboon.pngbin0 -> 49030 bytes
-rw-r--r--tests/avif/bad_input.c72
-rw-r--r--tests/avif/compare_avif_to_png.c100
-rw-r--r--tests/avif/dice_with_alpha.avifbin0 -> 6248 bytes
-rw-r--r--tests/avif/dice_with_alpha.pngbin0 -> 48210 bytes
-rw-r--r--tests/avif/plum_blossom_12bit.avifbin0 -> 5202 bytes
-rw-r--r--tests/avif/plum_blossom_12bit.pngbin0 -> 10185 bytes
-rw-r--r--tests/avif/sunset.avifbin0 -> 1174 bytes
-rw-r--r--tests/avif/sunset.pngbin0 -> 47803 bytes
-rwxr-xr-xtravis/main.sh5
-rw-r--r--windows/Makefile.vc11
-rw-r--r--windows/Makefiletest.vc5
-rw-r--r--windows/msys/Makefile3
39 files changed, 1351 insertions, 9 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b13767d..57cd95d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,6 +20,7 @@ OPTION(ENABLE_FREETYPE "Enable Freetype2 support" 0)
OPTION(ENABLE_FONTCONFIG "Enable FontConfig support" 0)
OPTION(ENABLE_WEBP "Enable WebP support" 0)
OPTION(ENABLE_HEIF "Enable HEIF support" 0)
+OPTION(ENABLE_AVIF "Enable AVIF support" 0)
OPTION(ENABLE_RAQM "Enable RAQM support" 0)
if (BUILD_TEST)
@@ -124,6 +125,13 @@ else (USE_EXT_GD)
FIND_PACKAGE(HEIF REQUIRED)
ENDIF (ENABLE_HEIF)
+ IF (ENABLE_AVIF)
+ FIND_PACKAGE(libavif 0.8.2 REQUIRED CONFIG)
+ SET(HAVE_LIBAVIF 1)
+ SET(AVIF_LIBRARIES avif)
+ SET(AVIF_FOUND 1)
+ ENDIF (ENABLE_AVIF)
+
IF (ENABLE_LIQ)
FIND_PACKAGE(LIQ REQUIRED)
ENDIF (ENABLE_LIQ)
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 55f386f..cdb066a 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -1,3 +1,4 @@
+Ben Morss (morsssss)
chapg
Chen Pingping (Wilson)
Chris Reuter
diff --git a/README.md b/README.md
index 96268ea..aa8fa3f 100644
--- a/README.md
+++ b/README.md
@@ -39,8 +39,9 @@ GD has builtin support for:
It also has optional support for more formats via external libraries:
+* [AVIF](https://en.wikipedia.org/wiki/AV1#AV1_Image_File_Format_(AVIF)) via [libavif](https://github.com/AOMediaCodec/libavif)
* [HEIF](https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format) via [libheif](https://github.com/strukturag/libheif/)
- * Also it includes [AVIF](https://en.wikipedia.org/wiki/AV1#AV1_Image_File_Format_%28AVIF%29) read support if your system's `libheif` has AV1 decoding.
+ * This includes [AVIF](https://en.wikipedia.org/wiki/AV1#AV1_Image_File_Format_%28AVIF%29) read support if your system's `libheif` has AV1 decoding.
* [JPEG](https://en.wikipedia.org/wiki/JPEG) via [IJG/libjpeg](http://www.ijg.org/) or [libjpeg-turbo](http://libjpeg-turbo.virtualgl.org/)
* Does not include [JPEG 2000](https://en.wikipedia.org/wiki/JPEG_2000)
* [PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics) via [libpng](http://www.libpng.org/)
diff --git a/configure.ac b/configure.ac
index 5a4c357..535db68 100644
--- a/configure.ac
+++ b/configure.ac
@@ -289,6 +289,13 @@ dnl Check for heif support.
GD_LIB_PKG_CHECK([LIBHEIF], [HEIF], [heif], [libheif >= 1.7.0], [
AC_CHECK_LIB([heif], [heif_get_version], [dnl
AS_VAR_APPEND([LIBHEIF_LIBS], [" -lheif"])
+ ])
+])
+
+dnl Check for avif support.
+GD_LIB_PKG_CHECK([LIBAVIF], [AVIF], [avif], [libavif >= 0.8.2], [
+ AC_CHECK_LIB([avif], [avifVersion], [dnl
+ AS_VAR_APPEND([LIBAVIF_LIBS], [" -lavif"])
gd_found_lib=yes
])
])
@@ -323,6 +330,7 @@ AC_MSG_RESULT([
Support for JPEG library: $gd_with_LIBJPEG
Support for WebP library: $gd_with_LIBWEBP
Support for HEIF library: $gd_with_LIBHEIF
+ Support for AVIF library: $gd_with_LIBAVIF
Support for TIFF library: $gd_with_LIBTIFF
Support for Freetype 2.x library: $gd_with_LIBFREETYPE
Support for Fontconfig library: $gd_with_LIBFONTCONFIG
diff --git a/docs/README.CMAKE b/docs/README.CMAKE
index 2be2045..c9356b2 100644
--- a/docs/README.CMAKE
+++ b/docs/README.CMAKE
@@ -20,6 +20,7 @@ ENABLE_FREETYPE=1
ENABLE_FONTCONFIG=1
ENABLE_XPM=1
ENABLE_WEBP=1
+ENABLE_AVIF=1
ENABLE_RAQM=1
You can optionally run our tests suite using:
diff --git a/docs/naturaldocs/project/Menu.txt b/docs/naturaldocs/project/Menu.txt
index 250d988..12df023 100644
--- a/docs/naturaldocs/project/Menu.txt
+++ b/docs/naturaldocs/project/Menu.txt
@@ -50,6 +50,7 @@ File: About LibGD 2.3.0-dev (no auto-title, preamble.txt)
Group: Image Formats {
+ File: AVIF IO (no auto-title, gd_avif.c)
File: BMP IO (no auto-title, gd_bmp.c)
File: GD IO (no auto-title, gd_gd.c)
File: GD2 IO (no auto-title, gd_gd2.c)
diff --git a/examples/avif2jpeg.c b/examples/avif2jpeg.c
new file mode 100644
index 0000000..6ced15d
--- /dev/null
+++ b/examples/avif2jpeg.c
@@ -0,0 +1,55 @@
+/**
+ * A short program which converts a .avif file into a .jpg file -
+ * just to get a little practice with the basic functionality.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "gd.h"
+
+int main(int argc, char **argv)
+{
+ gdImagePtr im;
+ FILE *in, *out;
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: avif2jpeg infile.avif outfile.jpg\n");
+ exit(1);
+ }
+
+ printf("Reading infile %s\n", argv[1]);
+
+ in = fopen(argv[1], "rb");
+ if (!in) {
+ fprintf(stderr, "\nError: input file %s does not exist.\n", argv[1]);
+ exit(1);
+ }
+
+ im = gdImageCreateFromAvif(in);
+ fclose(in);
+ if (!im) {
+ fprintf(stderr, "\nError: input file %s is not in AVIF format.\n", argv[1]);
+ exit(1);
+ }
+
+ out = fopen(argv[2], "wb");
+ if (!out) {
+ fprintf(stderr, "\nError: can't write to output file %s\n", argv[2]);
+ gdImageDestroy(im);
+ exit(1);
+ }
+
+ gdImageJpeg(im, out, 75);
+
+ printf("Wrote outfile %s.\n", argv[2]);
+
+ fclose(out);
+ gdImageDestroy(im);
+
+ return 0;
+}
diff --git a/examples/jpeg2avif.c b/examples/jpeg2avif.c
new file mode 100644
index 0000000..af55201
--- /dev/null
+++ b/examples/jpeg2avif.c
@@ -0,0 +1,57 @@
+/**
+ * A short program which converts a .jpg file into a .avif file -
+ * just to get a little practice with the basic functionality.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "gd.h"
+
+int main(int argc, char **argv)
+{
+ gdImagePtr im;
+ FILE *in, *out;
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: jpeg2avif filename.jpg filename.avif\n");
+ exit(1);
+ }
+
+ printf("Reading infile %s\n", argv[1]);
+
+ in = fopen(argv[1], "rb");
+ if (!in) {
+ fprintf(stderr, "Error: input file %s does not exist.\n", argv[1]);
+ exit(1);
+ }
+
+ im = gdImageCreateFromJpeg(in);
+ fclose(in);
+ if (!im) {
+ fprintf(stderr, "Error: input file %s is not in JPEG format.\n", argv[1]);
+ exit(1);
+ }
+
+ out = fopen(argv[2], "wb");
+ if (!out) {
+ fprintf(stderr, "Error: can't write to output file %s\n", argv[2]);
+ gdImageDestroy(im);
+ exit(1);
+ }
+
+ fprintf(stderr, "Encoding...\n");
+
+ gdImageAvif(im, out);
+
+ printf("Wrote outfile %s.\n", argv[2]);
+
+ fclose(out);
+ gdImageDestroy(im);
+
+ return 0;
+}
diff --git a/examples/jpeg2avifex.c b/examples/jpeg2avifex.c
new file mode 100644
index 0000000..a8afb3b
--- /dev/null
+++ b/examples/jpeg2avifex.c
@@ -0,0 +1,98 @@
+/**
+ * A short program which converts a .jpg file into a .avif file -
+ * just to get a little practice with the basic functionality.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "gd.h"
+
+static void usage() {
+ fprintf(stderr, "Usage: jpeg2avifex [-q quality] [-s speed] infile.jpg outfile.avif\n");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ gdImagePtr im;
+ FILE *in, *out;
+ int c;
+ int speed = -1, quality = -1; // use default values if unspecified
+ char *infile, *outfile;
+ int failed = 0;
+
+ if (argc < 3) {
+ usage();
+ }
+
+ while ((c = getopt(argc, argv, "q:s:")) != -1) {
+ switch (c) {
+ case 'q':
+ quality = atoi(optarg);
+ break;
+
+ case 's':
+ speed = atoi(optarg);
+ break;
+
+ default:
+ usage();
+ }
+ }
+
+ if (optind > argc - 2)
+ usage();
+
+ infile = strdup(argv[optind++]);
+ outfile = strdup(argv[optind]);
+
+ printf("Reading infile %s\n", infile);
+
+ in = fopen(infile, "rb");
+ if (!in) {
+ fprintf(stderr, "\nError: input file %s does not exist.\n", infile);
+ failed = 1;
+ goto cleanup;
+ }
+
+ im = gdImageCreateFromJpeg(in);
+ fclose(in);
+ if (!im) {
+ fprintf(stderr, "\nError: input file %s is not in JPEG format.\n", infile);
+ failed = 1;
+ goto cleanup;
+ }
+
+ out = fopen(outfile, "wb");
+ if (!out) {
+ fprintf(stderr, "\nError: can't write to output file %s\n", outfile);
+ failed = 1;
+ goto cleanup;
+ }
+
+ fprintf(stderr, "Encoding...\n");
+
+ gdImageAvifEx(im, out, quality, speed);
+
+ printf("Wrote outfile %s.\n", outfile);
+
+ fclose(out);
+
+cleanup:
+ if (im)
+ gdImageDestroy(im);
+
+ gdFree(infile);
+ gdFree(outfile);
+
+ exit(failed);
+}
diff --git a/examples/png2avif.c b/examples/png2avif.c
new file mode 100644
index 0000000..18463e7
--- /dev/null
+++ b/examples/png2avif.c
@@ -0,0 +1,58 @@
+/**
+ * A short program which converts a .png file into a .avif file -
+ * just to get a little practice with the basic functionality.
+ * We convert losslessly.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "gd.h"
+
+int main(int argc, char **argv)
+{
+ gdImagePtr im;
+ FILE *in, *out;
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: png2avif infile.png outfile.avif\n");
+ exit(1);
+ }
+
+ printf("Reading infile %s\n", argv[1]);
+
+ in = fopen(argv[1], "rb");
+ if (!in) {
+ fprintf(stderr, "Error: input file %s does not exist.\n", argv[1]);
+ exit(1);
+ }
+
+ im = gdImageCreateFromPng(in);
+ fclose(in);
+ if (!im) {
+ fprintf(stderr, "Error: input file %s is not in PNG format.\n", argv[1]);
+ exit(1);
+ }
+
+ out = fopen(argv[2], "wb");
+ if (!out) {
+ fprintf(stderr, "Error: can't write to output file %s\n", argv[2]);
+ gdImageDestroy(im);
+ exit(1);
+ }
+
+ fprintf(stderr, "Encoding...\n");
+
+ gdImageAvifEx(im, out, 100, 0);
+
+ printf("Wrote outfile %s.\n", argv[2]);
+
+ fclose(out);
+ gdImageDestroy(im);
+
+ return 0;
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ccd76e9..509c422 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -2,6 +2,7 @@ SET (LIBGD_SRC_FILES
bmp.h
gd.c
gd.h
+ gd_avif.c
gd_bmp.c
gd_color.c
gd_color.h
@@ -130,6 +131,7 @@ SET(LIBGD_DEP_LIBS
${XPM_LIBRARIES}
${FONTCONFIG_LIBRARY}
${WEBP_LIBRARIES}
+ ${AVIF_LIBRARIES}
${RAQM_LIBRARIES}
${HEIF_LIBRARIES}
)
diff --git a/src/Makefile.am b/src/Makefile.am
index 69a5c18..09bf192 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -60,6 +60,7 @@ libgd_la_SOURCES = \
bmp.h \
gd.c \
gd.h \
+ gd_avif.c \
gd_bmp.c \
gd_color.c \
gd_color.h \
diff --git a/src/config.h.cmake b/src/config.h.cmake
index fb86f3c..2b46a17 100644
--- a/src/config.h.cmake
+++ b/src/config.h.cmake
@@ -27,12 +27,18 @@
/* Define to 1 if you have the <inttypes.h> header file. */
#cmakedefine HAVE_INTTYPES_H
+/* Define if you have avif */
+#cmakedefine HAVE_LIBAVIF
+
/* Define if you have fontconfig */
#cmakedefine HAVE_LIBFONTCONFIG
/* Define if you have freetype */
#cmakedefine HAVE_LIBFREETYPE
+/* Define if you have heif */
+#cmakedefine HAVE_LIBHEIF
+
/* Define if you have liq */
#cmakedefine HAVE_LIBIMAGEQUANT
@@ -54,9 +60,6 @@
/* Define if you have webp */
#cmakedefine HAVE_LIBWEBP
-/* Define if you have heif */
-#cmakedefine HAVE_LIBHEIF
-
/* Define if you have xpm */
#cmakedefine HAVE_LIBXPM
diff --git a/src/gd.h b/src/gd.h
index 55fa5b3..7b5870f 100644
--- a/src/gd.h
+++ b/src/gd.h
@@ -668,6 +668,10 @@ BGD_DECLARE(gdImagePtr) gdImageCreateFromHeif(FILE *inFile);
BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifPtr(int size, void *data);
BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifCtx(gdIOCtx *infile);
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvif(FILE *inFile);
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifPtr(int size, void *data);
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifCtx(gdIOCtx *infile);
+
BGD_DECLARE(gdImagePtr) gdImageCreateFromTiff(FILE *inFile);
BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffCtx(gdIOCtx *infile);
BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffPtr(int size, void *data);
@@ -1131,6 +1135,11 @@ BGD_DECLARE(void *) gdImageHeifPtr(gdImagePtr im, int *size);
BGD_DECLARE(void *) gdImageHeifPtrEx(gdImagePtr im, int *size, int quality, gdHeifCodec codec, gdHeifChroma chroma);
BGD_DECLARE(void) gdImageHeifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, gdHeifCodec codec, gdHeifChroma chroma);
+BGD_DECLARE(void) gdImageAvif(gdImagePtr im, FILE *outFile);
+BGD_DECLARE(void) gdImageAvifEx(gdImagePtr im, FILE *outFile, int quality, int speed);
+BGD_DECLARE(void *) gdImageAvifPtr(gdImagePtr im, int *size);
+BGD_DECLARE(void *) gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed);
+BGD_DECLARE(void) gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed);
/**
* Group: GifAnim
diff --git a/src/gd_avif.c b/src/gd_avif.c
new file mode 100644
index 0000000..d8f8284
--- /dev/null
+++ b/src/gd_avif.c
@@ -0,0 +1,680 @@
+/**
+ * File: AVIF IO
+ *
+ * Read and write AVIF images using libavif (https://github.com/AOMediaCodec/libavif) .
+ * Currently, the only ICC profile we support is sRGB.
+ * Since that's what web browsers use, it's sufficient for now.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <math.h>
+
+#include "gd.h"
+#include "gd_errors.h"
+#include "gdhelpers.h"
+#include "gd_intern.h"
+
+#ifdef HAVE_LIBAVIF
+#include <avif/avif.h>
+
+/*
+ Define defaults for encoding images:
+ CHROMA_SUBSAMPLING_DEFAULT: 4:2:0 is commonly used for Chroma subsampling.
+ CHROMA_SUBAMPLING_HIGH_QUALITY: Use 4:4:4, or no subsampling, when a sufficient high quality is requested.
+ SUBAMPLING_HIGH_QUALITY_THRESHOLD: At or above this value, use CHROMA_SUBAMPLING_HIGH_QUALITY
+ QUANTIZER_DEFAULT:
+ We need more testing to really know what quantizer settings are optimal,
+ but teams at Google have been using maximum=30 as a starting point.
+ QUALITY_DEFAULT: following gd conventions, -1 indicates the default.
+ SPEED_DEFAULT: AVIF_SPEED_DEFAULT is -1. This simply tells the AVIF encoder to use the default speed.
+*/
+
+#define CHROMA_SUBSAMPLING_DEFAULT AVIF_PIXEL_FORMAT_YUV420
+#define CHROMA_SUBAMPLING_HIGH_QUALITY AVIF_PIXEL_FORMAT_YUV444
+#define HIGH_QUALITY_SUBSAMPLING_THRESHOLD 90
+#define QUANTIZER_DEFAULT 30
+#define QUALITY_DEFAULT -1
+#define SPEED_DEFAULT AVIF_SPEED_DEFAULT
+
+// This initial size for the gdIOCtx is standard among GD image conversion functions.
+#define NEW_DYNAMIC_CTX_SIZE 2048
+
+// Our quality param ranges from 0 to 100.
+// To calculate quality, we convert from AVIF's quantizer scale, which runs from 63 to 0.
+#define MAX_QUALITY 100
+
+// These constants are for computing the number of tiles and threads to use during encoding.
+// Maximum threads are from libavif/contrib/gkd-pixbuf/loader.c.
+#define MIN_TILE_AREA (512 * 512)
+#define MAX_TILES 8
+#define MAX_THREADS 64
+
+/*** Macros ***/
+
+/*
+ From gd_png.c:
+ convert the 7-bit alpha channel to an 8-bit alpha channel.
+ We do a little bit-flipping magic, repeating the MSB
+ as the LSB, to ensure that 0 maps to 0 and
+ 127 maps to 255. We also have to invert to match
+ PNG's convention in which 255 is opaque.
+*/
+#define alpha7BitTo8Bit(alpha7Bit) \
+ (alpha7Bit == 127 ? \
+ 0 : \
+ 255 - ((alpha7Bit << 1) + (alpha7Bit >> 6)))
+
+#define alpha8BitTo7Bit(alpha8Bit) (gdAlphaMax - (alpha8Bit >> 1))
+
+
+/*** Helper functions ***/
+
+/* Convert the quality param we expose to the quantity params used by libavif.
+ The *Quantizer* params values can range from 0 to 63, with 0 = highest quality and 63 = worst.
+ We make the scale 0-100, and we reverse this, so that 0 = worst quality and 100 = highest.
+
+ Values below 0 are set to 0, and values below MAX_QUALITY are set to MAX_QUALITY.
+*/
+static int quality2Quantizer(int quality) {
+ int clampedQuality = CLAMP(quality, 0, MAX_QUALITY);
+
+ float scaleFactor = (float) AVIF_QUANTIZER_WORST_QUALITY / (float) MAX_QUALITY;
+
+ return round(scaleFactor * (MAX_QUALITY - clampedQuality));
+}
+
+/*
+ As of February 2021, this algorithm reflects the latest research on how many tiles
+ and threads to include for a given image size.
+ This is subject to change as research continues.
+
+ Returns false if there was an error, true if all was well.
+ */
+static avifBool setEncoderTilesAndThreads(avifEncoder *encoder, avifRGBImage *rgb) {
+ int imageArea, tiles, tilesLog2, encoderTiles;
+
+ // _gdImageAvifCtx(), the calling function, checks this operation for overflow
+ imageArea = rgb->width * rgb->height;
+
+ tiles = (int) ceil((double) imageArea / MIN_TILE_AREA);
+ tiles = MIN(tiles, MAX_TILES);
+ tiles = MIN(tiles, MAX_THREADS);
+
+ // The number of tiles in any dimension will always be a power of 2. We can only specify log(2)tiles.
+
+ tilesLog2 = floor(log2(tiles));
+
+ // If the image's width is greater than the height, use more tile columns
+ // than tile rows to make the tile size close to a square.
+
+ if (rgb->width >= rgb->height) {
+ encoder->tileRowsLog2 = tilesLog2 / 2;
+ encoder->tileColsLog2 = tilesLog2 - encoder->tileRowsLog2;
+ } else {
+ encoder->tileColsLog2 = tilesLog2 / 2;
+ encoder->tileRowsLog2 = tilesLog2 - encoder->tileColsLog2;
+ }
+
+ // It's good to have one thread per tile.
+ encoderTiles = (1 << encoder->tileRowsLog2) * (1 << encoder->tileColsLog2);
+ encoder->maxThreads = encoderTiles;
+
+ return AVIF_TRUE;
+}
+
+/*
+ We can handle AVIF images whose color profile is sRGB, or whose color profile isn't set.
+*/
+static avifBool isAvifSrgbImage(avifImage *avifIm) {
+ return
+ (avifIm->colorPrimaries == AVIF_COLOR_PRIMARIES_BT709 ||
+ avifIm->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED) &&
+ (avifIm->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_SRGB ||
+ avifIm->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED)
+ ;
+}
+
+/*
+ Check the result from an Avif function to see if it's an error.
+ If so, decode the error and output it, and return true.
+ Otherwise, return false.
+*/
+static avifBool isAvifError(avifResult result, const char *msg) {
+ if (result != AVIF_RESULT_OK) {
+ gd_error("avif error - %s: %s", msg, avifResultToString(result));
+ return AVIF_TRUE;
+ }
+
+ return AVIF_FALSE;
+}
+
+
+/*
+ <readfromCtx> implements the avifIOReadFunc interface by calling the relevant functions
+ in the gdIOCtx. Our logic is inspired by avifIOMemoryReaderRead() and avifIOFileReaderRead().
+ We don't know whether we're reading from a file or from memory. We don't have to know,
+ since we rely on the helper functions in the gdIOCtx.
+ We assume we've stashed the gdIOCtx in io->data, as we do in createAvifIOFromCtx().
+
+ We ignore readFlags, just as the avifIO*ReaderRead() functions do.
+
+ If there's a problem, this returns an avifResult error.
+ If things go well, return AVIF_RESULT_OK.
+ Of course these AVIF codes shouldn't be returned by any top-level GD function.
+*/
+static avifResult readFromCtx(avifIO *io, uint32_t readFlags, uint64_t offset, size_t size, avifROData *out)
+{
+ void *dataBuf = NULL;
+ gdIOCtx *ctx = (gdIOCtx *) io->data;
+
+ // TODO: if we set sizeHint, this will be more efficient.
+
+ if (offset > LONG_MAX || size < 0)
+ return AVIF_RESULT_IO_ERROR;
+
+ // Try to seek offset bytes forward. If we pass the end of the buffer, throw an error.
+ if (!ctx->seek(ctx, offset))
+ return AVIF_RESULT_IO_ERROR;
+
+ dataBuf = gdMalloc(size);
+ if (!dataBuf) {
+ gd_error("avif error - couldn't allocate memory");
+ return AVIF_RESULT_UNKNOWN_ERROR;
+ }
+
+ // Read the number of bytes requested.
+ // If getBuf() returns a negative value, that means there was an error.
+ int charsRead = ctx->getBuf(ctx, dataBuf, size);
+ if (charsRead < 0) {
+ gdFree(dataBuf);
+ return AVIF_RESULT_IO_ERROR;
+ }
+
+ out->data = dataBuf;
+ out->size = charsRead;
+ return charsRead == size ? AVIF_RESULT_OK : AVIF_RESULT_TRUNCATED_DATA;
+}
+
+// avif.h says this is optional, but it seemed easy to implement.
+static void destroyAvifIO(struct avifIO *io) {
+ avifFree(io);
+}
+
+/* Set up an avifIO object.
+ The functions in the gdIOCtx struct may point either to a file or a memory buffer.
+ To us, that's immaterial.
+ Our task is simply to assign avifIO functions to the proper functions from gdIOCtx.
+ The destroy function needs to destroy the avifIO object and anything else it uses.
+
+ Returns NULL if memory for the object can't be allocated.
+*/
+
+// TODO: can we get sizeHint somehow?
+static avifIO *createAvifIOFromCtx(gdIOCtx *ctx) {
+ avifIO *io;
+
+ io = gdMalloc(sizeof(*io));
+ if (io == NULL)
+ return NULL;
+
+ // TODO: setting persistent=FALSE is safe, but it's less efficient. Is it necessary?
+ io->persistent = AVIF_FALSE;
+ io->read = readFromCtx;
+ io->write = NULL; // this function is currently unused; see avif.h
+ io->destroy = destroyAvifIO;
+ io->sizeHint = 0; // sadly, we don't get this information from the gdIOCtx.
+ io->data = ctx;
+
+ return io;
+}
+
+
+/*** Decoding functions ***/
+
+/*
+ Function: gdImageCreateFromAvif
+
+ <gdImageCreateFromAvif> is called to load truecolor images from
+ AVIF format files. Invoke <gdImageCreateFromAvif> with an
+ already opened pointer to a file containing the desired
+ image. <gdImageCreateFromAvif> returns a <gdImagePtr> to the new
+ truecolor image, or NULL if unable to load the image (most often
+ because the file is corrupt or does not contain a AVIF
+ image). <gdImageCreateFromAvif> does not close the file.
+
+ This function creates a gdIOCtx struct from the file pointer it's passed.
+ And then it relies on <gdImageCreateFromAvifCtx> to do the real decoding work.
+ If the file contains an image sequence, we simply read the first one, discarding the rest.
+
+ Variants:
+
+ <gdImageCreateFromAvifPtr> creates an image from AVIF data
+ already in memory.
+
+ <gdImageCreateFromAvifCtx> reads data from the function
+ pointers in a <gdIOCtx> structure.
+
+ Parameters:
+
+ infile - pointer to the input file
+
+ Returns:
+
+ A pointer to the new truecolor image. This will need to be
+ destroyed with <gdImageDestroy> once it is no longer needed.
+
+ On error, returns 0.
+*/
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvif(FILE *infile)
+{
+ gdImagePtr im;
+ gdIOCtx *ctx = gdNewFileCtx(infile);
+
+ if (!ctx)
+ return NULL;
+
+ im = gdImageCreateFromAvifCtx(ctx);
+ ctx->gd_free(ctx);
+
+ return im;
+}
+
+/*
+ Function: gdImageCreateFromAvifPtr
+
+ See <gdImageCreateFromAvif>.
+
+ Parameters:
+
+ size - size of Avif data in bytes.
+ data - pointer to Avif data.
+*/
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifPtr(int size, void *data)
+{
+ gdImagePtr im;
+ gdIOCtx *ctx = gdNewDynamicCtxEx(size, data, 0);
+
+ if (!ctx)
+ return 0;
+
+ im = gdImageCreateFromAvifCtx(ctx);
+ ctx->gd_free(ctx);
+
+ return im;
+}
+
+/*
+ Function: gdImageCreateFromAvifCtx
+
+ See <gdImageCreateFromAvif>.
+
+ Additional details: the AVIF library comes with functions to create an IO object from
+ a file and from a memory pointer. Of course, it doesn't have a way to create an IO object
+ from a gdIOCtx. So, here, we use our own helper function, <createAvifIOfromCtx>.
+
+ Otherwise, we create the image by calling AVIF library functions in order:
+ * avifDecoderCreate(), to create the decoder
+ * avifDecoderSetIO(), to tell libavif how to read from our data structure
+ * avifDecoderParse(), to parse the image
+ * avifDecoderNextImage(), to read the first image from the decoder
+ * avifRGBImageSetDefaults(), to create the avifRGBImage
+ * avifRGBImageAllocatePixels(), to allocate memory for the pixels
+ * avifImageYUVToRGB(), to convert YUV to RGB
+
+ Finally, we create a new gd image and copy over the pixel data.
+
+ Parameters:
+
+ ctx - a gdIOCtx struct
+*/
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifCtx (gdIOCtx *ctx)
+{
+ int x, y;
+ gdImage *im = NULL;
+ avifResult result;
+ avifIO *io;
+ avifDecoder *decoder;
+ avifRGBImage rgb;
+
+ // this lets us know that memory hasn't been allocated yet for the pixels
+ rgb.pixels = NULL;
+
+ decoder = avifDecoderCreate();
+
+ io = createAvifIOFromCtx(ctx);
+ if (!io) {
+ gd_error("avif error - Could not allocate memory");
+ goto cleanup;
+ }
+
+ avifDecoderSetIO(decoder, io);
+
+ result = avifDecoderParse(decoder);
+ if (isAvifError(result, "Could not parse image"))
+ goto cleanup;
+
+ // Note again that, for an image sequence, we read only the first image, ignoring the rest.
+ result = avifDecoderNextImage(decoder);
+ if (isAvifError(result, "Could not decode image"))
+ goto cleanup;
+
+ if (!isAvifSrgbImage(decoder->image))
+ gd_error_ex(LOG_WARNING, "Image's color profile is not sRGB");
+
+ // Set up the avifRGBImage, and convert it from YUV to an 8-bit RGB image.
+ // (While AVIF image pixel depth can be 8, 10, or 12 bits, GD truecolor images are 8-bit.)
+ avifRGBImageSetDefaults(&rgb, decoder->image);
+ rgb.depth = 8;
+ avifRGBImageAllocatePixels(&rgb);
+
+ result = avifImageYUVToRGB(decoder->image, &rgb);
+ if (isAvifError(result, "Conversion from YUV to RGB failed"))
+ goto cleanup;
+
+ im = gdImageCreateTrueColor(decoder->image->width, decoder->image->height);
+ if (!im) {
+ gd_error("avif error - Could not create GD truecolor image");
+ goto cleanup;
+ }
+
+ im->saveAlphaFlag = 1;
+
+ // Read the pixels from the AVIF image and copy them into the GD image.
+
+ uint8_t *p = rgb.pixels;
+
+ for (y = 0; y < decoder->image->height; y++) {
+ for (x = 0; x < decoder->image->width; x++) {
+ uint8_t r = *(p++);
+ uint8_t g = *(p++);
+ uint8_t b = *(p++);
+ uint8_t a = alpha8BitTo7Bit(*(p++));
+ im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, a);
+ }
+ }
+
+cleanup:
+ // if io has been allocated, this frees it
+ avifDecoderDestroy(decoder);
+
+ if (rgb.pixels)
+ avifRGBImageFreePixels(&rgb);
+
+ return im;
+}
+
+
+/*** Encoding functions ***/
+
+/*
+ Function: gdImageAvifEx
+
+ <gdImageAvifEx> outputs the specified image to the specified file in
+ AVIF format. The file must be open for writing. Under MSDOS and
+ all versions of Windows, it is important to use "wb" as opposed to
+ simply "w" as the mode when opening the file, and under Unix there
+ is no penalty for doing so. <gdImageAvifEx> does not close the file;
+ your code must do so.
+
+ Variants:
+
+ <gdImageAvifEx> writes the image to a file, encoding with the default quality and speed.
+
+ <gdImageAvifPtrEx> stores the image in RAM.
+
+ <gdImageAvifPtr> stores the image in RAM, encoding with the default quality and speed.
+
+ <gdImageAvifCtx> stores the image using a <gdIOCtx> struct.
+
+ Parameters:
+
+ im - The image to save.
+ outFile - The FILE pointer to write to.
+ quality - Compression quality (0-100). 0 is lowest-quality, 100 is highest.
+ speed - The speed of compression (0-10). 0 is slowest, 10 is fastest.
+
+ Notes on parameters:
+ quality - If quality = -1, we use a default quality as defined in QUALITY_DEFAULT.
+ For information on how we convert this quality to libavif's quantity param, see <quality2Quantizer>.
+
+ speed - At slower speeds, encoding may be quite slow. Use judiciously.
+
+ Qualities or speeds that are lower than the minimum value get clamped to the minimum value,
+ abd qualities or speeds that are lower than the maximum value get clamped to the maxmum value.
+
+
+ Returns:
+
+ * for <gdImageAvifEx>, <gdImageAvif>, and <gdImageAvifCtx>, nothing.
+ * for <gdImageAvifPtrEx> and <gdImageAvifPtr>, a pointer to the image in memory.
+*/
+
+/*
+ Function: _gdImageAvifCtx
+
+ We need this underscored function because gdImageAvifCtx() can't return anything.
+ And our functions that operate on a memory buffer need to know whether the encoding has succeeded.
+
+ If we're passed the QUALITY_DEFAULT of -1, set the quantizer params to QUANTIZER_DEFAULT.
+
+ This function returns 0 on success, or 1 on failure.
+ */
+static avifBool _gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed)
+{
+ avifResult result;
+ avifRGBImage rgb;
+ avifRWData avifOutput = AVIF_DATA_EMPTY;
+ avifBool failed = AVIF_FALSE;
+ avifBool lossless = quality == 100;
+ avifEncoder *encoder = NULL;
+
+ uint32_t val;
+ uint8_t *p;
+ uint8_t a;
+ int x, y;
+
+ if (im == NULL)
+ return 1;
+
+ if (!gdImageTrueColor(im)) {
+ gd_error("avif doesn't support palette images");
+ return 1;
+ }
+
+ if (!gdImageSX(im) || !gdImageSY(im)) {
+ gd_error("image dimensions must not be zero");
+ return 1;
+ }
+
+ if (overflow2(gdImageSX(im), gdImageSY(im))) {
+ gd_error("image dimensions are too large");
+ return 1;
+ }
+
+ if (speed != AVIF_SPEED_DEFAULT)
+ speed = CLAMP(speed, AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST);
+
+ avifPixelFormat subsampling = quality >= HIGH_QUALITY_SUBSAMPLING_THRESHOLD ?
+ CHROMA_SUBAMPLING_HIGH_QUALITY : CHROMA_SUBSAMPLING_DEFAULT;
+
+ // Create the AVIF image.
+ // Set the ICC to sRGB, as that's what gd supports right now.
+ // Note that MATRIX_COEFFICIENTS_IDENTITY enables lossless conversion from RGB to YUV.
+
+ avifImage *avifIm = avifImageCreate(gdImageSX(im), gdImageSY(im), 8, subsampling);
+
+ avifIm->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
+ avifIm->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
+ avifIm->matrixCoefficients = lossless ? AVIF_MATRIX_COEFFICIENTS_IDENTITY : AVIF_MATRIX_COEFFICIENTS_BT709;
+
+ avifRGBImageSetDefaults(&rgb, avifIm);
+ // this allocates memory, and sets rgb.rowBytes and rgb.pixels.
+ avifRGBImageAllocatePixels(&rgb);
+
+ // Parse RGB data from the GD image, and copy it into the AVIF RGB image.
+ // Convert 7-bit GD alpha channel values to 8-bit AVIF values.
+
+ p = rgb.pixels;
+ for (y = 0; y < rgb.height; y++) {
+ for (x = 0; x < rgb.width; x++) {
+ val = im->tpixels[y][x];
+
+ *(p++) = gdTrueColorGetRed(val);
+ *(p++) = gdTrueColorGetGreen(val);
+ *(p++) = gdTrueColorGetBlue(val);
+ *(p++) = alpha7BitTo8Bit(gdTrueColorGetAlpha(val));
+ }
+ }
+
+ // Convert the RGB image to YUV.
+
+ result = avifImageRGBToYUV(avifIm, &rgb);
+ failed = isAvifError(result, "Could not convert image to YUV");
+ if (failed)
+ goto cleanup;
+
+ // Encode the image in AVIF format.
+
+ encoder = avifEncoderCreate();
+ int quantizerQuality = quality == QUALITY_DEFAULT ?
+ QUANTIZER_DEFAULT : quality2Quantizer(quality);
+
+ encoder->minQuantizer = quantizerQuality;
+ encoder->maxQuantizer = quantizerQuality;
+ encoder->minQuantizerAlpha = quantizerQuality;
+ encoder->maxQuantizerAlpha = quantizerQuality;
+ encoder->speed = speed;
+
+ failed = !setEncoderTilesAndThreads(encoder, &rgb);
+ if (failed)
+ goto cleanup;
+
+ //TODO: is there a reason to use timeSscales != 1?
+ result = avifEncoderAddImage(encoder, avifIm, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
+ failed = isAvifError(result, "Could not encode image");
+ if (failed)
+ goto cleanup;
+
+ result = avifEncoderFinish(encoder, &avifOutput);
+ failed = isAvifError(result, "Could not finish encoding");
+ if (failed)
+ goto cleanup;
+
+ // Write the AVIF image bytes to the GD ctx.
+
+ gdPutBuf(avifOutput.data, avifOutput.size, outfile);
+
+cleanup:
+ if (rgb.pixels)
+ avifRGBImageFreePixels(&rgb);
+
+ if (encoder)
+ avifEncoderDestroy(encoder);
+
+ if (avifOutput.data)
+ avifRWDataFree(&avifOutput);
+
+ return failed;
+}
+
+BGD_DECLARE(void) gdImageAvifEx(gdImagePtr im, FILE *outFile, int quality, int speed)
+{
+ gdIOCtx *out = gdNewFileCtx(outFile);
+
+ if (out == NULL)
+ return;
+
+ gdImageAvifCtx(im, out, quality, speed);
+ out->gd_free(out);
+}
+
+BGD_DECLARE(void) gdImageAvif(gdImagePtr im, FILE *outFile)
+{
+ gdImageAvifEx(im, outFile, QUALITY_DEFAULT, AVIF_SPEED_DEFAULT);
+}
+
+BGD_DECLARE(void *) gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed)
+{
+ void *rv;
+ gdIOCtx *out = gdNewDynamicCtx(NEW_DYNAMIC_CTX_SIZE, NULL);
+
+ if (out == NULL) {
+ return NULL;
+ }
+
+ if (_gdImageAvifCtx(im, out, quality, speed))
+ rv = NULL;
+ else
+ rv = gdDPExtractData(out, size);
+
+ out->gd_free(out);
+ return rv;
+}
+
+BGD_DECLARE(void *) gdImageAvifPtr(gdImagePtr im, int *size)
+{
+ return gdImageAvifPtrEx(im, size, QUALITY_DEFAULT, AVIF_SPEED_DEFAULT);
+}
+
+
+BGD_DECLARE(void) gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed)
+{
+ _gdImageAvifCtx(im, outfile, quality, speed);
+}
+
+#else /* !HAVE_LIBAVIF */
+
+static void *_noAvifError(void)
+{
+ gd_error("AVIF image support has been disabled\n");
+ return NULL;
+}
+
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvif(FILE *ctx)
+{
+ return _noAvifError();
+}
+
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifPtr(int size, void *data)
+{
+ return _noAvifError();
+}
+
+BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifCtx(gdIOCtx *ctx)
+{
+ return _noAvifError();
+}
+
+BGD_DECLARE(void) gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed)
+{
+ _noAvifError();
+}
+
+BGD_DECLARE(void) gdImageAvifEx(gdImagePtr im, FILE *outFile, int quality, int speed)
+{
+ _noAvifError();
+}
+
+BGD_DECLARE(void) gdImageAvif(gdImagePtr im, FILE *outFile)
+{
+ _noAvifError();
+}
+
+BGD_DECLARE(void *) gdImageAvifPtr(gdImagePtr im, int *size)
+{
+ return _noAvifError();
+}
+
+BGD_DECLARE(void *) gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed)
+{
+ return _noAvifError();
+}
+
+#endif /* HAVE_LIBAVIF */
diff --git a/src/gd_filename.c b/src/gd_filename.c
index 6b67c3a..ddfaa02 100644
--- a/src/gd_filename.c
+++ b/src/gd_filename.c
@@ -51,6 +51,10 @@ static const struct FileType {
{".xbm", gdImageCreateFromXbm, NULL, NULL},
{".tga", gdImageCreateFromTga, NULL, NULL},
+#ifdef HAVE_LIBAVIF
+ {".avif", gdImageCreateFromAvif, gdImageAvif, NULL},
+#endif
+
#ifdef HAVE_LIBPNG
{".png", gdImageCreateFromPng, gdImagePng, NULL},
#endif
diff --git a/src/gd_webp.c b/src/gd_webp.c
index ab15109..a0b4787 100644
--- a/src/gd_webp.c
+++ b/src/gd_webp.c
@@ -42,10 +42,10 @@
Variants:
- <gdImageCreateFromJpegPtr> creates an image from WebP data
+ <gdImageCreateFromWebpPtr> creates an image from WebP data
already in memory.
- <gdImageCreateFromJpegCtx> reads its data via the function
+ <gdImageCreateFromWebpCtx> reads its data via the function
pointers in a <gdIOCtx> structure.
Parameters:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 6775948..b279f63 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -17,6 +17,7 @@ if (BUILD_TEST)
include_directories (BEFORE ${GD_INCLUDE_DIR} "${GDTEST_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/tests/gdtest")
SET(TESTS_DIRS
+ avif
bmp
fontconfig
freetype
diff --git a/tests/Makefile.am b/tests/Makefile.am
index efbe26a..3630810 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -13,6 +13,7 @@ CLEANFILES =
EXTRA_DIST =
TESTS =
+include avif/Makemodule.am
include bmp/Makemodule.am
include fontconfig/Makemodule.am
include freetype/Makemodule.am
@@ -99,6 +100,7 @@ EXTRA_DIST += \
# We don't keep any media files in the top dir ... just generated outputs.
CLEANFILES += \
+ *.avif \
*.bmp \
*.gd \
*.gd2 \
diff --git a/tests/avif/.gitignore b/tests/avif/.gitignore
new file mode 100644
index 0000000..66233b0
--- /dev/null
+++ b/tests/avif/.gitignore
@@ -0,0 +1,5 @@
+/avif_im2im
+/avif_null
+/avif_ptr_double_free
+/bad_input
+/compare_avif_to_png
diff --git a/tests/avif/CMakeLists.txt b/tests/avif/CMakeLists.txt
new file mode 100644
index 0000000..e2df47b
--- /dev/null
+++ b/tests/avif/CMakeLists.txt
@@ -0,0 +1,17 @@
+IF(AVIF_FOUND)
+LIST(APPEND TESTS_FILES
+ avif_ptr_double_free
+ avif_im2im
+ avif_null
+ bad_input
+)
+
+IF(PNG_FOUND)
+LIST(APPEND TESTS_FILES
+ compare_avif_to_png
+)
+ENDIF(PNG_FOUND)
+
+ENDIF(AVIF_FOUND)
+
+ADD_GD_TESTS()
diff --git a/tests/avif/Makemodule.am b/tests/avif/Makemodule.am
new file mode 100644
index 0000000..b446a93
--- /dev/null
+++ b/tests/avif/Makemodule.am
@@ -0,0 +1,16 @@
+if HAVE_LIBAVIF
+libgd_test_programs += \
+ avif/avif_ptr_double_free
+ avif/avif_im2im
+ avif/avif_null
+ avif/bad_input
+
+if HAVE_LIBPNG
+libgd_test_programs += \
+ avif/compare_avif_to_png
+endif
+
+endif
+
+EXTRA_DIST += \
+ avif/CMakeLists.txt
diff --git a/tests/avif/avif_im2im.c b/tests/avif/avif_im2im.c
new file mode 100644
index 0000000..3a07ebd
--- /dev/null
+++ b/tests/avif/avif_im2im.c
@@ -0,0 +1,67 @@
+/**
+ * File: avif_im2im
+ *
+ * Sanity check for AVIF encoding and decoding.
+ * We create a simple gd image, we encode it to AVIF, and we decode it back to gd.
+ * Then we make sure the image we started with and the image we finish with are the same.
+ *
+ */
+
+#include "gd.h"
+#include "gdtest.h"
+#include <stdio.h>
+
+int main()
+{
+ gdImagePtr srcGdIm, destGdIm;
+ void *avifImageDataPtr;
+ FILE *fp;
+ int r, g, b;
+ int size = 0;
+ CuTestImageResult result = {0, 0};
+
+ // Create new gd image and add some shapes to it.
+ srcGdIm = gdImageCreateTrueColor(100, 100);
+ gdTestAssertMsg(srcGdIm != NULL, "could not create source image\n");
+
+ r = gdImageColorAllocate(srcGdIm, 0xFF, 0, 0);
+ g = gdImageColorAllocate(srcGdIm, 0, 0xFF, 0);
+ b = gdImageColorAllocate(srcGdIm, 0, 0, 0xFF);
+ gdImageFilledRectangle(srcGdIm, 0, 0, 99, 99, r);
+ gdImageRectangle(srcGdIm, 20, 20, 79, 79, g);
+ gdImageEllipse(srcGdIm, 70, 25, 30, 20, b);
+
+ // Encode the gd image to a test AVIF file.
+ fp = gdTestTempFp();
+ gdImageAvif(srcGdIm, fp);
+ fclose(fp);
+
+ // Encode the gd image to an AVIF image in memory.
+ avifImageDataPtr = gdImageAvifPtrEx(srcGdIm, &size, 100, 10);
+ gdTestAssertMsg(avifImageDataPtr != NULL, "gdImageAvifPtr() returned null\n");
+ gdTestAssertMsg(size > 0, "gdImageAvifPtr() returned a non-positive size\n");
+
+ // Encode the AVIF image back into a gd image.
+ destGdIm = gdImageCreateFromAvifPtr(size, avifImageDataPtr);
+ gdTestAssertMsg(destGdIm != NULL, "gdImageAvifPtr() returned null\n");
+
+ // Encode that gd image to a test AVIF file.
+ fp = gdTestTempFp();
+ gdImageAvif(destGdIm, fp);
+ fclose(fp);
+
+ // Make sure the image we started with is the same as the image after two conversions.
+ gdTestImageDiff(srcGdIm, destGdIm, NULL, &result);
+ gdTestAssertMsg(result.pixels_changed == 0, "pixels changed: %d\n", result.pixels_changed);
+
+ if (srcGdIm)
+ gdImageDestroy(srcGdIm);
+
+ if (destGdIm)
+ gdImageDestroy(destGdIm);
+
+ if (avifImageDataPtr)
+ gdFree(avifImageDataPtr);
+
+ return gdNumFailures();
+}
diff --git a/tests/avif/avif_null.c b/tests/avif/avif_null.c
new file mode 100644
index 0000000..bbcc9c6
--- /dev/null
+++ b/tests/avif/avif_null.c
@@ -0,0 +1,23 @@
+/**
+ * File: avif_null.c
+ *
+ * Simple test case, confirming that if you try to create an AVIF image from a
+ * null file pointer, the creation will fail, and it will return NULL.
+ */
+
+#include "gd.h"
+#include "gdtest.h"
+
+
+int main()
+{
+ gdImagePtr im;
+
+ im = gdImageCreateFromAvif(NULL);
+ if (!gdTestAssert(im == NULL))
+ gdImageDestroy(im);
+
+ gdImageAvif(im, NULL); /* noop safely */
+
+ return gdNumFailures();
+}
diff --git a/tests/avif/avif_ptr_double_free.c b/tests/avif/avif_ptr_double_free.c
new file mode 100644
index 0000000..8160950
--- /dev/null
+++ b/tests/avif/avif_ptr_double_free.c
@@ -0,0 +1,34 @@
+/**
+ * Test that failure to convert to AVIF returns NULL
+ *
+ * We are creating an image, set its width to zero, and pass this image to
+ * gdImageAvifPtr().
+ * This is supposed to fail, and as such should return NULL.
+ *
+ * See also <https://github.com/libgd/libgd/issues/381>
+ */
+
+#include "gd.h"
+#include "gdtest.h"
+
+int main()
+{
+ gdImagePtr src, dst;
+ int size;
+
+ src = gdImageCreateTrueColor(1, 10);
+ gdTestAssert(src != NULL);
+
+ src->sx = 0; // making the width 0 should cause gdImageAvifPtr() to fail
+
+ dst = gdImageAvifPtr(src, &size);
+ gdTestAssert(dst == NULL);
+
+ if (src)
+ gdImageDestroy(src);
+
+ if (dst)
+ gdImageDestroy(dst);
+
+ return gdNumFailures();
+}
diff --git a/tests/avif/baboon.avif b/tests/avif/baboon.avif
new file mode 100644
index 0000000..f5821db
--- /dev/null
+++ b/tests/avif/baboon.avif
Binary files differ
diff --git a/tests/avif/baboon.png b/tests/avif/baboon.png
new file mode 100644
index 0000000..fdc5dcb
--- /dev/null
+++ b/tests/avif/baboon.png
Binary files differ
diff --git a/tests/avif/bad_input.c b/tests/avif/bad_input.c
new file mode 100644
index 0000000..a9f976b
--- /dev/null
+++ b/tests/avif/bad_input.c
@@ -0,0 +1,72 @@
+/**
+ * File: bad_input.c
+ *
+ * Make sure that the AVIF encoding and decoding functions handle bad input gracefully.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "gd.h"
+#include "gdtest.h"
+
+#define PATH "avif/"
+#define MAX_FILEPATH_LENGTH 50
+
+#define NON_AVIF_FILE_NAME "sunset.png"
+#define AVIF_FILE_NAME "sunset.avif"
+
+int main() {
+ FILE *fp;
+ int retval;
+ char nonAvifFilePath[MAX_FILEPATH_LENGTH], avifFilePath[MAX_FILEPATH_LENGTH];
+ gdImagePtr realIm, badIm;
+ void *rv;
+ int size;
+
+// Create paths for our files.
+ strcpy(avifFilePath, PATH);
+ strcat(avifFilePath, AVIF_FILE_NAME);
+
+ strcpy(nonAvifFilePath, PATH);
+ strcat(nonAvifFilePath, NON_AVIF_FILE_NAME);
+
+// Read in an AVIF image for testing.
+
+ fp = gdTestFileOpen(avifFilePath);
+ realIm = gdImageCreateFromAvif(fp);
+ fclose(fp);
+ if (!gdTestAssertMsg(realIm != NULL, "gdImageCreateFromAvif() failed\n"))
+ return 1;
+
+// Try to decode a non-AVIF file.
+
+ fp = gdTestFileOpen(nonAvifFilePath);
+ badIm = gdImageCreateFromAvif(fp);
+ fclose(fp);
+ gdTestAssertMsg(badIm == NULL, "gdImageCreateFromAvif() failed to return NULL when passed a non-AVIF file\n");
+
+ if (badIm)
+ gdImageDestroy(badIm);
+
+ // Try to encode a valid image with bad quality parameters. This should still work.
+
+ rv = gdImageAvifPtrEx(realIm, &size, 400, 10);
+ gdTestAssertMsg(rv != NULL, "gdImageAvifPtrEx() rejected an overly high quality param instead of clamping it to a valid value");
+ gdFree(rv);
+
+ rv = gdImageAvifPtrEx(realIm, &size, -4, 10);
+ gdTestAssertMsg(rv != NULL, "gdImageAvifPtrEx() rejected a negative quality param instead of clamping it to a valid value");
+ gdFree(rv);
+
+ rv = gdImageAvifPtrEx(realIm, &size, 30, 30);
+ gdTestAssertMsg(rv != NULL, "gdImageAvifPtrEx() rejected an overly high speed param instead of clamping it to a valid value");
+ gdFree(rv);
+
+ rv = gdImageAvifPtrEx(realIm, &size, 30, -4);
+ gdTestAssertMsg(rv != NULL, "gdImageAvifPtrEx() rejected a negative speed param instead of clamping it to a valid value");
+ gdFree(rv);
+
+ gdImageDestroy(realIm);
+
+ return gdNumFailures();
+}
diff --git a/tests/avif/compare_avif_to_png.c b/tests/avif/compare_avif_to_png.c
new file mode 100644
index 0000000..716eae9
--- /dev/null
+++ b/tests/avif/compare_avif_to_png.c
@@ -0,0 +1,100 @@
+/**
+ * File: compare_avif_to_png
+ *
+ * Thorough check for AVIF encoding and decoding.
+ * This test reqiures a set of PNG images that have been losslessly encoded to AVIFs.
+ * For each such image, we encode the PNG into an AVIF, with the GD format as an intermediary,
+ * then compare the resulting AVIF with the original PNG.
+ *
+ * We then do this process in reverse, encoding the AVIF into a PNG,
+ * and compare the resulting PNG with the original AVIF.
+ *
+ * We report any discrepancies in the images, or any other errors that may occur.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "gd.h"
+#include "gdtest.h"
+
+#define PATH "avif/"
+#define MAX_FILEPATH_LENGTH 200
+
+int main() {
+ FILE *fp;
+ gdImagePtr imFromPng = NULL, imFromAvif = NULL;
+ void *avifImDataPtr = NULL, *pngImDataPtr = NULL;
+ int size;
+ char filePath[MAX_FILEPATH_LENGTH], pngFilePath[MAX_FILEPATH_LENGTH], avifFilePath[MAX_FILEPATH_LENGTH];
+ char errMsg[MAX_FILEPATH_LENGTH + 100];
+
+ const int filesCount = 4;
+ const char *filenames[filesCount] = {"baboon", "dice_with_alpha", "plum_blossom_12bit", "sunset"};
+
+ for (int i = 0; i < filesCount; i++) {
+
+ // First, encode each PNG into an AVIF (with the GD format as an intermediary),
+ // then compare the result with the original PNG.
+
+ strcpy(filePath, PATH);
+ strcat(filePath, filenames[i]);
+ strcat(strcpy(pngFilePath, filePath), ".png");
+ strcat(strcpy(avifFilePath, filePath), ".avif");
+
+ fp = gdTestFileOpen(pngFilePath);
+ imFromPng = gdImageCreateFromPng(fp);
+ fclose(fp);
+
+ strcat(strcpy(errMsg, filenames[i]), ".png: gdImageCreateFromPng failed\n");
+ if (!gdTestAssertMsg(imFromPng != NULL, errMsg))
+ goto avif2png;
+
+ strcat(strcpy(errMsg, filenames[i]), ": gdImageAvifPtrEx failed\n");
+ avifImDataPtr = gdImageAvifPtrEx(imFromPng, &size, 100, 0);
+ if (!gdTestAssertMsg(avifImDataPtr != NULL, errMsg))
+ goto avif2png;
+
+ strcat(strcpy(errMsg, filenames[i]), ": gdImageCreateFromAvifPtr failed\n");
+ imFromAvif = gdImageCreateFromAvifPtr(size, avifImDataPtr);
+ if (!gdTestAssertMsg(imFromAvif != NULL, errMsg))
+ goto avif2png;
+
+ strcat(strcpy(errMsg, filenames[i]), ".png: Encoded AVIF image did not match original PNG\n");
+ gdTestAssertMsg(gdAssertImageEquals(imFromPng, imFromAvif), errMsg);
+
+ // Then, decode each AVIF into a GD format, and compare that with the orginal PNG.
+
+avif2png:
+ continue;
+
+/* Skip this reverse test for now, until we can find images that encode to PNGs
+ losslessly.
+
+ fp = gdTestFileOpen(avifFilePath);
+ imFromAvif = gdImageCreateFromAvif(fp);
+ fclose(fp);
+
+ strcat(strcpy(errMsg, filenames[i]), ".avif: gdImageCreateFromAvif failed\n");
+ if (!gdTestAssertMsg(imFromAvif != NULL, errMsg))
+ continue;
+
+ strcat(strcpy(errMsg, filenames[i]), ".avif: Encoded PNG image did not match original AVIF\n");
+ gdTestAssertMsg(gdAssertImageEqualsToFile(pngFilePath, imFromAvif), errMsg);
+*/
+
+}
+
+ if (imFromPng)
+ gdImageDestroy(imFromPng);
+
+ if (imFromAvif)
+ gdImageDestroy(imFromAvif);
+
+ if (avifImDataPtr)
+ gdFree(avifImDataPtr);
+
+ if (pngImDataPtr)
+ gdFree(pngImDataPtr);
+
+ return gdNumFailures();
+}
diff --git a/tests/avif/dice_with_alpha.avif b/tests/avif/dice_with_alpha.avif
new file mode 100644
index 0000000..dce625b
--- /dev/null
+++ b/tests/avif/dice_with_alpha.avif
Binary files differ
diff --git a/tests/avif/dice_with_alpha.png b/tests/avif/dice_with_alpha.png
new file mode 100644
index 0000000..d2809d1
--- /dev/null
+++ b/tests/avif/dice_with_alpha.png
Binary files differ
diff --git a/tests/avif/plum_blossom_12bit.avif b/tests/avif/plum_blossom_12bit.avif
new file mode 100644
index 0000000..7959d15
--- /dev/null
+++ b/tests/avif/plum_blossom_12bit.avif
Binary files differ
diff --git a/tests/avif/plum_blossom_12bit.png b/tests/avif/plum_blossom_12bit.png
new file mode 100644
index 0000000..e6901e2
--- /dev/null
+++ b/tests/avif/plum_blossom_12bit.png
Binary files differ
diff --git a/tests/avif/sunset.avif b/tests/avif/sunset.avif
new file mode 100644
index 0000000..4964558
--- /dev/null
+++ b/tests/avif/sunset.avif
Binary files differ
diff --git a/tests/avif/sunset.png b/tests/avif/sunset.png
new file mode 100644
index 0000000..21f45d5
--- /dev/null
+++ b/tests/avif/sunset.png
Binary files differ
diff --git a/travis/main.sh b/travis/main.sh
index 2d29b5d..9965c40 100755
--- a/travis/main.sh
+++ b/travis/main.sh
@@ -78,6 +78,9 @@ build_autotools() {
m distclean
}
+# TODO: When we switch to Ubuntu 21+ (Hirsute), we can reenable libavif coverage,
+# as Ubuntu 21+ supports libavif 0.8.2+.
+# "-DENABLE_AVIF=1"
cmake_args=(
"-DBUILD_SHARED_LIBS=1"
"-DBUILD_STATIC_LIBS=1"
@@ -94,7 +97,7 @@ cmake_args=(
)
# libxpm-dev is unavaible in brew repo
-# Once it gets avaible, please modify this code block.
+# Once it gets available, please modify this code block.
if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
cmake_args+=("-DENABLE_XPM=1")
fi
diff --git a/windows/Makefile.vc b/windows/Makefile.vc
index d97b22f..3fefe80 100644
--- a/windows/Makefile.vc
+++ b/windows/Makefile.vc
@@ -95,10 +95,11 @@ LIB_OBJS= \
$(LIBGD_OBJ_DIR)\gd_crop.obj \
$(LIBGD_OBJ_DIR)\gd_color_map.obj \
$(LIBGD_OBJ_DIR)\gd_heif.obj \
+ $(LIBGD_OBJ_DIR)\gd_avif.obj \
$(LIBGD_OBJ_DIR)\gd_webp.obj
LIBS=kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib Dnsapi.lib Gdi32.Lib
-LIBS_GD=libjpeg_a.lib freetype_a.lib libpng_a.lib libiconv_a.lib zlib_a.lib libheif_a.lib libwebp_a.lib libxpm_a.lib libtiff.lib
+LIBS_GD=libjpeg_a.lib freetype_a.lib libpng_a.lib libiconv_a.lib zlib_a.lib libheif_a.lib libwebp_a.lib libxpm_a.lib libavif_a.lib libtiff.lib
PROG_EXES= \
$(LIBGD_OBJ_DIR)\gdcmpgif.exe \
@@ -135,11 +136,13 @@ CFLAGS= $(CFLAGS) \
/DHAVE_FT2BUILD_H=1\
/DHAVE_GD_H=1\
/DHAVE_ICONV_H=1\
+ /DHAVE_LIBAVIF=1\
/DHAVE_LIBFREETYPE=1\
/DHAVE_LIBJPEG=1\
/DHAVE_LIBPNG=1\
/DHAVE_LIBWEBP=1\
/DHAVE_LIBHEIF=1\
+ /DHAVE_LIBAVIF=1\
/DHAVE_LIBZ=1\
/DHAVE_LIBXPM=1\
/DHAVE_LIBTIFF=1\
@@ -186,6 +189,9 @@ make_dirs:
@echo #ifndef HAVE_ICONV_H>> $(GD_CONFIG_H)
@echo #define HAVE_ICONV_H>> $(GD_CONFIG_H)
@echo #endif>> $(GD_CONFIG_H)
+ @echo #ifndef HAVE_LIBAVIF>> $(GD_CONFIG_H)
+ @echo #define HAVE_LIBAVIF>> $(GD_CONFIG_H)
+ @echo #endif>> $(GD_CONFIG_H)
@echo #ifndef HAVE_LIBFREETYPE>> $(GD_CONFIG_H)
@echo #define HAVE_LIBFREETYPE>> $(GD_CONFIG_H)
@echo #endif>> $(GD_CONFIG_H)
@@ -201,6 +207,9 @@ make_dirs:
@echo #ifndef HAVE_LIBHEIF>> $(GD_CONFIG_H)
@echo #define HAVE_LIBHEIF>> $(GD_CONFIG_H)
@echo #endif>> $(GD_CONFIG_H)
+ @echo #ifndef HAVE_LIBAVIF>> $(GD_CONFIG_H)
+ @echo #define HAVE_LIBAVIF>> $(GD_CONFIG_H)
+ @echo #endif>> $(GD_CONFIG_H)
@echo #ifndef HAVE_LIBZ>> $(GD_CONFIG_H)
@echo #define HAVE_LIBZ>> $(GD_CONFIG_H)
@echo #endif>> $(GD_CONFIG_H)
diff --git a/windows/Makefiletest.vc b/windows/Makefiletest.vc
index 827a4e7..6e5d598 100644
--- a/windows/Makefiletest.vc
+++ b/windows/Makefiletest.vc
@@ -1,5 +1,10 @@
TESTS=bmp\bmp_im2im \
bmp\bmp_null \
+avif\avif_ptr_double_free \
+avif\avif_im2im \
+avif\avif_null \
+avif\compare_avif_to_png \
+avif\bad_input \
freetype\bug00132 \
gd\gd_im2im \
gd\gd_null \
diff --git a/windows/msys/Makefile b/windows/msys/Makefile
index 8c5d380..d183ac2 100644
--- a/windows/msys/Makefile
+++ b/windows/msys/Makefile
@@ -98,7 +98,8 @@ gd_topal.c gd_wbmp.c gdcache.c gdfontg.c gdfontl.c gdfontmb.c \
gdfonts.c gdfontt.c gdft.c gdhelpers.c gdkanji.c gdtables.c gdxpm.c \
wbmp.c gd_filter.c gd_nnquant.c gd_rotate.c gd_matrix.c \
gd_interpolation.c gd_crop.c gd_webp.c gd_heif.c gd_tiff.c gd_tga.c \
-gd_bmp.c gd_xbm.c gd_color_match.c gd_version.c gd_filename.c
+gd_bmp.c gd_xbm.c gd_color_match.c gd_version.c gd_filename.c \
+gd_avif.c
OBJ=$(SRC:.c=.o)