Revert avifImageRGBToYUV() API changes To avoid backward compatibility issues. Add avoidLibYUV member to avifRGBImage. chromaUpsampling has no impact on libyuv use.
diff --git a/CHANGELOG.md b/CHANGELOG.md index 14fc6b9..ff86177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -7,11 +7,11 @@ ## [Unreleased] There are incompatible ABI changes in this release. The alphaRange member was -removed from avifImage struct. The chromaDownsampling member was added to the -avifRGBImage struct. The imageDimensionLimit member was added to the avifDecoder -struct. avifImageCopy() and avifImageAllocatePlanes() signatures changed. It is -necessary to recompile your code. Also check the return values of -avifImageCopy() and avifImageAllocatePlanes(). +removed from avifImage struct. The chromaDownsampling and avoidLibYUV members +were added to the avifRGBImage struct. The imageDimensionLimit member was added +to the avifDecoder struct. avifImageCopy() and avifImageAllocatePlanes() +signatures changed. It is necessary to recompile your code. Also check the +return values of avifImageCopy() and avifImageAllocatePlanes(). ### Changed * Update aom.cmd: v3.4.0 @@ -23,7 +23,10 @@ void to report invalid parameters or memory allocation failures. * avifImageRGBToYUV() now uses libyuv fast paths by default. It may slightly change conversion results. The old behavior can be restored by setting - avifRGBImage::chromaDownsampling to AVIF_CHROMA_DOWNSAMPLING_BEST_QUALITY. + avifRGBImage::chromaDownsampling to AVIF_CHROMA_DOWNSAMPLING_BEST_QUALITY + and avifRGBImage::avoidLibYUV to AVIF_TRUE. +* avifRGBImage::chromaUpsampling now only applies to conversions that need + upsampling chroma from 4:2:0 or 4:2:2 and has no impact on the use of libyuv. ### Removed * alphaRange field was removed from the avifImage struct. It it presumed that @@ -34,6 +37,7 @@ * Add chromaDownsampling field to avifRGBImage struct * Add imageDimensionLimit field to avifDecoder struct * Add autoTiling field to avifEncoder struct +* Add AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV value to avifChromaDownsampling enum * avifdec: Add --dimension-limit, which specifies the image dimension limit (width or height) that should be tolerated * avifenc: Add --sharpyuv, which enables "sharp" RGB to YUV420 conversion, which
diff --git a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc index 6d0e060..6284870 100644 --- a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc +++ b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
@@ -166,8 +166,7 @@ } rgb_image.pixels = static_cast<uint8_t*>(bitmap_pixels); rgb_image.rowBytes = bitmap_info.stride; - res = avifImageYUVToRGB(decoder.decoder->image, &rgb_image, - AVIF_YUV_TO_RGB_DEFAULT); + res = avifImageYUVToRGB(decoder.decoder->image, &rgb_image); AndroidBitmap_unlockPixels(env, bitmap); if (res != AVIF_RESULT_OK) { LOGE("Failed to convert YUV Pixels to RGB. Status: %d", res);
diff --git a/apps/avifdec.c b/apps/avifdec.c index 27ab40d..004f681 100644 --- a/apps/avifdec.c +++ b/apps/avifdec.c
@@ -62,7 +62,7 @@ int pngCompressionLevel = -1; // -1 is a sentinel to avifPNGWrite() to skip calling png_set_compression_level() avifCodecChoice codecChoice = AVIF_CODEC_CHOICE_AUTO; avifBool infoOnly = AVIF_FALSE; - avifYUVToRGBFlags upsamplingFlags = AVIF_YUV_TO_RGB_DEFAULT; + avifChromaUpsampling upsamplingFlags = AVIF_CHROMA_UPSAMPLING_AUTOMATIC; avifBool ignoreICC = AVIF_FALSE; avifBool rawColor = AVIF_FALSE; avifBool allowProgressive = AVIF_FALSE; @@ -153,11 +153,11 @@ } else if (!strcmp(arg, "-u") || !strcmp(arg, "--upsampling")) { NEXTARG(); if (!strcmp(arg, "automatic")) { - upsamplingFlags = AVIF_YUV_TO_RGB_DEFAULT; + upsamplingFlags = AVIF_CHROMA_UPSAMPLING_AUTOMATIC; } else if (!strcmp(arg, "fastest")) { - upsamplingFlags = AVIF_CHROMA_UPSAMPLING_NEAREST; + upsamplingFlags = AVIF_CHROMA_UPSAMPLING_FASTEST; } else if (!strcmp(arg, "best")) { - upsamplingFlags = AVIF_YUV_TO_RGB_AVOID_LIBYUV | AVIF_CHROMA_UPSAMPLING_BILINEAR; + upsamplingFlags = AVIF_CHROMA_UPSAMPLING_BEST_QUALITY; } else if (!strcmp(arg, "nearest")) { upsamplingFlags = AVIF_CHROMA_UPSAMPLING_NEAREST; } else if (!strcmp(arg, "bilinear")) {
diff --git a/apps/avifenc.c b/apps/avifenc.c index 199f841..918d521 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c
@@ -266,7 +266,7 @@ avifImage * image, uint32_t * outDepth, avifAppSourceTiming * sourceTiming, - avifRGBToYUVFlags flags) + avifChromaDownsampling flags) { if (sourceTiming) { // A source timing of all 0s is a sentinel value hinting that the value is unset / should be @@ -484,7 +484,7 @@ avifColorPrimaries colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED; avifTransferCharacteristics transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; avifMatrixCoefficients matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; - avifRGBToYUVFlags flags = AVIF_RGB_TO_YUV_DEFAULT; + avifChromaDownsampling flags = AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC; int argIndex = 1; while (argIndex < argc) {
diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c index 4cb54ba..2e2bd60 100644 --- a/apps/shared/avifjpeg.c +++ b/apps/shared/avifjpeg.c
@@ -243,7 +243,7 @@ avifImage * avif, avifPixelFormat requestedFormat, uint32_t requestedDepth, - avifRGBToYUVFlags flags, + avifChromaDownsampling flags, avifBool ignoreICC, avifBool ignoreExif, avifBool ignoreXMP) @@ -318,6 +318,7 @@ avif->depth = requestedDepth ? requestedDepth : 8; avifRGBImageSetDefaults(&rgb, avif); rgb.format = AVIF_RGB_FORMAT_RGB; + rgb.chromaDownsampling = flags; rgb.depth = 8; avifRGBImageAllocatePixels(&rgb); @@ -328,7 +329,7 @@ memcpy(pixelRow, buffer[0], rgb.rowBytes); ++row; } - if (avifImageRGBToYUV(avif, &rgb, flags) != AVIF_RESULT_OK) { + if (avifImageRGBToYUV(avif, &rgb) != AVIF_RESULT_OK) { fprintf(stderr, "Conversion to YUV failed: %s\n", inputFilename); goto cleanup; } @@ -381,7 +382,7 @@ return ret; } -avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int jpegQuality, avifYUVToRGBFlags conversionFlags) +avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int jpegQuality, avifChromaUpsampling flags) { avifBool ret = AVIF_FALSE; FILE * f = NULL; @@ -395,9 +396,10 @@ avifRGBImage rgb; avifRGBImageSetDefaults(&rgb, avif); rgb.format = AVIF_RGB_FORMAT_RGB; + rgb.chromaUpsampling = flags; rgb.depth = 8; avifRGBImageAllocatePixels(&rgb); - if (avifImageYUVToRGB(avif, &rgb, conversionFlags) != AVIF_RESULT_OK) { + if (avifImageYUVToRGB(avif, &rgb) != AVIF_RESULT_OK) { fprintf(stderr, "Conversion to RGB failed: %s\n", outputFilename); goto cleanup; }
diff --git a/apps/shared/avifjpeg.h b/apps/shared/avifjpeg.h index 214bcca..6611063 100644 --- a/apps/shared/avifjpeg.h +++ b/apps/shared/avifjpeg.h
@@ -14,11 +14,11 @@ avifImage * avif, avifPixelFormat requestedFormat, uint32_t requestedDepth, - avifRGBToYUVFlags flags, + avifChromaDownsampling flags, avifBool ignoreICC, avifBool ignoreExif, avifBool ignoreXMP); -avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int jpegQuality, avifYUVToRGBFlags conversionFlags); +avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int jpegQuality, avifChromaUpsampling flags); #ifdef __cplusplus } // extern "C"
diff --git a/apps/shared/avifpng.c b/apps/shared/avifpng.c index 873cb39..d40ab1a 100644 --- a/apps/shared/avifpng.c +++ b/apps/shared/avifpng.c
@@ -205,7 +205,7 @@ avifImage * avif, avifPixelFormat requestedFormat, uint32_t requestedDepth, - avifRGBToYUVFlags flags, + avifChromaDownsampling flags, avifBool ignoreICC, avifBool ignoreExif, avifBool ignoreXMP, @@ -328,6 +328,7 @@ } avifRGBImageSetDefaults(&rgb, avif); + rgb.chromaDownsampling = flags; rgb.depth = imgBitDepth; avifRGBImageAllocatePixels(&rgb); rowPointers = (png_bytep *)malloc(sizeof(png_bytep) * rgb.height); @@ -335,7 +336,7 @@ rowPointers[y] = &rgb.pixels[y * rgb.rowBytes]; } png_read_image(png, rowPointers); - if (avifImageRGBToYUV(avif, &rgb, flags) != AVIF_RESULT_OK) { + if (avifImageRGBToYUV(avif, &rgb) != AVIF_RESULT_OK) { fprintf(stderr, "Conversion to YUV failed: %s\n", inputFilename); goto cleanup; } @@ -367,7 +368,7 @@ return readResult; } -avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint32_t requestedDepth, avifYUVToRGBFlags conversionFlags, int compressionLevel) +avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint32_t requestedDepth, avifChromaUpsampling flags, int compressionLevel) { volatile avifBool writeResult = AVIF_FALSE; png_structp png = NULL; @@ -388,6 +389,7 @@ } avifRGBImageSetDefaults(&rgb, avif); + rgb.chromaUpsampling = flags; rgb.depth = rgbDepth; int colorType = PNG_COLOR_TYPE_RGBA; if (!avif->alphaPlane) { @@ -395,7 +397,7 @@ rgb.format = AVIF_RGB_FORMAT_RGB; } avifRGBImageAllocatePixels(&rgb); - if (avifImageYUVToRGB(avif, &rgb, conversionFlags) != AVIF_RESULT_OK) { + if (avifImageYUVToRGB(avif, &rgb) != AVIF_RESULT_OK) { fprintf(stderr, "Conversion to RGB failed: %s\n", outputFilename); goto cleanup; }
diff --git a/apps/shared/avifpng.h b/apps/shared/avifpng.h index 9597211..cd52e29 100644 --- a/apps/shared/avifpng.h +++ b/apps/shared/avifpng.h
@@ -15,16 +15,12 @@ avifImage * avif, avifPixelFormat requestedFormat, uint32_t requestedDepth, - avifRGBToYUVFlags flags, + avifChromaDownsampling flags, avifBool ignoreICC, avifBool ignoreExif, avifBool ignoreXMP, uint32_t * outPNGDepth); -avifBool avifPNGWrite(const char * outputFilename, - const avifImage * avif, - uint32_t requestedDepth, - avifYUVToRGBFlags conversionFlags, - int compressionLevel); +avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint32_t requestedDepth, avifChromaUpsampling flags, int compressionLevel); #ifdef __cplusplus } // extern "C"
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c index c0c4470..7d2db88 100644 --- a/apps/shared/avifutil.c +++ b/apps/shared/avifutil.c
@@ -227,7 +227,7 @@ avifAppFileFormat avifReadImage(const char * filename, avifPixelFormat requestedFormat, int requestedDepth, - avifRGBToYUVFlags flags, + avifChromaDownsampling flags, avifBool ignoreICC, avifBool ignoreExif, avifBool ignoreXMP,
diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h index c1b77cd..d6da3fe 100644 --- a/apps/shared/avifutil.h +++ b/apps/shared/avifutil.h
@@ -63,7 +63,7 @@ avifAppFileFormat avifReadImage(const char * filename, avifPixelFormat requestedFormat, int requestedDepth, - avifRGBToYUVFlags flags, + avifChromaDownsampling flags, avifBool ignoreICC, avifBool ignoreExif, avifBool ignoreXMP,
diff --git a/contrib/gdk-pixbuf/loader.c b/contrib/gdk-pixbuf/loader.c index c9a760b..32c6eee 100644 --- a/contrib/gdk-pixbuf/loader.c +++ b/contrib/gdk-pixbuf/loader.c
@@ -124,7 +124,7 @@ rgb.pixels = gdk_pixbuf_get_pixels(output); rgb.rowBytes = gdk_pixbuf_get_rowstride(output); - ret = avifImageYUVToRGB(image, &rgb, AVIF_YUV_TO_RGB_DEFAULT); + ret = avifImageYUVToRGB(image, &rgb); if (ret != AVIF_RESULT_OK) { g_set_error(error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "Failed to convert YUV to RGB: %s", avifResultToString(ret)); @@ -448,7 +448,7 @@ rgb.format = AVIF_RGB_FORMAT_RGB; } - res = avifImageRGBToYUV(avif, &rgb, AVIF_RGB_TO_YUV_DEFAULT); + res = avifImageRGBToYUV(avif, &rgb); if ( res != AVIF_RESULT_OK ) { g_set_error(error, GDK_PIXBUF_ERROR,
diff --git a/examples/avif_example_decode_file.c b/examples/avif_example_decode_file.c index 1f8d1fa..f3fafb9 100644 --- a/examples/avif_example_decode_file.c +++ b/examples/avif_example_decode_file.c
@@ -53,14 +53,14 @@ // * this frame's sequence timing avifRGBImageSetDefaults(&rgb, decoder->image); - // Override YUV(A)->RGB(A) defaults here: depth, format, ignoreAlpha, alphaPremultiplied, etc + // Override YUV(A)->RGB(A) defaults here: + // depth, format, chromaUpsampling, avoidLibYUV, ignoreAlpha, alphaPremultiplied, etc. // Alternative: set rgb.pixels and rgb.rowBytes yourself, which should match your chosen rgb.format // Be sure to use uint16_t* instead of uint8_t* for rgb.pixels/rgb.rowBytes if (rgb.depth > 8) avifRGBImageAllocatePixels(&rgb); - // Other flags than AVIF_YUV_TO_RGB_DEFAULT, such as AVIF_CHROMA_UPSAMPLING_NEAREST, can be passed. - if (avifImageYUVToRGB(decoder->image, &rgb, AVIF_YUV_TO_RGB_DEFAULT) != AVIF_RESULT_OK) { + if (avifImageYUVToRGB(decoder->image, &rgb) != AVIF_RESULT_OK) { fprintf(stderr, "Conversion from YUV failed: %s\n", inputFilename); goto cleanup; }
diff --git a/examples/avif_example_decode_memory.c b/examples/avif_example_decode_memory.c index d5a9aa9..2cf4a0b 100644 --- a/examples/avif_example_decode_memory.c +++ b/examples/avif_example_decode_memory.c
@@ -75,14 +75,14 @@ // * this frame's sequence timing avifRGBImageSetDefaults(&rgb, decoder->image); - // Override YUV(A)->RGB(A) defaults here: depth, format, ignoreAlpha, alphaPremultiplied, etc + // Override YUV(A)->RGB(A) defaults here: + // depth, format, chromaUpsampling, avoidLibYUV, ignoreAlpha, alphaPremultiplied, etc. // Alternative: set rgb.pixels and rgb.rowBytes yourself, which should match your chosen rgb.format // Be sure to use uint16_t* instead of uint8_t* for rgb.pixels/rgb.rowBytes if (rgb.depth > 8) avifRGBImageAllocatePixels(&rgb); - // Other flags than AVIF_YUV_TO_RGB_DEFAULT, such as AVIF_CHROMA_UPSAMPLING_NEAREST, can be passed. - if (avifImageYUVToRGB(decoder->image, &rgb, AVIF_YUV_TO_RGB_DEFAULT) != AVIF_RESULT_OK) { + if (avifImageYUVToRGB(decoder->image, &rgb) != AVIF_RESULT_OK) { fprintf(stderr, "Conversion from YUV failed: %s\n", inputFilename); goto cleanup; }
diff --git a/examples/avif_example_encode.c b/examples/avif_example_encode.c index b95cf0a..34b6cdc 100644 --- a/examples/avif_example_encode.c +++ b/examples/avif_example_encode.c
@@ -57,7 +57,8 @@ printf("Encoding from converted RGBA\n"); avifRGBImageSetDefaults(&rgb, image); - // Override RGB(A)->YUV(A) defaults here: depth, format, ignoreAlpha, alphaPremultiplied, etc + // Override RGB(A)->YUV(A) defaults here: + // depth, format, chromaDownsampling, avoidLibYUV, ignoreAlpha, alphaPremultiplied, etc. // Alternative: set rgb.pixels and rgb.rowBytes yourself, which should match your chosen rgb.format // Be sure to use uint16_t* instead of uint8_t* for rgb.pixels/rgb.rowBytes if (rgb.depth > 8) @@ -66,8 +67,7 @@ // Fill your RGB(A) data here memset(rgb.pixels, 255, rgb.rowBytes * image->height); - // Other flags than AVIF_RGB_TO_YUV_DEFAULT, such as AVIF_RGB_TO_YUV_AVOID_LIBYUV, can be passed. - avifResult convertResult = avifImageRGBToYUV(image, &rgb, AVIF_RGB_TO_YUV_DEFAULT); + avifResult convertResult = avifImageRGBToYUV(image, &rgb); if (convertResult != AVIF_RESULT_OK) { fprintf(stderr, "Failed to convert to YUV(A): %s\n", avifResultToString(convertResult)); goto cleanup;
diff --git a/include/avif/avif.h b/include/avif/avif.h index a946d94..9e0203d 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h
@@ -530,6 +530,9 @@ // If libavif is built with a version of libyuv offering a fast conversion between RGB and YUV for // the given inputs, libavif will use it. See reformat_libyuv.c for the details. +// libyuv is faster but may have slightly less precision than built-in conversion, so avoidLibYUV +// should be set to AVIF_TRUE when AVIF_CHROMA_UPSAMPLING_BEST_QUALITY or +// AVIF_CHROMA_DOWNSAMPLING_BEST_QUALITY is used, to get the most precise but slowest results. // Note to libavif maintainers: The lookup tables in avifImageYUVToRGBLibYUV // rely on the ordering of this enum values for their correctness. So changing @@ -558,14 +561,39 @@ AVIF_API uint32_t avifRGBFormatChannelCount(avifRGBFormat format); AVIF_API avifBool avifRGBFormatHasAlpha(avifRGBFormat format); +typedef enum avifChromaUpsampling +{ + AVIF_CHROMA_UPSAMPLING_AUTOMATIC = 0, // Chooses best trade off of speed/quality (uses BILINEAR libyuv if available, + // or fallbacks to NEAREST libyuv if available, or fallbacks to BILINEAR built-in) + AVIF_CHROMA_UPSAMPLING_FASTEST = 1, // Chooses speed over quality (same as NEAREST) + AVIF_CHROMA_UPSAMPLING_BEST_QUALITY = 2, // Chooses the best quality upsampling, given settings (same as BILINEAR) + AVIF_CHROMA_UPSAMPLING_NEAREST = 3, // Uses nearest-neighbor filter + AVIF_CHROMA_UPSAMPLING_BILINEAR = 4 // Uses bilinear filter +} avifChromaUpsampling; + +typedef enum avifChromaDownsampling +{ + AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC = 0, // Chooses best trade off of speed/quality (same as AVERAGE) + AVIF_CHROMA_DOWNSAMPLING_FASTEST = 1, // Chooses speed over quality (same as AVERAGE) + AVIF_CHROMA_DOWNSAMPLING_BEST_QUALITY = 2, // Chooses the best quality upsampling (same as AVERAGE) + AVIF_CHROMA_DOWNSAMPLING_AVERAGE = 3, // Uses averaging filter + AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV = 4 // Uses sharp yuv filter (libsharpyuv), available for 4:2:0 only, ignored for 4:2:2 +} avifChromaDownsampling; + typedef struct avifRGBImage { - uint32_t width; // must match associated avifImage - uint32_t height; // must match associated avifImage - uint32_t depth; // legal depths [8, 10, 12, 16]. if depth>8, pixels must be uint16_t internally - avifRGBFormat format; // all channels are always full range - avifBool ignoreAlpha; // Used for XRGB formats, treats formats containing alpha (such as ARGB) as if they were - // RGB, treating the alpha bits as if they were all 1. + uint32_t width; // must match associated avifImage + uint32_t height; // must match associated avifImage + uint32_t depth; // legal depths [8, 10, 12, 16]. if depth>8, pixels must be uint16_t internally + avifRGBFormat format; // all channels are always full range + avifChromaUpsampling chromaUpsampling; // How to upsample from 4:2:0 or 4:2:2 UV when converting to RGB (ignored for 4:4:4 and 4:0:0). + // Ignored when converting to YUV. Defaults to AVIF_CHROMA_UPSAMPLING_AUTOMATIC. + avifChromaDownsampling chromaDownsampling; // How to downsample to 4:2:0 or 4:2:2 UV when converting from RGB (ignored for 4:4:4 and 4:0:0). + // Ignored when converting to RGB. Defaults to AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC. + avifBool avoidLibYUV; // If AVIF_FALSE and libyuv conversion between RGB and YUV (including upsampling or downsampling if any) + // is available for the avifImage/avifRGBImage combination, then libyuv is used. Default is AVIF_FALSE. + avifBool ignoreAlpha; // Used for XRGB formats, treats formats containing alpha (such as ARGB) as if they were RGB, treating + // the alpha bits as if they were all 1. avifBool alphaPremultiplied; // indicates if RGB value is pre-multiplied by alpha. Default: false avifBool isFloat; // indicates if RGBA values are in half float (f16) format. Valid only when depth == 16. Default: false @@ -583,44 +611,9 @@ AVIF_API void avifRGBImageAllocatePixels(avifRGBImage * rgb); AVIF_API void avifRGBImageFreePixels(avifRGBImage * rgb); -// Conversion options. -typedef enum avifRGBToYUVFlag -{ - AVIF_RGB_TO_YUV_DEFAULT = 0, // Uses the first available downsampling filter among: - // libyuv average, built-in average - - // libyuv preference - - AVIF_RGB_TO_YUV_AVOID_LIBYUV = (1 << 0), // Only picks built-in paths. The default is to prefer libyuv if available. - - // Conversion from RGB to YUV 4:2:2 or YUV 4:2:0 (ignored in all other cases) - - // Chroma downsampling filter. Set at most one: - AVIF_CHROMA_DOWNSAMPLING_AVERAGE = (1 << 10), // only use the averaging filter (libyuv or built-in) - AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV = (1 << 11), // only use sharp yuv filter (libsharpyuv), - // available for 4:2:0 only, ignored for 4:2:2 -} avifRGBToYUVFlag; -typedef uint32_t avifRGBToYUVFlags; -typedef enum avifYUVToRGBFlag -{ - AVIF_YUV_TO_RGB_DEFAULT = 0, // Uses the first available upsampling filter among: - // libyuv bilinear, libyuv nearest-neighbor, built-in bilinear - - // libyuv preference - - AVIF_YUV_TO_RGB_AVOID_LIBYUV = (1 << 0), // Only picks built-in paths. The default is to prefer libyuv if available. - - // Conversion from YUV 4:2:2 or YUV 4:2:0 to RGB (ignored in all other cases) - - // Chroma upsampling filter. Set at most one: - AVIF_CHROMA_UPSAMPLING_NEAREST = (1 << 10), // only use the fast nearest-neighbor filter (libyuv or built-in) - AVIF_CHROMA_UPSAMPLING_BILINEAR = (1 << 11) // only use the good-quality bilinear filter (libyuv or built-in) -} avifYUVToRGBFlag; -typedef uint32_t avifYUVToRGBFlags; - -// Conversion functions. -AVIF_API avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb, avifRGBToYUVFlags flags); -AVIF_API avifResult avifImageYUVToRGB(const avifImage * image, avifRGBImage * rgb, avifYUVToRGBFlags flags); +// The main conversion functions +AVIF_API avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb); +AVIF_API avifResult avifImageYUVToRGB(const avifImage * image, avifRGBImage * rgb); // Premultiply handling functions. // (Un)premultiply is automatically done by the main conversion functions above,
diff --git a/include/avif/internal.h b/include/avif/internal.h index 283502d..5cc5aab 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h
@@ -149,7 +149,7 @@ // * AVIF_RESULT_OK - Converted successfully with libyuv // * AVIF_RESULT_NOT_IMPLEMENTED - The fast path for this combination is not implemented with libyuv, use built-in YUV conversion // * [any other error] - Return error to caller -avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb, avifYUVToRGBFlags flags); +avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb); // Returns: // * AVIF_RESULT_OK - Converted successfully with libsharpyuv
diff --git a/src/avif.c b/src/avif.c index d6e4afb..4ef4780 100644 --- a/src/avif.c +++ b/src/avif.c
@@ -457,6 +457,9 @@ rgb->height = image->height; rgb->depth = image->depth; rgb->format = AVIF_RGB_FORMAT_RGBA; + rgb->chromaUpsampling = AVIF_CHROMA_UPSAMPLING_AUTOMATIC; + rgb->chromaDownsampling = AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC; + rgb->avoidLibYUV = AVIF_FALSE; rgb->ignoreAlpha = AVIF_FALSE; rgb->pixels = NULL; rgb->rowBytes = 0;
diff --git a/src/reformat.c b/src/reformat.c index d792a85..951c46b 100644 --- a/src/reformat.c +++ b/src/reformat.c
@@ -194,7 +194,7 @@ return AVIF_CLAMP(unorm, 0, state->yuvMaxChannel); } -avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb, avifRGBToYUVFlags flags) +avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb) { if (!rgb->pixels || rgb->format == AVIF_RGB_FORMAT_RGB_565) { return AVIF_RESULT_REFORMAT_FAILED; @@ -227,7 +227,7 @@ avifBool converted = AVIF_FALSE; // Try converting with libsharpyuv. - if ((flags & AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV) && image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { + if ((rgb->chromaDownsampling == AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV) && (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420)) { const avifResult libSharpYUVResult = avifImageRGBToYUVLibSharpYUV(image, rgb, &state); if (libSharpYUVResult != AVIF_RESULT_OK) { // Return the error if sharpyuv was requested but failed for any reason, including libsharpyuv not being available. @@ -236,7 +236,7 @@ converted = AVIF_TRUE; } - if (!converted && !(flags & AVIF_RGB_TO_YUV_AVOID_LIBYUV) && (alphaMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP)) { + if (!converted && !rgb->avoidLibYUV && (alphaMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP)) { avifResult libyuvResult = avifImageRGBToYUVLibYUV(image, rgb); if (libyuvResult == AVIF_RESULT_OK) { converted = AVIF_TRUE; @@ -479,7 +479,7 @@ } // Note: This function handles alpha (un)multiply. -static avifResult avifImageYUVAnyToRGBAnySlow(const avifImage * image, avifRGBImage * rgb, avifReformatState * state, avifYUVToRGBFlags flags) +static avifResult avifImageYUVAnyToRGBAnySlow(const avifImage * image, avifRGBImage * rgb, avifReformatState * state) { // Aliases for some state const float kr = state->kr; @@ -647,7 +647,8 @@ } } - if (flags & AVIF_CHROMA_UPSAMPLING_NEAREST) { + if ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) || + (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST)) { // Nearest neighbor; ignore all UVs but the closest one Cb = unormFloatTableUV[unormU[0][0]]; Cr = unormFloatTableUV[unormV[0][0]]; @@ -1163,10 +1164,10 @@ return AVIF_RESULT_OK; } -static avifResult avifRGBImageToF16(avifRGBImage * rgb, avifYUVToRGBFlags flags) +static avifResult avifRGBImageToF16(avifRGBImage * rgb) { avifResult libyuvResult = AVIF_RESULT_NOT_IMPLEMENTED; - if (!(flags & AVIF_YUV_TO_RGB_AVOID_LIBYUV)) { + if (!rgb->avoidLibYUV) { libyuvResult = avifRGBImageToF16LibYUV(rgb); } if (libyuvResult != AVIF_RESULT_NOT_IMPLEMENTED) { @@ -1195,7 +1196,7 @@ return AVIF_RESULT_OK; } -avifResult avifImageYUVToRGB(const avifImage * image, avifRGBImage * rgb, avifYUVToRGBFlags flags) +avifResult avifImageYUVToRGB(const avifImage * image, avifRGBImage * rgb) { if (!image->yuvPlanes[AVIF_CHAN_Y]) { return AVIF_RESULT_REFORMAT_FAILED; @@ -1206,16 +1207,10 @@ return AVIF_RESULT_REFORMAT_FAILED; } - // At most one filter can be specified. - if ((flags & AVIF_CHROMA_UPSAMPLING_NEAREST) && (flags & AVIF_CHROMA_UPSAMPLING_BILINEAR)) { - return AVIF_RESULT_REFORMAT_FAILED; - } - avifAlphaMultiplyMode alphaMultiplyMode = state.toRGBAlphaMode; avifBool convertedWithLibYUV = AVIF_FALSE; - if (!(flags & AVIF_YUV_TO_RGB_AVOID_LIBYUV) && - ((alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP) || avifRGBFormatHasAlpha(rgb->format))) { - avifResult libyuvResult = avifImageYUVToRGBLibYUV(image, rgb, flags); + if (!rgb->avoidLibYUV && ((alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP) || avifRGBFormatHasAlpha(rgb->format))) { + avifResult libyuvResult = avifImageYUVToRGBLibYUV(image, rgb); if (libyuvResult == AVIF_RESULT_OK) { convertedWithLibYUV = AVIF_TRUE; } else { @@ -1263,7 +1258,8 @@ const avifBool hasColor = (image->yuvRowBytes[AVIF_CHAN_U] && image->yuvRowBytes[AVIF_CHAN_V] && (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV400)); - if ((!hasColor || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (flags & AVIF_CHROMA_UPSAMPLING_NEAREST)) && + if ((!hasColor || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || + ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) || (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST))) && (alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP || avifRGBFormatHasAlpha(rgb->format))) { // Explanations on the above conditional: // * None of these fast paths currently support bilinear upsampling, so avoid all of them @@ -1325,7 +1321,7 @@ if (convertResult == AVIF_RESULT_NOT_IMPLEMENTED) { // If we get here, there is no fast path for this combination. Time to be slow! - convertResult = avifImageYUVAnyToRGBAnySlow(image, rgb, &state, flags); + convertResult = avifImageYUVAnyToRGBAnySlow(image, rgb, &state); // The slow path also handles alpha (un)multiply, so forget the operation here. alphaMultiplyMode = AVIF_ALPHA_MULTIPLY_MODE_NO_OP; @@ -1351,7 +1347,7 @@ // Convert pixels to half floats (F16), if necessary. if (rgb->isFloat) { - return avifRGBImageToF16(rgb, flags); + return avifRGBImageToF16(rgb); } return AVIF_RESULT_OK;
diff --git a/src/reformat_libyuv.c b/src/reformat_libyuv.c index 9a9a18d..f4cb5b2 100644 --- a/src/reformat_libyuv.c +++ b/src/reformat_libyuv.c
@@ -12,11 +12,10 @@ (void)rgb; return AVIF_RESULT_NOT_IMPLEMENTED; } -avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb, avifYUVToRGBFlags flags) +avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb) { (void)image; (void)rgb; - (void)flags; return AVIF_RESULT_NOT_IMPLEMENTED; } avifResult avifRGBImagePremultiplyAlphaLibYUV(avifRGBImage * rgb) @@ -325,15 +324,13 @@ static avifResult avifImageYUVToRGBLibYUV8bpc(const avifImage * image, avifRGBImage * rgb, const struct YuvConstants * matrixYUV, - const struct YuvConstants * matrixYVU, - avifYUVToRGBFlags flags); + const struct YuvConstants * matrixYVU); static avifResult avifImageYUVToRGBLibYUVHighBitDepth(const avifImage * image, avifRGBImage * rgb, const struct YuvConstants * matrixYUV, - const struct YuvConstants * matrixYVU, - avifYUVToRGBFlags flags); + const struct YuvConstants * matrixYVU); -avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb, avifYUVToRGBFlags flags) +avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb) { // See if the current settings can be accomplished with libyuv, and use it (if possible). @@ -469,11 +466,11 @@ } if ((image->depth == 8) && (rgb->depth == 8)) { - return avifImageYUVToRGBLibYUV8bpc(image, rgb, matrixYUV, matrixYVU, flags); + return avifImageYUVToRGBLibYUV8bpc(image, rgb, matrixYUV, matrixYVU); } if (((image->depth == 10) || (image->depth == 12)) && (rgb->depth == 8)) { - return avifImageYUVToRGBLibYUVHighBitDepth(image, rgb, matrixYUV, matrixYVU, flags); + return avifImageYUVToRGBLibYUVHighBitDepth(image, rgb, matrixYUV, matrixYVU); } // This function didn't do anything; use the built-in YUV conversion @@ -497,8 +494,7 @@ avifResult avifImageYUVToRGBLibYUV8bpc(const avifImage * image, avifRGBImage * rgb, const struct YuvConstants * matrixYUV, - const struct YuvConstants * matrixYVU, - avifYUVToRGBFlags flags) + const struct YuvConstants * matrixYVU) { // See if the current settings can be accomplished with libyuv, and use it (if possible). @@ -575,7 +571,10 @@ }; YUVToRGBMatrixFilter yuvToRgbMatrixFilter = lutYuvToRgbMatrixFilter[rgb->format][image->yuvFormat]; if (yuvToRgbMatrixFilter != NULL) { - const enum FilterMode filter = (flags & AVIF_CHROMA_UPSAMPLING_NEAREST) ? kFilterNone : kFilterBilinear; + const enum FilterMode filter = ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) || + (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST)) + ? kFilterNone + : kFilterBilinear; if (yuvToRgbMatrixFilter(image->yuvPlanes[AVIF_CHAN_Y], image->yuvRowBytes[AVIF_CHAN_Y], image->yuvPlanes[uPlaneIndex], @@ -594,7 +593,7 @@ } // Only proceed with the nearest-neighbor filter if explicitly specified or left as default. - if (flags & AVIF_CHROMA_UPSAMPLING_BILINEAR) { + if ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BEST_QUALITY) || (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BILINEAR)) { return AVIF_RESULT_NOT_IMPLEMENTED; } // Lookup table for YUV To RGB Matrix (nearest-neighbor filter). @@ -636,8 +635,7 @@ avifResult avifImageYUVToRGBLibYUVHighBitDepth(const avifImage * image, avifRGBImage * rgb, const struct YuvConstants * matrixYUV, - const struct YuvConstants * matrixYVU, - avifYUVToRGBFlags flags) + const struct YuvConstants * matrixYVU) { // See if the current settings can be accomplished with libyuv, and use it (if possible). @@ -704,7 +702,10 @@ }; YUVToRGBMatrixFilter yuvToRgbMatrixFilter = lutYuvToRgbMatrixFilter[depthIndex][rgb->format][image->yuvFormat]; if (yuvToRgbMatrixFilter != NULL) { - const enum FilterMode filter = (flags & AVIF_CHROMA_UPSAMPLING_NEAREST) ? kFilterNone : kFilterBilinear; + const enum FilterMode filter = + ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) || (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST)) + ? kFilterNone + : kFilterBilinear; if (yuvToRgbMatrixFilter((const uint16_t *)image->yuvPlanes[AVIF_CHAN_Y], image->yuvRowBytes[AVIF_CHAN_Y] / 2, (const uint16_t *)image->yuvPlanes[uPlaneIndex], @@ -723,7 +724,7 @@ } // Only proceed with the nearest-neighbor filter if explicitly specified or left as default. - if (flags & AVIF_CHROMA_UPSAMPLING_BILINEAR) { + if ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BEST_QUALITY) || (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BILINEAR)) { return AVIF_RESULT_NOT_IMPLEMENTED; } // Lookup table for YUV To RGB Matrix (nearest-neighbor filter).
diff --git a/tests/avifyuv.c b/tests/avifyuv.c index a98dad7..b7e2df1 100644 --- a/tests/avifyuv.c +++ b/tests/avifyuv.c
@@ -203,8 +203,8 @@ } } - avifImageRGBToYUV(image, &srcRGB, AVIF_RGB_TO_YUV_DEFAULT); - avifImageYUVToRGB(image, &dstRGB, AVIF_YUV_TO_RGB_DEFAULT); + avifImageRGBToYUV(image, &srcRGB); + avifImageYUVToRGB(image, &dstRGB); for (int y = 0; y < dim; ++y) { const uint8_t * srcRow = &srcRGB.pixels[y * srcRGB.rowBytes]; @@ -344,23 +344,23 @@ avifImageFreePlanes(image, AVIF_PLANES_ALL); image->depth = yuvDepth; image->yuvRange = yuvRange; - avifImageRGBToYUV(image, &srcRGB, AVIF_RGB_TO_YUV_DEFAULT); + avifImageRGBToYUV(image, &srcRGB); avifRGBImage intermediateRGB; avifRGBImageSetDefaults(&intermediateRGB, image); intermediateRGB.depth = rgbDepth; intermediateRGB.format = rgbFormat; avifRGBImageAllocatePixels(&intermediateRGB); - avifImageYUVToRGB(image, &intermediateRGB, AVIF_YUV_TO_RGB_DEFAULT); + avifImageYUVToRGB(image, &intermediateRGB); avifImageFreePlanes(image, AVIF_PLANES_ALL); - avifImageRGBToYUV(image, &intermediateRGB, AVIF_RGB_TO_YUV_DEFAULT); + avifImageRGBToYUV(image, &intermediateRGB); avifRGBImage dstRGB; avifRGBImageSetDefaults(&dstRGB, image); dstRGB.depth = yuvDepth; avifRGBImageAllocatePixels(&dstRGB); - avifImageYUVToRGB(image, &dstRGB, AVIF_YUV_TO_RGB_DEFAULT); + avifImageYUVToRGB(image, &dstRGB); avifBool moveOn = AVIF_FALSE; for (uint32_t j = 0; j < originalHeight; ++j) {
diff --git a/tests/gtest/are_images_equal.cc b/tests/gtest/are_images_equal.cc index ac9c1d2..93a1012 100644 --- a/tests/gtest/are_images_equal.cc +++ b/tests/gtest/are_images_equal.cc
@@ -31,10 +31,10 @@ // Make sure no color conversion happens. decoded[i]->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; if (avifReadImage(argv[i + 1], requestedFormat, kRequestedDepth, - AVIF_RGB_TO_YUV_DEFAULT, /*ignoreICC=*/AVIF_FALSE, - /*ignoreExif=*/AVIF_FALSE, /*ignoreXMP=*/AVIF_FALSE, - decoded[i].get(), &depth[i], nullptr, - nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) { + AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, + /*ignoreICC=*/AVIF_FALSE, /*ignoreExif=*/AVIF_FALSE, + /*ignoreXMP=*/AVIF_FALSE, decoded[i].get(), &depth[i], + nullptr, nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) { std::cerr << "Image " << argv[i + 1] << " cannot be read." << std::endl; return 2; }
diff --git a/tests/gtest/avifmetadatatest.cc b/tests/gtest/avifmetadatatest.cc index 4ac7961..5017f13 100644 --- a/tests/gtest/avifmetadatatest.cc +++ b/tests/gtest/avifmetadatatest.cc
@@ -121,8 +121,8 @@ const bool expect_xmp = std::get<6>(GetParam()); const testutil::AvifImagePtr image = testutil::ReadImage( - data_path, file_name, AVIF_PIXEL_FORMAT_NONE, 0, AVIF_RGB_TO_YUV_DEFAULT, - !use_icc, !use_exif, !use_xmp); + data_path, file_name, AVIF_PIXEL_FORMAT_NONE, 0, + AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, !use_icc, !use_exif, !use_xmp); ASSERT_NE(image, nullptr); EXPECT_NE(image->width * image->height, 0u);
diff --git a/tests/gtest/avifrgbtoyuvtest.cc b/tests/gtest/avifrgbtoyuvtest.cc index 08e417e..81b5675 100644 --- a/tests/gtest/avifrgbtoyuvtest.cc +++ b/tests/gtest/avifrgbtoyuvtest.cc
@@ -112,12 +112,11 @@ //------------------------------------------------------------------------------ class RGBToYUVTest - : public testing::TestWithParam< - std::tuple</*rgb_depth=*/int, /*yuv_depth=*/int, avifRGBFormat, - avifPixelFormat, avifRange, avifMatrixCoefficients, - /*sharpYuv=*/bool, - /*add_noise=*/bool, /*rgb_step=*/uint32_t, - /*max_abs_average_diff=*/double, /*min_psnr=*/double>> {}; + : public testing::TestWithParam<std::tuple< + /*rgb_depth=*/int, /*yuv_depth=*/int, avifRGBFormat, avifPixelFormat, + avifRange, avifMatrixCoefficients, avifChromaDownsampling, + /*add_noise=*/bool, /*rgb_step=*/uint32_t, + /*max_abs_average_diff=*/double, /*min_psnr=*/double>> {}; // Converts from RGB to YUV and back to RGB for all RGB combinations, separated // by a color step for reasonable timing. If add_noise is true, also applies @@ -129,7 +128,7 @@ const avifPixelFormat yuv_format = std::get<3>(GetParam()); const avifRange yuv_range = std::get<4>(GetParam()); const avifMatrixCoefficients matrix_coefficients = std::get<5>(GetParam()); - const avifRGBToYUVFlags rgb_to_yuv_flags = std::get<6>(GetParam()); + const avifChromaDownsampling chroma_downsampling = std::get<6>(GetParam()); // Whether to add noise to the input RGB samples. Should only impact // subsampled chroma (4:2:2 and 4:2:0). const bool add_noise = std::get<7>(GetParam()); @@ -154,6 +153,7 @@ yuv->matrixCoefficients = matrix_coefficients; yuv->yuvRange = yuv_range; testutil::AvifRgbImage src_rgb(yuv.get(), rgb_depth, rgb_format); + src_rgb.chromaDownsampling = chroma_downsampling; testutil::AvifRgbImage dst_rgb(yuv.get(), rgb_depth, rgb_format); const testutil::RgbChannelOffsets offsets = testutil::GetRgbChannelOffsets(rgb_format); @@ -184,10 +184,8 @@ ModifyImageChannel(&src_rgb, offsets.b, kBlueNoise); } - ASSERT_EQ(avifImageRGBToYUV(yuv.get(), &src_rgb, rgb_to_yuv_flags), - AVIF_RESULT_OK); - ASSERT_EQ(avifImageYUVToRGB(yuv.get(), &dst_rgb, AVIF_YUV_TO_RGB_DEFAULT), - AVIF_RESULT_OK); + ASSERT_EQ(avifImageRGBToYUV(yuv.get(), &src_rgb), AVIF_RESULT_OK); + ASSERT_EQ(avifImageYUVToRGB(yuv.get(), &dst_rgb), AVIF_RESULT_OK); GetDiffSumAndSqDiffSum(src_rgb, dst_rgb, &diff_sum, &abs_diff_sum, &sq_diff_sum, &max_abs_diff); num_diffs += src_rgb.width * src_rgb.height * 3; // Alpha is lossless. @@ -205,12 +203,8 @@ ModifyImageChannel(&src_rgb, offsets.b, kBlueNoise); } - ASSERT_EQ( - avifImageRGBToYUV(yuv.get(), &src_rgb, AVIF_RGB_TO_YUV_DEFAULT), - AVIF_RESULT_OK); - ASSERT_EQ( - avifImageYUVToRGB(yuv.get(), &dst_rgb, AVIF_YUV_TO_RGB_DEFAULT), - AVIF_RESULT_OK); + ASSERT_EQ(avifImageRGBToYUV(yuv.get(), &src_rgb), AVIF_RESULT_OK); + ASSERT_EQ(avifImageYUVToRGB(yuv.get(), &dst_rgb), AVIF_RESULT_OK); GetDiffSumAndSqDiffSum(src_rgb, dst_rgb, &diff_sum, &abs_diff_sum, &sq_diff_sum, &max_abs_diff); num_diffs += @@ -254,8 +248,9 @@ const avifPixelFormat yuv_format = std::get<3>(GetParam()); const avifRange yuv_range = std::get<4>(GetParam()); const avifMatrixCoefficients matrix_coefficients = std::get<5>(GetParam()); + const avifChromaDownsampling chroma_downsampling = std::get<6>(GetParam()); // Whether to add noise to the input RGB samples. - const bool add_noise = std::get<6>(GetParam()); + const bool add_noise = std::get<7>(GetParam()); // Threshold to pass. const double min_psnr = std::get<9>(GetParam()); // Deduced constants. @@ -274,6 +269,7 @@ yuv->matrixCoefficients = matrix_coefficients; yuv->yuvRange = yuv_range; testutil::AvifRgbImage src_rgb(yuv.get(), rgb_depth, rgb_format); + src_rgb.chromaDownsampling = chroma_downsampling; testutil::AvifRgbImage dst_rgb(yuv.get(), rgb_depth, rgb_format); const testutil::RgbChannelOffsets offsets = testutil::GetRgbChannelOffsets(rgb_format); @@ -294,10 +290,8 @@ testutil::FillImageChannel(&src_rgb, offsets.a, rgb_max); } - ASSERT_EQ(avifImageRGBToYUV(yuv.get(), &src_rgb, AVIF_RGB_TO_YUV_DEFAULT), - AVIF_RESULT_OK); - ASSERT_EQ(avifImageYUVToRGB(yuv.get(), &dst_rgb, AVIF_YUV_TO_RGB_DEFAULT), - AVIF_RESULT_OK); + ASSERT_EQ(avifImageRGBToYUV(yuv.get(), &src_rgb), AVIF_RESULT_OK); + ASSERT_EQ(avifImageYUVToRGB(yuv.get(), &dst_rgb), AVIF_RESULT_OK); GetDiffSumAndSqDiffSum(src_rgb, dst_rgb, &diff_sum, &abs_diff_sum, &sq_diff_sum, &max_abs_diff); num_diffs += src_rgb.width * src_rgb.height * 3; @@ -330,7 +324,7 @@ /*yuv_depth=*/Values(8), Values(AVIF_RGB_FORMAT_RGBA), Values(AVIF_PIXEL_FORMAT_YUV420), Values(AVIF_RANGE_FULL), Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), /*add_noise=*/Values(true), /*rgb_step=*/Values(3), /*max_abs_average_diff=*/Values(0.1), // The color drift is almost @@ -340,39 +334,42 @@ // Keeping RGB samples in full range and same or higher bit depth should not // bring any loss in the roundtrip. -INSTANTIATE_TEST_SUITE_P( - Identity8b, RGBToYUVTest, - Combine(/*rgb_depth=*/Values(8), - /*yuv_depth=*/Values(8, 10, 12), ValuesIn(kAllRgbFormats), - Values(AVIF_PIXEL_FORMAT_YUV444), Values(AVIF_RANGE_FULL), - Values(kMatrixCoefficientsIdentity), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), - /*add_noise=*/Values(true), - /*rgb_step=*/Values(31), - /*max_abs_average_diff=*/Values(0.), - /*min_psnr=*/Values(99.))); -INSTANTIATE_TEST_SUITE_P( - Identity10b, RGBToYUVTest, - Combine(/*rgb_depth=*/Values(10), - /*yuv_depth=*/Values(10, 12), ValuesIn(kAllRgbFormats), - Values(AVIF_PIXEL_FORMAT_YUV444), Values(AVIF_RANGE_FULL), - Values(kMatrixCoefficientsIdentity), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), - /*add_noise=*/Values(true), - /*rgb_step=*/Values(101), - /*max_abs_average_diff=*/Values(0.), - /*min_psnr=*/Values(99.))); -INSTANTIATE_TEST_SUITE_P( - Identity12b, RGBToYUVTest, - Combine(/*rgb_depth=*/Values(12), - /*yuv_depth=*/Values(12), ValuesIn(kAllRgbFormats), - Values(AVIF_PIXEL_FORMAT_YUV444), Values(AVIF_RANGE_FULL), - Values(kMatrixCoefficientsIdentity), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), - /*add_noise=*/Values(true), - /*rgb_step=*/Values(401), - /*max_abs_average_diff=*/Values(0.), - /*min_psnr=*/Values(99.))); +INSTANTIATE_TEST_SUITE_P(Identity8b, RGBToYUVTest, + Combine(/*rgb_depth=*/Values(8), + /*yuv_depth=*/Values(8, 10, 12), + ValuesIn(kAllRgbFormats), + Values(AVIF_PIXEL_FORMAT_YUV444), + Values(AVIF_RANGE_FULL), + Values(kMatrixCoefficientsIdentity), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), + /*add_noise=*/Values(true), + /*rgb_step=*/Values(31), + /*max_abs_average_diff=*/Values(0.), + /*min_psnr=*/Values(99.))); +INSTANTIATE_TEST_SUITE_P(Identity10b, RGBToYUVTest, + Combine(/*rgb_depth=*/Values(10), + /*yuv_depth=*/Values(10, 12), + ValuesIn(kAllRgbFormats), + Values(AVIF_PIXEL_FORMAT_YUV444), + Values(AVIF_RANGE_FULL), + Values(kMatrixCoefficientsIdentity), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), + /*add_noise=*/Values(true), + /*rgb_step=*/Values(101), + /*max_abs_average_diff=*/Values(0.), + /*min_psnr=*/Values(99.))); +INSTANTIATE_TEST_SUITE_P(Identity12b, RGBToYUVTest, + Combine(/*rgb_depth=*/Values(12), + /*yuv_depth=*/Values(12), + ValuesIn(kAllRgbFormats), + Values(AVIF_PIXEL_FORMAT_YUV444), + Values(AVIF_RANGE_FULL), + Values(kMatrixCoefficientsIdentity), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), + /*add_noise=*/Values(true), + /*rgb_step=*/Values(401), + /*max_abs_average_diff=*/Values(0.), + /*min_psnr=*/Values(99.))); // 4:4:4 and chroma subsampling have similar distortions on plain color inputs. INSTANTIATE_TEST_SUITE_P( @@ -383,7 +380,7 @@ Values(AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV422, AVIF_PIXEL_FORMAT_YUV420), Values(AVIF_RANGE_FULL), Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), /*add_noise=*/Values(false), /*rgb_step=*/Values(17), /*max_abs_average_diff=*/Values(0.02), // The color drift is centered. @@ -393,39 +390,42 @@ // Converting grey RGB samples to full-range monochrome of same or greater bit // depth should be lossless. -INSTANTIATE_TEST_SUITE_P( - MonochromeLossless8b, RGBToYUVTest, - Combine(/*rgb_depth=*/Values(8), - /*yuv_depth=*/Values(8, 10, 12), ValuesIn(kAllRgbFormats), - Values(AVIF_PIXEL_FORMAT_YUV400), Values(AVIF_RANGE_FULL), - Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), - /*add_noise=*/Values(false), - /*rgb_step=*/Values(1), - /*max_abs_average_diff=*/Values(0.), - /*min_psnr=*/Values(99.))); -INSTANTIATE_TEST_SUITE_P( - MonochromeLossless10b, RGBToYUVTest, - Combine(/*rgb_depth=*/Values(10), - /*yuv_depth=*/Values(10, 12), ValuesIn(kAllRgbFormats), - Values(AVIF_PIXEL_FORMAT_YUV400), Values(AVIF_RANGE_FULL), - Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), - /*add_noise=*/Values(false), - /*rgb_step=*/Values(1), - /*max_abs_average_diff=*/Values(0.), - /*min_psnr=*/Values(99.))); -INSTANTIATE_TEST_SUITE_P( - MonochromeLossless12b, RGBToYUVTest, - Combine(/*rgb_depth=*/Values(12), - /*yuv_depth=*/Values(12), ValuesIn(kAllRgbFormats), - Values(AVIF_PIXEL_FORMAT_YUV400), Values(AVIF_RANGE_FULL), - Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), - /*add_noise=*/Values(false), - /*rgb_step=*/Values(1), - /*max_abs_average_diff=*/Values(0.), - /*min_psnr=*/Values(99.))); +INSTANTIATE_TEST_SUITE_P(MonochromeLossless8b, RGBToYUVTest, + Combine(/*rgb_depth=*/Values(8), + /*yuv_depth=*/Values(8, 10, 12), + ValuesIn(kAllRgbFormats), + Values(AVIF_PIXEL_FORMAT_YUV400), + Values(AVIF_RANGE_FULL), + Values(kMatrixCoefficientsBT601), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), + /*add_noise=*/Values(false), + /*rgb_step=*/Values(1), + /*max_abs_average_diff=*/Values(0.), + /*min_psnr=*/Values(99.))); +INSTANTIATE_TEST_SUITE_P(MonochromeLossless10b, RGBToYUVTest, + Combine(/*rgb_depth=*/Values(10), + /*yuv_depth=*/Values(10, 12), + ValuesIn(kAllRgbFormats), + Values(AVIF_PIXEL_FORMAT_YUV400), + Values(AVIF_RANGE_FULL), + Values(kMatrixCoefficientsBT601), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), + /*add_noise=*/Values(false), + /*rgb_step=*/Values(1), + /*max_abs_average_diff=*/Values(0.), + /*min_psnr=*/Values(99.))); +INSTANTIATE_TEST_SUITE_P(MonochromeLossless12b, RGBToYUVTest, + Combine(/*rgb_depth=*/Values(12), + /*yuv_depth=*/Values(12), + ValuesIn(kAllRgbFormats), + Values(AVIF_PIXEL_FORMAT_YUV400), + Values(AVIF_RANGE_FULL), + Values(kMatrixCoefficientsBT601), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), + /*add_noise=*/Values(false), + /*rgb_step=*/Values(1), + /*max_abs_average_diff=*/Values(0.), + /*min_psnr=*/Values(99.))); // Can be used to print the drift of all RGB to YUV conversion possibilities. // Also used for coverage. @@ -437,7 +437,7 @@ Values(AVIF_PIXEL_FORMAT_YUV420), Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL), Values(kMatrixCoefficientsBT601, kMatrixCoefficientsBT709), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV), + Values(AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV), /*add_noise=*/Values(true), /*rgb_step=*/Values(17), /*max_abs_average_diff=*/Values(1.2), // Sharp YUV introduces some @@ -452,7 +452,7 @@ Values(AVIF_PIXEL_FORMAT_YUV420), Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL), Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV), + Values(AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV), /*add_noise=*/Values(true), /*rgb_step=*/Values(211), // High or it would be too slow. /*max_abs_average_diff=*/Values(1.2), // Sharp YUV introduces some @@ -467,7 +467,7 @@ Values(AVIF_PIXEL_FORMAT_YUV420), Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL), Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV), + Values(AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV), /*add_noise=*/Values(true), /*rgb_step=*/Values(840), // High or it would be too slow. /*max_abs_average_diff=*/Values(1.2), // Sharp YUV introduces some @@ -485,7 +485,7 @@ AVIF_PIXEL_FORMAT_YUV420), Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL), Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), /*add_noise=*/Values(false, true), /*rgb_step=*/Values(61), // High or it would be too slow. /*max_abs_average_diff=*/Values(1.), // Not very accurate because @@ -499,7 +499,7 @@ AVIF_PIXEL_FORMAT_YUV420), Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL), Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), /*add_noise=*/Values(false, true), /*rgb_step=*/Values(211), // High or it would be too slow. /*max_abs_average_diff=*/Values(0.2), // Not very accurate because @@ -513,7 +513,7 @@ AVIF_PIXEL_FORMAT_YUV420), Values(AVIF_RANGE_LIMITED, AVIF_RANGE_FULL), Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), /*add_noise=*/Values(false, true), /*rgb_step=*/Values(809), // High or it would be too slow. /*max_abs_average_diff=*/Values(0.3), // Not very accurate because @@ -532,7 +532,7 @@ AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400), Values(AVIF_RANGE_FULL, AVIF_RANGE_LIMITED), Values(kMatrixCoefficientsBT601), - /*avif_rgb_to_yuv_flags=*/Values(AVIF_RGB_TO_YUV_DEFAULT), + Values(AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC), /*add_noise=*/Values(false, true), /*rgb_step=*/Values(3), // way faster and 99% similar to rgb_step=1 /*max_abs_average_diff=*/Values(10.),
diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc index 25827b6..57d8929 100644 --- a/tests/gtest/aviftest_helpers.cc +++ b/tests/gtest/aviftest_helpers.cc
@@ -233,7 +233,7 @@ AvifImagePtr ReadImage(const char* folder_path, const char* file_name, avifPixelFormat requested_format, int requested_depth, - avifRGBToYUVFlags flags, avifBool ignore_icc, + avifChromaDownsampling flags, avifBool ignore_icc, avifBool ignore_exif, avifBool ignore_xmp) { testutil::AvifImagePtr image(avifImageCreateEmpty(), avifImageDestroy); if (!image ||
diff --git a/tests/gtest/aviftest_helpers.h b/tests/gtest/aviftest_helpers.h index 842b60d..a0b1c05 100644 --- a/tests/gtest/aviftest_helpers.h +++ b/tests/gtest/aviftest_helpers.h
@@ -75,7 +75,8 @@ AvifImagePtr ReadImage( const char* folder_path, const char* file_name, avifPixelFormat requested_format = AVIF_PIXEL_FORMAT_NONE, - int requested_depth = 0, avifRGBToYUVFlags flags = AVIF_RGB_TO_YUV_DEFAULT, + int requested_depth = 0, + avifChromaDownsampling flags = AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, avifBool ignore_icc = false, avifBool ignore_exif = false, avifBool ignore_xmp = false);
diff --git a/tests/oss-fuzz/avif_decode_fuzzer.cc b/tests/oss-fuzz/avif_decode_fuzzer.cc index 702185c..ba3f924 100644 --- a/tests/oss-fuzz/avif_decode_fuzzer.cc +++ b/tests/oss-fuzz/avif_decode_fuzzer.cc
@@ -8,7 +8,7 @@ static avifRGBFormat rgbFormats[] = { AVIF_RGB_FORMAT_RGB, AVIF_RGB_FORMAT_RGBA }; static size_t rgbFormatsCount = sizeof(rgbFormats) / sizeof(rgbFormats[0]); - static avifYUVToRGBFlags upsamplingFlags[] = { AVIF_CHROMA_UPSAMPLING_BILINEAR, AVIF_CHROMA_UPSAMPLING_NEAREST }; + static avifChromaUpsampling upsamplingFlags[] = { AVIF_CHROMA_UPSAMPLING_BILINEAR, AVIF_CHROMA_UPSAMPLING_NEAREST }; static size_t upsamplingFlagsCount = sizeof(upsamplingFlags) / sizeof(upsamplingFlags[0]); static uint32_t rgbDepths[] = { 8, 10 }; @@ -47,10 +47,11 @@ for (size_t rgbDepthsIndex = 0; rgbDepthsIndex < rgbDepthsCount; ++rgbDepthsIndex) { // Convert to RGB rgb.format = rgbFormats[rgbFormatsIndex]; + rgb.chromaUpsampling = upsamplingFlags[upsamplingFlagsIndex]; + rgb.avoidLibYUV = AVIF_TRUE; rgb.depth = rgbDepths[rgbDepthsIndex]; avifRGBImageAllocatePixels(&rgb); - avifResult rgbResult = - avifImageYUVToRGB(decoder->image, &rgb, AVIF_YUV_TO_RGB_AVOID_LIBYUV | upsamplingFlags[upsamplingFlagsIndex]); + avifResult rgbResult = avifImageYUVToRGB(decoder->image, &rgb); // Since avifImageRGBToYUV() ignores upsamplingFlags, we only need // to test avifImageRGBToYUV() with a single upsamplingFlagsIndex. if ((rgbResult == AVIF_RESULT_OK) && (upsamplingFlagsIndex == 0)) { @@ -60,7 +61,7 @@ decoder->image->height, yuvDepths[yuvDepthsIndex], decoder->image->yuvFormat); - avifResult yuvResult = avifImageRGBToYUV(tempImage, &rgb, AVIF_RGB_TO_YUV_AVOID_LIBYUV); + avifResult yuvResult = avifImageRGBToYUV(tempImage, &rgb); if (yuvResult != AVIF_RESULT_OK) { } avifImageDestroy(tempImage);