Skip to content

Commit

Permalink
Move oddness check to CropRectRequiresUpsampling()
Browse files Browse the repository at this point in the history
Remove yuvFormat arg from avifCropRectFromCleanApertureBox().
Make avifCropRectConvertCleanApertureBox() behave exactly as before.
Fix comments.
Update avifclaptest.
Print warnings in avifdec.
  • Loading branch information
y-guyon committed Nov 22, 2024
1 parent 0e4343c commit 73a91d8
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 49 deletions.
18 changes: 18 additions & 0 deletions apps/avifdec.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,24 @@ 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)) {
if (cropRect.x != 0 || cropRect.y != 0 || cropRect.width != decoder->image->width ||
cropRect.height != decoder->image->height) {
// TODO: Implement, see https://github.com/AOMediaCodec/libavif/issues/2427
fprintf(stderr, "Warning: Clean Aperture values were ignored, the output image was NOT cropped\n");
}
} else {
// 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.
Expand Down
4 changes: 2 additions & 2 deletions apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2290,10 +2290,9 @@ int main(int argc, char * argv[])

// Validate clap
avifCropRect cropRect;
avifBool upsampleBeforeCropping;
avifDiagnostics diag;
avifDiagnosticsClearError(&diag);
if (!avifCropRectFromCleanApertureBox(&cropRect, &upsampleBeforeCropping, &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,
Expand All @@ -2307,6 +2306,7 @@ int main(int argc, char * argv[])
diag.error);
goto cleanup;
}
// avifCropRectRequiresUpsampling() does not need to be called for clap validation.
}
if (irotAngle != 0xff) {
image->transformFlags |= AVIF_TRANSFORM_IROT;
Expand Down
5 changes: 2 additions & 3 deletions apps/shared/avifutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,14 @@ static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uin
avifBool upsampleBeforeCropping;
avifDiagnostics diag;
avifDiagnosticsClearError(&diag);
avifBool validClap =
avifCropRectFromCleanApertureBox(&cropRect, &upsampleBeforeCropping, &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%s\n",
cropRect.x,
cropRect.y,
cropRect.width,
cropRect.height,
upsampleBeforeCropping ? " (upsample before cropping)" : "");
avifCropRectRequiresUpsampling(&cropRect, avif->yuvFormat) ? " (upsample before cropping)" : "");
} else {
printf(" * Invalid: %s\n", diag.error);
}
Expand Down
7 changes: 3 additions & 4 deletions include/avif/avif.h
Original file line number Diff line number Diff line change
Expand Up @@ -546,21 +546,20 @@ 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.
// If upsampleBeforeCropping is true, the image must be upsampled from 4:2:0 or 4:2:2 to 4:4:4 before
// Clean Aperture values are applied.
AVIF_NODISCARD AVIF_API avifBool avifCropRectFromCleanApertureBox(avifCropRect * cropRect,
avifBool * upsampleBeforeCropping,
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);
// 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.
AVIF_NODISCARD AVIF_API avifBool avifCropRectRequiresUpsampling(const avifCropRect * cropRect, avifPixelFormat yuvFormat);

// Deprecated. Use avifCropRectFromCleanApertureBox() instead.
AVIF_NODISCARD AVIF_API avifBool
Expand Down
56 changes: 30 additions & 26 deletions src/avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,11 +750,9 @@ static avifBool avifCropRectIsValid(const avifCropRect * cropRect, uint32_t imag
}

