-
Notifications
You must be signed in to change notification settings - Fork 212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ignore descr props after transf props #2571
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2731,15 +2731,21 @@ static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_ | |
|
||
uint8_t associationCount; | ||
AVIF_CHECKERR(avifROStreamRead(&s, &associationCount, 1), AVIF_RESULT_BMFF_PARSE_FAILED); | ||
avifBool transformativePropertySeen = AVIF_FALSE; | ||
for (uint8_t associationIndex = 0; associationIndex < associationCount; ++associationIndex) { | ||
uint8_t essential; | ||
AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &essential, /*bitCount=*/1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) essential; | ||
uint32_t propertyIndex; | ||
AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &propertyIndex, /*bitCount=*/propertyIndexIsU15 ? 15 : 7), | ||
AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(7/15) property_index; | ||
|
||
// ISO/IEC 14496-12 Section 8.11.14.3: | ||
// 0 indicating that no property is associated (the essential indicator shall also be 0) | ||
wantehchang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (propertyIndex == 0) { | ||
// Not associated with any item | ||
if (essential) { | ||
avifDiagnosticsPrintf(diag, "Box[ipma] for item ID [%u] contains an illegal essential property index 0", itemID); | ||
return AVIF_RESULT_BMFF_PARSE_FAILED; | ||
} | ||
continue; | ||
} | ||
--propertyIndex; // 1-indexed | ||
|
@@ -2756,6 +2762,23 @@ static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_ | |
// Copy property to item | ||
const avifProperty * srcProp = &meta->properties.prop[propertyIndex]; | ||
|
||
// ISO/IEC 23000-22:2019/Amd. 2:2021 Section 7.3.9: | ||
// All transformative properties associated with coded and derived images shall be marked as essential, | ||
// and shall be from the set defined in 7.3.6.7 or the applicable MIAF profile. No other essential | ||
// transformative property shall be associated with such images. | ||
const avifBool isTransformative = !memcmp(srcProp->type, "clap", 4) || !memcmp(srcProp->type, "irot", 4) || | ||
!memcmp(srcProp->type, "imir", 4); | ||
// ISO/IEC 23008-12:2022 Section 3.1.28: | ||
// item property: descriptive or transformative information | ||
const avifBool isDescriptive = !isTransformative; | ||
// ISO/IEC 23008-12:2022 Section 6.5.1: | ||
// Readers shall allow and ignore descriptive properties following the first transformative or | ||
// unrecognized property, whichever is earlier, in the sequence associating properties with an item. | ||
// No need to check for unrecognized properties as they cannot be transformative according to MIAF. | ||
if (transformativePropertySeen && isDescriptive) { | ||
continue; | ||
} | ||
|
||
// Some properties are supported and parsed by libavif. | ||
// Other properties are forwarded to the user as opaque blobs. | ||
const avifBool supportedType = !srcProp->isOpaque; | ||
|
@@ -2790,7 +2813,16 @@ static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_ | |
"a1op", | ||
|
||
// HEIF: Section 6.5.11.1: "essential shall be equal to 1 for an 'lsel' item property." | ||
"lsel" | ||
"lsel", | ||
|
||
// MIAF 2019/Amd. 2:2021: Section 7.3.9: | ||
// All transformative properties associated with coded and derived images shall be | ||
// marked as essential | ||
// It makes no sense to allow for non-essential crop/orientation associated with an item | ||
// that is not a coded or derived image, so for simplicity 'item' is not checked here. | ||
"clap", | ||
"irot", | ||
"imir" | ||
|
||
}; | ||
size_t essentialTypesCount = sizeof(essentialTypes) / sizeof(essentialTypes[0]); | ||
|
@@ -2811,6 +2843,15 @@ static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_ | |
*dstProp = *srcProp; | ||
} else { | ||
if (essential) { | ||
// ISO/IEC 23008-12 Section 10.2.1: | ||
// Under any brand, the primary item (or an alternative if alternative support is required) | ||
// shall be processable by a reader implementing only the required features of that brand. | ||
// Specifically, given that each brand has a set of properties that a reader is required to | ||
// support: the item shall not have properties that are marked as essential and are outside | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [This should be fixed in HEIF.] The colon (:) after "support" should be a comma (,). |
||
// this set. | ||
// It is assumed that this rule also applies to items the primary item depends on (such as | ||
// the cells of a grid). | ||
|
||
// Discovered an essential item property that libavif doesn't support! | ||
// Make a note to ignore this item later. | ||
item->hasUnsupportedEssentialProperty = AVIF_TRUE; | ||
|
@@ -2825,6 +2866,11 @@ static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_ | |
AVIF_CHECKRES( | ||
avifRWDataSet(&dstProp->u.opaque.boxPayload, srcProp->u.opaque.boxPayload.data, srcProp->u.opaque.boxPayload.size)); | ||
} | ||
|
||
if (isTransformative) { | ||
AVIF_ASSERT_OR_RETURN(supportedType); | ||
transformativePropertySeen = AVIF_TRUE; | ||
} | ||
} | ||
} | ||
return AVIF_RESULT_OK; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,27 @@ License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LIC | |
Source: `avifenc circle-trns-after-plte.png` with custom properties added in | ||
`avifRWStreamWriteProperties()`: FullBox `1234`, Box `abcd` and Box `uuid`. | ||
|
||
### File [clap_irot_imir_non_essential.avif](clap_irot_imir_non_essential.avif) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is the Compliance Warden report. Does it meet your expectations? The only issue I see is that Compliance Warden should use the Amendment 2 version of [miaf][Rule #25] Section 7.3.9.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes. Thank you for checking.
Section 7.3.9 is part of w18260 "Revised text of ISO/IEC FDIS 23000-22 Multi-Image Application Format". Are you saying Amd2 was published since then and there is no need to refer to an FDIS output document? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Section 7.3.9 is in both ISO/IEC 23000-22:2019 and ISO/IEC 23000-22:2019/Amd. 2:2021. In ISO/IEC 23000-22:2019, the paragraph reads:
In ISO/IEC 23000-22:2019/Amd. 2:2021, the paragraph is modified and reads:
So Compliance Warden quotes the older version of the paragraph in the error message. It is better to quote the newer version of the paragraph in the error message. In general Compliance Warden should not refer to an FDIS output document. |
||
|
||
![](clap_irot_imir_non_essential.avif) | ||
|
||
License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) | ||
|
||
Source: File generated with `TransformTest.ClapIrotImir` from | ||
`aviftransformtest.cc`, where `clap`, `irot` and `imir` are tagged as | ||
non-essential. | ||
|
||
### File [clop_irot_imor.avif](clop_irot_imor.avif) | ||
y-guyon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
![](clop_irot_imor.avif) | ||
|
||
License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) | ||
|
||
Source: File generated with `TransformTest.ClapIrotImir` from | ||
`aviftransformtest.cc`, where essential `clap` property was replaced by made-up | ||
non-essential `clop` property and essential `imir` property was replaced by | ||
made-up non-essential `imor` property. | ||
|
||
### File [draw_points.png](draw_points.png) | ||
|
||
![](draw_points.png) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// Copyright 2025 Google LLC | ||
// SPDX-License-Identifier: BSD-2-Clause | ||
|
||
#include "avif/avif.h" | ||
#include "aviftest_helpers.h" | ||
#include "gtest/gtest.h" | ||
|
||
namespace avif { | ||
namespace { | ||
|
||
// Used to pass the data folder path to the GoogleTest suites. | ||
const char* data_path = nullptr; | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
TEST(TransformTest, ClapIrotImir) { | ||
if (!testutil::Av1EncoderAvailable() || !testutil::Av1DecoderAvailable()) { | ||
GTEST_SKIP() << "AV1 codec unavailable, skip test."; | ||
} | ||
|
||
avifDiagnostics diag{}; | ||
ImagePtr image = | ||
testutil::CreateImage(/*width=*/12, /*height=*/34, /*depth=*/10, | ||
AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL); | ||
ASSERT_NE(image, nullptr); | ||
testutil::FillImageGradient(image.get()); // The pixels do not matter. | ||
|
||
image->transformFlags |= AVIF_TRANSFORM_CLAP; | ||
const avifCropRect rect{/*x=*/4, /*y=*/6, /*width=*/8, /*height=*/10}; | ||
ASSERT_TRUE(avifCleanApertureBoxFromCropRect( | ||
&image->clap, &rect, image->width, image->height, &diag)); | ||
|
||
image->transformFlags |= AVIF_TRANSFORM_IROT; | ||
image->irot.angle = 1; | ||
image->transformFlags |= AVIF_TRANSFORM_IMIR; | ||
image->imir.axis = 1; | ||
|
||
// Encode. | ||
EncoderPtr encoder(avifEncoderCreate()); | ||
ASSERT_NE(encoder, nullptr); | ||
encoder->speed = AVIF_SPEED_FASTEST; | ||
testutil::AvifRwData encoded; | ||
ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded), | ||
AVIF_RESULT_OK); | ||
|
||
// Decode. | ||
ImagePtr decoded(avifImageCreateEmpty()); | ||
ASSERT_NE(decoded, nullptr); | ||
DecoderPtr decoder(avifDecoderCreate()); | ||
ASSERT_NE(decoder, nullptr); | ||
ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), encoded.data, | ||
encoded.size), | ||
AVIF_RESULT_OK); | ||
|
||
EXPECT_EQ(decoded->transformFlags, image->transformFlags); | ||
EXPECT_EQ(decoded->clap.widthN, image->clap.widthN); | ||
EXPECT_EQ(decoded->clap.widthD, image->clap.widthD); | ||
EXPECT_EQ(decoded->clap.heightN, image->clap.heightN); | ||
EXPECT_EQ(decoded->clap.heightD, image->clap.heightD); | ||
EXPECT_EQ(decoded->clap.horizOffN, image->clap.horizOffN); | ||
EXPECT_EQ(decoded->clap.horizOffD, image->clap.horizOffD); | ||
EXPECT_EQ(decoded->clap.vertOffN, image->clap.vertOffN); | ||
EXPECT_EQ(decoded->clap.vertOffD, image->clap.vertOffD); | ||
EXPECT_EQ(decoded->irot.angle, image->irot.angle); | ||
EXPECT_EQ(decoded->imir.axis, image->imir.axis); | ||
} | ||
|
||
TEST(TransformTest, ClapIrotImirNonEssential) { | ||
// Invalid file with non-essential transformative properties. | ||
const std::string path = | ||
std::string(data_path) + "clap_irot_imir_non_essential.avif"; | ||
DecoderPtr decoder(avifDecoderCreate()); | ||
ASSERT_NE(decoder, nullptr); | ||
ASSERT_EQ(avifDecoderSetIOFile(decoder.get(), path.c_str()), AVIF_RESULT_OK); | ||
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_BMFF_PARSE_FAILED); | ||
} | ||
|
||
TEST(TransformTest, ClopIrotImor) { | ||
// File with a non-essential unrecognized property 'clop', an essential | ||
// transformation property 'irot', and a non-essential unrecognized property | ||
// 'imor'. | ||
const std::string path = std::string(data_path) + "clop_irot_imor.avif"; | ||
DecoderPtr decoder(avifDecoderCreate()); | ||
ASSERT_NE(decoder, nullptr); | ||
ASSERT_EQ(avifDecoderSetIOFile(decoder.get(), path.c_str()), AVIF_RESULT_OK); | ||
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); | ||
|
||
// 'imor' shall be ignored by libavif as it is after a transformative property | ||
// in the 'ipma' association order. | ||
ASSERT_EQ(decoder->image->numProperties, 1u); | ||
const avifImageItemProperty& clop = decoder->image->properties[0]; | ||
EXPECT_EQ(std::string(clop.boxtype, clop.boxtype + 4), "clop"); | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
} // namespace | ||
} // namespace avif | ||
|
||
int main(int argc, char** argv) { | ||
::testing::InitGoogleTest(&argc, argv); | ||
if (argc != 2) { | ||
std::cerr << "There must be exactly one argument containing the path to " | ||
"the test data folder" | ||
<< std::endl; | ||
return 1; | ||
} | ||
avif::data_path = argv[1]; | ||
return RUN_ALL_TESTS(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@farindk FYI.
We should audit the AVIF encoders in libavif and libheif and make sure they generate an
ItemPropertyAssociationBox
that meets the requirements checked in this pull request:property_index
is 0,essential
shall also be 0.ItemPropertyAssociationBox
? Otherwise they will be ignored by readers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should always be the case in libheif.
libheif never generates property_index=0. Is there any potential use case for this that I have missed?
I will have to check this: strukturag/libheif#1443
BTW: what is the rationale for this rule? I cannot think about any case where it would be important to have the descriptive items before the transformative items.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dirk: Thanks for the quick reply!
I also asked this question. Please see Yannis's reply in #2571 (comment).
This comes from the following in ISO/IEC 23008-12:2022 Section 6.5.1:
Note that it says "allow and ignore", so it isn't a hard rule. I don't know what is the rationale is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if a reader may ignore properties that are in the "wrong order", it effectively forces the writer to use the "correct order".
I also think that "Readers shall allow and ignore descriptive properties following the first transformative or unrecognized property" does not work well in practice because when encoders add new descriptive properties that are unknown to old readers, readers may ignore all descriptive properties following that new property, even if the new property was not essential (but a successive old property may be essential). I.e. images may be decoded differently by adding a non-essential property.
I thought that this is what the
essential
flag is for. When the reader sees an essential property that it does not understand, it knows that the decoded output might be incorrect. Non-essential properties can be ignored.The separation of transformative and descriptive properties on-the-other hand makes little sense to me. Even more so since that means that the set of transformative properties may never be extended in future standards as old decoders have no way to classify them as transformative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not "may" but "shall ignore". To me it is a hard rule.
I disagree: MIAF* forbids non-essential transformative properties, and descriptive properties should not impact decoding.
*and maybe HEIF? that is unclear to me because it depends on profiles if I remember correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
colr
should not be a descriptive property. It probably should be marked as essential. Sincecolr
is widely supported, whether it is marked as essential or not doesn't really matter in practice.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this also seems to be non-consistent. HEIF says in 6.5.1 that "Descriptive properties are non-essential, unless stated otherwise in their specification".
According to HEIF,
colr
is not essential, but in all files I looked at, it was marked as essential.In fact, many of the descriptive properties affect how the decoded image should look like. Also boxes like
pasp
. Thus, we might need some clarification whatessential
really means.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dirk: Do you have the access to view MPEGGroup/FileFormat#113? The following problematic requirment in ISO/IEC 23008-12:2022 (HEIF) Clause 6.5.1 will be removed in Amd2 of HEIF:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know MPEGGroup/FileFormat#113, but the point of my last comment is a bit different:
if all properties that influence how a decoded image will look like, should be marked
essential
, then more properties should be essential. Not justcolr
(which is usually markedessential
despite the standard telling otherwise), but also, for examplepasp
, which might indicate a stretching at the decoder for the image to be displayed in the correct aspect ratio.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding:
There is also a note:
"NOTE Each property descriptor definition below specifies whether the property is mandatory, i.e. whether the property is required to be present in the file. Note that when a property is mandatory, it might or might not be essential."
My understanding:
essential=1
means the player has to understand and apply the property.essential
, that is orthogonal.essential
only mean apply the property).So it is somewhat clear to me. I agree it could be constrained further to remove the possibility of non-essential
colr
, which makes rendering non-deterministic depending on the player's choice.This is aligned with the permissive spirit of HEIF in my opinion. I suggest filing an issue to have all render-impacting descriptive properties be essential in MIAF, much like transformative properties.