diff --git a/CHANGELOG.md b/CHANGELOG.md index 73e29ba8e2..f5eb5567d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,11 @@ The changes are relative to the previous release, unless the baseline is specifi * For dependencies, the deprecated way of setting AVIF_LOCAL_* to ON is removed. Dependency options can now only be set to OFF/LOCAL/SYSTEM. * Change the default quality for alpha to be the same as the quality for color. +* Allow decoding subsampled images with odd Clean Aperture dimensions or offsets. +* Deprecate avifCropRectConvertCleanApertureBox() and + avifCleanApertureBoxConvertCropRect(). Replace them with + avifCropRectFromCleanApertureBox() and avifCleanApertureBoxFromCropRect(). +* Add avifCropRectRequiresUpsampling(). ## [1.1.1] - 2024-07-30 diff --git a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc index 66f6e902dd..83eb3e3d93 100644 --- a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc +++ b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc @@ -85,11 +85,15 @@ bool CreateDecoderAndParse(AvifDecoderWrapper* const decoder, avifDiagnostics diag; // If the image does not have a valid 'clap' property, then we simply display // the whole image. + // TODO(vigneshv): Handle the case of avifCropRectRequiresUpsampling() + // returning true. if (!(decoder->decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) || - !avifCropRectConvertCleanApertureBox( + !avifCropRectFromCleanApertureBox( &decoder->crop, &decoder->decoder->image->clap, decoder->decoder->image->width, decoder->decoder->image->height, - decoder->decoder->image->yuvFormat, &diag)) { + &diag) || + avifCropRectRequiresUpsampling(&decoder->crop, + decoder->decoder->image->yuvFormat)) { decoder->crop.width = decoder->decoder->image->width; decoder->crop.height = decoder->decoder->image->height; decoder->crop.x = 0; @@ -414,9 +418,8 @@ FUNC(jstring, versionString) { libyuv_version[0] = '\0'; } char version_string[512]; - snprintf(version_string, sizeof(version_string), - "libavif: %s. Codecs: %s.%s", avifVersion(), - codec_versions, libyuv_version); + snprintf(version_string, sizeof(version_string), "libavif: %s. Codecs: %s.%s", + avifVersion(), codec_versions, libyuv_version); return env->NewStringUTF(version_string); } diff --git a/apps/avifdec.c b/apps/avifdec.c index f79c31f712..9c0e33cf68 100644 --- a/apps/avifdec.c +++ b/apps/avifdec.c @@ -347,6 +347,18 @@ int main(int argc, char * argv[]) printf("Image details:\n"); avifImageDump(decoder->image, 0, 0, decoder->progressiveState); + if (decoder->image->transformFlags & AVIF_TRANSFORM_CLAP) { + avifCropRect cropRect; + if (!avifCropRectFromCleanApertureBox(&cropRect, + &decoder->image->clap, + decoder->image->width, + decoder->image->height, + &decoder->diag)) { + // Should happen only if AVIF_STRICT_CLAP_VALID is disabled. + fprintf(stderr, "Warning: Invalid Clean Aperture values\n"); + } + } + if (ignoreICC && (decoder->image->icc.size > 0)) { printf("[--ignore-icc] Discarding ICC profile.\n"); // This cannot fail. diff --git a/apps/avifenc.c b/apps/avifenc.c index 66054c5e2a..f52ee00ade 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -388,7 +388,7 @@ static avifBool strpre(const char * str, const char * prefix) return strncmp(str, prefix, strlen(prefix)) == 0; } -static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, avifPixelFormat yuvFormat, uint32_t clapValues[8]) +static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, uint32_t clapValues[8]) { avifCleanApertureBox clap; avifCropRect cropRect; @@ -399,13 +399,12 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, avifPixelFormat avifDiagnostics diag; avifDiagnosticsClearError(&diag); - avifBool convertResult = avifCleanApertureBoxConvertCropRect(&clap, &cropRect, srcW, srcH, yuvFormat, &diag); + avifBool convertResult = avifCleanApertureBoxFromCropRect(&clap, &cropRect, srcW, srcH, &diag); if (!convertResult) { fprintf(stderr, - "ERROR: Impossible crop rect: imageSize:[%ux%u], pixelFormat:%s, cropRect:[%u,%u, %ux%u] - %s\n", + "ERROR: Impossible crop rect: imageSize:[%ux%u], cropRect:[%u,%u, %ux%u] - %s\n", srcW, srcH, - avifPixelFormatToString(yuvFormat), cropRect.x, cropRect.y, cropRect.width, @@ -2298,7 +2297,7 @@ int main(int argc, char * argv[]) image->pasp.vSpacing = settings.paspValues[1]; } if (cropConversionRequired) { - if (!convertCropToClap(image->width, image->height, image->yuvFormat, settings.clapValues)) { + if (!convertCropToClap(image->width, image->height, settings.clapValues)) { goto cleanup; } settings.clapValid = AVIF_TRUE; @@ -2318,7 +2317,7 @@ int main(int argc, char * argv[]) avifCropRect cropRect; avifDiagnostics diag; avifDiagnosticsClearError(&diag); - if (!avifCropRectConvertCleanApertureBox(&cropRect, &image->clap, image->width, image->height, image->yuvFormat, &diag)) { + if (!avifCropRectFromCleanApertureBox(&cropRect, &image->clap, image->width, image->height, &diag)) { fprintf(stderr, "ERROR: Invalid clap: width:[%d / %d], height:[%d / %d], horizOff:[%d / %d], vertOff:[%d / %d] - %s\n", (int32_t)image->clap.widthN, diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c index 5e10d11e88..98cfbc3b6e 100644 --- a/apps/shared/avifjpeg.c +++ b/apps/shared/avifjpeg.c @@ -1300,7 +1300,7 @@ avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int if (avif->transformFlags & AVIF_TRANSFORM_CLAP) { avifCropRect cropRect; avifDiagnostics diag; - if (avifCropRectConvertCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, avif->yuvFormat, &diag) && + if (avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag) && (cropRect.x != 0 || cropRect.y != 0 || cropRect.width != avif->width || cropRect.height != avif->height)) { // TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Implement. fprintf(stderr, diff --git a/apps/shared/avifpng.c b/apps/shared/avifpng.c index cb630bf176..08b33efec5 100644 --- a/apps/shared/avifpng.c +++ b/apps/shared/avifpng.c @@ -731,7 +731,7 @@ avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, uint3 if (avif->transformFlags & AVIF_TRANSFORM_CLAP) { avifCropRect cropRect; avifDiagnostics diag; - if (avifCropRectConvertCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, avif->yuvFormat, &diag) && + if (avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag) && (cropRect.x != 0 || cropRect.y != 0 || cropRect.width != avif->width || cropRect.height != avif->height)) { // TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Implement. fprintf(stderr, diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c index 2e6a756b30..d600ee8b9d 100644 --- a/apps/shared/avifutil.c +++ b/apps/shared/avifutil.c @@ -101,14 +101,14 @@ static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uin avifCropRect cropRect; avifDiagnostics diag; avifDiagnosticsClearError(&diag); - avifBool validClap = - avifCropRectConvertCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, avif->yuvFormat, &diag); + avifBool validClap = avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag); if (validClap) { - printf(" * Valid, derived crop rect: X: %d, Y: %d, W: %d, H: %d\n", + printf(" * Valid, derived crop rect: X: %d, Y: %d, W: %d, H: %d%s\n", cropRect.x, cropRect.y, cropRect.width, - cropRect.height); + cropRect.height, + avifCropRectRequiresUpsampling(&cropRect, avif->yuvFormat) ? " (upsample before cropping)" : ""); } else { printf(" * Invalid: %s\n", diag.error); } diff --git a/apps/shared/y4m.c b/apps/shared/y4m.c index f569122f9b..86094252af 100644 --- a/apps/shared/y4m.c +++ b/apps/shared/y4m.c @@ -496,7 +496,7 @@ avifBool y4mWrite(const char * outputFilename, const avifImage * avif) if (avif->transformFlags & AVIF_TRANSFORM_CLAP) { avifCropRect cropRect; avifDiagnostics diag; - if (avifCropRectConvertCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, avif->yuvFormat, &diag) && + if (avifCropRectFromCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, &diag) && (cropRect.x != 0 || cropRect.y != 0 || cropRect.width != avif->width || cropRect.height != avif->height)) { // TODO: https://github.com/AOMediaCodec/libavif/issues/2427 - Implement. fprintf(stderr, diff --git a/include/avif/avif.h b/include/avif/avif.h index db971b13fd..3d2ad8aafd 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -484,6 +484,10 @@ typedef struct avifPixelAspectRatioBox typedef struct avifCleanApertureBox { // 'clap' from ISO/IEC 14496-12:2022 12.1.4.3 + // Note that ISO/IEC 23000-22:2024 7.3.6.7 requires the decoded image to be upsampled to 4:4:4 before + // clean aperture is applied if a clean aperture size or offset is odd in a subsampled dimension. + // However, AV1 supports odd dimensions with chroma subsampling in those directions, so only apply the + // requirements to offsets. // a fractional number which defines the width of the clean aperture image uint32_t widthN; @@ -542,18 +546,27 @@ typedef struct avifCropRect // These will return AVIF_FALSE if the resultant values violate any standards, and if so, the output // values are not guaranteed to be complete or correct and should not be used. -AVIF_NODISCARD AVIF_API avifBool avifCropRectConvertCleanApertureBox(avifCropRect * cropRect, - const avifCleanApertureBox * clap, - uint32_t imageW, - uint32_t imageH, - avifPixelFormat yuvFormat, - avifDiagnostics * diag); -AVIF_NODISCARD AVIF_API avifBool avifCleanApertureBoxConvertCropRect(avifCleanApertureBox * clap, - const avifCropRect * cropRect, - uint32_t imageW, - uint32_t imageH, - avifPixelFormat yuvFormat, - avifDiagnostics * diag); +AVIF_NODISCARD AVIF_API avifBool avifCropRectFromCleanApertureBox(avifCropRect * cropRect, + const avifCleanApertureBox * clap, + uint32_t imageW, + uint32_t imageH, + avifDiagnostics * diag); +AVIF_NODISCARD AVIF_API avifBool avifCleanApertureBoxFromCropRect(avifCleanApertureBox * clap, + const avifCropRect * cropRect, + uint32_t imageW, + uint32_t imageH, + avifDiagnostics * diag); +// If this function returns true, the image must be upsampled from 4:2:0 or 4:2:2 to 4:4:4 before +// Clean Aperture values are applied. This can be done by converting the avifImage to RGB using +// avifImageYUVToRGB() and only using the cropRect region of the avifRGBImage. +AVIF_NODISCARD AVIF_API avifBool avifCropRectRequiresUpsampling(const avifCropRect * cropRect, avifPixelFormat yuvFormat); + +// Deprecated. Use avifCropRectFromCleanApertureBox() instead. +AVIF_NODISCARD AVIF_API avifBool +avifCropRectConvertCleanApertureBox(avifCropRect *, const avifCleanApertureBox *, uint32_t, uint32_t, avifPixelFormat, avifDiagnostics *); +// Deprecated. Use avifCleanApertureBoxFromCropRect() instead. +AVIF_NODISCARD AVIF_API avifBool +avifCleanApertureBoxConvertCropRect(avifCleanApertureBox *, const avifCropRect *, uint32_t, uint32_t, avifPixelFormat, avifDiagnostics *); // --------------------------------------------------------------------------- // avifContentLightLevelInformationBox @@ -1124,7 +1137,7 @@ typedef enum avifStrictFlag AVIF_STRICT_PIXI_REQUIRED = (1 << 0), // This demands that the values surfaced in the clap box are valid, determined by attempting to - // convert the clap box to a crop rect using avifCropRectConvertCleanApertureBox(). If this + // convert the clap box to a crop rect using avifCropRectFromCleanApertureBox(). If this // function returns AVIF_FALSE and this strict flag is set, the decode will fail. AVIF_STRICT_CLAP_VALID = (1 << 1), @@ -1297,6 +1310,10 @@ typedef struct avifDecoder // legal to call avifImageYUVToRGB() on this in between calls to avifDecoderNextImage(), but use // avifImageCopy() if you want to make a complete, permanent copy of this image's YUV content or // metadata. + // + // For each field among clap, irot and imir, if the corresponding avifTransformFlag is set, the + // transform must be applied before rendering or converting the image, or forwarded along as + // attached metadata. avifImage * image; // Counts and timing for the current image in an image sequence. Uninteresting for single image files. diff --git a/src/avif.c b/src/avif.c index b3339ae9de..c53b363bfd 100644 --- a/src/avif.c +++ b/src/avif.c @@ -749,19 +749,8 @@ static avifBool overflowsInt32(int64_t x) return (x < INT32_MIN) || (x > INT32_MAX); } -static avifBool avifCropRectIsValid(const avifCropRect * cropRect, uint32_t imageW, uint32_t imageH, avifPixelFormat yuvFormat, avifDiagnostics * diag) - +static avifBool avifCropRectIsValid(const avifCropRect * cropRect, uint32_t imageW, uint32_t imageH, avifDiagnostics * diag) { - // ISO/IEC 23000-22:2019/Amd. 2:2021, Section 7.3.6.7: - // The clean aperture property is restricted according to the chroma - // sampling format of the input image (4:4:4, 4:2:2:, 4:2:0, or 4:0:0) as - // follows: - // ... - // - If chroma is subsampled horizontally (i.e., 4:2:2 and 4:2:0), the - // leftmost pixel of the clean aperture shall be even numbers; - // - If chroma is subsampled vertically (i.e., 4:2:0), the topmost line - // of the clean aperture shall be even numbers. - if ((cropRect->width == 0) || (cropRect->height == 0)) { avifDiagnosticsPrintf(diag, "[Strict] crop rect width and height must be nonzero"); return AVIF_FALSE; @@ -771,28 +760,14 @@ static avifBool avifCropRectIsValid(const avifCropRect * cropRect, uint32_t imag avifDiagnosticsPrintf(diag, "[Strict] crop rect is out of the image's bounds"); return AVIF_FALSE; } - - if ((yuvFormat == AVIF_PIXEL_FORMAT_YUV420) || (yuvFormat == AVIF_PIXEL_FORMAT_YUV422)) { - if ((cropRect->x % 2) != 0) { - avifDiagnosticsPrintf(diag, "[Strict] crop rect X offset must be even due to this image's YUV subsampling"); - return AVIF_FALSE; - } - } - if (yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { - if ((cropRect->y % 2) != 0) { - avifDiagnosticsPrintf(diag, "[Strict] crop rect Y offset must be even due to this image's YUV subsampling"); - return AVIF_FALSE; - } - } return AVIF_TRUE; } -avifBool avifCropRectConvertCleanApertureBox(avifCropRect * cropRect, - const avifCleanApertureBox * clap, - uint32_t imageW, - uint32_t imageH, - avifPixelFormat yuvFormat, - avifDiagnostics * diag) +avifBool avifCropRectFromCleanApertureBox(avifCropRect * cropRect, + const avifCleanApertureBox * clap, + uint32_t imageW, + uint32_t imageH, + avifDiagnostics * diag) { avifDiagnosticsClearError(diag); @@ -819,9 +794,6 @@ avifBool avifCropRectConvertCleanApertureBox(avifCropRect * cropRect, } // ISO/IEC 23000-22:2019/Amd. 2:2021, Section 7.3.6.7: - // The clean aperture property is restricted according to the chroma - // sampling format of the input image (4:4:4, 4:2:2:, 4:2:0, or 4:0:0) as - // follows: // - cleanApertureWidth and cleanApertureHeight shall be integers; // - The leftmost pixel and the topmost line of the clean aperture as // defined in ISO/IEC 14496-12:2020, Section 12.1.4.1 shall be integers; @@ -898,19 +870,18 @@ avifBool avifCropRectConvertCleanApertureBox(avifCropRect * cropRect, cropRect->y = (uint32_t)(cropY.n / cropY.d); cropRect->width = (uint32_t)clapW; cropRect->height = (uint32_t)clapH; - return avifCropRectIsValid(cropRect, imageW, imageH, yuvFormat, diag); + return avifCropRectIsValid(cropRect, imageW, imageH, diag); } -avifBool avifCleanApertureBoxConvertCropRect(avifCleanApertureBox * clap, - const avifCropRect * cropRect, - uint32_t imageW, - uint32_t imageH, - avifPixelFormat yuvFormat, - avifDiagnostics * diag) +avifBool avifCleanApertureBoxFromCropRect(avifCleanApertureBox * clap, + const avifCropRect * cropRect, + uint32_t imageW, + uint32_t imageH, + avifDiagnostics * diag) { avifDiagnosticsClearError(diag); - if (!avifCropRectIsValid(cropRect, imageW, imageH, yuvFormat, diag)) { + if (!avifCropRectIsValid(cropRect, imageW, imageH, diag)) { return AVIF_FALSE; } @@ -965,6 +936,68 @@ avifBool avifCleanApertureBoxConvertCropRect(avifCleanApertureBox * clap, return AVIF_TRUE; } +avifBool avifCropRectRequiresUpsampling(const avifCropRect * cropRect, avifPixelFormat yuvFormat) +{ + // ISO/IEC 23000-22:2024 FDIS, Section 7.3.6.7: + // - If any of the following conditions hold true, the image is first implicitly upsampled to 4:4:4: + // - chroma is subsampled horizontally (i.e., 4:2:2 and 4:2:0) and cleanApertureWidth is odd + // - chroma is subsampled horizontally (i.e., 4:2:2 and 4:2:0) and left-most pixel is on an odd position + // - chroma is subsampled vertically (i.e., 4:2:0) and cleanApertureHeight is odd + // - chroma is subsampled vertically (i.e., 4:2:0) and topmost line is on an odd position + + // AV1 supports odd dimensions with chroma subsampling in those directions, so only look for x and y. + return ((yuvFormat == AVIF_PIXEL_FORMAT_YUV420 || yuvFormat == AVIF_PIXEL_FORMAT_YUV422) && (cropRect->x % 2)) || + (yuvFormat == AVIF_PIXEL_FORMAT_YUV420 && (cropRect->y % 2)); +} + +avifBool avifCropRectConvertCleanApertureBox(avifCropRect * cropRect, + const avifCleanApertureBox * clap, + uint32_t imageW, + uint32_t imageH, + avifPixelFormat yuvFormat, + avifDiagnostics * diag) +{ + if (!avifCropRectFromCleanApertureBox(cropRect, clap, imageW, imageH, diag)) { + return AVIF_FALSE; + } + // Keep the same pre-deprecation behavior. + + // ISO/IEC 23000-22:2019/Amd. 2:2021, Section 7.3.6.7: + // - If chroma is subsampled horizontally (i.e., 4:2:2 and 4:2:0), + // the leftmost pixel of the clean aperture shall be even numbers; + // - If chroma is subsampled vertically (i.e., 4:2:0), + // the topmost line of the clean aperture shall be even numbers. + + if (avifCropRectRequiresUpsampling(cropRect, yuvFormat)) { + avifDiagnosticsPrintf(diag, "[Strict] crop rect X and Y offsets must be even due to this image's YUV subsampling"); + return AVIF_FALSE; + } + return AVIF_TRUE; +} + +avifBool avifCleanApertureBoxConvertCropRect(avifCleanApertureBox * clap, + const avifCropRect * cropRect, + uint32_t imageW, + uint32_t imageH, + avifPixelFormat yuvFormat, + avifDiagnostics * diag) +{ + // Keep the same pre-deprecation behavior. + + // ISO/IEC 23000-22:2019/Amd. 2:2021, Section 7.3.6.7: + // - If chroma is subsampled horizontally (i.e., 4:2:2 and 4:2:0), + // the leftmost pixel of the clean aperture shall be even numbers; + // - If chroma is subsampled vertically (i.e., 4:2:0), + // the topmost line of the clean aperture shall be even numbers. + + if (avifCropRectRequiresUpsampling(cropRect, yuvFormat)) { + avifDiagnosticsPrintf(diag, "[Strict] crop rect X and Y offsets must be even due to this image's YUV subsampling"); + return AVIF_FALSE; + } + + return avifCleanApertureBoxFromCropRect(clap, cropRect, imageW, imageH, diag); +} + // --------------------------------------------------------------------------- avifBool avifIsAlpha(avifItemCategory itemCategory) diff --git a/src/read.c b/src/read.c index e1d1a2f036..a0455ca0e4 100644 --- a/src/read.c +++ b/src/read.c @@ -367,19 +367,6 @@ static uint32_t avifCodecConfigurationBoxGetDepth(const avifCodecConfigurationBo return 8; } -// This is used as a hint to validating the clap box in avifDecoderItemValidateProperties. -static avifPixelFormat avifCodecConfigurationBoxGetFormat(const avifCodecConfigurationBox * av1C) -{ - if (av1C->monochrome) { - return AVIF_PIXEL_FORMAT_YUV400; - } else if (av1C->chromaSubsamplingY == 1) { - return AVIF_PIXEL_FORMAT_YUV420; - } else if (av1C->chromaSubsamplingX == 1) { - return AVIF_PIXEL_FORMAT_YUV422; - } - return AVIF_PIXEL_FORMAT_YUV444; -} - static const avifPropertyArray * avifSampleTableGetProperties(const avifSampleTable * sampleTable, avifCodecType codecType) { for (uint32_t i = 0; i < sampleTable->sampleDescriptions.count; ++i) { @@ -1239,9 +1226,19 @@ static avifResult avifDecoderItemValidateProperties(const avifDecoderItem * item if (item->miniBoxPixelFormat != AVIF_PIXEL_FORMAT_NONE) { // This is a MinimizedImageBox ('mini'). - if (item->miniBoxPixelFormat != avifCodecConfigurationBoxGetFormat(&configProp->u.av1C)) { + avifPixelFormat av1CPixelFormat; + if (configProp->u.av1C.monochrome) { + av1CPixelFormat = AVIF_PIXEL_FORMAT_YUV400; + } else if (configProp->u.av1C.chromaSubsamplingY == 1) { + av1CPixelFormat = AVIF_PIXEL_FORMAT_YUV420; + } else if (configProp->u.av1C.chromaSubsamplingX == 1) { + av1CPixelFormat = AVIF_PIXEL_FORMAT_YUV422; + } else { + av1CPixelFormat = AVIF_PIXEL_FORMAT_YUV444; + } + if (item->miniBoxPixelFormat != av1CPixelFormat) { if (!memcmp(configPropName, "av2C", 4) && item->miniBoxPixelFormat == AVIF_PIXEL_FORMAT_YUV400 && - avifCodecConfigurationBoxGetFormat(&configProp->u.av1C) == AVIF_PIXEL_FORMAT_YUV420) { + av1CPixelFormat == AVIF_PIXEL_FORMAT_YUV420) { // avm does not handle monochrome as of research-v8.0.0. // 4:2:0 is used instead. } else { @@ -1250,7 +1247,7 @@ static avifResult avifDecoderItemValidateProperties(const avifDecoderItem * item item->id, avifPixelFormatToString(item->miniBoxPixelFormat), configPropName, - avifPixelFormatToString(avifCodecConfigurationBoxGetFormat(&configProp->u.av1C))); + avifPixelFormatToString(av1CPixelFormat)); return AVIF_RESULT_BMFF_PARSE_FAILED; } } @@ -1287,8 +1284,7 @@ static avifResult avifDecoderItemValidateProperties(const avifDecoderItem * item avifCropRect cropRect; const uint32_t imageW = ispeProp->u.ispe.width; const uint32_t imageH = ispeProp->u.ispe.height; - const avifPixelFormat configFormat = avifCodecConfigurationBoxGetFormat(&configProp->u.av1C); - avifBool validClap = avifCropRectConvertCleanApertureBox(&cropRect, &clapProp->u.clap, imageW, imageH, configFormat, diag); + const avifBool validClap = avifCropRectFromCleanApertureBox(&cropRect, &clapProp->u.clap, imageW, imageH, diag); if (!validClap) { return AVIF_RESULT_BMFF_PARSE_FAILED; } diff --git a/tests/gtest/avifclaptest.cc b/tests/gtest/avifclaptest.cc index 77dfaf26e2..e052290088 100644 --- a/tests/gtest/avifclaptest.cc +++ b/tests/gtest/avifclaptest.cc @@ -82,14 +82,13 @@ using InvalidClapPropertyTest = INSTANTIATE_TEST_SUITE_P(Parameterized, InvalidClapPropertyTest, ::testing::ValuesIn(kInvalidClapPropertyTestParams)); -// Negative tests for the avifCropRectConvertCleanApertureBox() function. +// Negative tests for the avifCropRectFromCleanApertureBox() function. TEST_P(InvalidClapPropertyTest, ValidateClapProperty) { const InvalidClapPropertyParam& param = GetParam(); avifCropRect crop_rect; avifDiagnostics diag; - EXPECT_FALSE(avifCropRectConvertCleanApertureBox(&crop_rect, ¶m.clap, - param.width, param.height, - param.yuv_format, &diag)); + EXPECT_FALSE(avifCropRectFromCleanApertureBox( + &crop_rect, ¶m.clap, param.width, param.height, &diag)); } struct ValidClapPropertyParam { @@ -99,6 +98,7 @@ struct ValidClapPropertyParam { avifCleanApertureBox clap; avifCropRect expected_crop_rect; + bool expected_upsample_before_cropping; }; constexpr ValidClapPropertyParam kValidClapPropertyTestParams[] = { @@ -110,7 +110,8 @@ constexpr ValidClapPropertyParam kValidClapPropertyTestParams[] = { 160, AVIF_PIXEL_FORMAT_YUV420, {96, 1, 132, 1, 0, 1, 0, 1}, - {12, 14, 96, 132}}, + {12, 14, 96, 132}, + false}, // pcX = -30 + (120 - 1)/2 = 29.5 // pcY = -40 + (160 - 1)/2 = 39.5 // leftmost = 29.5 - (60 - 1)/2 = 0 @@ -120,7 +121,8 @@ constexpr ValidClapPropertyParam kValidClapPropertyTestParams[] = { AVIF_PIXEL_FORMAT_YUV420, {60, 1, 80, 1, static_cast(-30), 1, static_cast(-40), 1}, - {0, 0, 60, 80}}, + {0, 0, 60, 80}, + false}, // pcX = -1/2 + (100 - 1)/2 = 49 // pcY = -1/2 + (100 - 1)/2 = 49 // leftmost = 49 - (99 - 1)/2 = 0 @@ -129,7 +131,18 @@ constexpr ValidClapPropertyParam kValidClapPropertyTestParams[] = { 100, AVIF_PIXEL_FORMAT_YUV420, {99, 1, 99, 1, static_cast(-1), 2, static_cast(-1), 2}, - {0, 0, 99, 99}}, + {0, 0, 99, 99}, + false}, + // pcX = 1/2 + (100 - 1)/2 = 50 + // pcY = 1/2 + (100 - 1)/2 = 50 + // leftmost = 50 - (99 - 1)/2 = 1 + // topmost = 50 - (99 - 1)/2 = 1 + {100, + 100, + AVIF_PIXEL_FORMAT_YUV420, + {99, 1, 99, 1, 1, 2, 1, 2}, + {1, 1, 99, 99}, + true}, }; using ValidClapPropertyTest = ::testing::TestWithParam; @@ -137,19 +150,63 @@ using ValidClapPropertyTest = ::testing::TestWithParam; INSTANTIATE_TEST_SUITE_P(Parameterized, ValidClapPropertyTest, ::testing::ValuesIn(kValidClapPropertyTestParams)); -// Positive tests for the avifCropRectConvertCleanApertureBox() function. +// Positive tests for the avifCropRectFromCleanApertureBox() and +// avifCleanApertureBoxFromCropRect() functions. TEST_P(ValidClapPropertyTest, ValidateClapProperty) { const ValidClapPropertyParam& param = GetParam(); avifCropRect crop_rect; avifDiagnostics diag; - EXPECT_TRUE(avifCropRectConvertCleanApertureBox(&crop_rect, ¶m.clap, - param.width, param.height, - param.yuv_format, &diag)) + ASSERT_TRUE(avifCropRectFromCleanApertureBox( + &crop_rect, ¶m.clap, param.width, param.height, &diag)) << diag.error; + const avifBool upsample_before_cropping = + avifCropRectRequiresUpsampling(&crop_rect, param.yuv_format); EXPECT_EQ(crop_rect.x, param.expected_crop_rect.x); EXPECT_EQ(crop_rect.y, param.expected_crop_rect.y); EXPECT_EQ(crop_rect.width, param.expected_crop_rect.width); EXPECT_EQ(crop_rect.height, param.expected_crop_rect.height); + EXPECT_EQ(upsample_before_cropping, param.expected_upsample_before_cropping); + + // Deprecated function coverage. + avifBool success = avifCropRectConvertCleanApertureBox( + &crop_rect, ¶m.clap, param.width, param.height, param.yuv_format, + &diag); + EXPECT_EQ(success, !upsample_before_cropping); + if (success) { + EXPECT_EQ(crop_rect.x, param.expected_crop_rect.x); + EXPECT_EQ(crop_rect.y, param.expected_crop_rect.y); + EXPECT_EQ(crop_rect.width, param.expected_crop_rect.width); + EXPECT_EQ(crop_rect.height, param.expected_crop_rect.height); + } + + avifCleanApertureBox clap; + ASSERT_TRUE(avifCleanApertureBoxFromCropRect( + &clap, ¶m.expected_crop_rect, param.width, param.height, &diag)) + << diag.error; + EXPECT_EQ(clap.widthN, param.clap.widthN); + EXPECT_EQ(clap.widthD, param.clap.widthD); + EXPECT_EQ(clap.heightN, param.clap.heightN); + EXPECT_EQ(clap.heightD, param.clap.heightD); + EXPECT_EQ(clap.horizOffN, param.clap.horizOffN); + EXPECT_EQ(clap.horizOffD, param.clap.horizOffD); + EXPECT_EQ(clap.vertOffN, param.clap.vertOffN); + EXPECT_EQ(clap.vertOffD, param.clap.vertOffD); + + // Deprecated function coverage. + success = avifCleanApertureBoxConvertCropRect( + &clap, ¶m.expected_crop_rect, param.width, param.height, + param.yuv_format, &diag); + EXPECT_EQ(success, !upsample_before_cropping); + if (success) { + EXPECT_EQ(clap.widthN, param.clap.widthN); + EXPECT_EQ(clap.widthD, param.clap.widthD); + EXPECT_EQ(clap.heightN, param.clap.heightN); + EXPECT_EQ(clap.heightD, param.clap.heightD); + EXPECT_EQ(clap.horizOffN, param.clap.horizOffN); + EXPECT_EQ(clap.horizOffD, param.clap.horizOffD); + EXPECT_EQ(clap.vertOffN, param.clap.vertOffN); + EXPECT_EQ(clap.vertOffD, param.clap.vertOffD); + } } } // namespace