avifBool avifCropRectFromCleanApertureBox(avifCropRect * cropRect,
avifBool * upsampleBeforeCropping,
const avifCleanApertureBox * clap,
uint32_t imageW,
uint32_t imageH,
avifPixelFormat yuvFormat,
avifDiagnostics * diag)
{
avifDiagnosticsClearError(diag);
Expand All @@ -764,16 +762,6 @@ avifBool avifCropRectFromCleanApertureBox(avifCropRect * cropRect,
// positive or negative. For cleanApertureWidth and cleanApertureHeight,
// N shall be positive and D shall be strictly positive.

// ISO/IEC 23000-22:2024, 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;
// - 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

const int32_t widthN = (int32_t)clap->widthN;
const int32_t widthD = (int32_t)clap->widthD;
const int32_t heightN = (int32_t)clap->heightN;
Expand All @@ -792,9 +780,6 @@ avifBool avifCropRectFromCleanApertureBox(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;
Expand Down Expand Up @@ -871,14 +856,21 @@ avifBool avifCropRectFromCleanApertureBox(avifCropRect * cropRect,
cropRect->y = (uint32_t)(cropY.n / cropY.d);
cropRect->width = (uint32_t)clapW;
cropRect->height = (uint32_t)clapH;
if (!avifCropRectIsValid(cropRect, imageW, imageH, diag)) {
return AVIF_FALSE;
}
return avifCropRectIsValid(cropRect, imageW, imageH, diag);
}

*upsampleBeforeCropping = ((yuvFormat == AVIF_PIXEL_FORMAT_YUV420 || yuvFormat == AVIF_PIXEL_FORMAT_YUV422) &&
(cropRect->width % 2 || cropRect->x % 2)) ||
(yuvFormat == AVIF_PIXEL_FORMAT_YUV420 && (cropRect->height % 2 || cropRect->y % 2));
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,
Expand All @@ -888,10 +880,22 @@ avifBool avifCropRectConvertCleanApertureBox(avifCropRect * cropRect,
avifPixelFormat yuvFormat,
avifDiagnostics * diag)
{
// Keep the same pre-deprecation behavior.
avifBool upsampleBeforeCropping;
return avifCropRectFromCleanApertureBox(cropRect, &upsampleBeforeCropping, clap, imageW, imageH, yuvFormat, diag) &&
!upsampleBeforeCropping;
if (avifCropRectFromCleanApertureBox(cropRect, clap, imageW, imageH, 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 AVIF_TRUE;
}
return AVIF_FALSE;
}

avifBool avifCleanApertureBoxConvertCropRect(avifCleanApertureBox * clap,
Expand Down
5 changes: 1 addition & 4 deletions src/read.c
Original file line number Diff line number Diff line change
Expand Up @@ -1277,12 +1277,9 @@ static avifResult avifDecoderItemValidateProperties(const avifDecoderItem * item
}

avifCropRect cropRect;
avifBool upsampleBeforeCropping;
const uint32_t imageW = ispeProp->u.ispe.width;
const uint32_t imageH = ispeProp->u.ispe.height;
const avifPixelFormat configFormat = avifCodecConfigurationBoxGetFormat(&configProp->u.av1C);
const avifBool validClap =
avifCropRectFromCleanApertureBox(&cropRect, &upsampleBeforeCropping, &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;
}
Expand Down
18 changes: 8 additions & 10 deletions tests/gtest/avifclaptest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,9 @@ INSTANTIATE_TEST_SUITE_P(Parameterized, InvalidClapPropertyTest,
TEST_P(InvalidClapPropertyTest, ValidateClapProperty) {
const InvalidClapPropertyParam& param = GetParam();
avifCropRect crop_rect;
avifBool upsampleBeforeCropping;
avifDiagnostics diag;
EXPECT_FALSE(avifCropRectFromCleanApertureBox(
&crop_rect, &upsampleBeforeCropping, &param.clap, param.width,
param.height, param.yuv_format, &diag));
&crop_rect, &param.clap, param.width, param.height, &diag));
}

struct ValidClapPropertyParam {
Expand Down Expand Up @@ -134,7 +132,7 @@ constexpr ValidClapPropertyParam kValidClapPropertyTestParams[] = {
AVIF_PIXEL_FORMAT_YUV420,
{99, 1, 99, 1, static_cast<uint32_t>(-1), 2, static_cast<uint32_t>(-1), 2},
{0, 0, 99, 99},
true},
false},
// pcX = -1/2 + (100 - 1)/2 = 49
// pcY = -1/2 + (100 - 1)/2 = 49
// leftmost = 49 - (99 - 1)/2 = 0
Expand All @@ -156,23 +154,23 @@ INSTANTIATE_TEST_SUITE_P(Parameterized, ValidClapPropertyTest,
TEST_P(ValidClapPropertyTest, ValidateClapProperty) {
const ValidClapPropertyParam& param = GetParam();
avifCropRect crop_rect;
avifBool upsampleBeforeCropping;
avifDiagnostics diag;
EXPECT_TRUE(avifCropRectFromCleanApertureBox(
&crop_rect, &upsampleBeforeCropping, &param.clap, param.width,
param.height, param.yuv_format, &diag))
ASSERT_TRUE(avifCropRectFromCleanApertureBox(
&crop_rect, &param.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(upsampleBeforeCropping, param.expected_upsample_before_cropping);
EXPECT_EQ(upsample_before_cropping, param.expected_upsample_before_cropping);

// Deprecated function coverage.
EXPECT_EQ(avifCropRectConvertCleanApertureBox(&crop_rect, &param.clap,
param.width, param.height,
param.yuv_format, &diag),
!upsampleBeforeCropping);
!upsample_before_cropping);
}

} // namespace

0 comments on commit 73a91d8

Please sign in to comment.