From 816a298784088bb113408d29abfcfa721e35c6e6 Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Thu, 29 Aug 2024 17:32:41 +0200 Subject: [PATCH 1/9] Add properties to avifImage --- include/avif/avif.h | 16 ++++++++++++ include/avif/internal.h | 8 ++++++ src/avif.c | 55 +++++++++++++++++++++++++++++++++++++++++ src/read.c | 44 ++++++++++++++++++++++++++++++--- src/stream.c | 4 ++- 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/include/avif/avif.h b/include/avif/avif.h index f3d87a8ea9..7f80f242fa 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -743,6 +743,18 @@ typedef enum avifSampleTransformRecipe } avifSampleTransformRecipe; #endif // AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM +// --------------------------------------------------------------------------- +// Opaque image item properties + +typedef struct avifImageItemProperty +{ + char boxtype[4]; // boxtype as defined in ISO/IEC 14496-12. + uint8_t usertype[16]; // Universally Unique IDentifier as defined in + // IETF RFC 4122 and ISO/IEC 9834-8. + // Undefined unless boxtype is "uuid". + avifRWData boxpayload; // BoxPayload as defined in ISO/IEC 14496-12. +} avifImageItemProperty; + // --------------------------------------------------------------------------- // avifImage @@ -809,6 +821,10 @@ typedef struct avifImage // Version 1.0.0 ends here. Add any new members after this line. + // Opaque image item properties found at decoding. Ignored at encoding. + avifImageItemProperty * properties; // Must be null if numProperties is 0. Must be non-null otherwise. + size_t numProperties; + #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) // Gain map image and metadata. NULL if no gain map is present. // Owned by the avifImage and gets freed when calling avifImageDestroy(). diff --git a/include/avif/internal.h b/include/avif/internal.h index c044716ab6..e947d5ccb1 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -149,6 +149,13 @@ void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage); // Ignores the gainMap field (which exists only if AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP is defined). void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes); +// Appends an opaque image item property. +AVIF_API avifResult avifImagePushProperty(avifImage * image, + const uint8_t boxtype[4], + const uint8_t usertype[16], + const uint8_t * boxpayload, + size_t boxpayloadLength); + // --------------------------------------------------------------------------- #if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM) @@ -644,6 +651,7 @@ typedef struct avifBoxHeader size_t size; uint8_t type[4]; + uint8_t usertype[16]; // Undefined unless |type| is "uuid". } avifBoxHeader; typedef struct avifROStream diff --git a/src/avif.c b/src/avif.c index a81885800f..285fd0a5e6 100644 --- a/src/avif.c +++ b/src/avif.c @@ -228,6 +228,31 @@ void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avif } } +static avifResult avifImageCopyProperties(avifImage * dstImage, const avifImage * srcImage) +{ + for (size_t i = 0; i < dstImage->numProperties; ++i) { + avifRWDataFree(&dstImage->properties[i].boxpayload); + } + avifFree(dstImage->properties); + dstImage->properties = NULL; + dstImage->numProperties = 0; + + if (srcImage->numProperties != 0) { + dstImage->properties = avifAlloc(srcImage->numProperties * sizeof(srcImage->properties[0])); + AVIF_CHECKERR(dstImage->properties != NULL, AVIF_RESULT_OUT_OF_MEMORY); + memset(dstImage->properties, 0, srcImage->numProperties * sizeof(srcImage->properties[0])); + dstImage->numProperties = srcImage->numProperties; + for (size_t i = 0; i < srcImage->numProperties; ++i) { + memcpy(dstImage->properties[i].boxtype, srcImage->properties[i].boxtype, sizeof(srcImage->properties[i].boxtype)); + memcpy(dstImage->properties[i].usertype, srcImage->properties[i].usertype, sizeof(srcImage->properties[i].usertype)); + AVIF_CHECKRES(avifRWDataSet(&dstImage->properties[i].boxpayload, + srcImage->properties[i].boxpayload.data, + srcImage->properties[i].boxpayload.size)); + } + } + return AVIF_RESULT_OK; +} + avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes) { avifImageFreePlanes(dstImage, AVIF_PLANES_ALL); @@ -238,6 +263,8 @@ avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifP AVIF_CHECKRES(avifRWDataSet(&dstImage->exif, srcImage->exif.data, srcImage->exif.size)); AVIF_CHECKRES(avifImageSetMetadataXMP(dstImage, srcImage->xmp.data, srcImage->xmp.size)); + AVIF_CHECKRES(avifImageCopyProperties(dstImage, srcImage)); + if ((planes & AVIF_PLANES_YUV) && srcImage->yuvPlanes[AVIF_CHAN_Y]) { if ((srcImage->yuvFormat != AVIF_PIXEL_FORMAT_YUV400) && (!srcImage->yuvPlanes[AVIF_CHAN_U] || !srcImage->yuvPlanes[AVIF_CHAN_V])) { @@ -343,6 +370,11 @@ void avifImageDestroy(avifImage * image) avifRWDataFree(&image->icc); avifRWDataFree(&image->exif); avifRWDataFree(&image->xmp); + for (size_t i = 0; i < image->numProperties; ++i) { + avifRWDataFree(&image->properties[i].boxpayload); + } + avifFree(image->properties); + image->properties = NULL; avifFree(image); } @@ -356,6 +388,29 @@ avifResult avifImageSetMetadataXMP(avifImage * image, const uint8_t * xmp, size_ return avifRWDataSet(&image->xmp, xmp, xmpSize); } +avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t usertype[16], const uint8_t * boxpayload, size_t boxpayloadLength) +{ + AVIF_CHECKERR(image->numProperties < SIZE_MAX, AVIF_RESULT_INVALID_ARGUMENT); + // Shallow copy the current properties. + const size_t numProperties = image->numProperties + 1; + avifImageItemProperty * const properties = avifAlloc(numProperties * sizeof(properties[0])); + AVIF_CHECKERR(properties != NULL, AVIF_RESULT_OUT_OF_MEMORY); + if (image->numProperties != 0) { + memcpy(properties, image->properties, image->numProperties * sizeof(properties[0])); + } + // Free the old array and replace it by the new one. + avifFree(image->properties); + image->properties = properties; + image->numProperties = numProperties; + // Set the new property. + avifImageItemProperty * const property = &image->properties[image->numProperties - 1]; + memset(property, 0, sizeof(*property)); + memcpy(property->boxtype, boxtype, sizeof(property->boxtype)); + memcpy(property->usertype, usertype, sizeof(property->usertype)); + AVIF_CHECKRES(avifRWDataSet(&property->boxpayload, boxpayload, boxpayloadLength)); + return AVIF_RESULT_OK; +} + avifResult avifImageAllocatePlanes(avifImage * image, avifPlanesFlags planes) { if (image->width == 0 || image->height == 0) { diff --git a/src/read.c b/src/read.c index 5c8b68ac55..e468d9e41a 100644 --- a/src/read.c +++ b/src/read.c @@ -139,6 +139,12 @@ typedef struct avifAV1LayeredImageIndexingProperty uint32_t layerSize[3]; } avifAV1LayeredImageIndexingProperty; +typedef struct avifOpaqueProperty +{ + uint8_t usertype[16]; // Same as in avifImageItemProperty. + avifRWData boxpayload; // Same as in avifImageItemProperty. +} avifOpaqueProperty; + // --------------------------------------------------------------------------- // Top-level structures @@ -148,6 +154,7 @@ struct avifMeta; typedef struct avifProperty { uint8_t type[4]; + avifBool isOpaque; union { avifImageSpatialExtents ispe; @@ -163,6 +170,7 @@ typedef struct avifProperty avifLayerSelectorProperty lsel; avifAV1LayeredImageIndexingProperty a1lx; avifContentLightLevelInformationBox clli; + avifOpaqueProperty opaque; } u; } avifProperty; AVIF_ARRAY_DECLARE(avifPropertyArray, avifProperty, prop); @@ -298,12 +306,22 @@ static avifSampleTable * avifSampleTableCreate(void) return sampleTable; } +static void avifPropertyArrayDestroy(avifPropertyArray * array) +{ + for (size_t i = 0; i < array->count; ++i) { + if (array->prop[i].isOpaque) { + avifRWDataFree(&array->prop[i].u.opaque.boxpayload); + } + } + avifArrayDestroy(array); +} + static void avifSampleTableDestroy(avifSampleTable * sampleTable) { avifArrayDestroy(&sampleTable->chunks); for (uint32_t i = 0; i < sampleTable->sampleDescriptions.count; ++i) { avifSampleDescription * description = &sampleTable->sampleDescriptions.description[i]; - avifArrayDestroy(&description->properties); + avifPropertyArrayDestroy(&description->properties); } avifArrayDestroy(&sampleTable->sampleDescriptions); avifArrayDestroy(&sampleTable->sampleToChunks); @@ -769,7 +787,7 @@ static void avifMetaDestroy(avifMeta * meta) { for (uint32_t i = 0; i < meta->items.count; ++i) { avifDecoderItem * item = meta->items.item[i]; - avifArrayDestroy(&item->properties); + avifPropertyArrayDestroy(&item->properties); avifArrayDestroy(&item->extents); if (item->ownsMergedExtents) { avifRWDataFree(&item->mergedExtents); @@ -777,7 +795,7 @@ static void avifMetaDestroy(avifMeta * meta) avifFree(item); } avifArrayDestroy(&meta->items); - avifArrayDestroy(&meta->properties); + avifPropertyArrayDestroy(&meta->properties); avifRWDataFree(&meta->idat); #if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM) avifArrayDestroy(&meta->sampleTransformExpression); @@ -823,7 +841,7 @@ static avifResult avifMetaFindOrCreateItem(avifMeta * meta, uint32_t itemID, avi return AVIF_RESULT_OUT_OF_MEMORY; } if (!avifArrayCreate(&(*item)->extents, sizeof(avifExtent), 1)) { - avifArrayDestroy(&(*item)->properties); + avifPropertyArrayDestroy(&(*item)->properties); avifFree(*item); *item = NULL; avifArrayPop(&meta->items); @@ -2567,6 +2585,7 @@ static avifResult avifParseItemPropertyContainerBox(avifPropertyArray * properti avifProperty * prop = (avifProperty *)avifArrayPush(properties); AVIF_CHECKERR(prop != NULL, AVIF_RESULT_OUT_OF_MEMORY); memcpy(prop->type, header.type, 4); + prop->isOpaque = AVIF_FALSE; if (!memcmp(header.type, "ispe", 4)) { AVIF_CHECKERR(avifParseImageSpatialExtentsProperty(prop, avifROStreamCurrent(&s), header.size, diag), AVIF_RESULT_BMFF_PARSE_FAILED); @@ -2606,6 +2625,11 @@ static avifResult avifParseItemPropertyContainerBox(avifPropertyArray * properti AVIF_RESULT_BMFF_PARSE_FAILED); } else if (!memcmp(header.type, "clli", 4)) { AVIF_CHECKRES(avifParseContentLightLevelInformationBox(prop, avifROStreamCurrent(&s), header.size, diag)); + } else { + prop->isOpaque = AVIF_TRUE; + memset(&prop->u.opaque, 0, sizeof(prop->u.opaque)); + memcpy(prop->u.opaque.usertype, header.usertype, sizeof(prop->u.opaque.usertype)); + AVIF_CHECKRES(avifRWDataSet(&prop->u.opaque.boxpayload, avifROStreamCurrent(&s), header.size)); } AVIF_CHECKERR(avifROStreamSkip(&s, header.size), AVIF_RESULT_BMFF_PARSE_FAILED); @@ -6020,6 +6044,18 @@ avifResult avifDecoderReset(avifDecoder * decoder) AVIF_CHECKRES(avifReadCodecConfigProperty(decoder->image, colorProperties, colorCodecType)); + // Expose as raw bytes all other properties that libavif does not care about. + for (size_t i = 0; i < colorProperties->count; ++i) { + const avifProperty * property = &colorProperties->prop[i]; + if (property->isOpaque) { + AVIF_CHECKRES(avifImagePushProperty(decoder->image, + property->type, + property->u.opaque.usertype, + property->u.opaque.boxpayload.data, + property->u.opaque.boxpayload.size)); + } + } + return AVIF_RESULT_OK; } diff --git a/src/stream.c b/src/stream.c index f220fed127..a3265d5da2 100644 --- a/src/stream.c +++ b/src/stream.c @@ -257,7 +257,9 @@ avifBool avifROStreamReadBoxHeaderPartial(avifROStream * stream, avifBoxHeader * } if (!memcmp(header->type, "uuid", 4)) { - AVIF_CHECK(avifROStreamSkip(stream, 16)); // unsigned int(8) usertype[16] = extended_type; + AVIF_CHECK(avifROStreamRead(stream, header->usertype, 16)); // unsigned int(8) usertype[16] = extended_type; + } else { + memset(header->usertype, 0, sizeof(header->usertype)); } size_t bytesRead = stream->offset - startOffset; From ecc7d7d95956bfe16d63a05c58013639040f5482 Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Fri, 4 Oct 2024 11:27:09 +0200 Subject: [PATCH 2/9] Apply comments --- include/avif/avif.h | 6 +++--- include/avif/internal.h | 6 +++--- src/avif.c | 14 +++++++------- src/read.c | 10 +++++----- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/avif/avif.h b/include/avif/avif.h index 7f80f242fa..21d2ce1849 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -748,11 +748,11 @@ typedef enum avifSampleTransformRecipe typedef struct avifImageItemProperty { - char boxtype[4]; // boxtype as defined in ISO/IEC 14496-12. + uint8_t boxtype[4]; // boxtype as defined in ISO/IEC 14496-12. uint8_t usertype[16]; // Universally Unique IDentifier as defined in // IETF RFC 4122 and ISO/IEC 9834-8. - // Undefined unless boxtype is "uuid". - avifRWData boxpayload; // BoxPayload as defined in ISO/IEC 14496-12. + // Unused unless boxtype is "uuid". + avifRWData boxPayload; // BoxPayload as defined in ISO/IEC 14496-12. } avifImageItemProperty; // --------------------------------------------------------------------------- diff --git a/include/avif/internal.h b/include/avif/internal.h index e947d5ccb1..d47eabfd9c 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -153,8 +153,8 @@ void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avif AVIF_API avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t usertype[16], - const uint8_t * boxpayload, - size_t boxpayloadLength); + const uint8_t * boxPayload, + size_t boxPayloadLength); // --------------------------------------------------------------------------- @@ -651,7 +651,7 @@ typedef struct avifBoxHeader size_t size; uint8_t type[4]; - uint8_t usertype[16]; // Undefined unless |type| is "uuid". + uint8_t usertype[16]; // Unused unless |type| is "uuid". } avifBoxHeader; typedef struct avifROStream diff --git a/src/avif.c b/src/avif.c index 285fd0a5e6..69dc0f4a90 100644 --- a/src/avif.c +++ b/src/avif.c @@ -231,7 +231,7 @@ void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avif static avifResult avifImageCopyProperties(avifImage * dstImage, const avifImage * srcImage) { for (size_t i = 0; i < dstImage->numProperties; ++i) { - avifRWDataFree(&dstImage->properties[i].boxpayload); + avifRWDataFree(&dstImage->properties[i].boxPayload); } avifFree(dstImage->properties); dstImage->properties = NULL; @@ -245,9 +245,9 @@ static avifResult avifImageCopyProperties(avifImage * dstImage, const avifImage for (size_t i = 0; i < srcImage->numProperties; ++i) { memcpy(dstImage->properties[i].boxtype, srcImage->properties[i].boxtype, sizeof(srcImage->properties[i].boxtype)); memcpy(dstImage->properties[i].usertype, srcImage->properties[i].usertype, sizeof(srcImage->properties[i].usertype)); - AVIF_CHECKRES(avifRWDataSet(&dstImage->properties[i].boxpayload, - srcImage->properties[i].boxpayload.data, - srcImage->properties[i].boxpayload.size)); + AVIF_CHECKRES(avifRWDataSet(&dstImage->properties[i].boxPayload, + srcImage->properties[i].boxPayload.data, + srcImage->properties[i].boxPayload.size)); } } return AVIF_RESULT_OK; @@ -371,7 +371,7 @@ void avifImageDestroy(avifImage * image) avifRWDataFree(&image->exif); avifRWDataFree(&image->xmp); for (size_t i = 0; i < image->numProperties; ++i) { - avifRWDataFree(&image->properties[i].boxpayload); + avifRWDataFree(&image->properties[i].boxPayload); } avifFree(image->properties); image->properties = NULL; @@ -388,7 +388,7 @@ avifResult avifImageSetMetadataXMP(avifImage * image, const uint8_t * xmp, size_ return avifRWDataSet(&image->xmp, xmp, xmpSize); } -avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t usertype[16], const uint8_t * boxpayload, size_t boxpayloadLength) +avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t usertype[16], const uint8_t * boxPayload, size_t boxPayloadLength) { AVIF_CHECKERR(image->numProperties < SIZE_MAX, AVIF_RESULT_INVALID_ARGUMENT); // Shallow copy the current properties. @@ -407,7 +407,7 @@ avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], co memset(property, 0, sizeof(*property)); memcpy(property->boxtype, boxtype, sizeof(property->boxtype)); memcpy(property->usertype, usertype, sizeof(property->usertype)); - AVIF_CHECKRES(avifRWDataSet(&property->boxpayload, boxpayload, boxpayloadLength)); + AVIF_CHECKRES(avifRWDataSet(&property->boxPayload, boxPayload, boxPayloadLength)); return AVIF_RESULT_OK; } diff --git a/src/read.c b/src/read.c index e468d9e41a..e97da611c2 100644 --- a/src/read.c +++ b/src/read.c @@ -142,7 +142,7 @@ typedef struct avifAV1LayeredImageIndexingProperty typedef struct avifOpaqueProperty { uint8_t usertype[16]; // Same as in avifImageItemProperty. - avifRWData boxpayload; // Same as in avifImageItemProperty. + avifRWData boxPayload; // Same as in avifImageItemProperty. } avifOpaqueProperty; // --------------------------------------------------------------------------- @@ -310,7 +310,7 @@ static void avifPropertyArrayDestroy(avifPropertyArray * array) { for (size_t i = 0; i < array->count; ++i) { if (array->prop[i].isOpaque) { - avifRWDataFree(&array->prop[i].u.opaque.boxpayload); + avifRWDataFree(&array->prop[i].u.opaque.boxPayload); } } avifArrayDestroy(array); @@ -2629,7 +2629,7 @@ static avifResult avifParseItemPropertyContainerBox(avifPropertyArray * properti prop->isOpaque = AVIF_TRUE; memset(&prop->u.opaque, 0, sizeof(prop->u.opaque)); memcpy(prop->u.opaque.usertype, header.usertype, sizeof(prop->u.opaque.usertype)); - AVIF_CHECKRES(avifRWDataSet(&prop->u.opaque.boxpayload, avifROStreamCurrent(&s), header.size)); + AVIF_CHECKRES(avifRWDataSet(&prop->u.opaque.boxPayload, avifROStreamCurrent(&s), header.size)); } AVIF_CHECKERR(avifROStreamSkip(&s, header.size), AVIF_RESULT_BMFF_PARSE_FAILED); @@ -6051,8 +6051,8 @@ avifResult avifDecoderReset(avifDecoder * decoder) AVIF_CHECKRES(avifImagePushProperty(decoder->image, property->type, property->u.opaque.usertype, - property->u.opaque.boxpayload.data, - property->u.opaque.boxpayload.size)); + property->u.opaque.boxPayload.data, + property->u.opaque.boxPayload.size)); } } From 77f72d474a2d2267c2779579f55e2c067ac0ac80 Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Fri, 4 Oct 2024 14:44:38 +0200 Subject: [PATCH 3/9] Add avifpropertytest Add circle_custom_properties.avif. Adapt avifParseItemPropertyAssociation(). --- include/avif/avif.h | 4 +- src/read.c | 38 +++++--------- tests/CMakeLists.txt | 1 + tests/data/README.md | 9 ++++ tests/data/circle_custom_properties.avif | Bin 0 -> 1065 bytes tests/gtest/avifpropertytest.cc | 63 +++++++++++++++++++++++ 6 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 tests/data/circle_custom_properties.avif create mode 100644 tests/gtest/avifpropertytest.cc diff --git a/include/avif/avif.h b/include/avif/avif.h index 21d2ce1849..49af9f01a1 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -749,10 +749,10 @@ typedef enum avifSampleTransformRecipe typedef struct avifImageItemProperty { uint8_t boxtype[4]; // boxtype as defined in ISO/IEC 14496-12. - uint8_t usertype[16]; // Universally Unique IDentifier as defined in - // IETF RFC 4122 and ISO/IEC 9834-8. + uint8_t usertype[16]; // Universally Unique IDentifier as defined in IETF RFC 4122 and ISO/IEC 9834-8. // Unused unless boxtype is "uuid". avifRWData boxPayload; // BoxPayload as defined in ISO/IEC 14496-12. + // Starts with the version and flags fields in case of a FullBox. } avifImageItemProperty; // --------------------------------------------------------------------------- diff --git a/src/read.c b/src/read.c index e97da611c2..178d2666c5 100644 --- a/src/read.c +++ b/src/read.c @@ -2708,32 +2708,9 @@ static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_ // Copy property to item const avifProperty * srcProp = &meta->properties.prop[propertyIndex]; - static const char * supportedTypes[] = { - "ispe", - "auxC", - "colr", - "av1C", -#if defined(AVIF_CODEC_AVM) - "av2C", -#endif - "pasp", - "clap", - "irot", - "imir", - "pixi", - "a1op", - "lsel", - "a1lx", - "clli" - }; - size_t supportedTypesCount = sizeof(supportedTypes) / sizeof(supportedTypes[0]); - avifBool supportedType = AVIF_FALSE; - for (size_t i = 0; i < supportedTypesCount; ++i) { - if (!memcmp(srcProp->type, supportedTypes[i], 4)) { - supportedType = AVIF_TRUE; - break; - } - } + // Some properties are supported and parsed by libavif. + // Other properties are forwarded to the user as opaque blobs. + const avifBool supportedType = !srcProp->isOpaque; if (supportedType) { if (essential) { // Verify that it is legal for this property to be flagged as essential. Any @@ -2790,6 +2767,15 @@ static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_ // Make a note to ignore this item later. item->hasUnsupportedEssentialProperty = AVIF_TRUE; } + + // Will be forwarded to the user through avifImage::properties. + avifProperty * dstProp = (avifProperty *)avifArrayPush(&item->properties); + AVIF_CHECKERR(dstProp != NULL, AVIF_RESULT_OUT_OF_MEMORY); + dstProp->isOpaque = AVIF_TRUE; + memcpy(dstProp->type, srcProp->type, sizeof(dstProp->type)); + memcpy(dstProp->u.opaque.usertype, srcProp->u.opaque.usertype, sizeof(dstProp->u.opaque.usertype)); + AVIF_CHECKRES( + avifRWDataSet(&dstProp->u.opaque.boxPayload, srcProp->u.opaque.boxPayload.data, srcProp->u.opaque.boxPayload.size)); } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 702739c099..6dc12e0625 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -138,6 +138,7 @@ if(AVIF_ENABLE_GTEST) add_avif_gtest(avifopaquetest) add_avif_gtest_with_data(avifpng16bittest) add_avif_gtest_with_data(avifprogressivetest) + add_avif_gtest_with_data(avifpropertytest) add_avif_gtest(avifrangetest) add_avif_gtest_with_data(avifreadimagetest) add_avif_internal_gtest(avifrgbtest) diff --git a/tests/data/README.md b/tests/data/README.md index d424f072a6..d095fe27f8 100644 --- a/tests/data/README.md +++ b/tests/data/README.md @@ -22,6 +22,15 @@ chunk. Since the PNG specification version 1.2 says "the tRNS chunk [...] must follow the PLTE chunk, if any", libpng considers the tRNS chunk as invalid and ignores it. +### File [circle_custom_properties.avif](circle_custom_properties.avif) + +![](circle_custom_properties.avif) + +License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) + +Source: `avifenc circle-trns-after-plte.png` with custom properties added in +`avifRWStreamWriteProperties()`: FullBox `1234`, Box `abcd` and Box `uuid`. + ### File [draw_points.png](draw_points.png) ![](draw_points.png) diff --git a/tests/data/circle_custom_properties.avif b/tests/data/circle_custom_properties.avif new file mode 100644 index 0000000000000000000000000000000000000000..0f29c78c38add79fb2da2f0b0a4c2fc403c4e61f GIT binary patch literal 1065 zcmZQzU{FXasVqn=%S>Yc0uY^>nP!-qnV9D5Xz0kmz<4k>wImTF2Ly^4DLF+DCIdr3 zW^xIP4Ws!AGD~v797Z6ilbMsB4C1*kFfcKIM1YvdA4uH-^1(dDmp~e1wNqwh9@q(B z87Uwq6{G~DBeBfD&^bRRA8a#11}F`Zam*>mNCc^r$}CESn83immsnbn1GW=n>%Yu` zq5>d&DYGCsA4rR278j%fX%I*OVjCb9Ff=kY0SPcNF|zo1_t5e{G6h^hUYC8@c^RzNkGIhlz? zl~#!`KdWRGM;&=Y*v9nd2Jy~IIEcQth#l9?j%X8*Z zm!IX$)0sl=3^#Aw?-)Jt4*Pxm6>BO=l1#o{lHRdZ%7^d%b*WOIEu0o0TbQJH6^u?X z3o9`57#wQ2{_oPYwwPjf>%I`z-!F>yO_dH_cB^6XX`Q3TI}W}w^*HuQYxZt7$M;!> zelA|Q(Ct8Zd5hW9gS~FkPV^PVFWK6Bl(+N53#KW5Hu8&z?&|h$5B_5@H^s5&*{4On zV!C%$yC41CV{4i0ysKNkeXabiwbDxbN2g|gopN+z?0_H8nPmM>8jTA z#lGQpH_KO@=2HK{ryiyC`Eik=gyykD?+s?DyiuDJo%+D^@9TSW-?jt?vA)smIQ>JL U<^N%3j?ldmr7r0H3Y+u_0O1aJ<^TWy literal 0 HcmV?d00001 diff --git a/tests/gtest/avifpropertytest.cc b/tests/gtest/avifpropertytest.cc new file mode 100644 index 0000000000..107fa2e671 --- /dev/null +++ b/tests/gtest/avifpropertytest.cc @@ -0,0 +1,63 @@ +// Copyright 2024 Google LLC +// SPDX-License-Identifier: BSD-2-Clause + +#include +#include +#include +#include + +#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(AvifPropertyTest, Parse) { + const std::string path = + std::string(data_path) + "circle_custom_properties.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); + ASSERT_EQ(decoder->image->numProperties, 3u); + + const avifImageItemProperty& p1234 = decoder->image->properties[0]; + EXPECT_EQ(std::string(p1234.boxtype, p1234.boxtype + 4), "1234"); + EXPECT_EQ(std::vector(p1234.boxPayload.data, + p1234.boxPayload.data + p1234.boxPayload.size), + std::vector({/*version*/ 0, /*flags*/ 0, 0, 0, + /*FullBoxPayload*/ 1, 2, 3, 4})); + + const avifImageItemProperty& abcd = decoder->image->properties[1]; + EXPECT_EQ(std::string(abcd.boxtype, abcd.boxtype + 4), "abcd"); + EXPECT_EQ(std::string(reinterpret_cast(abcd.boxPayload.data)), + "abcd"); + + const avifImageItemProperty& uuid = decoder->image->properties[2]; + EXPECT_EQ(std::string(uuid.boxtype, uuid.boxtype + 4), "uuid"); + EXPECT_EQ(std::string(uuid.usertype, uuid.usertype + 16), "extended_type 16"); + EXPECT_EQ(uuid.boxPayload.size, 0); +} + +//------------------------------------------------------------------------------ + +} // 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(); +} From 213f331331236d8baebf06127e578554a7f4a678 Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Fri, 4 Oct 2024 15:11:57 +0200 Subject: [PATCH 4/9] Fuzz avifImage::properties --- tests/gtest/avif_fuzztest_dec.cc | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/gtest/avif_fuzztest_dec.cc b/tests/gtest/avif_fuzztest_dec.cc index 2aeeef516a..dcdffec827 100644 --- a/tests/gtest/avif_fuzztest_dec.cc +++ b/tests/gtest/avif_fuzztest_dec.cc @@ -2,6 +2,7 @@ // SPDX-License-Identifier: BSD-2-Clause // Decodes an arbitrary sequence of bytes. +#include #include #include "avif/avif.h" @@ -27,15 +28,24 @@ void Decode(const std::string& arbitrary_bytes, bool is_persistent, ImagePtr decoded(avifImageCreateEmpty()); ASSERT_NE(decoded, nullptr); - avifIO* const io = avifIOCreateMemoryReader( - reinterpret_cast(arbitrary_bytes.data()), - arbitrary_bytes.size()); + const uint8_t* data = + reinterpret_cast(arbitrary_bytes.data()); + avifIO* const io = avifIOCreateMemoryReader(data, arbitrary_bytes.size()); if (io == nullptr) return; // The Chrome's avifIO object is not persistent. io->persistent = is_persistent; avifDecoderSetIO(decoder.get(), io); if (avifDecoderParse(decoder.get()) != AVIF_RESULT_OK) return; + + for (size_t i = 0; i < decoder->image->numProperties; ++i) { + const avifRWData& boxPayload = decoder->image->properties[i].boxPayload; + // Each custom property should be found as is in the input bitstream. + EXPECT_NE(std::search(data, data + arbitrary_bytes.size(), boxPayload.data, + boxPayload.data + boxPayload.size), + data + arbitrary_bytes.size()); + } + while (avifDecoderNextImage(decoder.get()) == AVIF_RESULT_OK) { EXPECT_GT(decoder->image->width, 0u); EXPECT_GT(decoder->image->height, 0u); From 1013db2d8a9b900c99654ff0277a87f6c38e6f1f Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Fri, 4 Oct 2024 15:24:16 +0200 Subject: [PATCH 5/9] Reword properties comment in avif.h --- include/avif/avif.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/avif/avif.h b/include/avif/avif.h index 49af9f01a1..96323c38d8 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -821,8 +821,10 @@ typedef struct avifImage // Version 1.0.0 ends here. Add any new members after this line. - // Opaque image item properties found at decoding. Ignored at encoding. - avifImageItemProperty * properties; // Must be null if numProperties is 0. Must be non-null otherwise. + // Other properties attached to this image item (primary or gainmap). + // At decoding: Forwarded here as opaque byte sequences by the avifDecoder. + // At encoding: Ignored. + avifImageItemProperty * properties; // Defined if numProperties is at least 1. size_t numProperties; #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) From 59bbcc35baffcfcc5c9f06e85daa25d65e72091b Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Thu, 7 Nov 2024 16:57:03 +0100 Subject: [PATCH 6/9] Reword comments Use a stronger check in avifImagePushProperty(). --- include/avif/avif.h | 6 +++--- src/avif.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/avif/avif.h b/include/avif/avif.h index 96323c38d8..3c41476568 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -750,9 +750,9 @@ typedef struct avifImageItemProperty { uint8_t boxtype[4]; // boxtype as defined in ISO/IEC 14496-12. uint8_t usertype[16]; // Universally Unique IDentifier as defined in IETF RFC 4122 and ISO/IEC 9834-8. - // Unused unless boxtype is "uuid". + // Used only when boxtype is "uuid". avifRWData boxPayload; // BoxPayload as defined in ISO/IEC 14496-12. - // Starts with the version and flags fields in case of a FullBox. + // Starts with the version (1 byte) and flags (3 bytes) fields in case of a FullBox. } avifImageItemProperty; // --------------------------------------------------------------------------- @@ -824,7 +824,7 @@ typedef struct avifImage // Other properties attached to this image item (primary or gainmap). // At decoding: Forwarded here as opaque byte sequences by the avifDecoder. // At encoding: Ignored. - avifImageItemProperty * properties; // Defined if numProperties is at least 1. + avifImageItemProperty * properties; // NULL only if numProperties is 0. size_t numProperties; #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) diff --git a/src/avif.c b/src/avif.c index 69dc0f4a90..fdc3d1d61d 100644 --- a/src/avif.c +++ b/src/avif.c @@ -390,7 +390,7 @@ avifResult avifImageSetMetadataXMP(avifImage * image, const uint8_t * xmp, size_ avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t usertype[16], const uint8_t * boxPayload, size_t boxPayloadLength) { - AVIF_CHECKERR(image->numProperties < SIZE_MAX, AVIF_RESULT_INVALID_ARGUMENT); + AVIF_CHECKERR(image->numProperties < SIZE_MAX / sizeof(avifImageItemProperty), AVIF_RESULT_INVALID_ARGUMENT); // Shallow copy the current properties. const size_t numProperties = image->numProperties + 1; avifImageItemProperty * const properties = avifAlloc(numProperties * sizeof(properties[0])); From 9ecd87ea1143238a496e7debb64b4700f4e4513c Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Fri, 8 Nov 2024 11:53:38 +0100 Subject: [PATCH 7/9] Add CHANGELOG entry [skip ci] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32636083ad..a0d8101c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ The changes are relative to the previous release, unless the baseline is specifi * Add avif(Un)SignedFraction structs and avifDoubleTo(Un)SignedFraction utility functions. +### Added since 1.1.1 +* Add the properties and numProperties fields to avifImage. They are filled by + the avifDecoder instance with the properties unrecognized by libavif. + ## [1.1.1] - 2024-07-30 ### Changed since 1.1.0 From 77998bc61768b41a2485443e8382d6430545fd58 Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Fri, 15 Nov 2024 10:10:42 +0100 Subject: [PATCH 8/9] Move Added section up in CHANGELOG [skip ci] --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0d8101c86..76f226a82d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ The changes are relative to the previous release, unless the baseline is specifi ## [Unreleased] +### Added since 1.1.1 +* Add the properties and numProperties fields to avifImage. They are filled by + the avifDecoder instance with the properties unrecognized by libavif. + ### Changed since 1.1.1 * avifenc: Allow large images to be encoded. * Fix empty CMAKE_CXX_FLAGS_RELEASE if -DAVIF_CODEC_AOM=LOCAL -DAVIF_LIBYUV=OFF @@ -30,10 +34,6 @@ The changes are relative to the previous release, unless the baseline is specifi * Add avif(Un)SignedFraction structs and avifDoubleTo(Un)SignedFraction utility functions. -### Added since 1.1.1 -* Add the properties and numProperties fields to avifImage. They are filled by - the avifDecoder instance with the properties unrecognized by libavif. - ## [1.1.1] - 2024-07-30 ### Changed since 1.1.0 From 65806a57dcd2c36d1764ce6362f69690666c734d Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Fri, 15 Nov 2024 10:19:07 +0100 Subject: [PATCH 9/9] Apply nits --- include/avif/avif.h | 1 + include/avif/internal.h | 2 +- src/avif.c | 9 +++++---- tests/gtest/avif_fuzztest_dec.cc | 6 +++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/avif/avif.h b/include/avif/avif.h index 3c41476568..ceda2b67d5 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -746,6 +746,7 @@ typedef enum avifSampleTransformRecipe // --------------------------------------------------------------------------- // Opaque image item properties +// This struct represents an opaque ItemProperty (Box) or ItemFullProperty (FullBox) in ISO/IEC 14496-12. typedef struct avifImageItemProperty { uint8_t boxtype[4]; // boxtype as defined in ISO/IEC 14496-12. diff --git a/include/avif/internal.h b/include/avif/internal.h index d47eabfd9c..26e7e50689 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -154,7 +154,7 @@ AVIF_API avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t usertype[16], const uint8_t * boxPayload, - size_t boxPayloadLength); + size_t boxPayloadSize); // --------------------------------------------------------------------------- diff --git a/src/avif.c b/src/avif.c index fdc3d1d61d..93c46033e6 100644 --- a/src/avif.c +++ b/src/avif.c @@ -238,7 +238,7 @@ static avifResult avifImageCopyProperties(avifImage * dstImage, const avifImage dstImage->numProperties = 0; if (srcImage->numProperties != 0) { - dstImage->properties = avifAlloc(srcImage->numProperties * sizeof(srcImage->properties[0])); + dstImage->properties = (avifImageItemProperty *)avifAlloc(srcImage->numProperties * sizeof(srcImage->properties[0])); AVIF_CHECKERR(dstImage->properties != NULL, AVIF_RESULT_OUT_OF_MEMORY); memset(dstImage->properties, 0, srcImage->numProperties * sizeof(srcImage->properties[0])); dstImage->numProperties = srcImage->numProperties; @@ -375,6 +375,7 @@ void avifImageDestroy(avifImage * image) } avifFree(image->properties); image->properties = NULL; + image->numProperties = 0; avifFree(image); } @@ -388,12 +389,12 @@ avifResult avifImageSetMetadataXMP(avifImage * image, const uint8_t * xmp, size_ return avifRWDataSet(&image->xmp, xmp, xmpSize); } -avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t usertype[16], const uint8_t * boxPayload, size_t boxPayloadLength) +avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t usertype[16], const uint8_t * boxPayload, size_t boxPayloadSize) { AVIF_CHECKERR(image->numProperties < SIZE_MAX / sizeof(avifImageItemProperty), AVIF_RESULT_INVALID_ARGUMENT); // Shallow copy the current properties. const size_t numProperties = image->numProperties + 1; - avifImageItemProperty * const properties = avifAlloc(numProperties * sizeof(properties[0])); + avifImageItemProperty * const properties = (avifImageItemProperty *)avifAlloc(numProperties * sizeof(properties[0])); AVIF_CHECKERR(properties != NULL, AVIF_RESULT_OUT_OF_MEMORY); if (image->numProperties != 0) { memcpy(properties, image->properties, image->numProperties * sizeof(properties[0])); @@ -407,7 +408,7 @@ avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], co memset(property, 0, sizeof(*property)); memcpy(property->boxtype, boxtype, sizeof(property->boxtype)); memcpy(property->usertype, usertype, sizeof(property->usertype)); - AVIF_CHECKRES(avifRWDataSet(&property->boxPayload, boxPayload, boxPayloadLength)); + AVIF_CHECKRES(avifRWDataSet(&property->boxPayload, boxPayload, boxPayloadSize)); return AVIF_RESULT_OK; } diff --git a/tests/gtest/avif_fuzztest_dec.cc b/tests/gtest/avif_fuzztest_dec.cc index dcdffec827..b35df1523c 100644 --- a/tests/gtest/avif_fuzztest_dec.cc +++ b/tests/gtest/avif_fuzztest_dec.cc @@ -39,10 +39,10 @@ void Decode(const std::string& arbitrary_bytes, bool is_persistent, if (avifDecoderParse(decoder.get()) != AVIF_RESULT_OK) return; for (size_t i = 0; i < decoder->image->numProperties; ++i) { - const avifRWData& boxPayload = decoder->image->properties[i].boxPayload; + const avifRWData& box_payload = decoder->image->properties[i].boxPayload; // Each custom property should be found as is in the input bitstream. - EXPECT_NE(std::search(data, data + arbitrary_bytes.size(), boxPayload.data, - boxPayload.data + boxPayload.size), + EXPECT_NE(std::search(data, data + arbitrary_bytes.size(), box_payload.data, + box_payload.data + box_payload.size), data + arbitrary_bytes.size()); }