xref: /PHP-8.4/ext/gd/libgd/gd_avif.c (revision b817a4f7)
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <limits.h>
9 #include <math.h>
10 
11 #include "gd.h"
12 #include "gd_errors.h"
13 #include "gdhelpers.h"
14 #include "gd_intern.h"
15 
16 #ifdef HAVE_LIBAVIF
17 #include <avif/avif.h>
18 
19 /*
20 	Define defaults for encoding images:
21 		CHROMA_SUBSAMPLING_DEFAULT: 4:2:0 is commonly used for Chroma subsampling.
22 		CHROMA_SUBAMPLING_HIGH_QUALITY: Use 4:4:4, or no subsampling, when a sufficient high quality is requested.
23 		SUBAMPLING_HIGH_QUALITY_THRESHOLD: At or above this value, use CHROMA_SUBAMPLING_HIGH_QUALITY
24 		QUANTIZER_DEFAULT:
25 			We need more testing to really know what quantizer settings are optimal,
26 			but teams at Google have been using maximum=30 as a starting point.
27 		QUALITY_DEFAULT: following gd conventions, -1 indicates the default.
28 		SPEED_DEFAULT:
29 			AVIF_SPEED_DEFAULT is simply the default encoding speed of the AV1 codec.
30 			This could be as slow as 0. So we use 6, which is currently considered to be a fine default.
31 */
32 
33 #define CHROMA_SUBSAMPLING_DEFAULT AVIF_PIXEL_FORMAT_YUV420
34 #define CHROMA_SUBAMPLING_HIGH_QUALITY AVIF_PIXEL_FORMAT_YUV444
35 #define HIGH_QUALITY_SUBSAMPLING_THRESHOLD 90
36 #define QUANTIZER_DEFAULT 30
37 #define QUALITY_DEFAULT -1
38 #define SPEED_DEFAULT 6
39 
40 // This initial size for the gdIOCtx is standard among GD image conversion functions.
41 #define NEW_DYNAMIC_CTX_SIZE 2048
42 
43 // Our quality param ranges from 0 to 100.
44 // To calculate quality, we convert from AVIF's quantizer scale, which runs from 63 to 0.
45 #define MAX_QUALITY 100
46 
47 // These constants are for computing the number of tiles and threads to use during encoding.
48 // Maximum threads are from libavif/contrib/gkd-pixbuf/loader.c.
49 #define MIN_TILE_AREA (512 * 512)
50 #define MAX_TILES 8
51 #define MAX_THREADS 64
52 
53 /*** Macros ***/
54 
55 /*
56 	From gd_png.c:
57 		convert the 7-bit alpha channel to an 8-bit alpha channel.
58 		We do a little bit-flipping magic, repeating the MSB
59 		as the LSB, to ensure that 0 maps to 0 and
60 		127 maps to 255. We also have to invert to match
61 		PNG's convention in which 255 is opaque.
62 */
63 #define alpha7BitTo8Bit(alpha7Bit) \
64 	(alpha7Bit == 127 ? \
65 				0 : \
66 				255 - ((alpha7Bit << 1) + (alpha7Bit >> 6)))
67 
68 #define alpha8BitTo7Bit(alpha8Bit) (gdAlphaMax - (alpha8Bit >> 1))
69 
70 
71 /*** Helper functions ***/
72 
73 /* Convert the quality param we expose to the quantity params used by libavif.
74 	 The *Quantizer* params values can range from 0 to 63, with 0 = highest quality and 63 = worst.
75 	 We make the scale 0-100, and we reverse this, so that 0 = worst quality and 100 = highest.
76 
77 	 Values below 0 are set to 0, and values below MAX_QUALITY are set to MAX_QUALITY.
78 */
quality2Quantizer(int quality)79 static int quality2Quantizer(int quality) {
80 	int clampedQuality = CLAMP(quality, 0, MAX_QUALITY);
81 
82 	float scaleFactor = (float) AVIF_QUANTIZER_WORST_QUALITY / (float) MAX_QUALITY;
83 
84 	return round(scaleFactor * (MAX_QUALITY - clampedQuality));
85 }
86 
87 /*
88 	 As of February 2021, this algorithm reflects the latest research on how many tiles
89 	 and threads to include for a given image size.
90 	 This is subject to change as research continues.
91 
92 	 Returns false if there was an error, true if all was well.
93  */
setEncoderTilesAndThreads(avifEncoder * encoder,avifRGBImage * rgb)94 static avifBool setEncoderTilesAndThreads(avifEncoder *encoder, avifRGBImage *rgb) {
95 	int imageArea, tiles, tilesLog2, encoderTiles;
96 
97 	// _gdImageAvifCtx(), the calling function, checks this operation for overflow
98 	imageArea = rgb->width * rgb->height;
99 
100 	tiles = (int) ceil((double) imageArea / MIN_TILE_AREA);
101 	tiles = MIN(tiles, MAX_TILES);
102 	tiles = MIN(tiles, MAX_THREADS);
103 
104 	// The number of tiles in any dimension will always be a power of 2. We can only specify log(2)tiles.
105 
106 	tilesLog2 = floor(log2(tiles));
107 
108 	// If the image's width is greater than the height, use more tile columns
109 	// than tile rows to make the tile size close to a square.
110 
111 	if (rgb->width >= rgb->height) {
112 		encoder->tileRowsLog2 = tilesLog2 / 2;
113 		encoder->tileColsLog2 = tilesLog2 - encoder->tileRowsLog2;
114 	} else {
115 		encoder->tileColsLog2 = tilesLog2 / 2;
116 		encoder->tileRowsLog2 = tilesLog2 - encoder->tileColsLog2;
117 	}
118 
119 	// It's good to have one thread per tile.
120 	encoderTiles = (1 << encoder->tileRowsLog2) * (1 << encoder->tileColsLog2);
121 	encoder->maxThreads = encoderTiles;
122 
123 	return AVIF_TRUE;
124 }
125 
126 /*
127 	 We can handle AVIF images whose color profile is sRGB, or whose color profile isn't set.
128 */
isAvifSrgbImage(avifImage * avifIm)129 static avifBool isAvifSrgbImage(avifImage *avifIm) {
130 	return
131 		(avifIm->colorPrimaries == AVIF_COLOR_PRIMARIES_BT709 ||
132 			avifIm->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED) &&
133 		(avifIm->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_SRGB ||
134 			avifIm->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED)
135 	;
136 }
137 
138 /*
139 	 Check the result from an Avif function to see if it's an error.
140 	 If so, decode the error and output it, and return true.
141 	 Otherwise, return false.
142 */
isAvifError(avifResult result,const char * msg)143 static avifBool isAvifError(avifResult result, const char *msg) {
144 	if (result != AVIF_RESULT_OK) {
145 		gd_error("avif error - %s: %s\n", msg, avifResultToString(result));
146 		return AVIF_TRUE;
147 	}
148 
149 	return AVIF_FALSE;
150 }
151 
152 
153 typedef struct avifIOCtxReader {
154 	avifIO io; // this must be the first member for easy casting to avifIO*
155 	avifROData rodata;
156 } avifIOCtxReader;
157 
158 /*
159 	<readfromCtx> implements the avifIOReadFunc interface by calling the relevant functions
160 	in the gdIOCtx. Our logic is inspired by avifIOMemoryReaderRead() and avifIOFileReaderRead().
161 	We don't know whether we're reading from a file or from memory. We don't have to know,
162 	since we rely on the helper functions in the gdIOCtx.
163 	We assume we've stashed the gdIOCtx in io->data, as we do in createAvifIOFromCtx().
164 
165 	We ignore readFlags, just as the avifIO*ReaderRead() functions do.
166 
167 	If there's a problem, this returns an avifResult error.
168 	If things go well, return AVIF_RESULT_OK.
169 	Of course these AVIF codes shouldn't be returned by any top-level GD function.
170 */
readFromCtx(avifIO * io,uint32_t readFlags,uint64_t offset,size_t size,avifROData * out)171 static avifResult readFromCtx(avifIO *io, uint32_t readFlags, uint64_t offset, size_t size, avifROData *out)
172 {
173 	gdIOCtx *ctx = (gdIOCtx *) io->data;
174 	avifIOCtxReader *reader = (avifIOCtxReader *) io;
175 
176 	// readFlags is unsupported
177 	if (readFlags != 0) {
178 		return AVIF_RESULT_IO_ERROR;
179 	}
180 
181 	// TODO: if we set sizeHint, this will be more efficient.
182 
183 	if (offset > INT_MAX || size > INT_MAX)
184 		return AVIF_RESULT_IO_ERROR;
185 
186 	// Try to seek offset bytes forward. If we pass the end of the buffer, throw an error.
187 	if (!ctx->seek(ctx, (int) offset))
188 		return AVIF_RESULT_IO_ERROR;
189 
190 	if (size > reader->rodata.size) {
191 		reader->rodata.data = gdRealloc((void *) reader->rodata.data, size);
192 		reader->rodata.size = size;
193 	}
194 	if (!reader->rodata.data) {
195 		gd_error("avif error - couldn't allocate memory");
196 		return AVIF_RESULT_UNKNOWN_ERROR;
197 	}
198 
199 	// Read the number of bytes requested.
200 	// If getBuf() returns a negative value, that means there was an error.
201 	int charsRead = ctx->getBuf(ctx, (void *) reader->rodata.data, (int) size);
202 	if (charsRead < 0) {
203 		return AVIF_RESULT_IO_ERROR;
204 	}
205 
206 	out->data = reader->rodata.data;
207 	out->size = charsRead;
208 	return AVIF_RESULT_OK;
209 }
210 
211 // avif.h says this is optional, but it seemed easy to implement.
destroyAvifIO(struct avifIO * io)212 static void destroyAvifIO(struct avifIO *io) {
213 	avifIOCtxReader *reader = (avifIOCtxReader *) io;
214 	if (reader->rodata.data != NULL) {
215 		gdFree((void *) reader->rodata.data);
216 	}
217 	gdFree(reader);
218 }
219 
220 /* Set up an avifIO object.
221 	 The functions in the gdIOCtx struct may point either to a file or a memory buffer.
222 	 To us, that's immaterial.
223 	 Our task is simply to assign avifIO functions to the proper functions from gdIOCtx.
224 	 The destroy function needs to destroy the avifIO object and anything else it uses.
225 
226 	 Returns NULL if memory for the object can't be allocated.
227 */
228 
229 // TODO: can we get sizeHint somehow?
createAvifIOFromCtx(gdIOCtx * ctx)230 static avifIO *createAvifIOFromCtx(gdIOCtx *ctx) {
231 	struct avifIOCtxReader *reader;
232 
233 	reader = gdMalloc(sizeof(*reader));
234 	if (reader == NULL)
235 		return NULL;
236 
237 	// TODO: setting persistent=FALSE is safe, but it's less efficient. Is it necessary?
238 	reader->io.persistent = AVIF_FALSE;
239 	reader->io.read = readFromCtx;
240 	reader->io.write = NULL; // this function is currently unused; see avif.h
241 	reader->io.destroy = destroyAvifIO;
242 	reader->io.sizeHint = 0; // sadly, we don't get this information from the gdIOCtx.
243 	reader->io.data = ctx;
244 	reader->rodata.data = NULL;
245 	reader->rodata.size = 0;
246 
247 	return (avifIO *) reader;
248 }
249 
250 
251 /*** Decoding functions ***/
252 
253 /*
254 	Function: gdImageCreateFromAvif
255 
256 		<gdImageCreateFromAvif> is called to load truecolor images from
257 		AVIF format files. Invoke <gdImageCreateFromAvif> with an
258 		already opened pointer to a file containing the desired
259 		image. <gdImageCreateFromAvif> returns a <gdImagePtr> to the new
260 		truecolor image, or NULL if unable to load the image (most often
261 		because the file is corrupt or does not contain a AVIF
262 		image). <gdImageCreateFromAvif> does not close the file.
263 
264 		This function creates a gdIOCtx struct from the file pointer it's passed.
265 		And then it relies on <gdImageCreateFromAvifCtx> to do the real decoding work.
266 		If the file contains an image sequence, we simply read the first one, discarding the rest.
267 
268 	Variants:
269 
270 		<gdImageCreateFromAvifPtr> creates an image from AVIF data
271 		already in memory.
272 
273 		<gdImageCreateFromAvifCtx> reads data from the function
274 		pointers in a <gdIOCtx> structure.
275 
276 	Parameters:
277 
278 		infile - pointer to the input file
279 
280 	Returns:
281 
282 		A pointer to the new truecolor image.	This will need to be
283 		destroyed with <gdImageDestroy> once it is no longer needed.
284 
285 		On error, returns 0.
286 */
gdImageCreateFromAvif(FILE * infile)287 gdImagePtr gdImageCreateFromAvif(FILE *infile)
288 {
289 	gdImagePtr im;
290 	gdIOCtx *ctx = gdNewFileCtx(infile);
291 
292 	if (!ctx)
293 		return NULL;
294 
295 	im = gdImageCreateFromAvifCtx(ctx);
296 	ctx->gd_free(ctx);
297 
298 	return im;
299 }
300 
301 /*
302 	Function: gdImageCreateFromAvifPtr
303 
304 		See <gdImageCreateFromAvif>.
305 
306 	Parameters:
307 
308 		size						- size of Avif data in bytes.
309 		data						- pointer to Avif data.
310 */
gdImageCreateFromAvifPtr(int size,void * data)311 gdImagePtr gdImageCreateFromAvifPtr(int size, void *data)
312 {
313 	gdImagePtr im;
314 	gdIOCtx *ctx = gdNewDynamicCtxEx(size, data, 0);
315 
316 	if (!ctx)
317 		return 0;
318 
319 	im = gdImageCreateFromAvifCtx(ctx);
320 	ctx->gd_free(ctx);
321 
322 	return im;
323 }
324 
325 /*
326 	Function: gdImageCreateFromAvifCtx
327 
328 		See <gdImageCreateFromAvif>.
329 
330 		Additional details: the AVIF library comes with functions to create an IO object from
331 		a file and from a memory pointer. Of course, it doesn't have a way to create an IO object
332 		from a gdIOCtx. So, here, we use our own helper function, <createAvifIOfromCtx>.
333 
334 		Otherwise, we create the image by calling AVIF library functions in order:
335 		* avifDecoderCreate(), to create the decoder
336 		* avifDecoderSetIO(), to tell libavif how to read from our data structure
337 		* avifDecoderParse(), to parse the image
338 		* avifDecoderNextImage(), to read the first image from the decoder
339 		* avifRGBImageSetDefaults(), to create the avifRGBImage
340 		* avifRGBImageAllocatePixels(), to allocate memory for the pixels
341 		* avifImageYUVToRGB(), to convert YUV to RGB
342 
343 		Finally, we create a new gd image and copy over the pixel data.
344 
345 	Parameters:
346 
347 		ctx							- a gdIOCtx struct
348 */
gdImageCreateFromAvifCtx(gdIOCtx * ctx)349 gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx)
350 {
351 	uint32_t x, y;
352 	gdImage *im = NULL;
353 	avifResult result;
354 	avifIO *io;
355 	avifDecoder *decoder;
356 	avifRGBImage rgb;
357 
358 	// this lets us know that memory hasn't been allocated yet for the pixels
359 	rgb.pixels = NULL;
360 
361 	decoder = avifDecoderCreate();
362 
363 	// Check if libavif version is >= 0.9.1
364 	// If so, allow the PixelInformationProperty ('pixi') to be missing in AV1 image
365 	// items. libheif v1.11.0 or older does not add the 'pixi' item property to
366 	// AV1 image items. (This issue has been corrected in libheif v1.12.0.)
367 
368 #if AVIF_VERSION >= 90100
369 	decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED;
370 #endif
371 
372 	io = createAvifIOFromCtx(ctx);
373 	if (!io) {
374 		gd_error("avif error - Could not allocate memory");
375 		goto cleanup;
376 	}
377 
378 	avifDecoderSetIO(decoder, io);
379 
380 	result = avifDecoderParse(decoder);
381 	if (isAvifError(result, "Could not parse image"))
382 		goto cleanup;
383 
384 	// Note again that, for an image sequence, we read only the first image, ignoring the rest.
385 	result = avifDecoderNextImage(decoder);
386 	if (isAvifError(result, "Could not decode image"))
387 		goto cleanup;
388 
389 	if (!isAvifSrgbImage(decoder->image))
390 		gd_error_ex(GD_NOTICE, "Image's color profile is not sRGB");
391 
392 	// Set up the avifRGBImage, and convert it from YUV to an 8-bit RGB image.
393 	// (While AVIF image pixel depth can be 8, 10, or 12 bits, GD truecolor images are 8-bit.)
394 	avifRGBImageSetDefaults(&rgb, decoder->image);
395 	rgb.depth = 8;
396 #if AVIF_VERSION >= 1000000
397 	result = avifRGBImageAllocatePixels(&rgb);
398 	if (isAvifError(result, "Allocating RGB pixels failed"))
399 		goto cleanup;
400 #else
401 	avifRGBImageAllocatePixels(&rgb);
402 #endif
403 
404 	result = avifImageYUVToRGB(decoder->image, &rgb);
405 	if (isAvifError(result, "Conversion from YUV to RGB failed"))
406 		goto cleanup;
407 
408 	im = gdImageCreateTrueColor(decoder->image->width, decoder->image->height);
409 	if (!im) {
410 		gd_error("avif error - Could not create GD truecolor image");
411 		goto cleanup;
412 	}
413 
414 	im->saveAlphaFlag = 1;
415 
416 	// Read the pixels from the AVIF image and copy them into the GD image.
417 
418 	uint8_t *p = rgb.pixels;
419 
420 	for (y = 0; y < decoder->image->height; y++) {
421 		for (x = 0; x < decoder->image->width; x++) {
422 			uint8_t r = *(p++);
423 			uint8_t g = *(p++);
424 			uint8_t b = *(p++);
425 			uint8_t a = alpha8BitTo7Bit(*(p++));
426 			im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, a);
427 		}
428 	}
429 
430 cleanup:
431 	// if io has been allocated, this frees it
432 	avifDecoderDestroy(decoder);
433 
434 	if (rgb.pixels)
435 		avifRGBImageFreePixels(&rgb);
436 
437 	return im;
438 }
439 
440 
441 /*** Encoding functions ***/
442 
443 /*
444 	Function: gdImageAvifEx
445 
446 		<gdImageAvifEx> outputs the specified image to the specified file in
447 		AVIF format. The file must be open for writing. Under MSDOS and
448 		all versions of Windows, it is important to use "wb" as opposed to
449 		simply "w" as the mode when opening the file, and under Unix there
450 		is no penalty for doing so. <gdImageAvifEx> does not close the file;
451 		your code must do so.
452 
453 	Variants:
454 
455 		<gdImageAvifEx> writes the image to a file, encoding with the default quality and speed.
456 
457 		<gdImageAvifPtrEx> stores the image in RAM.
458 
459 		<gdImageAvifPtr> stores the image in RAM, encoding with the default quality and speed.
460 
461 		<gdImageAvifCtx> stores the image using a <gdIOCtx> struct.
462 
463 	Parameters:
464 
465 		im			- The image to save.
466 		outFile - The FILE pointer to write to.
467 		quality - Compression quality (0-100). 0 is lowest-quality, 100 is highest.
468 		speed	  - The speed of compression (0-10). 0 is slowest, 10 is fastest.
469 
470 	Notes on parameters:
471 		quality - If quality = -1, we use a default quality as defined in QUALITY_DEFAULT.
472 			For information on how we convert this quality to libavif's quantity param, see <quality2Quantizer>.
473 
474 		speed - At slower speeds, encoding may be quite slow. Use judiciously.
475 
476 		Qualities or speeds that are lower than the minimum value get clamped to the minimum value,
477 		and qualities or speeds that are lower than the maximum value get clamped to the maxmum value.
478 		Note that AVIF_SPEED_DEFAULT is -1. If we ever set SPEED_DEFAULT = AVIF_SPEED_DEFAULT,
479 		we'd want to add a conditional to ensure that value doesn't get clamped.
480 
481 
482 	Returns:
483 
484 		* for <gdImageAvifEx>, <gdImageAvif>, and <gdImageAvifCtx>, nothing.
485 		* for <gdImageAvifPtrEx> and <gdImageAvifPtr>, a pointer to the image in memory.
486 */
487 
488 /*
489 	 If we're passed the QUALITY_DEFAULT of -1, set the quantizer params to QUANTIZER_DEFAULT.
490 */
gdImageAvifCtx(gdImagePtr im,gdIOCtx * outfile,int quality,int speed)491 void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed)
492 {
493 	avifResult result;
494 	avifRGBImage rgb;
495 	avifRWData avifOutput = AVIF_DATA_EMPTY;
496 	avifBool lossless = quality == 100;
497 	avifEncoder *encoder = NULL;
498 
499 	uint32_t val;
500 	uint8_t *p;
501 	uint32_t x, y;
502 
503 	if (im == NULL)
504 		return;
505 
506 	if (!gdImageTrueColor(im)) {
507 		gd_error("avif error - avif doesn't support palette images");
508 		return;
509 	}
510 
511 	if (!gdImageSX(im) || !gdImageSY(im)) {
512 		gd_error("avif error - image dimensions must not be zero");
513 		return;
514 	}
515 
516 	if (overflow2(gdImageSX(im), gdImageSY(im))) {
517 		gd_error("avif error - image dimensions are too large");
518 		return;
519 	}
520 
521 	speed = CLAMP(speed, AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST);
522 
523 	avifPixelFormat subsampling = quality >= HIGH_QUALITY_SUBSAMPLING_THRESHOLD ?
524 		CHROMA_SUBAMPLING_HIGH_QUALITY : CHROMA_SUBSAMPLING_DEFAULT;
525 
526 	// Create the AVIF image.
527 	// Set the ICC to sRGB, as that's what gd supports right now.
528 	// Note that MATRIX_COEFFICIENTS_IDENTITY enables lossless conversion from RGB to YUV.
529 
530 	avifImage *avifIm = avifImageCreate(gdImageSX(im), gdImageSY(im), 8, subsampling);
531 #if AVIF_VERSION >= 1000000
532 	if (avifIm == NULL) {
533 		gd_error("avif error - Creating image failed\n");
534 		goto cleanup;
535 	}
536 #endif
537 	avifIm->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
538 	avifIm->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
539 	avifIm->matrixCoefficients = lossless ? AVIF_MATRIX_COEFFICIENTS_IDENTITY : AVIF_MATRIX_COEFFICIENTS_BT709;
540 
541 	avifRGBImageSetDefaults(&rgb, avifIm);
542 	// this allocates memory, and sets rgb.rowBytes and rgb.pixels.
543 #if AVIF_VERSION >= 1000000
544 	result = avifRGBImageAllocatePixels(&rgb);
545 	if (isAvifError(result, "Allocating RGB pixels failed"))
546 		goto cleanup;
547 #else
548 	avifRGBImageAllocatePixels(&rgb);
549 #endif
550 
551 	// Parse RGB data from the GD image, and copy it into the AVIF RGB image.
552 	// Convert 7-bit GD alpha channel values to 8-bit AVIF values.
553 
554 	p = rgb.pixels;
555 	for (y = 0; y < rgb.height; y++) {
556 		for (x = 0; x < rgb.width; x++) {
557 			val = im->tpixels[y][x];
558 
559 			*(p++) = gdTrueColorGetRed(val);
560 			*(p++) = gdTrueColorGetGreen(val);
561 			*(p++) = gdTrueColorGetBlue(val);
562 			*(p++) = alpha7BitTo8Bit(gdTrueColorGetAlpha(val));
563 		}
564 	}
565 
566 	// Convert the RGB image to YUV.
567 
568 	result = avifImageRGBToYUV(avifIm, &rgb);
569 	if (isAvifError(result, "Could not convert image to YUV"))
570 		goto cleanup;
571 
572 	// Encode the image in AVIF format.
573 
574 	encoder = avifEncoderCreate();
575 #if AVIF_VERSION >= 1000000
576 	if (encoder == NULL) {
577 		gd_error("avif error - Creating encoder failed\n");
578 		goto cleanup;
579 	}
580 #endif
581 	int quantizerQuality = quality == QUALITY_DEFAULT ?
582 		QUANTIZER_DEFAULT : quality2Quantizer(quality);
583 
584 	encoder->minQuantizer = quantizerQuality;
585 	encoder->maxQuantizer = quantizerQuality;
586 	encoder->minQuantizerAlpha = quantizerQuality;
587 	encoder->maxQuantizerAlpha = quantizerQuality;
588 	encoder->speed = speed;
589 
590 	if (!setEncoderTilesAndThreads(encoder, &rgb))
591 		goto cleanup;
592 
593 	//TODO: is there a reason to use timeSscales != 1?
594 	result = avifEncoderAddImage(encoder, avifIm, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
595 	if (isAvifError(result, "Could not encode image"))
596 		goto cleanup;
597 
598 	result = avifEncoderFinish(encoder, &avifOutput);
599 	if (isAvifError(result, "Could not finish encoding"))
600 		goto cleanup;
601 
602 	// Write the AVIF image bytes to the GD ctx.
603 
604 	gdPutBuf(avifOutput.data, avifOutput.size, outfile);
605 
606 cleanup:
607 	if (rgb.pixels)
608 		avifRGBImageFreePixels(&rgb);
609 
610 	if (encoder)
611 		avifEncoderDestroy(encoder);
612 
613 	if (avifOutput.data)
614 		avifRWDataFree(&avifOutput);
615 
616 	if (avifIm)
617 		avifImageDestroy(avifIm);
618 }
619 
gdImageAvifEx(gdImagePtr im,FILE * outFile,int quality,int speed)620 void gdImageAvifEx(gdImagePtr im, FILE *outFile, int quality, int speed)
621 {
622 	gdIOCtx *out = gdNewFileCtx(outFile);
623 
624 	if (out != NULL) {
625 		gdImageAvifCtx(im, out, quality, speed);
626 		out->gd_free(out);
627 	}
628 }
629 
gdImageAvif(gdImagePtr im,FILE * outFile)630 void gdImageAvif(gdImagePtr im, FILE *outFile)
631 {
632 	gdImageAvifEx(im, outFile, QUALITY_DEFAULT, SPEED_DEFAULT);
633 }
634 
gdImageAvifPtrEx(gdImagePtr im,int * size,int quality,int speed)635 void * gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed)
636 {
637 	void *rv;
638 	gdIOCtx *out = gdNewDynamicCtx(NEW_DYNAMIC_CTX_SIZE, NULL);
639 
640 	if (out == NULL) {
641 		return NULL;
642 	}
643 
644 	gdImageAvifCtx(im, out, quality, speed);
645 	rv = gdDPExtractData(out, size);
646 
647 	out->gd_free(out);
648 	return rv;
649 }
650 
gdImageAvifPtr(gdImagePtr im,int * size)651 void * gdImageAvifPtr(gdImagePtr im, int *size)
652 {
653 	return gdImageAvifPtrEx(im, size, QUALITY_DEFAULT, AVIF_SPEED_DEFAULT);
654 }
655 
656 #endif /* HAVE_LIBAVIF */
657