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 /*
154 	<readfromCtx> implements the avifIOReadFunc interface by calling the relevant functions
155 	in the gdIOCtx. Our logic is inspired by avifIOMemoryReaderRead() and avifIOFileReaderRead().
156 	We don't know whether we're reading from a file or from memory. We don't have to know,
157 	since we rely on the helper functions in the gdIOCtx.
158 	We assume we've stashed the gdIOCtx in io->data, as we do in createAvifIOFromCtx().
159 
160 	We ignore readFlags, just as the avifIO*ReaderRead() functions do.
161 
162 	If there's a problem, this returns an avifResult error.
163 	If things go well, return AVIF_RESULT_OK.
164 	Of course these AVIF codes shouldn't be returned by any top-level GD function.
165 */
readFromCtx(avifIO * io,uint32_t readFlags,uint64_t offset,size_t size,avifROData * out)166 static avifResult readFromCtx(avifIO *io, uint32_t readFlags, uint64_t offset, size_t size, avifROData *out)
167 {
168 	void *dataBuf = NULL;
169 	gdIOCtx *ctx = (gdIOCtx *) io->data;
170 
171 	// readFlags is unsupported
172 	if (readFlags != 0) {
173 		return AVIF_RESULT_IO_ERROR;
174 	}
175 
176 	// TODO: if we set sizeHint, this will be more efficient.
177 
178 	if (offset > INT_MAX || size > INT_MAX)
179 		return AVIF_RESULT_IO_ERROR;
180 
181 	// Try to seek offset bytes forward. If we pass the end of the buffer, throw an error.
182 	if (!ctx->seek(ctx, (int) offset))
183 		return AVIF_RESULT_IO_ERROR;
184 
185 	dataBuf = avifAlloc(size);
186 	if (!dataBuf) {
187 		gd_error("avif error - couldn't allocate memory");
188 		return AVIF_RESULT_UNKNOWN_ERROR;
189 	}
190 
191 	// Read the number of bytes requested.
192 	// If getBuf() returns a negative value, that means there was an error.
193 	int charsRead = ctx->getBuf(ctx, dataBuf, (int) size);
194 	if (charsRead < 0) {
195 		avifFree(dataBuf);
196 		return AVIF_RESULT_IO_ERROR;
197 	}
198 
199 	out->data = dataBuf;
200 	out->size = charsRead;
201 	return AVIF_RESULT_OK;
202 }
203 
204 // avif.h says this is optional, but it seemed easy to implement.
destroyAvifIO(struct avifIO * io)205 static void destroyAvifIO(struct avifIO *io) {
206 	gdFree(io);
207 }
208 
209 /* Set up an avifIO object.
210 	 The functions in the gdIOCtx struct may point either to a file or a memory buffer.
211 	 To us, that's immaterial.
212 	 Our task is simply to assign avifIO functions to the proper functions from gdIOCtx.
213 	 The destroy function needs to destroy the avifIO object and anything else it uses.
214 
215 	 Returns NULL if memory for the object can't be allocated.
216 */
217 
218 // TODO: can we get sizeHint somehow?
createAvifIOFromCtx(gdIOCtx * ctx)219 static avifIO *createAvifIOFromCtx(gdIOCtx *ctx) {
220 	avifIO *io;
221 
222 	io = gdMalloc(sizeof(*io));
223 	if (io == NULL)
224 		return NULL;
225 
226 	// TODO: setting persistent=FALSE is safe, but it's less efficient. Is it necessary?
227 	io->persistent = AVIF_FALSE;
228 	io->read = readFromCtx;
229 	io->write = NULL; // this function is currently unused; see avif.h
230 	io->destroy = destroyAvifIO;
231 	io->sizeHint = 0; // sadly, we don't get this information from the gdIOCtx.
232 	io->data = ctx;
233 
234 	return io;
235 }
236 
237 
238 /*** Decoding functions ***/
239 
240 /*
241 	Function: gdImageCreateFromAvif
242 
243 		<gdImageCreateFromAvif> is called to load truecolor images from
244 		AVIF format files. Invoke <gdImageCreateFromAvif> with an
245 		already opened pointer to a file containing the desired
246 		image. <gdImageCreateFromAvif> returns a <gdImagePtr> to the new
247 		truecolor image, or NULL if unable to load the image (most often
248 		because the file is corrupt or does not contain a AVIF
249 		image). <gdImageCreateFromAvif> does not close the file.
250 
251 		This function creates a gdIOCtx struct from the file pointer it's passed.
252 		And then it relies on <gdImageCreateFromAvifCtx> to do the real decoding work.
253 		If the file contains an image sequence, we simply read the first one, discarding the rest.
254 
255 	Variants:
256 
257 		<gdImageCreateFromAvifPtr> creates an image from AVIF data
258 		already in memory.
259 
260 		<gdImageCreateFromAvifCtx> reads data from the function
261 		pointers in a <gdIOCtx> structure.
262 
263 	Parameters:
264 
265 		infile - pointer to the input file
266 
267 	Returns:
268 
269 		A pointer to the new truecolor image.	This will need to be
270 		destroyed with <gdImageDestroy> once it is no longer needed.
271 
272 		On error, returns 0.
273 */
gdImageCreateFromAvif(FILE * infile)274 gdImagePtr gdImageCreateFromAvif(FILE *infile)
275 {
276 	gdImagePtr im;
277 	gdIOCtx *ctx = gdNewFileCtx(infile);
278 
279 	if (!ctx)
280 		return NULL;
281 
282 	im = gdImageCreateFromAvifCtx(ctx);
283 	ctx->gd_free(ctx);
284 
285 	return im;
286 }
287 
288 /*
289 	Function: gdImageCreateFromAvifPtr
290 
291 		See <gdImageCreateFromAvif>.
292 
293 	Parameters:
294 
295 		size						- size of Avif data in bytes.
296 		data						- pointer to Avif data.
297 */
gdImageCreateFromAvifPtr(int size,void * data)298 gdImagePtr gdImageCreateFromAvifPtr(int size, void *data)
299 {
300 	gdImagePtr im;
301 	gdIOCtx *ctx = gdNewDynamicCtxEx(size, data, 0);
302 
303 	if (!ctx)
304 		return 0;
305 
306 	im = gdImageCreateFromAvifCtx(ctx);
307 	ctx->gd_free(ctx);
308 
309 	return im;
310 }
311 
312 /*
313 	Function: gdImageCreateFromAvifCtx
314 
315 		See <gdImageCreateFromAvif>.
316 
317 		Additional details: the AVIF library comes with functions to create an IO object from
318 		a file and from a memory pointer. Of course, it doesn't have a way to create an IO object
319 		from a gdIOCtx. So, here, we use our own helper function, <createAvifIOfromCtx>.
320 
321 		Otherwise, we create the image by calling AVIF library functions in order:
322 		* avifDecoderCreate(), to create the decoder
323 		* avifDecoderSetIO(), to tell libavif how to read from our data structure
324 		* avifDecoderParse(), to parse the image
325 		* avifDecoderNextImage(), to read the first image from the decoder
326 		* avifRGBImageSetDefaults(), to create the avifRGBImage
327 		* avifRGBImageAllocatePixels(), to allocate memory for the pixels
328 		* avifImageYUVToRGB(), to convert YUV to RGB
329 
330 		Finally, we create a new gd image and copy over the pixel data.
331 
332 	Parameters:
333 
334 		ctx							- a gdIOCtx struct
335 */
gdImageCreateFromAvifCtx(gdIOCtx * ctx)336 gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx)
337 {
338 	uint32_t x, y;
339 	gdImage *im = NULL;
340 	avifResult result;
341 	avifIO *io;
342 	avifDecoder *decoder;
343 	avifRGBImage rgb;
344 
345 	// this lets us know that memory hasn't been allocated yet for the pixels
346 	rgb.pixels = NULL;
347 
348 	decoder = avifDecoderCreate();
349 
350 	// Check if libavif version is >= 0.9.1
351 	// If so, allow the PixelInformationProperty ('pixi') to be missing in AV1 image
352 	// items. libheif v1.11.0 or older does not add the 'pixi' item property to
353 	// AV1 image items. (This issue has been corrected in libheif v1.12.0.)
354 
355 #if AVIF_VERSION >= 90100
356 	decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED;
357 #endif
358 
359 	io = createAvifIOFromCtx(ctx);
360 	if (!io) {
361 		gd_error("avif error - Could not allocate memory");
362 		goto cleanup;
363 	}
364 
365 	avifDecoderSetIO(decoder, io);
366 
367 	result = avifDecoderParse(decoder);
368 	if (isAvifError(result, "Could not parse image"))
369 		goto cleanup;
370 
371 	// Note again that, for an image sequence, we read only the first image, ignoring the rest.
372 	result = avifDecoderNextImage(decoder);
373 	if (isAvifError(result, "Could not decode image"))
374 		goto cleanup;
375 
376 	if (!isAvifSrgbImage(decoder->image))
377 		gd_error_ex(GD_NOTICE, "Image's color profile is not sRGB");
378 
379 	// Set up the avifRGBImage, and convert it from YUV to an 8-bit RGB image.
380 	// (While AVIF image pixel depth can be 8, 10, or 12 bits, GD truecolor images are 8-bit.)
381 	avifRGBImageSetDefaults(&rgb, decoder->image);
382 	rgb.depth = 8;
383 	avifRGBImageAllocatePixels(&rgb);
384 
385 	result = avifImageYUVToRGB(decoder->image, &rgb);
386 	if (isAvifError(result, "Conversion from YUV to RGB failed"))
387 		goto cleanup;
388 
389 	im = gdImageCreateTrueColor(decoder->image->width, decoder->image->height);
390 	if (!im) {
391 		gd_error("avif error - Could not create GD truecolor image");
392 		goto cleanup;
393 	}
394 
395 	im->saveAlphaFlag = 1;
396 
397 	// Read the pixels from the AVIF image and copy them into the GD image.
398 
399 	uint8_t *p = rgb.pixels;
400 
401 	for (y = 0; y < decoder->image->height; y++) {
402 		for (x = 0; x < decoder->image->width; x++) {
403 			uint8_t r = *(p++);
404 			uint8_t g = *(p++);
405 			uint8_t b = *(p++);
406 			uint8_t a = alpha8BitTo7Bit(*(p++));
407 			im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, a);
408 		}
409 	}
410 
411 cleanup:
412 	// if io has been allocated, this frees it
413 	avifDecoderDestroy(decoder);
414 
415 	if (rgb.pixels)
416 		avifRGBImageFreePixels(&rgb);
417 
418 	return im;
419 }
420 
421 
422 /*** Encoding functions ***/
423 
424 /*
425 	Function: gdImageAvifEx
426 
427 		<gdImageAvifEx> outputs the specified image to the specified file in
428 		AVIF format. The file must be open for writing. Under MSDOS and
429 		all versions of Windows, it is important to use "wb" as opposed to
430 		simply "w" as the mode when opening the file, and under Unix there
431 		is no penalty for doing so. <gdImageAvifEx> does not close the file;
432 		your code must do so.
433 
434 	Variants:
435 
436 		<gdImageAvifEx> writes the image to a file, encoding with the default quality and speed.
437 
438 		<gdImageAvifPtrEx> stores the image in RAM.
439 
440 		<gdImageAvifPtr> stores the image in RAM, encoding with the default quality and speed.
441 
442 		<gdImageAvifCtx> stores the image using a <gdIOCtx> struct.
443 
444 	Parameters:
445 
446 		im			- The image to save.
447 		outFile - The FILE pointer to write to.
448 		quality - Compression quality (0-100). 0 is lowest-quality, 100 is highest.
449 		speed	  - The speed of compression (0-10). 0 is slowest, 10 is fastest.
450 
451 	Notes on parameters:
452 		quality - If quality = -1, we use a default quality as defined in QUALITY_DEFAULT.
453 			For information on how we convert this quality to libavif's quantity param, see <quality2Quantizer>.
454 
455 		speed - At slower speeds, encoding may be quite slow. Use judiciously.
456 
457 		Qualities or speeds that are lower than the minimum value get clamped to the minimum value,
458 		and qualities or speeds that are lower than the maximum value get clamped to the maxmum value.
459 		Note that AVIF_SPEED_DEFAULT is -1. If we ever set SPEED_DEFAULT = AVIF_SPEED_DEFAULT,
460 		we'd want to add a conditional to ensure that value doesn't get clamped.
461 
462 
463 	Returns:
464 
465 		* for <gdImageAvifEx>, <gdImageAvif>, and <gdImageAvifCtx>, nothing.
466 		* for <gdImageAvifPtrEx> and <gdImageAvifPtr>, a pointer to the image in memory.
467 */
468 
469 /*
470 	 If we're passed the QUALITY_DEFAULT of -1, set the quantizer params to QUANTIZER_DEFAULT.
471 */
gdImageAvifCtx(gdImagePtr im,gdIOCtx * outfile,int quality,int speed)472 void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed)
473 {
474 	avifResult result;
475 	avifRGBImage rgb;
476 	avifRWData avifOutput = AVIF_DATA_EMPTY;
477 	avifBool lossless = quality == 100;
478 	avifEncoder *encoder = NULL;
479 
480 	uint32_t val;
481 	uint8_t *p;
482 	uint32_t x, y;
483 
484 	if (im == NULL)
485 		return;
486 
487 	if (!gdImageTrueColor(im)) {
488 		gd_error("avif error - avif doesn't support palette images");
489 		return;
490 	}
491 
492 	if (!gdImageSX(im) || !gdImageSY(im)) {
493 		gd_error("avif error - image dimensions must not be zero");
494 		return;
495 	}
496 
497 	if (overflow2(gdImageSX(im), gdImageSY(im))) {
498 		gd_error("avif error - image dimensions are too large");
499 		return;
500 	}
501 
502 	speed = CLAMP(speed, AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST);
503 
504 	avifPixelFormat subsampling = quality >= HIGH_QUALITY_SUBSAMPLING_THRESHOLD ?
505 		CHROMA_SUBAMPLING_HIGH_QUALITY : CHROMA_SUBSAMPLING_DEFAULT;
506 
507 	// Create the AVIF image.
508 	// Set the ICC to sRGB, as that's what gd supports right now.
509 	// Note that MATRIX_COEFFICIENTS_IDENTITY enables lossless conversion from RGB to YUV.
510 
511 	avifImage *avifIm = avifImageCreate(gdImageSX(im), gdImageSY(im), 8, subsampling);
512 
513 	avifIm->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
514 	avifIm->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
515 	avifIm->matrixCoefficients = lossless ? AVIF_MATRIX_COEFFICIENTS_IDENTITY : AVIF_MATRIX_COEFFICIENTS_BT709;
516 
517 	avifRGBImageSetDefaults(&rgb, avifIm);
518 	// this allocates memory, and sets rgb.rowBytes and rgb.pixels.
519 	avifRGBImageAllocatePixels(&rgb);
520 
521 	// Parse RGB data from the GD image, and copy it into the AVIF RGB image.
522 	// Convert 7-bit GD alpha channel values to 8-bit AVIF values.
523 
524 	p = rgb.pixels;
525 	for (y = 0; y < rgb.height; y++) {
526 		for (x = 0; x < rgb.width; x++) {
527 			val = im->tpixels[y][x];
528 
529 			*(p++) = gdTrueColorGetRed(val);
530 			*(p++) = gdTrueColorGetGreen(val);
531 			*(p++) = gdTrueColorGetBlue(val);
532 			*(p++) = alpha7BitTo8Bit(gdTrueColorGetAlpha(val));
533 		}
534 	}
535 
536 	// Convert the RGB image to YUV.
537 
538 	result = avifImageRGBToYUV(avifIm, &rgb);
539 	if (isAvifError(result, "Could not convert image to YUV"))
540 		goto cleanup;
541 
542 	// Encode the image in AVIF format.
543 
544 	encoder = avifEncoderCreate();
545 	int quantizerQuality = quality == QUALITY_DEFAULT ?
546 		QUANTIZER_DEFAULT : quality2Quantizer(quality);
547 
548 	encoder->minQuantizer = quantizerQuality;
549 	encoder->maxQuantizer = quantizerQuality;
550 	encoder->minQuantizerAlpha = quantizerQuality;
551 	encoder->maxQuantizerAlpha = quantizerQuality;
552 	encoder->speed = speed;
553 
554 	if (!setEncoderTilesAndThreads(encoder, &rgb))
555 		goto cleanup;
556 
557 	//TODO: is there a reason to use timeSscales != 1?
558 	result = avifEncoderAddImage(encoder, avifIm, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
559 	if (isAvifError(result, "Could not encode image"))
560 		goto cleanup;
561 
562 	result = avifEncoderFinish(encoder, &avifOutput);
563 	if (isAvifError(result, "Could not finish encoding"))
564 		goto cleanup;
565 
566 	// Write the AVIF image bytes to the GD ctx.
567 
568 	gdPutBuf(avifOutput.data, avifOutput.size, outfile);
569 
570 cleanup:
571 	if (rgb.pixels)
572 		avifRGBImageFreePixels(&rgb);
573 
574 	if (encoder)
575 		avifEncoderDestroy(encoder);
576 
577 	if (avifOutput.data)
578 		avifRWDataFree(&avifOutput);
579 }
580 
gdImageAvifEx(gdImagePtr im,FILE * outFile,int quality,int speed)581 void gdImageAvifEx(gdImagePtr im, FILE *outFile, int quality, int speed)
582 {
583 	gdIOCtx *out = gdNewFileCtx(outFile);
584 
585 	if (out != NULL) {
586 		gdImageAvifCtx(im, out, quality, speed);
587 		out->gd_free(out);
588 	}
589 }
590 
gdImageAvif(gdImagePtr im,FILE * outFile)591 void gdImageAvif(gdImagePtr im, FILE *outFile)
592 {
593 	gdImageAvifEx(im, outFile, QUALITY_DEFAULT, SPEED_DEFAULT);
594 }
595 
gdImageAvifPtrEx(gdImagePtr im,int * size,int quality,int speed)596 void * gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed)
597 {
598 	void *rv;
599 	gdIOCtx *out = gdNewDynamicCtx(NEW_DYNAMIC_CTX_SIZE, NULL);
600 
601 	if (out == NULL) {
602 		return NULL;
603 	}
604 
605 	gdImageAvifCtx(im, out, quality, speed);
606 	rv = gdDPExtractData(out, size);
607 
608 	out->gd_free(out);
609 	return rv;
610 }
611 
gdImageAvifPtr(gdImagePtr im,int * size)612 void * gdImageAvifPtr(gdImagePtr im, int *size)
613 {
614 	return gdImageAvifPtrEx(im, size, QUALITY_DEFAULT, AVIF_SPEED_DEFAULT);
615 }
616 
617 #endif /* HAVE_LIBAVIF */
618