xref: /PHP-8.1/ext/gd/libgd/gd_avif.c (revision 036bed01)
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 	avifRGBImageAllocatePixels(&rgb);
397 
398 	result = avifImageYUVToRGB(decoder->image, &rgb);
399 	if (isAvifError(result, "Conversion from YUV to RGB failed"))
400 		goto cleanup;
401 
402 	im = gdImageCreateTrueColor(decoder->image->width, decoder->image->height);
403 	if (!im) {
404 		gd_error("avif error - Could not create GD truecolor image");
405 		goto cleanup;
406 	}
407 
408 	im->saveAlphaFlag = 1;
409 
410 	// Read the pixels from the AVIF image and copy them into the GD image.
411 
412 	uint8_t *p = rgb.pixels;
413 
414 	for (y = 0; y < decoder->image->height; y++) {
415 		for (x = 0; x < decoder->image->width; x++) {
416 			uint8_t r = *(p++);
417 			uint8_t g = *(p++);
418 			uint8_t b = *(p++);
419 			uint8_t a = alpha8BitTo7Bit(*(p++));
420 			im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, a);
421 		}
422 	}
423 
424 cleanup:
425 	// if io has been allocated, this frees it
426 	avifDecoderDestroy(decoder);
427 
428 	if (rgb.pixels)
429 		avifRGBImageFreePixels(&rgb);
430 
431 	return im;
432 }
433 
434 
435 /*** Encoding functions ***/
436 
437 /*
438 	Function: gdImageAvifEx
439 
440 		<gdImageAvifEx> outputs the specified image to the specified file in
441 		AVIF format. The file must be open for writing. Under MSDOS and
442 		all versions of Windows, it is important to use "wb" as opposed to
443 		simply "w" as the mode when opening the file, and under Unix there
444 		is no penalty for doing so. <gdImageAvifEx> does not close the file;
445 		your code must do so.
446 
447 	Variants:
448 
449 		<gdImageAvifEx> writes the image to a file, encoding with the default quality and speed.
450 
451 		<gdImageAvifPtrEx> stores the image in RAM.
452 
453 		<gdImageAvifPtr> stores the image in RAM, encoding with the default quality and speed.
454 
455 		<gdImageAvifCtx> stores the image using a <gdIOCtx> struct.
456 
457 	Parameters:
458 
459 		im			- The image to save.
460 		outFile - The FILE pointer to write to.
461 		quality - Compression quality (0-100). 0 is lowest-quality, 100 is highest.
462 		speed	  - The speed of compression (0-10). 0 is slowest, 10 is fastest.
463 
464 	Notes on parameters:
465 		quality - If quality = -1, we use a default quality as defined in QUALITY_DEFAULT.
466 			For information on how we convert this quality to libavif's quantity param, see <quality2Quantizer>.
467 
468 		speed - At slower speeds, encoding may be quite slow. Use judiciously.
469 
470 		Qualities or speeds that are lower than the minimum value get clamped to the minimum value,
471 		and qualities or speeds that are lower than the maximum value get clamped to the maxmum value.
472 		Note that AVIF_SPEED_DEFAULT is -1. If we ever set SPEED_DEFAULT = AVIF_SPEED_DEFAULT,
473 		we'd want to add a conditional to ensure that value doesn't get clamped.
474 
475 
476 	Returns:
477 
478 		* for <gdImageAvifEx>, <gdImageAvif>, and <gdImageAvifCtx>, nothing.
479 		* for <gdImageAvifPtrEx> and <gdImageAvifPtr>, a pointer to the image in memory.
480 */
481 
482 /*
483 	 If we're passed the QUALITY_DEFAULT of -1, set the quantizer params to QUANTIZER_DEFAULT.
484 */
gdImageAvifCtx(gdImagePtr im,gdIOCtx * outfile,int quality,int speed)485 void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed)
486 {
487 	avifResult result;
488 	avifRGBImage rgb;
489 	avifRWData avifOutput = AVIF_DATA_EMPTY;
490 	avifBool lossless = quality == 100;
491 	avifEncoder *encoder = NULL;
492 
493 	uint32_t val;
494 	uint8_t *p;
495 	uint32_t x, y;
496 
497 	if (im == NULL)
498 		return;
499 
500 	if (!gdImageTrueColor(im)) {
501 		gd_error("avif error - avif doesn't support palette images");
502 		return;
503 	}
504 
505 	if (!gdImageSX(im) || !gdImageSY(im)) {
506 		gd_error("avif error - image dimensions must not be zero");
507 		return;
508 	}
509 
510 	if (overflow2(gdImageSX(im), gdImageSY(im))) {
511 		gd_error("avif error - image dimensions are too large");
512 		return;
513 	}
514 
515 	speed = CLAMP(speed, AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST);
516 
517 	avifPixelFormat subsampling = quality >= HIGH_QUALITY_SUBSAMPLING_THRESHOLD ?
518 		CHROMA_SUBAMPLING_HIGH_QUALITY : CHROMA_SUBSAMPLING_DEFAULT;
519 
520 	// Create the AVIF image.
521 	// Set the ICC to sRGB, as that's what gd supports right now.
522 	// Note that MATRIX_COEFFICIENTS_IDENTITY enables lossless conversion from RGB to YUV.
523 
524 	avifImage *avifIm = avifImageCreate(gdImageSX(im), gdImageSY(im), 8, subsampling);
525 
526 	avifIm->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
527 	avifIm->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
528 	avifIm->matrixCoefficients = lossless ? AVIF_MATRIX_COEFFICIENTS_IDENTITY : AVIF_MATRIX_COEFFICIENTS_BT709;
529 
530 	avifRGBImageSetDefaults(&rgb, avifIm);
531 	// this allocates memory, and sets rgb.rowBytes and rgb.pixels.
532 	avifRGBImageAllocatePixels(&rgb);
533 
534 	// Parse RGB data from the GD image, and copy it into the AVIF RGB image.
535 	// Convert 7-bit GD alpha channel values to 8-bit AVIF values.
536 
537 	p = rgb.pixels;
538 	for (y = 0; y < rgb.height; y++) {
539 		for (x = 0; x < rgb.width; x++) {
540 			val = im->tpixels[y][x];
541 
542 			*(p++) = gdTrueColorGetRed(val);
543 			*(p++) = gdTrueColorGetGreen(val);
544 			*(p++) = gdTrueColorGetBlue(val);
545 			*(p++) = alpha7BitTo8Bit(gdTrueColorGetAlpha(val));
546 		}
547 	}
548 
549 	// Convert the RGB image to YUV.
550 
551 	result = avifImageRGBToYUV(avifIm, &rgb);
552 	if (isAvifError(result, "Could not convert image to YUV"))
553 		goto cleanup;
554 
555 	// Encode the image in AVIF format.
556 
557 	encoder = avifEncoderCreate();
558 	int quantizerQuality = quality == QUALITY_DEFAULT ?
559 		QUANTIZER_DEFAULT : quality2Quantizer(quality);
560 
561 	encoder->minQuantizer = quantizerQuality;
562 	encoder->maxQuantizer = quantizerQuality;
563 	encoder->minQuantizerAlpha = quantizerQuality;
564 	encoder->maxQuantizerAlpha = quantizerQuality;
565 	encoder->speed = speed;
566 
567 	if (!setEncoderTilesAndThreads(encoder, &rgb))
568 		goto cleanup;
569 
570 	//TODO: is there a reason to use timeSscales != 1?
571 	result = avifEncoderAddImage(encoder, avifIm, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
572 	if (isAvifError(result, "Could not encode image"))
573 		goto cleanup;
574 
575 	result = avifEncoderFinish(encoder, &avifOutput);
576 	if (isAvifError(result, "Could not finish encoding"))
577 		goto cleanup;
578 
579 	// Write the AVIF image bytes to the GD ctx.
580 
581 	gdPutBuf(avifOutput.data, avifOutput.size, outfile);
582 
583 cleanup:
584 	if (rgb.pixels)
585 		avifRGBImageFreePixels(&rgb);
586 
587 	if (encoder)
588 		avifEncoderDestroy(encoder);
589 
590 	if (avifOutput.data)
591 		avifRWDataFree(&avifOutput);
592 
593 	if (avifIm)
594 		avifImageDestroy(avifIm);
595 }
596 
gdImageAvifEx(gdImagePtr im,FILE * outFile,int quality,int speed)597 void gdImageAvifEx(gdImagePtr im, FILE *outFile, int quality, int speed)
598 {
599 	gdIOCtx *out = gdNewFileCtx(outFile);
600 
601 	if (out != NULL) {
602 		gdImageAvifCtx(im, out, quality, speed);
603 		out->gd_free(out);
604 	}
605 }
606 
gdImageAvif(gdImagePtr im,FILE * outFile)607 void gdImageAvif(gdImagePtr im, FILE *outFile)
608 {
609 	gdImageAvifEx(im, outFile, QUALITY_DEFAULT, SPEED_DEFAULT);
610 }
611 
gdImageAvifPtrEx(gdImagePtr im,int * size,int quality,int speed)612 void * gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed)
613 {
614 	void *rv;
615 	gdIOCtx *out = gdNewDynamicCtx(NEW_DYNAMIC_CTX_SIZE, NULL);
616 
617 	if (out == NULL) {
618 		return NULL;
619 	}
620 
621 	gdImageAvifCtx(im, out, quality, speed);
622 	rv = gdDPExtractData(out, size);
623 
624 	out->gd_free(out);
625 	return rv;
626 }
627 
gdImageAvifPtr(gdImagePtr im,int * size)628 void * gdImageAvifPtr(gdImagePtr im, int *size)
629 {
630 	return gdImageAvifPtrEx(im, size, QUALITY_DEFAULT, AVIF_SPEED_DEFAULT);
631 }
632 
633 #endif /* HAVE_LIBAVIF */
634