From 014d6b30ebc77b70508a41f5a24302758ad76d09 Mon Sep 17 00:00:00 2001 From: Maryla Date: Thu, 23 Nov 2023 11:21:44 +0100 Subject: [PATCH 1/2] Make the gainMap member of avifImage a pointer. Allows adding members in the future without breaking the ABI. --- apps/avifenc.c | 14 +- apps/avifgainmaputil/combine_command.cc | 7 +- apps/avifgainmaputil/convert_command.cc | 4 +- .../avifgainmaputil/extractgainmap_command.cc | 5 +- apps/avifgainmaputil/imageio.cc | 5 +- apps/avifgainmaputil/printmetadata_command.cc | 4 +- apps/avifgainmaputil/swapbase_command.cc | 40 +-- apps/avifgainmaputil/tonemap_command.cc | 9 +- apps/shared/avifjpeg.c | 7 +- apps/shared/avifutil.c | 4 +- include/avif/avif.h | 15 +- src/avif.c | 52 +++- src/read.c | 53 ++-- src/write.c | 41 +-- .../avif_fuzztest_enc_dec_experimental.cc | 34 ++- ...avif_fuzztest_enc_dec_incr_experimental.cc | 20 +- tests/gtest/avifgainmaptest.cc | 270 ++++++++++-------- tests/gtest/avifincrtest_helpers.cc | 7 +- tests/gtest/avifjpeggainmaptest.cc | 11 +- 19 files changed, 340 insertions(+), 262 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index f167294cfc..e869631aac 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -531,10 +531,10 @@ static avifBool avifInputReadImage(avifInput * input, assert(AVIF_FALSE); } #if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION) - if (cached->image->gainMap.image != NULL) { - image->gainMap.image = avifImageCreateEmpty(); - const avifCropRect gainMapRect = { 0, 0, cached->image->gainMap.image->width, cached->image->gainMap.image->height }; - if (avifImageSetViewRect(image->gainMap.image, cached->image->gainMap.image, &gainMapRect) != AVIF_RESULT_OK) { + if (cached->image->gainMap && cached->image->gainMap->image) { + image->gainMap->image = avifImageCreateEmpty(); + const avifCropRect gainMapRect = { 0, 0, cached->image->gainMap->image->width, cached->image->gainMap->image->height }; + if (avifImageSetViewRect(image->gainMap->image, cached->image->gainMap->image, &gainMapRect) != AVIF_RESULT_OK) { assert(AVIF_FALSE); } } @@ -1192,7 +1192,7 @@ static avifBool avifEncodeImagesFixedQuality(const avifSettings * settings, } char gainMapStr[100] = { 0 }; #if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION) - if (firstImage->gainMap.image != NULL) { + if (firstImage->gainMap && firstImage->gainMap->image) { snprintf(gainMapStr, sizeof(gainMapStr), ", gain map quality [%d (%s)]", encoder->qualityGainMap, qualityString(encoder->qualityGainMap)); } #endif @@ -1305,7 +1305,7 @@ static avifBool avifEncodeImages(avifSettings * settings, avifBool hasGainMap = AVIF_FALSE; avifBool allQualitiesConstrained = settings->qualityIsConstrained && settings->qualityAlphaIsConstrained; #if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION) - hasGainMap = (firstImage->gainMap.image != NULL); + hasGainMap = (firstImage->gainMap && firstImage->gainMap->image); if (hasGainMap) { allQualitiesConstrained = allQualitiesConstrained && settings->qualityGainMapIsConstrained; } @@ -2503,7 +2503,7 @@ MAIN() const avifImage * avif = gridCells ? gridCells[0] : image; avifBool gainMapPresent = AVIF_FALSE; #if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION) - gainMapPresent = (avif->gainMap.image != NULL); + gainMapPresent = (avif->gainMap && avif->gainMap->image); #endif avifImageDump(avif, settings.gridDims[0], diff --git a/apps/avifgainmaputil/combine_command.cc b/apps/avifgainmaputil/combine_command.cc index 29c7611ef8..db28ebed48 100644 --- a/apps/avifgainmaputil/combine_command.cc +++ b/apps/avifgainmaputil/combine_command.cc @@ -105,15 +105,16 @@ avifResult CombineCommand::Run() { std::cout << "Creating a gain map of size " << gain_map_width << " x " << gain_map_height << "\n"; - base_image->gainMap.image = + base_image->gainMap = avifGainMapCreate(); + base_image->gainMap->image = avifImageCreate(gain_map_width, gain_map_height, arg_gain_map_depth_, gain_map_pixel_format); - if (base_image->gainMap.image == nullptr) { + if (base_image->gainMap->image == nullptr) { return AVIF_RESULT_OUT_OF_MEMORY; } avifDiagnostics diag; result = avifImageComputeGainMap(base_image.get(), alternate_image.get(), - &base_image->gainMap, &diag); + base_image->gainMap, &diag); if (result != AVIF_RESULT_OK) { std::cout << "Failed to compute gain map: " << avifResultToString(result) << " (" << diag.error << ")\n"; diff --git a/apps/avifgainmaputil/convert_command.cc b/apps/avifgainmaputil/convert_command.cc index 6a93103b1a..0d604bb7ad 100644 --- a/apps/avifgainmaputil/convert_command.cc +++ b/apps/avifgainmaputil/convert_command.cc @@ -66,7 +66,7 @@ avifResult ConvertCommand::Run() { image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB; } - if (image->gainMap.image == nullptr) { + if (image->gainMap == nullptr || image->gainMap->image == nullptr) { std::cerr << "Input image " << arg_input_filename_ << " does not contain a gain map\n"; return AVIF_RESULT_INVALID_ARGUMENT; @@ -75,7 +75,7 @@ avifResult ConvertCommand::Run() { if (arg_swap_base_) { int depth = arg_image_read_.depth; if (depth == 0) { - depth = image->gainMap.metadata.alternateHdrHeadroomN == 0 ? 8 : 10; + depth = image->gainMap->metadata.alternateHdrHeadroomN == 0 ? 8 : 10; } ImagePtr new_base( avifImageCreate(image->width, image->height, depth, image->yuvFormat)); diff --git a/apps/avifgainmaputil/extractgainmap_command.cc b/apps/avifgainmaputil/extractgainmap_command.cc index cc39fa7a9b..2df46ba9a8 100644 --- a/apps/avifgainmaputil/extractgainmap_command.cc +++ b/apps/avifgainmaputil/extractgainmap_command.cc @@ -30,13 +30,14 @@ avifResult ExtractGainMapCommand::Run() { return result; } - if (decoder->image->gainMap.image == nullptr) { + if (decoder->image->gainMap == nullptr || + decoder->image->gainMap->image == nullptr) { std::cerr << "Input image " << arg_input_filename_ << " does not contain a gain map\n"; return AVIF_RESULT_INVALID_ARGUMENT; } - return WriteImage(decoder->image->gainMap.image, arg_output_filename_, + return WriteImage(decoder->image->gainMap->image, arg_output_filename_, arg_image_encode_.quality, arg_image_encode_.speed); } diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc index 19c12dd473..62920ed4a9 100644 --- a/apps/avifgainmaputil/imageio.cc +++ b/apps/avifgainmaputil/imageio.cc @@ -66,10 +66,11 @@ avifResult WriteAvif(const avifImage* image, avifEncoder* encoder, const std::string& output_filename) { avifRWData encoded = AVIF_DATA_EMPTY; std::cout << "AVIF to be written:\n"; + const bool gain_map_present = + (image->gainMap != nullptr && image->gainMap->image != nullptr); avifImageDump(image, /*gridCols=*/1, - /*gridRows=*/1, - /*gainMapPresent=*/image->gainMap.image != nullptr, + /*gridRows=*/1, gain_map_present, AVIF_PROGRESSIVE_STATE_UNAVAILABLE); std::cout << "Encoding AVIF at quality " << encoder->quality << " speed " << encoder->speed << ", please wait...\n"; diff --git a/apps/avifgainmaputil/printmetadata_command.cc b/apps/avifgainmaputil/printmetadata_command.cc index ab2c5b8e01..352d127cb4 100644 --- a/apps/avifgainmaputil/printmetadata_command.cc +++ b/apps/avifgainmaputil/printmetadata_command.cc @@ -57,13 +57,13 @@ avifResult PrintMetadataCommand::Run() { << decoder->diag.error << ")\n"; return result; } - if (!decoder->gainMapPresent) { + if (!decoder->gainMapPresent || decoder->image->gainMap == nullptr) { std::cerr << "Input image " << arg_input_filename_ << " does not contain a gain map\n"; return AVIF_RESULT_INVALID_ARGUMENT; } - const avifGainMapMetadata& metadata = decoder->image->gainMap.metadata; + const avifGainMapMetadata& metadata = decoder->image->gainMap->metadata; const int w = 20; std::cout << " * " << std::left << std::setw(w) << "Base headroom: " << FormatFraction(metadata.baseHdrHeadroomN, diff --git a/apps/avifgainmaputil/swapbase_command.cc b/apps/avifgainmaputil/swapbase_command.cc index 1fde3dbefc..169ef50795 100644 --- a/apps/avifgainmaputil/swapbase_command.cc +++ b/apps/avifgainmaputil/swapbase_command.cc @@ -9,13 +9,13 @@ namespace avif { avifResult ChangeBase(avifImage& image, avifImage* swapped) { - if (image.gainMap.image == nullptr) { + if (image.gainMap == nullptr || image.gainMap->image == nullptr) { return AVIF_RESULT_INVALID_ARGUMENT; } const float headroom = - static_cast(image.gainMap.metadata.alternateHdrHeadroomN) / - image.gainMap.metadata.alternateHdrHeadroomD; + static_cast(image.gainMap->metadata.alternateHdrHeadroomN) / + image.gainMap->metadata.alternateHdrHeadroomD; const bool tone_mapping_to_sdr = (headroom == 0.0f); // The gain map's cicp values are those of the 'tmap' item and describe the @@ -24,13 +24,14 @@ avifResult ChangeBase(avifImage& image, avifImage* swapped) { // property. This property describes the colour properties of the // reconstructed image if the gain map input item is fully applied according // to ISO/AWI 214961. - if (image.gainMap.image->transferCharacteristics != + if (image.gainMap->image->transferCharacteristics != AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED || - image.gainMap.image->colorPrimaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED) { - swapped->colorPrimaries = image.gainMap.image->colorPrimaries; + image.gainMap->image->colorPrimaries != + AVIF_COLOR_PRIMARIES_UNSPECIFIED) { + swapped->colorPrimaries = image.gainMap->image->colorPrimaries; swapped->transferCharacteristics = - image.gainMap.image->transferCharacteristics; - swapped->matrixCoefficients = image.gainMap.image->matrixCoefficients; + image.gainMap->image->transferCharacteristics; + swapped->matrixCoefficients = image.gainMap->image->matrixCoefficients; } else { // If there is no cicp on the gain map, use the same values as the old base // image, except for the transfer characteristics. @@ -45,13 +46,13 @@ avifResult ChangeBase(avifImage& image, avifImage* swapped) { avifRGBImage swapped_rgb; avifRGBImageSetDefaults(&swapped_rgb, swapped); - avifContentLightLevelInformationBox clli = image.gainMap.image->clli; + avifContentLightLevelInformationBox clli = image.gainMap->image->clli; const bool compute_clli = !tone_mapping_to_sdr && clli.maxCLL == 0 && clli.maxPALL == 0; avifDiagnostics diag; avifResult result = avifImageApplyGainMap( - &image, &image.gainMap, headroom, swapped->transferCharacteristics, + &image, image.gainMap, headroom, swapped->transferCharacteristics, &swapped_rgb, (compute_clli ? &clli : nullptr), &diag); if (result != AVIF_RESULT_OK) { std::cout << "Failed to tone map image: " << avifResultToString(result) @@ -67,19 +68,19 @@ avifResult ChangeBase(avifImage& image, avifImage* swapped) { swapped->clli = clli; swapped->gainMap = image.gainMap; - // swapped has taken ownership of the gain map image, so remove ownership + // 'swapped' has taken ownership of the gain map, so remove ownership // from the old image to prevent a double free. - image.gainMap.image = nullptr; + image.gainMap = nullptr; // Set the gain map's cicp values and clli to the old base image's. - swapped->gainMap.image->clli = image.clli; - swapped->gainMap.image->colorPrimaries = image.colorPrimaries; - swapped->gainMap.image->transferCharacteristics = + swapped->gainMap->image->clli = image.clli; + swapped->gainMap->image->colorPrimaries = image.colorPrimaries; + swapped->gainMap->image->transferCharacteristics = image.transferCharacteristics; // Don't touch matrixCoefficients as it's needed to correctly decode the gain // map. // Swap base and alternate in the gain map metadata. - avifGainMapMetadata& metadata = swapped->gainMap.metadata; + avifGainMapMetadata& metadata = swapped->gainMap->metadata; metadata.backwardDirection = !metadata.backwardDirection; metadata.useBaseColorSpace = !metadata.useBaseColorSpace; std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN); @@ -124,7 +125,8 @@ avifResult SwapBaseCommand::Run() { return result; } - if (decoder->image->gainMap.image == nullptr) { + if (decoder->image->gainMap == nullptr || + decoder->image->gainMap->image == nullptr) { std::cerr << "Input image " << arg_input_filename_ << " does not contain a gain map\n"; return AVIF_RESULT_INVALID_ARGUMENT; @@ -132,10 +134,10 @@ avifResult SwapBaseCommand::Run() { int depth = arg_image_read_.depth; if (depth == 0) { - depth = decoder->image->gainMap.metadata.alternateHdrHeadroomN == 0 + depth = decoder->image->gainMap->metadata.alternateHdrHeadroomN == 0 ? 8 : std::max(decoder->image->depth, - decoder->image->gainMap.image->depth); + decoder->image->gainMap->image->depth); } avifPixelFormat pixel_format = (avifPixelFormat)arg_image_read_.pixel_format.value(); diff --git a/apps/avifgainmaputil/tonemap_command.cc b/apps/avifgainmaputil/tonemap_command.cc index 496a9d1c53..580f245f4c 100644 --- a/apps/avifgainmaputil/tonemap_command.cc +++ b/apps/avifgainmaputil/tonemap_command.cc @@ -98,7 +98,8 @@ avifResult TonemapCommand::Run() { arg_input_cicp_.value().matrix_coefficients; } - if (decoder->image->gainMap.image == nullptr) { + if (decoder->image->gainMap == nullptr || + decoder->image->gainMap->image == nullptr) { std::cerr << "Input image " << arg_input_filename_ << " does not contain a gain map\n"; return AVIF_RESULT_INVALID_ARGUMENT; @@ -106,7 +107,7 @@ avifResult TonemapCommand::Run() { avifGainMapMetadataDouble metadata; if (!avifGainMapMetadataFractionsToDouble( - &metadata, &decoder->image->gainMap.metadata)) { + &metadata, &decoder->image->gainMap->metadata)) { std::cerr << "Input image " << arg_input_filename_ << " has invalid gain map metadata\n"; return AVIF_RESULT_INVALID_ARGUMENT; @@ -123,7 +124,7 @@ avifResult TonemapCommand::Run() { metadata.alternateHdrHeadroom <= metadata.baseHdrHeadroom) || (headroom >= metadata.alternateHdrHeadroom && metadata.alternateHdrHeadroom >= metadata.baseHdrHeadroom)) { - clli_box = decoder->image->gainMap.image->clli; + clli_box = decoder->image->gainMap->image->clli; } clli_set = (clli_box.maxCLL != 0) || (clli_box.maxPALL != 0); } @@ -145,7 +146,7 @@ avifResult TonemapCommand::Run() { avifRGBImage tone_mapped_rgb; avifRGBImageSetDefaults(&tone_mapped_rgb, tone_mapped.get()); avifDiagnostics diag; - result = avifImageApplyGainMap(decoder->image, &decoder->image->gainMap, + result = avifImageApplyGainMap(decoder->image, decoder->image->gainMap, arg_headroom_, cicp.transfer_characteristics, &tone_mapped_rgb, clli_set ? nullptr : &clli_box, &diag); diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c index 85cbc4db63..f049b83e44 100644 --- a/apps/shared/avifjpeg.c +++ b/apps/shared/avifjpeg.c @@ -1136,7 +1136,12 @@ static avifBool avifJPEGReadInternal(FILE * f, // The primary XMP block (for the main image) must contain a node with an hdrgm:Version field if and only if a gain map is present. if (!ignoreGainMap && avifJPEGHasGainMapXMPNode(avif->xmp.data, avif->xmp.size)) { // Ignore the return value: continue even if we fail to find/parse/decode the gain map. - avifJPEGExtractGainMapImage(f, &cinfo, &avif->gainMap, chromaDownsampling); + avifGainMap * gainMap = avifGainMapCreate(); + if (avifJPEGExtractGainMapImage(f, &cinfo, gainMap, chromaDownsampling)) { + avif->gainMap = gainMap; + } else { + avifGainMapDestroy(gainMap); + } } if (avif->xmp.size > 0 && ignoreXMP) { diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c index f86a9cb50b..90ab792a19 100644 --- a/apps/shared/avifutil.c +++ b/apps/shared/avifutil.c @@ -132,7 +132,7 @@ static void avifImageDumpInternal(const avifImage * avif, #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) printf(" * Gain map : "); - avifImage * gainMap = avif->gainMap.image; + avifImage * gainMap = avif->gainMap ? avif->gainMap->image : NULL; if (gainMap != NULL) { printf("%ux%u pixels, %u bit, %s, %s Range, Matrix Coeffs. %u, Base Image is %s\n", gainMap->width, @@ -141,7 +141,7 @@ static void avifImageDumpInternal(const avifImage * avif, avifPixelFormatToString(gainMap->yuvFormat), (gainMap->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited", gainMap->matrixCoefficients, - (avif->gainMap.metadata.baseHdrHeadroomN == 0) ? "SDR" : "HDR"); + (avif->gainMap->metadata.baseHdrHeadroomN == 0) ? "SDR" : "HDR"); printf(" * Color Primaries: %u\n", gainMap->colorPrimaries); printf(" * Transfer Char. : %u\n", gainMap->transferCharacteristics); printf(" * Matrix Coeffs. : %u\n", gainMap->matrixCoefficients); diff --git a/include/avif/avif.h b/include/avif/avif.h index 91c59474ed..3e15827d4c 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -626,9 +626,11 @@ typedef struct avifGainMapMetadata } avifGainMapMetadata; // Gain map image and associated metadata. +// Must be allocated by calling avifGainMapCreate(). typedef struct avifGainMap { // Gain map pixels. + // Owned by the avifGainMap and gets freed when calling avifGainMapDestro(). // Used fields: width, height, depth, yufFormat, yuvRange, // yuvChromaSamplePosition, yuvPlanes, yuvRowBytes, imageOwnsYUVPlanes, // matrixCoefficients, transferCharacteristics. Other fields are ignored. @@ -639,6 +641,12 @@ typedef struct avifGainMap avifGainMapMetadata metadata; } avifGainMap; +// Allocates a gain map. Returns NULL if a memory allocation failed. +// The 'image' field is NULL by default and must be allocated separately. +AVIF_API avifGainMap * avifGainMapCreate(); +// Frees a gain map, including the 'image' field if non NULL. +AVIF_API void avifGainMapDestroy(avifGainMap * gainMap); + // Same as avifGainMapMetadata, but with fields of type double instead of uint32_t fractions. // Use avifGainMapMetadataDoubleToFractions() to convert this to a avifGainMapMetadata. // See avifGainMapMetadata for detailed descriptions of fields. @@ -731,10 +739,9 @@ typedef struct avifImage // Version 1.0.0 ends here. Add any new members after this line. #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - // Gain map image and metadata. If no gain map is present, gainMap.image is NULL. - // When calling avifImageDestroy on the containing image, the gain map image is also destroyed - // (the containing image "owns" the gain map). - avifGainMap gainMap; + // Gain map image and metadata. NULL if no gain map is present. + // Owned by the avifImage and gets freed when calling avifImageDestroy(). + avifGainMap * gainMap; #endif } avifImage; diff --git a/src/avif.c b/src/avif.c index 53f566cddd..8d93aac410 100644 --- a/src/avif.c +++ b/src/avif.c @@ -187,10 +187,6 @@ void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage) dstImage->clap = srcImage->clap; dstImage->irot = srcImage->irot; dstImage->imir = srcImage->imir; - -#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - dstImage->gainMap.metadata = srcImage->gainMap.metadata; -#endif } void avifImageCopySamples(avifImage * dstImage, const avifImage * srcImage, avifPlanesFlags planes) @@ -262,14 +258,24 @@ avifResult avifImageCopy(avifImage * dstImage, const avifImage * srcImage, avifP avifImageCopySamples(dstImage, srcImage, planes); #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - if (srcImage->gainMap.image) { - if (!dstImage->gainMap.image) { - dstImage->gainMap.image = avifImageCreateEmpty(); + if (srcImage->gainMap) { + if (!dstImage->gainMap) { + dstImage->gainMap = avifGainMapCreate(); + AVIF_CHECKERR(dstImage->gainMap, AVIF_RESULT_OUT_OF_MEMORY); + } + dstImage->gainMap->metadata = srcImage->gainMap->metadata; + if (srcImage->gainMap->image) { + if (!dstImage->gainMap->image) { + dstImage->gainMap->image = avifImageCreateEmpty(); + } + AVIF_CHECKRES(avifImageCopy(dstImage->gainMap->image, srcImage->gainMap->image, planes)); + } else if (dstImage->gainMap->image) { + avifImageDestroy(dstImage->gainMap->image); + dstImage->gainMap->image = NULL; } - AVIF_CHECKRES(avifImageCopy(dstImage->gainMap.image, srcImage->gainMap.image, planes)); - } else if (dstImage->gainMap.image) { - avifImageDestroy(dstImage->gainMap.image); - dstImage->gainMap.image = NULL; + } else if (dstImage->gainMap) { + avifGainMapDestroy(dstImage->gainMap); + dstImage->gainMap = NULL; } #endif // defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) @@ -313,8 +319,8 @@ avifResult avifImageSetViewRect(avifImage * dstImage, const avifImage * srcImage void avifImageDestroy(avifImage * image) { #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - if (image->gainMap.image) { - avifImageDestroy(image->gainMap.image); + if (image->gainMap) { + avifGainMapDestroy(image->gainMap); } #endif avifImageFreePlanes(image, AVIF_PLANES_ALL); @@ -1134,3 +1140,23 @@ void avifCodecVersions(char outBuffer[256]) append(&writePos, &remainingLen, availableCodecs[i].version()); } } + +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) +avifGainMap * avifGainMapCreate() +{ + avifGainMap * gainMap = (avifGainMap *)avifAlloc(sizeof(avifGainMap)); + if (!gainMap) { + return NULL; + } + memset(gainMap, 0, sizeof(avifGainMap)); + return gainMap; +} + +void avifGainMapDestroy(avifGainMap * gainMap) +{ + if (gainMap->image) { + avifImageDestroy(gainMap->image); + } + avifFree(gainMap); +} +#endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP diff --git a/src/read.c b/src/read.c index 7d1f20f436..6b7a33e9f1 100644 --- a/src/read.c +++ b/src/read.c @@ -1558,8 +1558,8 @@ static avifBool avifDecoderDataCopyTileToImage(avifDecoderData * data, avifImage * dst = dstImage; #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) { - dst = dst->gainMap.image; - assert(dst); + assert(dst->gainMap && dst->gainMap->image); + dst = dst->gainMap->image; } #endif if ((avifImageSetViewRect(&dstView, dst, &dstViewRect) != AVIF_RESULT_OK) || @@ -4463,16 +4463,19 @@ static avifResult avifDecoderFindGainMapItem(const avifDecoder * decoder, gainMapCodecType)); if (decoder->enableDecodingGainMap) { - decoder->image->gainMap.image = avifImageCreateEmpty(); + decoder->image->gainMap = avifGainMapCreate(); + AVIF_CHECKERR(decoder->image->gainMap, AVIF_RESULT_OUT_OF_MEMORY); + decoder->image->gainMap->image = avifImageCreateEmpty(); + AVIF_CHECKERR(decoder->image->gainMap->image, AVIF_RESULT_OUT_OF_MEMORY); // Look for a colr nclx box. Other colr box types (e.g. ICC) are not supported. for (uint32_t propertyIndex = 0; propertyIndex < gainMapItem->properties.count; ++propertyIndex) { avifProperty * prop = &gainMapItem->properties.prop[propertyIndex]; if (!memcmp(prop->type, "colr", 4) && prop->u.colr.hasNCLX) { - decoder->image->gainMap.image->colorPrimaries = prop->u.colr.colorPrimaries; - decoder->image->gainMap.image->transferCharacteristics = prop->u.colr.transferCharacteristics; - decoder->image->gainMap.image->matrixCoefficients = prop->u.colr.matrixCoefficients; - decoder->image->gainMap.image->yuvRange = prop->u.colr.range; + decoder->image->gainMap->image->colorPrimaries = prop->u.colr.colorPrimaries; + decoder->image->gainMap->image->transferCharacteristics = prop->u.colr.transferCharacteristics; + decoder->image->gainMap->image->matrixCoefficients = prop->u.colr.matrixCoefficients; + decoder->image->gainMap->image->yuvRange = prop->u.colr.range; break; } } @@ -4483,7 +4486,7 @@ static avifResult avifDecoderFindGainMapItem(const avifDecoder * decoder, // TODO(maryla): add other HDR boxes: mdcv, cclv, etc. const avifProperty * clliProp = avifPropertyArrayFind(&toneMappedImageItemTmp->properties, "clli"); if (clliProp) { - decoder->image->gainMap.image->clli = clliProp->u.clli; + decoder->image->gainMap->image->clli = clliProp->u.clli; } } @@ -4784,8 +4787,11 @@ avifResult avifDecoderReset(avifDecoder * decoder) // Read the gain map's metadata. avifROData tmapData; AVIF_CHECKRES(avifDecoderItemRead(toneMappedImageItem, decoder->io, &tmapData, 0, 0, data->diag)); - - AVIF_CHECKERR(avifParseToneMappedImageBox(&decoder->image->gainMap.metadata, tmapData.data, tmapData.size, data->diag), + if (decoder->image->gainMap == NULL) { + decoder->image->gainMap = avifGainMapCreate(); + AVIF_CHECKERR(decoder->image->gainMap, AVIF_RESULT_OUT_OF_MEMORY); + } + AVIF_CHECKERR(avifParseToneMappedImageBox(&decoder->image->gainMap->metadata, tmapData.data, tmapData.size, data->diag), AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE); } #endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP @@ -4860,13 +4866,13 @@ avifResult avifDecoderReset(avifDecoder * decoder) #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (mainItems[AVIF_ITEM_GAIN_MAP]) { - assert(decoder->image->gainMap.image); - decoder->image->gainMap.image->width = mainItems[AVIF_ITEM_GAIN_MAP]->width; - decoder->image->gainMap.image->height = mainItems[AVIF_ITEM_GAIN_MAP]->height; + assert(decoder->image->gainMap && decoder->image->gainMap->image); + decoder->image->gainMap->image->width = mainItems[AVIF_ITEM_GAIN_MAP]->width; + decoder->image->gainMap->image->height = mainItems[AVIF_ITEM_GAIN_MAP]->height; decoder->gainMapPresent = AVIF_TRUE; // Must be called after avifDecoderGenerateImageTiles() which among other things copies the // codec config property from the first tile of a grid to the grid item (when grids are used). - AVIF_CHECKRES(avifReadCodecConfigProperty(decoder->image->gainMap.image, + AVIF_CHECKRES(avifReadCodecConfigProperty(decoder->image->gainMap->image, &mainItems[AVIF_ITEM_GAIN_MAP]->properties, codecType[AVIF_ITEM_GAIN_MAP])); } @@ -5136,8 +5142,8 @@ static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextIma avifImage * dstImage = decoder->image; #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) { - dstImage = dstImage->gainMap.image; - assert(dstImage); + assert(dstImage->gainMap && dstImage->gainMap->image); + dstImage = dstImage->gainMap->image; } #endif AVIF_CHECKRES(avifDecoderDataAllocateGridImagePlanes(decoder->data, info, dstImage)); @@ -5154,9 +5160,10 @@ static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextIma switch (tile->input->itemCategory) { #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) case AVIF_ITEM_GAIN_MAP: - decoder->image->gainMap.image->width = src->width; - decoder->image->gainMap.image->height = src->height; - decoder->image->gainMap.image->depth = src->depth; + assert(decoder->image->gainMap && decoder->image->gainMap->image); + decoder->image->gainMap->image->width = src->width; + decoder->image->gainMap->image->height = src->height; + decoder->image->gainMap->image->depth = src->depth; break; #endif default: @@ -5180,8 +5187,8 @@ static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextIma avifImageStealPlanes(decoder->image, src, AVIF_PLANES_A); #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) } else if (tile->input->itemCategory == AVIF_ITEM_GAIN_MAP) { - assert(decoder->image->gainMap.image); - avifImageStealPlanes(decoder->image->gainMap.image, src, AVIF_PLANES_YUV); + assert(decoder->image->gainMap && decoder->image->gainMap->image); + avifImageStealPlanes(decoder->image->gainMap->image, src, AVIF_PLANES_YUV); #endif } else { // AVIF_ITEM_COLOR avifImageStealPlanes(decoder->image, src, AVIF_PLANES_YUV); @@ -5437,7 +5444,7 @@ uint32_t avifDecoderDecodedRowCount(const avifDecoder * decoder) for (int c = 0; c < AVIF_ITEM_CATEGORY_COUNT; ++c) { #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (c == AVIF_ITEM_GAIN_MAP) { - const avifImage * const gainMap = decoder->image->gainMap.image; + const avifImage * const gainMap = decoder->image->gainMap ? decoder->image->gainMap->image : NULL; if (decoder->gainMapPresent && decoder->enableDecodingGainMap && gainMap != NULL && gainMap->height != 0) { uint32_t gainMapRowCount = avifGetDecodedRowCount(decoder, &decoder->data->tileInfos[AVIF_ITEM_GAIN_MAP], gainMap); if (gainMap->height != decoder->image->height) { @@ -5445,7 +5452,7 @@ uint32_t avifDecoderDecodedRowCount(const avifDecoder * decoder) (uint32_t)floorf((float)gainMapRowCount / gainMap->height * decoder->image->height); // Make sure it matches the formula described in the comment of avifDecoderDecodedRowCount() in avif.h. assert((uint32_t)lround((double)scaledGainMapRowCount / decoder->image->height * - decoder->image->gainMap.image->height) <= gainMapRowCount); + decoder->image->gainMap->image->height) <= gainMapRowCount); gainMapRowCount = scaledGainMapRowCount; } minRowCount = AVIF_MIN(minRowCount, gainMapRowCount); diff --git a/src/write.c b/src/write.c index d76935d922..038f1cbd8d 100644 --- a/src/write.c +++ b/src/write.c @@ -1208,8 +1208,10 @@ static avifResult avifValidateGrid(uint32_t gridCols, const avifImage * bottomRightCell = cellImages[cellCount - 1]; #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (validateGainMap) { - firstCell = firstCell->gainMap.image; - bottomRightCell = bottomRightCell->gainMap.image; + assert(firstCell->gainMap && firstCell->gainMap->image); + firstCell = firstCell->gainMap->image; + assert(bottomRightCell->gainMap && bottomRightCell->gainMap->image); + bottomRightCell = bottomRightCell->gainMap->image; } #endif const uint32_t tileWidth = firstCell->width; @@ -1220,7 +1222,8 @@ static avifResult avifValidateGrid(uint32_t gridCols, const avifImage * cellImage = cellImages[cellIndex]; #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (validateGainMap) { - cellImage = cellImage->gainMap.image; + assert(cellImage->gainMap && cellImage->gainMap->image); + cellImage = cellImage->gainMap->image; } #endif const uint32_t expectedCellWidth = ((cellIndex + 1) % gridCols) ? tileWidth : bottomRightCell->width; @@ -1310,20 +1313,20 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_FALSE, &encoder->diag)); #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - const avifBool hasGainMap = (firstCell->gainMap.image != NULL); + const avifBool hasGainMap = (firstCell->gainMap && firstCell->gainMap->image != NULL); // Check that either all cells have a gain map, or none of them do. // If a gain map is present, check that they all have the same gain map metadata. for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) { const avifImage * cellImage = cellImages[cellIndex]; - const avifBool cellHasGainMap = (cellImage->gainMap.image != NULL); + const avifBool cellHasGainMap = (cellImage->gainMap && cellImage->gainMap->image); if (cellHasGainMap != hasGainMap) { avifDiagnosticsPrintf(&encoder->diag, "cells should either all have a gain map image, or none of them should, found a mix"); return AVIF_RESULT_INVALID_IMAGE_GRID; } if (hasGainMap) { - const avifGainMapMetadata * firstMetadata = &firstCell->gainMap.metadata; - const avifGainMapMetadata * cellMetadata = &cellImage->gainMap.metadata; + const avifGainMapMetadata * firstMetadata = &firstCell->gainMap->metadata; + const avifGainMapMetadata * cellMetadata = &cellImage->gainMap->metadata; if (cellMetadata->backwardDirection != firstMetadata->backwardDirection || cellMetadata->baseHdrHeadroomN != firstMetadata->baseHdrHeadroomN || cellMetadata->baseHdrHeadroomD != firstMetadata->baseHdrHeadroomD || @@ -1351,7 +1354,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } if (hasGainMap) { - AVIF_CHECKRES(avifValidateImageBasicProperties(firstCell->gainMap.image)); + AVIF_CHECKRES(avifValidateImageBasicProperties(firstCell->gainMap->image)); AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_TRUE, &encoder->diag)); } #endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP @@ -1485,13 +1488,13 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - if (firstCell->gainMap.image) { + if (firstCell->gainMap && firstCell->gainMap->image) { avifEncoderItem * toneMappedItem = avifEncoderDataCreateItem(encoder->data, "tmap", infeNameGainMap, /*infeNameSize=*/strlen(infeNameGainMap) + 1, /*cellIndex=*/0); - if (!avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap.metadata)) { + if (!avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata)) { avifDiagnosticsPrintf(&encoder->diag, "failed to write gain map metadata, some values may be negative or too large"); return AVIF_RESULT_ENCODE_GAIN_MAP_FAILED; } @@ -1508,9 +1511,9 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, *alternativeItemID = colorItemID; const uint32_t gainMapGridWidth = - avifGridWidth(gridCols, cellImages[0]->gainMap.image, cellImages[gridCols * gridRows - 1]->gainMap.image); + avifGridWidth(gridCols, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image); const uint32_t gainMapGridHeight = - avifGridHeight(gridRows, cellImages[0]->gainMap.image, cellImages[gridCols * gridRows - 1]->gainMap.image); + avifGridHeight(gridRows, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image); uint16_t gainMapItemID; AVIF_CHECKRES( @@ -1603,10 +1606,10 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, const avifImage * firstCellImage = firstCell; #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (item->itemCategory == AVIF_ITEM_GAIN_MAP) { - cellImage = cellImage->gainMap.image; - assert(cellImage); - firstCellImage = firstCell->gainMap.image; - assert(firstCellImage); + assert(cellImage->gainMap && cellImage->gainMap->image); + cellImage = cellImage->gainMap->image; + assert(firstCell->gainMap && firstCell->gainMap->image); + firstCellImage = firstCell->gainMap->image; } #endif avifImage * paddedCellImage = NULL; @@ -2181,8 +2184,8 @@ static avifResult avifRWStreamWriteProperties(avifItemPropertyDedup * const dedu const avifImage * itemMetadata = imageMetadata; #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (item->itemCategory == AVIF_ITEM_GAIN_MAP) { - itemMetadata = itemMetadata->gainMap.image; - assert(itemMetadata); + assert(itemMetadata->gainMap && itemMetadata->gainMap->image); + itemMetadata = itemMetadata->gainMap->image; } #endif uint32_t imageWidth = itemMetadata->width; @@ -2237,7 +2240,7 @@ static avifResult avifRWStreamWriteProperties(avifItemPropertyDedup * const dedu // Technically, the gain map is not an HDR image, but in the API, this is the most convenient // place to put this data. - AVIF_CHECKRES(avifEncoderWriteHDRProperties(&dedup->s, s, imageMetadata->gainMap.image, &item->ipma, dedup)); + AVIF_CHECKRES(avifEncoderWriteHDRProperties(&dedup->s, s, imageMetadata->gainMap->image, &item->ipma, dedup)); #endif } else { AVIF_CHECKRES(avifEncoderWriteHDRProperties(&dedup->s, s, itemMetadata, &item->ipma, dedup)); diff --git a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc index 24431ec8cb..759b8963c1 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_experimental.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_experimental.cc @@ -67,21 +67,24 @@ void EncodeDecodeValid(ImagePtr image, EncoderPtr encoder, DecoderPtr decoder) { EXPECT_EQ(decoded_image->depth, image->depth); EXPECT_EQ(decoded_image->yuvFormat, image->yuvFormat); - EXPECT_EQ(image->gainMap.image != nullptr, decoder->gainMapPresent); + ASSERT_EQ(image->gainMap != nullptr, decoder->gainMapPresent); + ASSERT_EQ(image->gainMap->image != nullptr, decoder->gainMapPresent); if (decoder->gainMapPresent && decoder->enableDecodingGainMap) { - ASSERT_NE(decoded_image->gainMap.image, nullptr); - EXPECT_EQ(decoded_image->gainMap.image->width, image->gainMap.image->width); - EXPECT_EQ(decoded_image->gainMap.image->height, - image->gainMap.image->height); - EXPECT_EQ(decoded_image->gainMap.image->depth, image->gainMap.image->depth); - EXPECT_EQ(decoded_image->gainMap.image->yuvFormat, - image->gainMap.image->yuvFormat); - EXPECT_EQ(image->gainMap.image->gainMap.image, nullptr); - EXPECT_EQ(decoded_image->gainMap.image->alphaPlane, nullptr); + ASSERT_NE(decoded_image->gainMap->image, nullptr); + EXPECT_EQ(decoded_image->gainMap->image->width, + image->gainMap->image->width); + EXPECT_EQ(decoded_image->gainMap->image->height, + image->gainMap->image->height); + EXPECT_EQ(decoded_image->gainMap->image->depth, + image->gainMap->image->depth); + EXPECT_EQ(decoded_image->gainMap->image->yuvFormat, + image->gainMap->image->yuvFormat); + EXPECT_EQ(image->gainMap->image->gainMap->image, nullptr); + EXPECT_EQ(decoded_image->gainMap->image->alphaPlane, nullptr); if (decoder->enableParsingGainMapMetadata) { - CheckGainMapMetadataMatches(decoded_image->gainMap.metadata, - image->gainMap.metadata); + CheckGainMapMetadataMatches(decoded_image->gainMap->metadata, + image->gainMap->metadata); } } @@ -97,10 +100,11 @@ void EncodeDecodeValid(ImagePtr image, EncoderPtr encoder, DecoderPtr decoder) { // because the C array fields in the struct seem to prevent fuzztest from // handling it natively. ImagePtr AddGainMapToImage( - ImagePtr image, ImagePtr gainMap, + ImagePtr image, ImagePtr gain_map, const std::array& metadata) { - image->gainMap.image = gainMap.release(); - std::memcpy(&image->gainMap.metadata, metadata.data(), metadata.size()); + image->gainMap = avifGainMapCreate(); + image->gainMap->image = gain_map.release(); + std::memcpy(&image->gainMap->metadata, metadata.data(), metadata.size()); return image; } diff --git a/tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc b/tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc index a7666c412d..396991d7ca 100644 --- a/tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc +++ b/tests/gtest/avif_fuzztest_enc_dec_incr_experimental.cc @@ -40,14 +40,15 @@ void EncodeDecodeGridValid(ImagePtr image, EncoderPtr encoder, const uint32_t encoded_height = std::min(image->height, grid_rows * cell_height); - const avifImage* gain_map = image->gainMap.image; + const avifImage* gain_map = + image->gainMap != nullptr ? image->gainMap->image : nullptr; if (gain_map != nullptr) { std::vector gain_map_cells = ImageToGrid(gain_map, grid_cols, grid_rows); if (gain_map_cells.empty()) return; ASSERT_EQ(gain_map_cells.size(), cells.size()); for (size_t i = 0; i < gain_map_cells.size(); ++i) { - cells[i]->gainMap.image = gain_map_cells[i].release(); + cells[i]->gainMap->image = gain_map_cells[i].release(); } } @@ -68,11 +69,11 @@ void EncodeDecodeGridValid(ImagePtr image, EncoderPtr encoder, !avifAreGridDimensionsValid( gain_map->yuvFormat, std::min(gain_map->width, - grid_cols * cells.front()->gainMap.image->width), + grid_cols * cells.front()->gainMap->image->width), std::min(gain_map->height, - grid_rows * cells.front()->gainMap.image->height), - cells.front()->gainMap.image->width, - cells.front()->gainMap.image->height, nullptr))) { + grid_rows * cells.front()->gainMap->image->height), + cells.front()->gainMap->image->width, + cells.front()->gainMap->image->height, nullptr))) { ASSERT_TRUE(encoder_result == AVIF_RESULT_INVALID_IMAGE_GRID) << avifResultToString(encoder_result); return; @@ -98,10 +99,11 @@ void EncodeDecodeGridValid(ImagePtr image, EncoderPtr encoder, // because the C array fields in the struct seem to prevent fuzztest from // handling it natively. ImagePtr AddGainMapToImage( - ImagePtr image, ImagePtr gainMap, + ImagePtr image, ImagePtr gain_map, const std::array& metadata) { - image->gainMap.image = gainMap.release(); - std::memcpy(&image->gainMap.metadata, metadata.data(), metadata.size()); + image->gainMap = avifGainMapCreate(); + image->gainMap->image = gain_map.release(); + std::memcpy(&image->gainMap->metadata, metadata.data(), metadata.size()); return image; } diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc index 05d71b3b91..3743c9b42c 100644 --- a/tests/gtest/avifgainmaptest.cc +++ b/tests/gtest/avifgainmaptest.cc @@ -88,8 +88,12 @@ ImagePtr CreateTestImageWithGainMap(bool base_rendition_is_hdr) { return nullptr; } testutil::FillImageGradient(gain_map.get()); - image->gainMap.image = gain_map.release(); // 'image' now owns the gain map. - image->gainMap.metadata = GetTestGainMapMetadata(base_rendition_is_hdr); + image->gainMap = avifGainMapCreate(); + if (image->gainMap == nullptr) { + return nullptr; + } + image->gainMap->image = gain_map.release(); // 'image' now owns the gain map. + image->gainMap->metadata = GetTestGainMapMetadata(base_rendition_is_hdr); if (base_rendition_is_hdr) { image->clli.maxCLL = 10; @@ -97,8 +101,8 @@ ImagePtr CreateTestImageWithGainMap(bool base_rendition_is_hdr) { } else { // Even though this is attached to the gain map, it represents the clli // information of the tone mapped image. - image->gainMap.image->clli.maxCLL = 10; - image->gainMap.image->clli.maxPALL = 5; + image->gainMap->image->clli.maxCLL = 10; + image->gainMap->image->clli.maxPALL = 5; } return image; @@ -132,18 +136,19 @@ TEST(GainMapTest, EncodeDecodeBaseImageSdr) { // Verify that the gain map is present and matches the input. EXPECT_TRUE(decoder->gainMapPresent); - ASSERT_NE(decoded->gainMap.image, nullptr); - EXPECT_EQ(decoded->gainMap.image->matrixCoefficients, - image->gainMap.image->matrixCoefficients); - EXPECT_EQ(decoded->gainMap.image->clli.maxCLL, - image->gainMap.image->clli.maxCLL); - EXPECT_EQ(decoded->gainMap.image->clli.maxPALL, - image->gainMap.image->clli.maxPALL); - EXPECT_EQ(decoded->gainMap.image->width, image->gainMap.image->width); - EXPECT_EQ(decoded->gainMap.image->height, image->gainMap.image->height); - EXPECT_EQ(decoded->gainMap.image->depth, image->gainMap.image->depth); - CheckGainMapMetadataMatches(decoded->gainMap.metadata, - image->gainMap.metadata); + ASSERT_NE(decoded->gainMap, nullptr); + ASSERT_NE(decoded->gainMap->image, nullptr); + EXPECT_EQ(decoded->gainMap->image->matrixCoefficients, + image->gainMap->image->matrixCoefficients); + EXPECT_EQ(decoded->gainMap->image->clli.maxCLL, + image->gainMap->image->clli.maxCLL); + EXPECT_EQ(decoded->gainMap->image->clli.maxPALL, + image->gainMap->image->clli.maxPALL); + EXPECT_EQ(decoded->gainMap->image->width, image->gainMap->image->width); + EXPECT_EQ(decoded->gainMap->image->height, image->gainMap->image->height); + EXPECT_EQ(decoded->gainMap->image->depth, image->gainMap->image->depth); + CheckGainMapMetadataMatches(decoded->gainMap->metadata, + image->gainMap->metadata); // Decode the image. result = avifDecoderNextImage(decoder.get()); @@ -152,7 +157,7 @@ TEST(GainMapTest, EncodeDecodeBaseImageSdr) { // Verify that the input and decoded images are close. EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0); - EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image), + EXPECT_GT(testutil::GetPsnr(*image->gainMap->image, *decoded->gainMap->image), 40.0); // Uncomment the following to save the encoded image as an AVIF file. @@ -185,13 +190,14 @@ TEST(GainMapTest, EncodeDecodeBaseImageHdr) { EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0); // Verify that the gain map is present and matches the input. EXPECT_TRUE(decoder->gainMapPresent); - ASSERT_NE(decoded->gainMap.image, nullptr); - EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image), + ASSERT_NE(decoded->gainMap, nullptr); + ASSERT_NE(decoded->gainMap->image, nullptr); + EXPECT_GT(testutil::GetPsnr(*image->gainMap->image, *decoded->gainMap->image), 40.0); EXPECT_EQ(decoded->clli.maxCLL, image->clli.maxCLL); EXPECT_EQ(decoded->clli.maxPALL, image->clli.maxPALL); - CheckGainMapMetadataMatches(decoded->gainMap.metadata, - image->gainMap.metadata); + CheckGainMapMetadataMatches(decoded->gainMap->metadata, + image->gainMap->metadata); // Uncomment the following to save the encoded image as an AVIF file. // std::ofstream("/tmp/avifgainmaptest_basehdr.avif", std::ios::binary) @@ -202,14 +208,14 @@ TEST(GainMapTest, EncodeDecodeMetadataSameDenominator) { ImagePtr image = CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/true); const uint32_t kDenominator = 1000; - image->gainMap.metadata.baseHdrHeadroomD = kDenominator; - image->gainMap.metadata.alternateHdrHeadroomD = kDenominator; + image->gainMap->metadata.baseHdrHeadroomD = kDenominator; + image->gainMap->metadata.alternateHdrHeadroomD = kDenominator; for (int c = 0; c < 3; ++c) { - image->gainMap.metadata.baseOffsetD[c] = kDenominator; - image->gainMap.metadata.alternateOffsetD[c] = kDenominator; - image->gainMap.metadata.gainMapGammaD[c] = kDenominator; - image->gainMap.metadata.gainMapMinD[c] = kDenominator; - image->gainMap.metadata.gainMapMaxD[c] = kDenominator; + image->gainMap->metadata.baseOffsetD[c] = kDenominator; + image->gainMap->metadata.alternateOffsetD[c] = kDenominator; + image->gainMap->metadata.gainMapGammaD[c] = kDenominator; + image->gainMap->metadata.gainMapMinD[c] = kDenominator; + image->gainMap->metadata.gainMapMaxD[c] = kDenominator; } EncoderPtr encoder(avifEncoderCreate()); @@ -231,24 +237,24 @@ TEST(GainMapTest, EncodeDecodeMetadataSameDenominator) { << avifResultToString(result) << " " << decoder->diag.error; // Verify that the gain map metadata matches the input. - CheckGainMapMetadataMatches(decoded->gainMap.metadata, - image->gainMap.metadata); + CheckGainMapMetadataMatches(decoded->gainMap->metadata, + image->gainMap->metadata); } TEST(GainMapTest, EncodeDecodeMetadataAllChannelsIdentical) { ImagePtr image = CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/true); for (int c = 0; c < 3; ++c) { - image->gainMap.metadata.baseOffsetN[c] = 1; - image->gainMap.metadata.baseOffsetD[c] = 2; - image->gainMap.metadata.alternateOffsetN[c] = 3; - image->gainMap.metadata.alternateOffsetD[c] = 4; - image->gainMap.metadata.gainMapGammaN[c] = 5; - image->gainMap.metadata.gainMapGammaD[c] = 6; - image->gainMap.metadata.gainMapMinN[c] = 7; - image->gainMap.metadata.gainMapMinD[c] = 8; - image->gainMap.metadata.gainMapMaxN[c] = 9; - image->gainMap.metadata.gainMapMaxD[c] = 10; + image->gainMap->metadata.baseOffsetN[c] = 1; + image->gainMap->metadata.baseOffsetD[c] = 2; + image->gainMap->metadata.alternateOffsetN[c] = 3; + image->gainMap->metadata.alternateOffsetD[c] = 4; + image->gainMap->metadata.gainMapGammaN[c] = 5; + image->gainMap->metadata.gainMapGammaD[c] = 6; + image->gainMap->metadata.gainMapMinN[c] = 7; + image->gainMap->metadata.gainMapMinD[c] = 8; + image->gainMap->metadata.gainMapMaxN[c] = 9; + image->gainMap->metadata.gainMapMaxD[c] = 10; } EncoderPtr encoder(avifEncoderCreate()); @@ -270,8 +276,8 @@ TEST(GainMapTest, EncodeDecodeMetadataAllChannelsIdentical) { << avifResultToString(result) << " " << decoder->diag.error; // Verify that the gain map metadata matches the input. - CheckGainMapMetadataMatches(decoded->gainMap.metadata, - image->gainMap.metadata); + CheckGainMapMetadataMatches(decoded->gainMap->metadata, + image->gainMap->metadata); } TEST(GainMapTest, EncodeDecodeGrid) { @@ -300,12 +306,14 @@ TEST(GainMapTest, EncodeDecodeGrid) { ASSERT_NE(gain_map, nullptr); testutil::FillImageGradient(gain_map.get()); // 'image' now owns the gain map. - image->gainMap.image = gain_map.release(); + image->gainMap = avifGainMapCreate(); + ASSERT_NE(image->gainMap, nullptr); + image->gainMap->image = gain_map.release(); // all cells must have the same metadata - image->gainMap.metadata = gain_map_metadata; + image->gainMap->metadata = gain_map_metadata; cell_ptrs.push_back(image.get()); - gain_map_ptrs.push_back(image->gainMap.image); + gain_map_ptrs.push_back(image->gainMap->image); cells.push_back(std::move(image)); } @@ -338,11 +346,11 @@ TEST(GainMapTest, EncodeDecodeGrid) { ASSERT_EQ(testutil::MergeGrid(kGridCols, kGridRows, cell_ptrs, merged.get()), AVIF_RESULT_OK); - ImagePtr merged_gain_map = - testutil::CreateImage(static_cast(decoded->gainMap.image->width), - static_cast(decoded->gainMap.image->height), - decoded->gainMap.image->depth, - decoded->gainMap.image->yuvFormat, AVIF_PLANES_YUV); + ImagePtr merged_gain_map = testutil::CreateImage( + static_cast(decoded->gainMap->image->width), + static_cast(decoded->gainMap->image->height), + decoded->gainMap->image->depth, decoded->gainMap->image->yuvFormat, + AVIF_PLANES_YUV); ASSERT_EQ(testutil::MergeGrid(kGridCols, kGridRows, gain_map_ptrs, merged_gain_map.get()), AVIF_RESULT_OK); @@ -351,9 +359,11 @@ TEST(GainMapTest, EncodeDecodeGrid) { ASSERT_GT(testutil::GetPsnr(*merged, *decoded), 40.0); // Verify that the gain map is present and matches the input. EXPECT_TRUE(decoder->gainMapPresent); - ASSERT_NE(decoded->gainMap.image, nullptr); - ASSERT_GT(testutil::GetPsnr(*merged_gain_map, *decoded->gainMap.image), 40.0); - CheckGainMapMetadataMatches(decoded->gainMap.metadata, gain_map_metadata); + ASSERT_NE(decoded->gainMap, nullptr); + ASSERT_NE(decoded->gainMap->image, nullptr); + ASSERT_GT(testutil::GetPsnr(*merged_gain_map, *decoded->gainMap->image), + 40.0); + CheckGainMapMetadataMatches(decoded->gainMap->metadata, gain_map_metadata); // Check that non-incremental and incremental decodings of a grid AVIF produce // the same pixels. @@ -391,9 +401,11 @@ TEST(GainMapTest, InvalidGrid) { ASSERT_NE(gain_map, nullptr); testutil::FillImageGradient(gain_map.get()); // 'image' now owns the gain map. - image->gainMap.image = gain_map.release(); + image->gainMap = avifGainMapCreate(); + ASSERT_NE(image->gainMap, nullptr); + image->gainMap->image = gain_map.release(); // all cells must have the same metadata - image->gainMap.metadata = gain_map_metadata; + image->gainMap->metadata = gain_map_metadata; cell_ptrs.push_back(image.get()); cells.push_back(std::move(image)); @@ -406,32 +418,33 @@ TEST(GainMapTest, InvalidGrid) { avifResult result; // Invalid: one cell has the wrong size. - cells[1]->gainMap.image->height = 90; + cells[1]->gainMap->image->height = 90; result = avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows, cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE); EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID) << avifResultToString(result) << " " << encoder->diag.error; - cells[1]->gainMap.image->height = cells[0]->gainMap.image->height; // Revert. + cells[1]->gainMap->image->height = + cells[0]->gainMap->image->height; // Revert. // Invalid: one cell has a different depth. - cells[1]->gainMap.image->depth = 12; + cells[1]->gainMap->image->depth = 12; result = avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows, cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE); EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID) << avifResultToString(result) << " " << encoder->diag.error; - cells[1]->gainMap.image->depth = cells[0]->gainMap.image->depth; // Revert. + cells[1]->gainMap->image->depth = cells[0]->gainMap->image->depth; // Revert. // Invalid: one cell has different gain map metadata. - cells[1]->gainMap.metadata.gainMapGammaN[0] = 42; + cells[1]->gainMap->metadata.gainMapGammaN[0] = 42; result = avifEncoderAddImageGrid(encoder.get(), kGridCols, kGridRows, cell_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE); EXPECT_EQ(result, AVIF_RESULT_INVALID_IMAGE_GRID) << avifResultToString(result) << " " << encoder->diag.error; - cells[1]->gainMap.metadata.gainMapGammaN[0] = - cells[0]->gainMap.metadata.gainMapGammaN[0]; // Revert. + cells[1]->gainMap->metadata.gainMapGammaN[0] = + cells[0]->gainMap->metadata.gainMapGammaN[0]; // Revert. } TEST(GainMapTest, SequenceNotSupported) { @@ -448,8 +461,9 @@ TEST(GainMapTest, SequenceNotSupported) { ASSERT_NE(gain_map, nullptr); testutil::FillImageGradient(gain_map.get()); // 'image' now owns the gain map. - image->gainMap.image = gain_map.release(); - + image->gainMap = avifGainMapCreate(); + ASSERT_NE(image->gainMap, nullptr); + image->gainMap->image = gain_map.release(); EncoderPtr encoder(avifEncoderCreate()); ASSERT_NE(encoder, nullptr); testutil::AvifRwData encoded; @@ -494,9 +508,7 @@ TEST(GainMapTest, IgnoreGainMap) { // Verify that the gain map was detected... EXPECT_TRUE(decoder->gainMapPresent); // ... but not decoded because enableDecodingGainMap is false by default. - EXPECT_EQ(decoded->gainMap.image, nullptr); - // Check that the gain map metadata was not populated either. - CheckGainMapMetadataMatches(decoded->gainMap.metadata, avifGainMapMetadata()); + EXPECT_EQ(decoded->gainMap, nullptr); } TEST(GainMapTest, IgnoreGainMapButReadMetadata) { @@ -525,11 +537,12 @@ TEST(GainMapTest, IgnoreGainMapButReadMetadata) { EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0); // Verify that the gain map was detected... EXPECT_TRUE(decoder->gainMapPresent); + ASSERT_NE(decoded->gainMap, nullptr); // ... but not decoded because enableDecodingGainMap is false by default. - EXPECT_EQ(decoded->gainMap.image, nullptr); + EXPECT_EQ(decoded->gainMap->image, nullptr); // Check that the gain map metadata WAS populated. - CheckGainMapMetadataMatches(decoded->gainMap.metadata, - image->gainMap.metadata); + CheckGainMapMetadataMatches(decoded->gainMap->metadata, + image->gainMap->metadata); } TEST(GainMapTest, IgnoreColorAndAlpha) { @@ -566,11 +579,12 @@ TEST(GainMapTest, IgnoreColorAndAlpha) { EXPECT_EQ(decoder->image->alphaRowBytes, 0u); // The gain map was decoded. EXPECT_TRUE(decoder->gainMapPresent); - ASSERT_NE(decoded->gainMap.image, nullptr); - EXPECT_GT(testutil::GetPsnr(*image->gainMap.image, *decoded->gainMap.image), + ASSERT_NE(decoded->gainMap, nullptr); + ASSERT_NE(decoded->gainMap->image, nullptr); + EXPECT_GT(testutil::GetPsnr(*image->gainMap->image, *decoded->gainMap->image), 40.0); - CheckGainMapMetadataMatches(decoded->gainMap.metadata, - image->gainMap.metadata); + CheckGainMapMetadataMatches(decoded->gainMap->metadata, + image->gainMap->metadata); } TEST(GainMapTest, IgnoreAll) { @@ -598,9 +612,9 @@ TEST(GainMapTest, IgnoreAll) { ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); EXPECT_TRUE(decoder->gainMapPresent); - CheckGainMapMetadataMatches(decoder->image->gainMap.metadata, - image->gainMap.metadata); - ASSERT_EQ(decoder->image->gainMap.image, nullptr); + CheckGainMapMetadataMatches(decoder->image->gainMap->metadata, + image->gainMap->metadata); + ASSERT_EQ(decoder->image->gainMap->image, nullptr); // But trying to access the next image should give an error because both // ignoreColorAndAlpha and enableDecodingGainMap are set. @@ -638,8 +652,7 @@ TEST(GainMapTest, NoGainMap) { EXPECT_GT(testutil::GetPsnr(*image, *decoded), 40.0); // Verify that no gain map was found. EXPECT_FALSE(decoder->gainMapPresent); - EXPECT_EQ(decoded->gainMap.image, nullptr); - CheckGainMapMetadataMatches(decoded->gainMap.metadata, avifGainMapMetadata()); + EXPECT_EQ(decoded->gainMap, nullptr); } TEST(GainMapTest, DecodeGainMapGrid) { @@ -667,13 +680,14 @@ TEST(GainMapTest, DecodeGainMapGrid) { EXPECT_EQ(decoded->width, 128u * 4u); EXPECT_EQ(decoded->height, 200u * 3u); EXPECT_EQ(decoded->depth, 10u); - ASSERT_NE(decoded->gainMap.image, nullptr); + ASSERT_NE(decoded->gainMap, nullptr); + ASSERT_NE(decoded->gainMap->image, nullptr); // Gain map: 2x2 grid of 64x80 tiles. - EXPECT_EQ(decoded->gainMap.image->width, 64u * 2u); - EXPECT_EQ(decoded->gainMap.image->height, 80u * 2u); - EXPECT_EQ(decoded->gainMap.image->depth, 8u); - EXPECT_EQ(decoded->gainMap.metadata.alternateHdrHeadroomN, 6u); - EXPECT_EQ(decoded->gainMap.metadata.alternateHdrHeadroomD, 2u); + EXPECT_EQ(decoded->gainMap->image->width, 64u * 2u); + EXPECT_EQ(decoded->gainMap->image->height, 80u * 2u); + EXPECT_EQ(decoded->gainMap->image->depth, 8u); + EXPECT_EQ(decoded->gainMap->metadata.alternateHdrHeadroomN, 6u); + EXPECT_EQ(decoded->gainMap->metadata.alternateHdrHeadroomD, 2u); // Decode the image. result = avifDecoderNextImage(decoder.get()); @@ -696,12 +710,13 @@ TEST(GainMapTest, DecodeColorGridGainMapNoGrid) { // Color+alpha: 4x3 grid of 128x200 tiles. EXPECT_EQ(decoded->width, 128u * 4u); EXPECT_EQ(decoded->height, 200u * 3u); - ASSERT_NE(decoded->gainMap.image, nullptr); + ASSERT_NE(decoded->gainMap, nullptr); + ASSERT_NE(decoded->gainMap->image, nullptr); // Gain map: single image of size 64x80. - EXPECT_EQ(decoded->gainMap.image->width, 64u); - EXPECT_EQ(decoded->gainMap.image->height, 80u); - EXPECT_EQ(decoded->gainMap.metadata.alternateHdrHeadroomN, 6u); - EXPECT_EQ(decoded->gainMap.metadata.alternateHdrHeadroomD, 2u); + EXPECT_EQ(decoded->gainMap->image->width, 64u); + EXPECT_EQ(decoded->gainMap->image->height, 80u); + EXPECT_EQ(decoded->gainMap->metadata.alternateHdrHeadroomN, 6u); + EXPECT_EQ(decoded->gainMap->metadata.alternateHdrHeadroomD, 2u); } TEST(GainMapTest, DecodeColorNoGridGainMapGrid) { @@ -719,12 +734,13 @@ TEST(GainMapTest, DecodeColorNoGridGainMapGrid) { // Color+alpha: single image of size 128x200 . EXPECT_EQ(decoded->width, 128u); EXPECT_EQ(decoded->height, 200u); - ASSERT_NE(decoded->gainMap.image, nullptr); + ASSERT_NE(decoded->gainMap, nullptr); + ASSERT_NE(decoded->gainMap->image, nullptr); // Gain map: 2x2 grid of 64x80 tiles. - EXPECT_EQ(decoded->gainMap.image->width, 64u * 2u); - EXPECT_EQ(decoded->gainMap.image->height, 80u * 2u); - EXPECT_EQ(decoded->gainMap.metadata.alternateHdrHeadroomN, 6u); - EXPECT_EQ(decoded->gainMap.metadata.alternateHdrHeadroomD, 2u); + EXPECT_EQ(decoded->gainMap->image->width, 64u * 2u); + EXPECT_EQ(decoded->gainMap->image->height, 80u * 2u); + EXPECT_EQ(decoded->gainMap->metadata.alternateHdrHeadroomN, 6u); + EXPECT_EQ(decoded->gainMap->metadata.alternateHdrHeadroomD, 2u); } #define EXPECT_FRACTION_NEAR(numerator, denominator, expected) \ @@ -849,12 +865,12 @@ TEST(GainMapTest, CreateTestImages) { avifDecoderReadFile(decoder.get(), image.get(), path.c_str()); ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result) << " " << decoder->diag.error; - ASSERT_NE(image->gainMap.image, nullptr); + ASSERT_NE(image->gainMap->image, nullptr); avifDiagnostics diag; result = - avifImageScale(image->gainMap.image, image->gainMap.image->width * 2, - image->gainMap.image->height * 2, &diag); + avifImageScale(image->gainMap->image, image->gainMap->image->width * 2, + image->gainMap->image->height * 2, &diag); ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result) << " " << decoder->diag.error; @@ -886,13 +902,12 @@ TEST(GainMapTest, CreateTestImages) { decoder.get(), sdr_with_gainmap.get(), sdr_path.c_str()); ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result) << " " << decoder->diag.error; - ASSERT_NE(sdr_with_gainmap->gainMap.image, nullptr); + ASSERT_NE(sdr_with_gainmap->gainMap->image, nullptr); // Move the gain map from the sdr image to the hdr image. - hdr_image->gainMap.image = sdr_with_gainmap->gainMap.image; - sdr_with_gainmap->gainMap.image = nullptr; - hdr_image->gainMap.metadata = sdr_with_gainmap->gainMap.metadata; - SwapBaseAndAlternate(hdr_image->gainMap.metadata); + hdr_image->gainMap = sdr_with_gainmap->gainMap; + sdr_with_gainmap->gainMap = nullptr; + SwapBaseAndAlternate(hdr_image->gainMap->metadata); const testutil::AvifRwData encoded = testutil::Encode(hdr_image.get(), /*speed=*/9, /*quality=*/90); @@ -904,9 +919,9 @@ TEST(GainMapTest, CreateTestImages) { } avifDiagnostics diag; - result = avifImageScale(hdr_image->gainMap.image, - hdr_image->gainMap.image->width / 2, - hdr_image->gainMap.image->height / 2, &diag); + result = avifImageScale(hdr_image->gainMap->image, + hdr_image->gainMap->image->width / 2, + hdr_image->gainMap->image->height / 2, &diag); ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result) << " " << decoder->diag.error; @@ -1007,9 +1022,9 @@ TEST_P(ToneMapTest, ToneMapImage) { ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result) << " " << decoder->diag.error; - ASSERT_NE(image->gainMap.image, nullptr); + ASSERT_NE(image->gainMap->image, nullptr); - ToneMapImageAndCompareToReference(image.get(), image->gainMap, hdr_headroom, + ToneMapImageAndCompareToReference(image.get(), *image->gainMap, hdr_headroom, out_depth, out_transfer_characteristics, out_rgb_format, reference_image.get(), min_psnr, max_psnr); @@ -1146,36 +1161,37 @@ TEST_P(CreateGainMapTest, Create) { (uint32_t)std::round((float)sdr_image->width / downscaling), 1u); const uint32_t gain_map_height = std::max( (uint32_t)std::round((float)sdr_image->height / downscaling), 1u); - avifGainMap gain_map; - memset(&gain_map, 0, sizeof(gain_map)); + std::unique_ptr gain_map( + avifGainMapCreate(), avifGainMapDestroy); ImagePtr gain_map_image(avifImageCreate(gain_map_width, gain_map_height, gain_map_depth, gain_map_format)); - gain_map.image = gain_map_image.get(); + gain_map->image = + gain_map_image.release(); // 'gain_map' now owns gain_map_image; avifDiagnostics diag; avifResult result = avifImageComputeGainMap(sdr_image.get(), hdr_image.get(), - &gain_map, &diag); + gain_map.get(), &diag); ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result) << " " << diag.error; - EXPECT_EQ(gain_map.image->width, gain_map_width); - EXPECT_EQ(gain_map.image->height, gain_map_height); + EXPECT_EQ(gain_map->image->width, gain_map_width); + EXPECT_EQ(gain_map->image->height, gain_map_height); - const float hdr_headroom = (float)gain_map.metadata.alternateHdrHeadroomN / - gain_map.metadata.alternateHdrHeadroomD; + const float hdr_headroom = (float)gain_map->metadata.alternateHdrHeadroomN / + gain_map->metadata.alternateHdrHeadroomD; // Tone map from sdr to hdr. float psnr_sdr_to_hdr_forward; ToneMapImageAndCompareToReference( - sdr_image.get(), gain_map, hdr_headroom, hdr_image->depth, + sdr_image.get(), *gain_map, hdr_headroom, hdr_image->depth, hdr_image->transferCharacteristics, AVIF_RGB_FORMAT_RGB, hdr_image.get(), min_psnr, max_psnr, &psnr_sdr_to_hdr_forward); // Tone map from hdr to sdr. - SwapBaseAndAlternate(gain_map.metadata); + SwapBaseAndAlternate(gain_map->metadata); float psnr_hdr_to_sdr_backward; ToneMapImageAndCompareToReference( - hdr_image.get(), gain_map, /*hdr_headroom=*/0.0, sdr_image->depth, + hdr_image.get(), *gain_map, /*hdr_headroom=*/0.0, sdr_image->depth, sdr_image->transferCharacteristics, AVIF_RGB_FORMAT_RGB, sdr_image.get(), min_psnr, max_psnr, &psnr_hdr_to_sdr_backward); @@ -1184,32 +1200,32 @@ TEST_P(CreateGainMapTest, Create) { // "/tmp/gain_map_sdr_to_hdr.png")); // Compute the gain map in the other direction (from hdr to sdr). - result = avifImageComputeGainMap(hdr_image.get(), sdr_image.get(), &gain_map, - &diag); + result = avifImageComputeGainMap(hdr_image.get(), sdr_image.get(), + gain_map.get(), &diag); ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result) << " " << diag.error; - const float hdr_headroom2 = (float)gain_map.metadata.baseHdrHeadroomN / - gain_map.metadata.baseHdrHeadroomD; + const float hdr_headroom2 = (float)gain_map->metadata.baseHdrHeadroomN / + gain_map->metadata.baseHdrHeadroomD; EXPECT_NEAR(hdr_headroom2, hdr_headroom, 0.001); // Tone map from hdr to sdr. float psnr_hdr_to_sdr_forward; ToneMapImageAndCompareToReference( - hdr_image.get(), gain_map, /*hdr_headroom=*/0.0, sdr_image->depth, + hdr_image.get(), *gain_map, /*hdr_headroom=*/0.0, sdr_image->depth, sdr_image->transferCharacteristics, AVIF_RGB_FORMAT_RGB, sdr_image.get(), min_psnr, max_psnr, &psnr_hdr_to_sdr_forward); // Tone map from sdr to hdr. - SwapBaseAndAlternate(gain_map.metadata); + SwapBaseAndAlternate(gain_map->metadata); float psnr_sdr_to_hdr_backward; ToneMapImageAndCompareToReference( - sdr_image.get(), gain_map, hdr_headroom, hdr_image->depth, + sdr_image.get(), *gain_map, hdr_headroom, hdr_image->depth, hdr_image->transferCharacteristics, AVIF_RGB_FORMAT_RGB, hdr_image.get(), min_psnr, max_psnr, &psnr_sdr_to_hdr_backward); // Uncomment the following to save the gain map as a PNG file. - // ASSERT_TRUE(testutil::WriteImage(gain_map.image, + // ASSERT_TRUE(testutil::WriteImage(gain_map->image, // "/tmp/gain_map_hdr_to_sdr.png")); // Results should be about the same whether the gain map was computed from sdr diff --git a/tests/gtest/avifincrtest_helpers.cc b/tests/gtest/avifincrtest_helpers.cc index 3fef504182..1f06caf65e 100644 --- a/tests/gtest/avifincrtest_helpers.cc +++ b/tests/gtest/avifincrtest_helpers.cc @@ -65,10 +65,11 @@ void ComparePartialYuva(const avifImage& image1, const avifImage& image2, } #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - if (image1.gainMap.image != nullptr && image2.gainMap.image != nullptr) { + if (image1.gainMap != nullptr && image1.gainMap->image != nullptr && + image2.gainMap != nullptr && image2.gainMap->image != nullptr) { const uint32_t gain_map_row_count = (uint32_t)roundf( - (float)row_count / image1.height * image1.gainMap.image->height); - ComparePartialYuva(*image1.gainMap.image, *image2.gainMap.image, + (float)row_count / image1.height * image1.gainMap->image->height); + ComparePartialYuva(*image1.gainMap->image, *image2.gainMap->image, gain_map_row_count); } #endif diff --git a/tests/gtest/avifjpeggainmaptest.cc b/tests/gtest/avifjpeggainmaptest.cc index efad8cff34..adf3903abe 100644 --- a/tests/gtest/avifjpeggainmaptest.cc +++ b/tests/gtest/avifjpeggainmaptest.cc @@ -82,14 +82,15 @@ TEST(JpegTest, ReadJpegWithGainMap) { /*ignore_xmp=*/true, /*allow_changing_cicp=*/true, /*ignore_gain_map=*/false); ASSERT_NE(image, nullptr); - ASSERT_NE(image->gainMap.image, nullptr); - EXPECT_EQ(image->gainMap.image->width, 512u); - EXPECT_EQ(image->gainMap.image->height, 384u); + ASSERT_NE(image->gainMap, nullptr); + ASSERT_NE(image->gainMap->image, nullptr); + EXPECT_EQ(image->gainMap->image->width, 512u); + EXPECT_EQ(image->gainMap->image->height, 384u); // Since ignore_xmp is true, there should be no XMP, even if it had to // be read to parse the gain map. EXPECT_EQ(image->xmp.size, 0u); - CheckGainMapMetadata(image->gainMap.metadata, + CheckGainMapMetadata(image->gainMap->metadata, /*gain_map_min=*/{0.0, 0.0, 0.0}, /*gain_map_max=*/{3.5, 3.6, 3.7}, /*gain_map_gamma=*/{1.0, 1.0, 1.0}, @@ -109,7 +110,7 @@ TEST(JpegTest, IgnoreGainMap) { /*ignore_xmp=*/false, /*allow_changing_cicp=*/true, /*ignore_gain_map=*/true); ASSERT_NE(image, nullptr); - EXPECT_EQ(image->gainMap.image, nullptr); + ASSERT_EQ(image->gainMap, nullptr); // Check there is xmp since ignore_xmp is false (just making sure that // ignore_gain_map=true has no impact on this). EXPECT_GT(image->xmp.size, 0u); From bc54094ab229a1674d7399b4a751357d6f4b5127 Mon Sep 17 00:00:00 2001 From: Maryla Date: Thu, 23 Nov 2023 15:32:26 +0100 Subject: [PATCH 2/2] Address minor review comments. --- apps/avifgainmaputil/printmetadata_command.cc | 4 +++- include/avif/avif.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/avifgainmaputil/printmetadata_command.cc b/apps/avifgainmaputil/printmetadata_command.cc index 352d127cb4..6a43e561d4 100644 --- a/apps/avifgainmaputil/printmetadata_command.cc +++ b/apps/avifgainmaputil/printmetadata_command.cc @@ -3,6 +3,7 @@ #include "printmetadata_command.h" +#include #include #include "avif/avif_cxx.h" @@ -57,11 +58,12 @@ avifResult PrintMetadataCommand::Run() { << decoder->diag.error << ")\n"; return result; } - if (!decoder->gainMapPresent || decoder->image->gainMap == nullptr) { + if (!decoder->gainMapPresent) { std::cerr << "Input image " << arg_input_filename_ << " does not contain a gain map\n"; return AVIF_RESULT_INVALID_ARGUMENT; } + assert(decoder->image->gainMap); const avifGainMapMetadata& metadata = decoder->image->gainMap->metadata; const int w = 20; diff --git a/include/avif/avif.h b/include/avif/avif.h index 3e15827d4c..53b7444a54 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -630,7 +630,7 @@ typedef struct avifGainMapMetadata typedef struct avifGainMap { // Gain map pixels. - // Owned by the avifGainMap and gets freed when calling avifGainMapDestro(). + // Owned by the avifGainMap and gets freed when calling avifGainMapDestroy(). // Used fields: width, height, depth, yufFormat, yuvRange, // yuvChromaSamplePosition, yuvPlanes, yuvRowBytes, imageOwnsYUVPlanes, // matrixCoefficients, transferCharacteristics. Other fields are ignored.