From 6bdda98a03d97d987ca343f1211d5ec2fb400102 Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Thu, 3 Oct 2024 14:38:47 +0000 Subject: [PATCH] Implement the HDR part of MinimizedImageBox (#2440) Write and parse tone mapping and gain map elements in SlimHEIF. --- src/read.c | 472 +++++++++++++++++++++++++++--------- src/write.c | 339 ++++++++++++++++++-------- tests/CMakeLists.txt | 4 +- tests/gtest/avifminitest.cc | 66 ++++- 4 files changed, 657 insertions(+), 224 deletions(-) diff --git a/src/read.c b/src/read.c index d711cb95cf..5c8b68ac55 100644 --- a/src/read.c +++ b/src/read.c @@ -2252,6 +2252,109 @@ static avifResult avifParseContentLightLevelInformationBox(avifProperty * prop, return AVIF_RESULT_OK; } +#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) && defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) +static avifResult avifSkipMasteringDisplayColourVolume(avifROStream * s) +{ + for (int c = 0; c < 3; c++) { + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) display_primaries_x; + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) display_primaries_y; + } + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) white_point_x; + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) white_point_y; + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) max_display_mastering_luminance; + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) min_display_mastering_luminance; + return AVIF_RESULT_OK; +} + +static avifResult avifSkipContentColourVolume(avifROStream * s) +{ + AVIF_CHECKERR(avifROStreamSkipBits(s, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(1) reserved = 0; // ccv_cancel_flag + AVIF_CHECKERR(avifROStreamSkipBits(s, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(1) reserved = 0; // ccv_persistence_flag + uint8_t ccvPrimariesPresent; + AVIF_CHECKERR(avifROStreamReadBitsU8(s, &ccvPrimariesPresent, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(1) ccv_primaries_present_flag; + uint8_t ccvMinLuminanceValuePresent, ccvMaxLuminanceValuePresent, ccvAvgLuminanceValuePresent; + AVIF_CHECKERR(avifROStreamReadBitsU8(s, &ccvMinLuminanceValuePresent, 1), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(1) ccv_min_luminance_value_present_flag; + AVIF_CHECKERR(avifROStreamReadBitsU8(s, &ccvMaxLuminanceValuePresent, 1), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(1) ccv_max_luminance_value_present_flag; + AVIF_CHECKERR(avifROStreamReadBitsU8(s, &ccvAvgLuminanceValuePresent, 1), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(1) ccv_avg_luminance_value_present_flag; + AVIF_CHECKERR(avifROStreamSkipBits(s, 2), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(2) reserved = 0; + + if (ccvPrimariesPresent) { + for (int c = 0; c < 3; c++) { + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // signed int(32) ccv_primaries_x[[c]]; + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // signed int(32) ccv_primaries_y[[c]]; + } + } + if (ccvMinLuminanceValuePresent) { + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) ccv_min_luminance_value; + } + if (ccvMaxLuminanceValuePresent) { + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) ccv_max_luminance_value; + } + if (ccvAvgLuminanceValuePresent) { + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) ccv_avg_luminance_value; + } + return AVIF_RESULT_OK; +} + +static avifResult avifSkipAmbientViewingEnvironment(avifROStream * s) +{ + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) ambient_illuminance; + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) ambient_light_x; + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) ambient_light_y; + return AVIF_RESULT_OK; +} + +static avifResult avifSkipReferenceViewingEnvironment(avifROStream * s) +{ + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) surround_luminance; + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) surround_light_x; + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) surround_light_y; + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) periphery_luminance; + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) periphery_light_x; + AVIF_CHECKERR(avifROStreamSkipBits(s, 16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) periphery_light_y; + return AVIF_RESULT_OK; +} + +static avifResult avifSkipNominalDiffuseWhite(avifROStream * s) +{ + AVIF_CHECKERR(avifROStreamSkipBits(s, 32), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) diffuse_white_luminance; + return AVIF_RESULT_OK; +} + +static avifResult avifParseMiniHDRProperties(avifROStream * s, uint32_t * hasClli, avifContentLightLevelInformationBox * clli) +{ + AVIF_CHECKERR(avifROStreamReadBitsU32(s, hasClli, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) clli_flag; + uint32_t hasMdcv, hasCclv, hasAmve, hasReve, hasNdwt; + AVIF_CHECKERR(avifROStreamReadBitsU32(s, &hasMdcv, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) mdcv_flag; + AVIF_CHECKERR(avifROStreamReadBitsU32(s, &hasCclv, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) cclv_flag; + AVIF_CHECKERR(avifROStreamReadBitsU32(s, &hasAmve, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) amve_flag; + AVIF_CHECKERR(avifROStreamReadBitsU32(s, &hasReve, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) reve_flag; + AVIF_CHECKERR(avifROStreamReadBitsU32(s, &hasNdwt, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) ndwt_flag; + if (*hasClli) { + AVIF_CHECKRES(avifParseContentLightLevelInformation(s, clli)); // ContentLightLevel clli; + } + if (hasMdcv) { + AVIF_CHECKRES(avifSkipMasteringDisplayColourVolume(s)); // MasteringDisplayColourVolume mdcv; + } + if (hasCclv) { + AVIF_CHECKRES(avifSkipContentColourVolume(s)); // ContentColourVolume cclv; + } + if (hasAmve) { + AVIF_CHECKRES(avifSkipAmbientViewingEnvironment(s)); // AmbientViewingEnvironment amve; + } + if (hasReve) { + AVIF_CHECKRES(avifSkipReferenceViewingEnvironment(s)); // ReferenceViewingEnvironment reve; + } + if (hasNdwt) { + AVIF_CHECKRES(avifSkipNominalDiffuseWhite(s)); // NominalDiffuseWhite ndwt; + } + return AVIF_RESULT_OK; +} +#endif // AVIF_ENABLE_EXPERIMENTAL_MINI && AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP + // Implementation of section 2.3.3 of AV1 Codec ISO Media File Format Binding specification v1.2.0. // See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax. static avifBool avifParseCodecConfiguration(avifROStream * s, avifCodecConfigurationBox * config, const char * configPropName, avifDiagnostics * diag) @@ -3570,13 +3673,14 @@ static avifProperty * avifDecoderItemAddProperty(avifDecoderItem * item, const a return itemProperty; } -static avifResult avifParseMinimizedImageBox(avifMeta * meta, +static avifResult avifParseMinimizedImageBox(avifDecoderData * data, uint64_t rawOffset, const uint8_t * raw, size_t rawLen, avifBool isAvifAccordingToMinorVersion, avifDiagnostics * diag) { + avifMeta * meta = data->meta; BEGIN_STREAM(s, raw, rawLen, diag, "Box[mini]"); meta->fromMiniBox = AVIF_TRUE; @@ -3688,80 +3792,89 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, // High Dynamic Range properties uint32_t hasGainmap = AVIF_FALSE; + uint32_t tmapHasIcc = AVIF_FALSE; + uint32_t gainmapWidth = 0, gainmapHeight = 0; + uint8_t gainmapMatrixCoefficients = 0; + uint32_t gainmapFullRange = 0; + uint32_t gainmapChromaSubsampling = 0; + uint32_t gainmapBitDepth = 0; + uint32_t tmapHasExplicitCicp = AVIF_FALSE; + uint8_t tmapColorPrimaries = AVIF_COLOR_PRIMARIES_UNKNOWN; + uint8_t tmapTransferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNKNOWN; + uint8_t tmapMatrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; + uint32_t tmapFullRange = AVIF_FALSE; + uint32_t hasClli = AVIF_FALSE, tmapHasClli = AVIF_FALSE; + avifContentLightLevelInformationBox clli = {}, tmapClli = {}; if (hasHdr) { - // bit(1) gainmap_flag; - // if (gainmap_flag) { - // unsigned int(small_dimensions_flag ? 7 : 15) gainmap_width_minus1; - // unsigned int(small_dimensions_flag ? 7 : 15) gainmap_height_minus1; - // bit(8) gainmap_matrix_coefficients; - // bit(1) gainmap_full_range_flag; - // bit(2) gainmap_chroma_subsampling; - // if (gainmap_chroma_subsampling == 1 || gainmap_chroma_subsampling == 2) - // bit(1) gainmap_chroma_is_horizontally_centered; - // if (gainmap_chroma_subsampling == 1) - // bit(1) gainmap_chroma_is_vertically_centered; - // bit(1) gainmap_float_flag; - // if (gainmap_float_flag) - // bit(2) gainmap_bit_depth_log2_minus4; - // else { - // bit(1) gainmap_high_bit_depth_flag; - // if (gainmap_high_bit_depth_flag) - // bit(3) gainmap_bit_depth_minus9; - // } - // bit(1) tmap_icc_flag; - // bit(1) tmap_explicit_cicp_flag; - // if (tmap_explicit_cicp_flag) { - // bit(8) tmap_colour_primaries; - // bit(8) tmap_transfer_characteristics; - // bit(8) tmap_matrix_coefficients; - // bit(1) tmap_full_range_flag; - // } - // else { - // tmap_colour_primaries = 1; - // tmap_transfer_characteristics = 13; - // tmap_matrix_coefficients = 6; - // tmap_full_range_flag = 1; - // } - // } - // bit(1) clli_flag; - // bit(1) mdcv_flag; - // bit(1) cclv_flag; - // bit(1) amve_flag; - // bit(1) reve_flag; - // bit(1) ndwt_flag; - // if (clli_flag) - // ContentLightLevel clli; - // if (mdcv_flag) - // MasteringDisplayColourVolume mdcv; - // if (cclv_flag) - // ContentColourVolume cclv; - // if (amve_flag) - // AmbientViewingEnvironment amve; - // if (reve_flag) - // ReferenceViewingEnvironment reve; - // if (ndwt_flag) - // NominalDiffuseWhite ndwt; - // if (gainmap_flag) { - // bit(1) tmap_clli_flag; - // bit(1) tmap_mdcv_flag; - // bit(1) tmap_cclv_flag; - // bit(1) tmap_amve_flag; - // bit(1) tmap_reve_flag; - // bit(1) tmap_ndwt_flag; - // if (tmap_clli_flag) - // ContentLightLevel tmap_clli; - // if (tmap_mdcv_flag) - // MasteringDisplayColourVolume tmap_mdcv; - // if (tmap_cclv_flag) - // ContentColourVolume tmap_cclv; - // if (tmap_amve_flag) - // AmbientViewingEnvironment tmap_amve; - // if (tmap_reve_flag) - // ReferenceViewingEnvironment tmap_reve; - // if (tmap_ndwt_flag) - // NominalDiffuseWhite tmap_ndwt; - // } +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &hasGainmap, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) gainmap_flag; + if (hasGainmap) { + // avifDecoderReset() requires the 'tmap' brand to be registered for the tone mapping derived image item to be parsed. + if (data->compatibleBrands.capacity == 0) { + AVIF_CHECKERR(avifArrayCreate(&data->compatibleBrands, sizeof(avifBrand), 1), AVIF_RESULT_OUT_OF_MEMORY); + } + avifBrand * brand = avifArrayPush(&data->compatibleBrands); + AVIF_CHECKERR(brand != NULL, AVIF_RESULT_OUT_OF_MEMORY); + memcpy(brand, "tmap", sizeof(avifBrand)); + + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapWidth, smallDimensionsFlag ? 7 : 15), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(small_dimensions_flag ? 7 : 15) gainmap_width_minus1; + ++gainmapWidth; + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapHeight, smallDimensionsFlag ? 7 : 15), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(small_dimensions_flag ? 7 : 15) gainmap_height_minus1; + ++gainmapHeight; + AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &gainmapMatrixCoefficients, 8), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(8) gainmap_matrix_coefficients; + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapFullRange, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) gainmap_full_range_flag; + + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapChromaSubsampling, 2), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(2) gainmap_chroma_subsampling; + uint32_t gainmapChromaIsHorizontallyCentered = 0, gainmapChromaIsVerticallyCentered = 0; + if (gainmapChromaSubsampling == 1 || gainmapChromaSubsampling == 2) { + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapChromaIsHorizontallyCentered, 1), + AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) gainmap_chroma_is_horizontally_centered; + } + if (gainmapChromaSubsampling == 1) { + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapChromaIsVerticallyCentered, 1), + AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) gainmap_chroma_is_vertically_centered; + } + + uint32_t gainmapFloatFlag; + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapFloatFlag, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) gainmap_float_flag; + if (gainmapFloatFlag) { + // bit(2) gainmap_bit_depth_log2_minus4; + return AVIF_RESULT_BMFF_PARSE_FAILED; // Either invalid AVIF or unsupported non-AVIF. + } else { + uint32_t gainmapHighBitDepthFlag; + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapHighBitDepthFlag, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) gainmap_high_bit_depth_flag; + if (gainmapHighBitDepthFlag) { + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapBitDepth, 3), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(3) gainmap_bit_depth_minus9; + gainmapBitDepth += 9; + } else { + gainmapBitDepth = 8; + } + } + + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &tmapHasIcc, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) tmap_icc_flag; + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &tmapHasExplicitCicp, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) tmap_explicit_cicp_flag; + if (tmapHasExplicitCicp) { + AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &tmapColorPrimaries, 8), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(8) tmap_colour_primaries; + AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &tmapTransferCharacteristics, 8), + AVIF_RESULT_BMFF_PARSE_FAILED); // bit(8) tmap_transfer_characteristics; + AVIF_CHECKERR(avifROStreamReadBitsU8(&s, &tmapMatrixCoefficients, 8), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(8) tmap_matrix_coefficients; + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &tmapFullRange, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) tmap_full_range_flag; + } else { + tmapColorPrimaries = AVIF_COLOR_PRIMARIES_BT709; // 1 + tmapTransferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB; // 13 + tmapMatrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; // 6 + tmapFullRange = 1; + } + } + AVIF_CHECKRES(avifParseMiniHDRProperties(&s, &hasClli, &clli)); + if (hasGainmap) { + AVIF_CHECKRES(avifParseMiniHDRProperties(&s, &tmapHasClli, &tmapClli)); + } +#else return AVIF_RESULT_NOT_IMPLEMENTED; +#endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP } // Chunk sizes @@ -3778,15 +3891,24 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(few_metadata_bytes_flag ? 10 : 20) icc_data_size_minus1; ++iccDataSize; } - // if (hdr_flag && gainmap_flag && tmap_icc_flag) - // unsigned int(few_metadata_bytes_flag ? 10 : 20) tmap_icc_data_size_minus1; + uint32_t tmapIccDataSize = 0; + if (hasHdr && hasGainmap && tmapHasIcc) { + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &tmapIccDataSize, fewMetadataBytesFlag ? 10 : 20), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(few_metadata_bytes_flag ? 10 : 20) tmap_icc_data_size_minus1; + ++tmapIccDataSize; + } - // if (hdr_flag && gainmap_flag) - // unsigned int(few_metadata_bytes_flag ? 10 : 20) gainmap_metadata_size; - // if (hdr_flag && gainmap_flag) - // unsigned int(few_item_data_bytes_flag ? 15 : 28) gainmap_item_data_size; - // if (hdr_flag && gainmap_flag && gainmap_item_data_size > 0) - // unsigned int(few_codec_config_bytes_flag ? 3 : 12) gainmap_item_codec_config_size; + uint32_t gainmapMetadataSize = 0, gainmapItemDataSize = 0, gainmapItemCodecConfigSize = 0; + if (hasHdr && hasGainmap) { + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapMetadataSize, fewMetadataBytesFlag ? 10 : 20), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(few_metadata_bytes_flag ? 10 : 20) gainmap_metadata_size; + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapItemDataSize, fewItemDataBytesFlag ? 15 : 28), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(few_item_data_bytes_flag ? 15 : 28) gainmap_item_data_size; + if (gainmapItemDataSize > 0) { + AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &gainmapItemCodecConfigSize, fewCodecConfigBytesFlag ? 3 : 12), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(few_codec_config_bytes_flag ? 3 : 12) gainmap_item_codec_config_size; + } + } uint32_t mainItemCodecConfigSize, mainItemDataSize; AVIF_CHECKERR(avifROStreamReadBitsU32(&s, &mainItemCodecConfigSize, fewCodecConfigBytesFlag ? 3 : 12), @@ -3825,18 +3947,20 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, AVIF_CHECKERR(padding == 0, AVIF_RESULT_BMFF_PARSE_FAILED); // Only accept zeros as padding. } - // Chunks + // Codec configuration ('av1C' always uses 4 bytes) avifCodecConfigurationBox alphaItemCodecConfig = { 0 }; if (hasAlpha && alphaItemDataSize != 0 && alphaItemCodecConfigSize != 0) { - // 'av1C' always uses 4 bytes. AVIF_CHECKERR(alphaItemCodecConfigSize == 4, AVIF_RESULT_BMFF_PARSE_FAILED); AVIF_CHECKERR(avifParseCodecConfiguration(&s, &alphaItemCodecConfig, (const char *)codecConfigType, diag), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) alpha_item_codec_config[alpha_item_codec_config_size]; } - // if (hdr_flag && gainmap_flag && gainmap_item_codec_config_size > 0) - // unsigned int(8) gainmap_item_codec_config[gainmap_item_codec_config_size]; + avifCodecConfigurationBox gainmapItemCodecConfig = { 0 }; + if (hasHdr && hasGainmap && gainmapItemCodecConfigSize != 0) { + AVIF_CHECKERR(gainmapItemCodecConfigSize == 4, AVIF_RESULT_BMFF_PARSE_FAILED); + AVIF_CHECKERR(avifParseCodecConfiguration(&s, &gainmapItemCodecConfig, (const char *)codecConfigType, diag), + AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) gainmap_item_codec_config[gainmap_item_codec_config_size]; + } avifCodecConfigurationBox mainItemCodecConfig; - // 'av1C' always uses 4 bytes. AVIF_CHECKERR(mainItemCodecConfigSize == 4, AVIF_RESULT_BMFF_PARSE_FAILED); AVIF_CHECKERR(avifParseCodecConfiguration(&s, &mainItemCodecConfig, (const char *)codecConfigType, diag), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) main_item_codec_config[main_item_codec_config_size]; @@ -3844,7 +3968,8 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, // Make sure all metadata and coded chunks fit into the 'meta' box whose size is rawLen. // There should be no missing nor unused byte. - AVIF_CHECKERR(avifROStreamRemainingBytes(&s) == (uint64_t)iccDataSize + alphaItemDataSize + mainItemDataSize + exifDataSize + xmpDataSize, + AVIF_CHECKERR(avifROStreamRemainingBytes(&s) == (uint64_t)iccDataSize + tmapIccDataSize + gainmapMetadataSize + alphaItemDataSize + + gainmapItemDataSize + mainItemDataSize + exifDataSize + xmpDataSize, AVIF_RESULT_BMFF_PARSE_FAILED); // Create the items and properties generated by the MinimizedImageBox. @@ -3894,6 +4019,23 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, alphaItem->miniBoxChromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN; } + avifDecoderItem * tmapItem = NULL; + if (hasGainmap) { + AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, /*itemID=*/3, &tmapItem)); + memcpy(tmapItem->type, "tmap", 4); + colorItem->dimgForID = tmapItem->id; + colorItem->dimgIdx = 0; + } + avifDecoderItem * gainmapItem = NULL; + if (gainmapItemDataSize != 0) { + AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, /*itemID=*/4, &gainmapItem)); + memcpy(gainmapItem->type, infeType, 4); + gainmapItem->width = gainmapWidth; + gainmapItem->height = gainmapHeight; + gainmapItem->dimgForID = tmapItem->id; + gainmapItem->dimgIdx = 1; + } + // Property with fixed index 1. avifProperty * colorCodecConfigProp = avifMetaCreateProperty(meta, (const char *)codecConfigType); AVIF_CHECKERR(colorCodecConfigProp, AVIF_RESULT_OUT_OF_MEMORY); @@ -3927,7 +4069,7 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, AVIF_CHECKERR(avifDecoderItemAddProperty(colorItem, colrPropNCLX), AVIF_RESULT_OUT_OF_MEMORY); // Property with fixed index 5. - if (iccDataSize) { + if (iccDataSize != 0) { avifProperty * colrPropICC = avifMetaCreateProperty(meta, "colr"); AVIF_CHECKERR(colrPropICC, AVIF_RESULT_OUT_OF_MEMORY); colrPropICC->u.colr.hasICC = AVIF_TRUE; // colour_type "rICC" or "prof" @@ -3939,11 +4081,6 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. } - // if (hdr_flag && gainmap_flag && tmap_icc_flag) - // unsigned int(8) tmap_icc_data[tmap_icc_data_size_minus1 + 1]; - // if (hdr_flag && gainmap_flag && gainmap_metadata_size > 0) - // unsigned int(8) gainmap_metadata[gainmap_metadata_size]; - if (hasAlpha) { // Property with fixed index 6. avifProperty * alphaCodecConfigProp = avifMetaCreateProperty(meta, (const char *)codecConfigType); @@ -3996,59 +4133,178 @@ static avifResult avifParseMinimizedImageBox(avifMeta * meta, AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. } - // HDR placeholders (unnecessary but added for specification matching). - for (int i = 11; i <= 29; ++i) { + if (hasClli) { + // Property with fixed index 11. + avifProperty * clliProp = avifMetaCreateProperty(meta, "clli"); + AVIF_CHECKERR(clliProp, AVIF_RESULT_OUT_OF_MEMORY); + clliProp->u.clli = clli; + AVIF_CHECKERR(avifDecoderItemAddProperty(colorItem, clliProp), AVIF_RESULT_OUT_OF_MEMORY); + } else { + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. + } + // Properties with fixed indices 12 to 16 are ignored by libavif (mdcv, cclv, amve, reve and ndwt). + for (int i = 12; i <= 16; ++i) { + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. + } + + if (gainmapItemCodecConfigSize != 0) { + // Property with fixed index 17. + avifProperty * gainmapCodecConfigProp = avifMetaCreateProperty(meta, (const char *)codecConfigType); + AVIF_CHECKERR(gainmapCodecConfigProp, AVIF_RESULT_OUT_OF_MEMORY); + gainmapCodecConfigProp->u.av1C = gainmapItemCodecConfig; + AVIF_CHECKERR(avifDecoderItemAddProperty(gainmapItem, gainmapCodecConfigProp), AVIF_RESULT_OUT_OF_MEMORY); + } else { + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. + } + + if (gainmapItemDataSize != 0) { + // Property with fixed index 18. + avifProperty * gainmapIspeProp = avifMetaCreateProperty(meta, "ispe"); + AVIF_CHECKERR(gainmapIspeProp, AVIF_RESULT_OUT_OF_MEMORY); + gainmapIspeProp->u.ispe.width = gainmapWidth; + gainmapIspeProp->u.ispe.height = gainmapHeight; + AVIF_CHECKERR(avifDecoderItemAddProperty(gainmapItem, gainmapIspeProp), AVIF_RESULT_OUT_OF_MEMORY); + + // Property with fixed index 19. + avifProperty * gainmapPixiProp = avifMetaCreateProperty(meta, "pixi"); + AVIF_CHECKERR(gainmapPixiProp, AVIF_RESULT_OUT_OF_MEMORY); + memcpy(gainmapPixiProp->type, "pixi", 4); + gainmapPixiProp->u.pixi.planeCount = gainmapChromaSubsampling == 0 ? 1 : 3; + for (uint8_t plane = 0; plane < gainmapPixiProp->u.pixi.planeCount; ++plane) { + gainmapPixiProp->u.pixi.planeDepths[plane] = (uint8_t)gainmapBitDepth; + } + AVIF_CHECKERR(avifDecoderItemAddProperty(gainmapItem, gainmapPixiProp), AVIF_RESULT_OUT_OF_MEMORY); + + // Property with fixed index 20. + avifProperty * gainmapColrPropNCLX = avifMetaCreateProperty(meta, "colr"); + AVIF_CHECKERR(gainmapColrPropNCLX, AVIF_RESULT_OUT_OF_MEMORY); + gainmapColrPropNCLX->u.colr.hasNCLX = AVIF_TRUE; // colour_type "nclx" + gainmapColrPropNCLX->u.colr.colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED; // 2 + gainmapColrPropNCLX->u.colr.transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; // 2 + gainmapColrPropNCLX->u.colr.matrixCoefficients = (avifMatrixCoefficients)gainmapMatrixCoefficients; + gainmapColrPropNCLX->u.colr.range = gainmapFullRange ? AVIF_RANGE_FULL : AVIF_RANGE_LIMITED; + AVIF_CHECKERR(avifDecoderItemAddProperty(gainmapItem, gainmapColrPropNCLX), AVIF_RESULT_OUT_OF_MEMORY); + } else { + // Placeholders 18, 19 and 20. + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); + } + + if (hasGainmap) { + // Property with fixed index 21. + avifProperty * tmapIspeProp = avifMetaCreateProperty(meta, "ispe"); + AVIF_CHECKERR(tmapIspeProp, AVIF_RESULT_OUT_OF_MEMORY); + tmapIspeProp->u.ispe.width = orientation <= 4 ? width : height; + tmapIspeProp->u.ispe.height = orientation <= 4 ? height : width; + AVIF_CHECKERR(avifDecoderItemAddProperty(tmapItem, tmapIspeProp), AVIF_RESULT_OUT_OF_MEMORY); + } else { + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. + } + + if (hasGainmap && (tmapHasExplicitCicp || !tmapHasIcc)) { + // Property with fixed index 22. + avifProperty * tmapColrPropNCLX = avifMetaCreateProperty(meta, "colr"); + AVIF_CHECKERR(tmapColrPropNCLX, AVIF_RESULT_OUT_OF_MEMORY); + tmapColrPropNCLX->u.colr.hasNCLX = AVIF_TRUE; // colour_type "nclx" + tmapColrPropNCLX->u.colr.colorPrimaries = (avifColorPrimaries)tmapColorPrimaries; + tmapColrPropNCLX->u.colr.transferCharacteristics = (avifTransferCharacteristics)tmapTransferCharacteristics; + tmapColrPropNCLX->u.colr.matrixCoefficients = (avifMatrixCoefficients)tmapMatrixCoefficients; + tmapColrPropNCLX->u.colr.range = tmapFullRange ? AVIF_RANGE_FULL : AVIF_RANGE_LIMITED; + AVIF_CHECKERR(avifDecoderItemAddProperty(tmapItem, tmapColrPropNCLX), AVIF_RESULT_OUT_OF_MEMORY); + } else { + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. + } + + if (tmapIccDataSize != 0) { + // Property with fixed index 23. + avifProperty * tmapColrPropICC = avifMetaCreateProperty(meta, "colr"); + AVIF_CHECKERR(tmapColrPropICC, AVIF_RESULT_OUT_OF_MEMORY); + tmapColrPropICC->u.colr.hasICC = AVIF_TRUE; // colour_type "rICC" or "prof" + tmapColrPropICC->u.colr.iccOffset = rawOffset + avifROStreamOffset(&s); + tmapColrPropICC->u.colr.iccSize = tmapIccDataSize; + AVIF_CHECKERR(avifROStreamSkip(&s, tmapColrPropICC->u.colr.iccSize), AVIF_RESULT_BMFF_PARSE_FAILED); + AVIF_CHECKERR(avifDecoderItemAddProperty(colorItem, tmapColrPropICC), AVIF_RESULT_OUT_OF_MEMORY); + } else { + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. + } + + if (tmapHasClli) { + // Property with fixed index 24. + avifProperty * tmapClliProp = avifMetaCreateProperty(meta, "clli"); + AVIF_CHECKERR(tmapClliProp, AVIF_RESULT_OUT_OF_MEMORY); + tmapClliProp->u.clli = tmapClli; + AVIF_CHECKERR(avifDecoderItemAddProperty(tmapItem, tmapClliProp), AVIF_RESULT_OUT_OF_MEMORY); + } else { + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. + } + // Properties with fixed indices 25 to 29 are ignored by libavif (mdcv, cclv, amve, reve and ndwt). + for (int i = 25; i <= 29; ++i) { + AVIF_CHECKERR(avifMetaCreateProperty(meta, "skip"), AVIF_RESULT_OUT_OF_MEMORY); // Placeholder. } + AVIF_ASSERT_OR_RETURN(meta->properties.count == 29); // Extents. + if (gainmapMetadataSize != 0) { + // Prepend the version field to the GainMapMetadata to form the ToneMapImage syntax. + tmapItem->size = gainmapMetadataSize + 1; + AVIF_CHECKRES(avifRWDataRealloc(&tmapItem->mergedExtents, tmapItem->size)); + tmapItem->ownsMergedExtents = AVIF_TRUE; + tmapItem->mergedExtents.data[0] = 0; // unsigned int(8) version = 0; + AVIF_CHECKERR(avifROStreamRead(&s, tmapItem->mergedExtents.data + 1, gainmapMetadataSize), AVIF_RESULT_BMFF_PARSE_FAILED); + } + if (hasAlpha) { avifExtent * alphaExtent = (avifExtent *)avifArrayPush(&alphaItem->extents); AVIF_CHECKERR(alphaExtent, AVIF_RESULT_OUT_OF_MEMORY); alphaExtent->offset = rawOffset + avifROStreamOffset(&s); - alphaExtent->size = (size_t)alphaItemDataSize; + alphaExtent->size = alphaItemDataSize; AVIF_CHECKERR(avifROStreamSkip(&s, alphaExtent->size), AVIF_RESULT_BMFF_PARSE_FAILED); alphaItem->size = alphaExtent->size; } - // if (hdr_flag && gainmap_flag && gainmap_item_data_size > 0) - // unsigned int(8) gainmap_item_data[gainmap_item_data_size]; + if (gainmapItemDataSize != 0) { + avifExtent * gainmapExtent = (avifExtent *)avifArrayPush(&gainmapItem->extents); + AVIF_CHECKERR(gainmapExtent, AVIF_RESULT_OUT_OF_MEMORY); + gainmapExtent->offset = rawOffset + avifROStreamOffset(&s); + gainmapExtent->size = gainmapItemDataSize; + AVIF_CHECKERR(avifROStreamSkip(&s, gainmapExtent->size), AVIF_RESULT_BMFF_PARSE_FAILED); + gainmapItem->size = gainmapExtent->size; + } avifExtent * colorExtent = (avifExtent *)avifArrayPush(&colorItem->extents); AVIF_CHECKERR(colorExtent, AVIF_RESULT_OUT_OF_MEMORY); colorExtent->offset = rawOffset + avifROStreamOffset(&s); - colorExtent->size = (size_t)mainItemDataSize; + colorExtent->size = mainItemDataSize; AVIF_CHECKERR(avifROStreamSkip(&s, colorExtent->size), AVIF_RESULT_BMFF_PARSE_FAILED); colorItem->size = colorExtent->size; if (hasExif) { avifDecoderItem * exifItem; - AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, /*itemID=*/3, &exifItem)); + AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, /*itemID=*/6, &exifItem)); memcpy(exifItem->type, "Exif", 4); - exifItem->descForID = colorItem->id; - colorItem->premByID = alphaIsPremultiplied; + exifItem->descForID = colorItem->id; // 'cdsc' avifExtent * exifExtent = (avifExtent *)avifArrayPush(&exifItem->extents); AVIF_CHECKERR(exifExtent, AVIF_RESULT_OUT_OF_MEMORY); exifExtent->offset = rawOffset + avifROStreamOffset(&s); - exifExtent->size = (size_t)exifDataSize; // Does not include unsigned int(32) exif_tiff_header_offset; + exifExtent->size = exifDataSize; // Does not include unsigned int(32) exif_tiff_header_offset; AVIF_CHECKERR(avifROStreamSkip(&s, exifExtent->size), AVIF_RESULT_BMFF_PARSE_FAILED); exifItem->size = exifExtent->size; } if (hasXmp) { avifDecoderItem * xmpItem; - AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, /*itemID=*/4, &xmpItem)); + AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, /*itemID=*/7, &xmpItem)); memcpy(xmpItem->type, "mime", 4); memcpy(xmpItem->contentType.contentType, AVIF_CONTENT_TYPE_XMP, sizeof(AVIF_CONTENT_TYPE_XMP)); - xmpItem->descForID = colorItem->id; - colorItem->premByID = alphaIsPremultiplied; + xmpItem->descForID = colorItem->id; // 'cdsc' avifExtent * xmpExtent = (avifExtent *)avifArrayPush(&xmpItem->extents); AVIF_CHECKERR(xmpExtent, AVIF_RESULT_OUT_OF_MEMORY); xmpExtent->offset = rawOffset + avifROStreamOffset(&s); - xmpExtent->size = (size_t)xmpDataSize; + xmpExtent->size = xmpDataSize; AVIF_CHECKERR(avifROStreamSkip(&s, xmpExtent->size), AVIF_RESULT_BMFF_PARSE_FAILED); xmpItem->size = xmpExtent->size; } @@ -4247,7 +4503,7 @@ static avifResult avifParse(avifDecoder * decoder) AVIF_CHECKERR(!miniSeen, AVIF_RESULT_BMFF_PARSE_FAILED); const avifBool isAvifAccordingToMinorVersion = !memcmp(ftyp.minorVersion, "avif", 4); AVIF_CHECKRES( - avifParseMinimizedImageBox(data->meta, boxOffset, boxContents.data, boxContents.size, isAvifAccordingToMinorVersion, data->diag)); + avifParseMinimizedImageBox(data, boxOffset, boxContents.data, boxContents.size, isAvifAccordingToMinorVersion, data->diag)); miniSeen = AVIF_TRUE; #endif } else if (isMoov) { diff --git a/src/write.c b/src/write.c index 1587586617..7e482804f6 100644 --- a/src/write.c +++ b/src/write.c @@ -676,6 +676,14 @@ static avifResult avifEncoderWriteColorProperties(avifRWStream * outputStream, return avifEncoderWriteExtendedColorProperties(dedupStream, outputStream, imageMetadata, ipma, dedup); } +static avifResult avifEncoderWriteContentLightLevelInformation(avifRWStream * outputStream, + const avifContentLightLevelInformationBox * clli) +{ + AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, clli->maxCLL, 16)); // unsigned int(16) max_content_light_level; + AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, clli->maxPALL, 16)); // unsigned int(16) max_pic_average_light_level; + return AVIF_RESULT_OK; +} + // Same as 'avifEncoderWriteColorProperties' but for properties related to High Dynamic Range only. static avifResult avifEncoderWriteHDRProperties(avifRWStream * dedupStream, avifRWStream * outputStream, @@ -690,18 +698,56 @@ static avifResult avifEncoderWriteHDRProperties(avifRWStream * dedupStream, } avifBoxMarker clli; AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "clli", AVIF_BOX_SIZE_TBD, &clli)); - AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->clli.maxCLL)); // unsigned int(16) max_content_light_level; - AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->clli.maxPALL)); // unsigned int(16) max_pic_average_light_level; + AVIF_CHECKRES(avifEncoderWriteContentLightLevelInformation(dedupStream, &imageMetadata->clli)); avifRWStreamFinishBox(dedupStream, clli); if (dedup) { AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, ipma, AVIF_FALSE)); } } - // TODO(maryla): add other HDR boxes: mdcv, cclv, etc. + // TODO(maryla): add other HDR boxes: mdcv, cclv, etc. (in avifEncoderWriteMiniHDRProperties() too) + + return AVIF_RESULT_OK; +} +#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) && defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) +static avifResult avifEncoderWriteMiniHDRProperties(avifRWStream * outputStream, const avifImage * imageMetadata) +{ + const avifBool hasClli = imageMetadata->clli.maxCLL != 0 || imageMetadata->clli.maxPALL != 0; + const avifBool hasMdcv = AVIF_FALSE; + const avifBool hasCclv = AVIF_FALSE; + const avifBool hasAmve = AVIF_FALSE; + const avifBool hasReve = AVIF_FALSE; + const avifBool hasNdwt = AVIF_FALSE; + AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasClli, 1)); // bit(1) clli_flag; + AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasMdcv, 1)); // bit(1) mdcv_flag; + AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasCclv, 1)); // bit(1) cclv_flag; + AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasAmve, 1)); // bit(1) amve_flag; + AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasReve, 1)); // bit(1) reve_flag; + AVIF_CHECKRES(avifRWStreamWriteBits(outputStream, hasNdwt, 1)); // bit(1) ndwt_flag; + + if (hasClli) { + // ContentLightLevel clli; + AVIF_CHECKRES(avifEncoderWriteContentLightLevelInformation(outputStream, &imageMetadata->clli)); + } + if (hasMdcv) { + // MasteringDisplayColourVolume mdcv; + } + if (hasCclv) { + // ContentColourVolume cclv; + } + if (hasAmve) { + // AmbientViewingEnvironment amve; + } + if (hasReve) { + // ReferenceViewingEnvironment reve; + } + if (hasNdwt) { + // NominalDiffuseWhite ndwt; + } return AVIF_RESULT_OK; } +#endif // AVIF_ENABLE_EXPERIMENTAL_MINI && AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP static avifResult avifEncoderWriteExtendedColorProperties(avifRWStream * dedupStream, avifRWStream * outputStream, @@ -1262,6 +1308,7 @@ static avifResult avifEncoderCreateBitDepthExtensionItems(avifEncoder * encoder, sampleTransformItem->itemCategory = AVIF_ITEM_SAMPLE_TRANSFORM; uint16_t sampleTransformItemID = sampleTransformItem->id; // 'altr' group + AVIF_ASSERT_OR_RETURN(encoder->data->alternativeItemIDs.count == 0); uint16_t * alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs); AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY); *alternativeItemID = sampleTransformItem->id; @@ -2330,26 +2377,72 @@ static avifBool avifEncoderIsMiniCompatible(const avifEncoder * encoder) } #endif + // Check for maximum field values and maximum chunk sizes. + + // width_minus1 and height_minus1 if (encoder->data->imageMetadata->width > (1 << 15) || encoder->data->imageMetadata->height > (1 << 15)) { return AVIF_FALSE; } + // icc_data_size_minus1, exif_data_size_minus1 and xmp_data_size_minus1 if (encoder->data->imageMetadata->icc.size > (1 << 20) || encoder->data->imageMetadata->exif.size > (1 << 20) || encoder->data->imageMetadata->xmp.size > (1 << 20)) { return AVIF_FALSE; } +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + // gainmap_width_minus1 and gainmap_height_minus1 + if (encoder->data->imageMetadata->gainMap != NULL && encoder->data->imageMetadata->gainMap->image != NULL && + (encoder->data->imageMetadata->gainMap->image->width > (1 << 15) || + encoder->data->imageMetadata->gainMap->image->height > (1 << 15))) { + return AVIF_FALSE; + } + // tmap_icc_data_size_minus1 + if (encoder->data->altImageMetadata->icc.size > (1 << 20)) { + return AVIF_FALSE; + } + // gainmap_metadata_size + if (encoder->data->imageMetadata->gainMap != NULL && avifGainMapMetadataSize(encoder->data->imageMetadata->gainMap) >= (1 << 20)) { + return AVIF_FALSE; + } +#endif // 4:4:4, 4:2:2, 4:2:0 and 4:0:0 are supported by a MinimizedImageBox. + // chroma_subsampling if (encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV444 && encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV422 && encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV420 && encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV400) { return AVIF_FALSE; } +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + // gainmap_chroma_subsampling + if (encoder->data->imageMetadata->gainMap != NULL && encoder->data->imageMetadata->gainMap->image != NULL && + (encoder->data->imageMetadata->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_YUV444 && + encoder->data->imageMetadata->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_YUV422 && + encoder->data->imageMetadata->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_YUV420 && + encoder->data->imageMetadata->gainMap->image->yuvFormat != AVIF_PIXEL_FORMAT_YUV400)) { + return AVIF_FALSE; + } +#endif + // colour_primaries, transfer_characteristics and matrix_coefficients if (encoder->data->imageMetadata->colorPrimaries > 255 || encoder->data->imageMetadata->transferCharacteristics > 255 || encoder->data->imageMetadata->matrixCoefficients > 255) { return AVIF_FALSE; } +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + // gainmap_colour_primaries, gainmap_transfer_characteristics and gainmap_matrix_coefficients + if (encoder->data->imageMetadata->gainMap != NULL && encoder->data->imageMetadata->gainMap->image != NULL && + (encoder->data->imageMetadata->gainMap->image->colorPrimaries > 255 || + encoder->data->imageMetadata->gainMap->image->transferCharacteristics > 255 || + encoder->data->imageMetadata->gainMap->image->matrixCoefficients > 255)) { + return AVIF_FALSE; + } + // tmap_colour_primaries, tmap_transfer_characteristics and tmap_matrix_coefficients + if (encoder->data->altImageMetadata->colorPrimaries > 255 || encoder->data->altImageMetadata->transferCharacteristics > 255 || + encoder->data->altImageMetadata->matrixCoefficients > 255) { + return AVIF_FALSE; + } +#endif const avifEncoderItem * colorItem = NULL; for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { @@ -2363,19 +2456,32 @@ static avifBool avifEncoderIsMiniCompatible(const avifEncoder * encoder) if (item->id == encoder->data->primaryItemID) { assert(!colorItem); colorItem = item; - // main_item_data_size_minus_one so 2^28 inclusive. + // main_item_data_size_minus1 if (item->encodeOutput->samples.count != 1 || item->encodeOutput->samples.sample[0].data.size > (1 << 28)) { return AVIF_FALSE; } continue; // The primary item can be stored in the MinimizedImageBox. } if (item->itemCategory == AVIF_ITEM_ALPHA && item->irefToID == encoder->data->primaryItemID) { - // alpha_item_data_size so 2^28 exclusive. + // alpha_item_data_size if (item->encodeOutput->samples.count != 1 || item->encodeOutput->samples.sample[0].data.size >= (1 << 28)) { return AVIF_FALSE; } continue; // The alpha auxiliary item can be stored in the MinimizedImageBox. } +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + if (item->itemCategory == AVIF_ITEM_GAIN_MAP) { + // gainmap_item_data_size + if (item->encodeOutput->samples.count != 1 || item->encodeOutput->samples.sample[0].data.size >= (1 << 28)) { + return AVIF_FALSE; + } + continue; // The gainmap input image item can be stored in the MinimizedImageBox. + } + if (!memcmp(item->type, "tmap", 4)) { + assert(item->itemCategory == AVIF_ITEM_COLOR); // Cannot be differentiated from the primary item by its itemCategory. + continue; // The tone mapping derived image item can be represented in the MinimizedImageBox. + } +#endif if (!memcmp(item->type, "mime", 4) && !memcmp(item->infeName, "XMP", item->infeNameSize)) { assert(item->metadataPayload.size == encoder->data->imageMetadata->xmp.size); continue; // XMP metadata can be stored in the MinimizedImageBox. @@ -2389,7 +2495,7 @@ static avifBool avifEncoderIsMiniCompatible(const avifEncoder * encoder) continue; // Exif metadata can be stored in the MinimizedImageBox if exif_tiff_header_offset is 0. } - // Items besides the colorItem, the alphaItem and Exif/XMP/ICC + // Items besides the colorItem, the alphaItem, the gainmap item and Exif/XMP/ICC/HDR // metadata are not directly supported by the MinimizedImageBox. return AVIF_FALSE; } @@ -2424,6 +2530,7 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * { const avifEncoderItem * colorItem = NULL; const avifEncoderItem * alphaItem = NULL; + const avifEncoderItem * gainmapItem = NULL; for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; if (item->id == encoder->data->primaryItemID) { @@ -2433,18 +2540,25 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * AVIF_ASSERT_OR_RETURN(!alphaItem); alphaItem = item; } +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + if (item->itemCategory == AVIF_ITEM_GAIN_MAP) { + AVIF_ASSERT_OR_RETURN(!gainmapItem); + gainmapItem = item; + } +#endif } AVIF_ASSERT_OR_RETURN(colorItem); const avifRWData * colorData = &colorItem->encodeOutput->samples.sample[0].data; const avifRWData * alphaData = alphaItem ? &alphaItem->encodeOutput->samples.sample[0].data : NULL; + const avifRWData * gainmapData = gainmapItem ? &gainmapItem->encodeOutput->samples.sample[0].data : NULL; const avifImage * const image = encoder->data->imageMetadata; const avifBool hasAlpha = alphaItem != NULL; const avifBool alphaIsPremultiplied = encoder->data->imageMetadata->alphaPremultiplied; - const avifBool hasHdr = AVIF_FALSE; // Not implemented. - const avifBool hasGainmap = AVIF_FALSE; // Not implemented. + const avifBool hasGainmap = gainmapItem != NULL; + const avifBool hasHdr = hasGainmap; // libavif only supports gainmap-based HDR encoding for now. const avifBool hasIcc = image->icc.size != 0; const uint32_t chromaSubsampling = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400 ? 0 : image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 ? 1 @@ -2483,11 +2597,25 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * const avifBool hasExplicitCodecTypes = AVIF_FALSE; // 'av01' and 'av1C' known from 'avif' minor_version field of FileTypeBox. - const uint32_t smallDimensionsFlag = image->width <= (1 << 7) && image->height <= (1 << 7); + uint32_t smallDimensionsFlag = image->width <= (1 << 7) && image->height <= (1 << 7); const uint32_t codecConfigSize = 4; // 'av1C' always uses 4 bytes. + uint32_t gainmapMetadataSize = 0; const uint32_t fewCodecConfigBytesFlag = codecConfigSize < (1 << 3); - const uint32_t fewItemDataBytesFlag = colorData->size <= (1 << 15) && (!alphaData || alphaData->size < (1 << 15)); - const uint32_t fewMetadataBytesFlag = image->icc.size <= (1 << 10) && image->exif.size <= (1 << 10) && image->xmp.size <= (1 << 10); + uint32_t fewItemDataBytesFlag = colorData->size <= (1 << 15) && (!alphaData || alphaData->size < (1 << 15)); + uint32_t fewMetadataBytesFlag = image->icc.size <= (1 << 10) && image->exif.size <= (1 << 10) && image->xmp.size <= (1 << 10); + +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + if (hasGainmap) { + AVIF_ASSERT_OR_RETURN(image->gainMap != NULL && image->gainMap->image != NULL); + gainmapMetadataSize = avifGainMapMetadataSize(image->gainMap); + AVIF_ASSERT_OR_RETURN(gainmapData != NULL); + + smallDimensionsFlag &= image->gainMap->image->width <= (1 << 7) && image->gainMap->image->height <= (1 << 7); + fewItemDataBytesFlag &= gainmapData->size < (1 << 15); + fewMetadataBytesFlag &= encoder->data->altImageMetadata->icc.size <= (1 << 10) && gainmapMetadataSize <= (1 << 10); + // image->gainMap->image->icc is ignored. + } +#endif avifBoxMarker mini; AVIF_CHECKRES(avifRWStreamWriteBox(s, "mini", AVIF_BOX_SIZE_TBD, &mini)); @@ -2552,80 +2680,69 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * } // High Dynamic Range properties + size_t tmapIccSize = 0; if (hasHdr) { - // bit(1) gainmap_flag; - // if (gainmap_flag) { - // unsigned int(small_dimensions_flag ? 7 : 15) gainmap_width_minus1; - // unsigned int(small_dimensions_flag ? 7 : 15) gainmap_height_minus1; - // bit(8) gainmap_matrix_coefficients; - // bit(1) gainmap_full_range_flag; - // bit(2) gainmap_chroma_subsampling; - // if (gainmap_chroma_subsampling == 1 || gainmap_chroma_subsampling == 2) - // bit(1) gainmap_chroma_is_horizontally_centered; - // if (gainmap_chroma_subsampling == 1) - // bit(1) gainmap_chroma_is_vertically_centered; - // bit(1) gainmap_float_flag; - // if (gainmap_float_flag) - // bit(2) gainmap_bit_depth_log2_minus4; - // else { - // bit(1) gainmap_high_bit_depth_flag; - // if (gainmap_high_bit_depth_flag) - // bit(3) gainmap_bit_depth_minus9; - // } - // bit(1) tmap_icc_flag; - // bit(1) tmap_explicit_cicp_flag; - // if (tmap_explicit_cicp_flag) { - // bit(8) tmap_colour_primaries; - // bit(8) tmap_transfer_characteristics; - // bit(8) tmap_matrix_coefficients; - // bit(1) tmap_full_range_flag; - // } - // else { - // tmap_colour_primaries = 1; - // tmap_transfer_characteristics = 13; - // tmap_matrix_coefficients = 6; - // tmap_full_range_flag = 1; - // } - // } - // bit(1) clli_flag; - // bit(1) mdcv_flag; - // bit(1) cclv_flag; - // bit(1) amve_flag; - // bit(1) reve_flag; - // bit(1) ndwt_flag; - // if (clli_flag) - // ContentLightLevel clli; - // if (mdcv_flag) - // MasteringDisplayColourVolume mdcv; - // if (cclv_flag) - // ContentColourVolume cclv; - // if (amve_flag) - // AmbientViewingEnvironment amve; - // if (reve_flag) - // ReferenceViewingEnvironment reve; - // if (ndwt_flag) - // NominalDiffuseWhite ndwt; - // if (gainmap_flag) { - // bit(1) tmap_clli_flag; - // bit(1) tmap_mdcv_flag; - // bit(1) tmap_cclv_flag; - // bit(1) tmap_amve_flag; - // bit(1) tmap_reve_flag; - // bit(1) tmap_ndwt_flag; - // if (tmap_clli_flag) - // ContentLightLevel tmap_clli; - // if (tmap_mdcv_flag) - // MasteringDisplayColourVolume tmap_mdcv; - // if (tmap_cclv_flag) - // ContentColourVolume tmap_cclv; - // if (tmap_amve_flag) - // AmbientViewingEnvironment tmap_amve; - // if (tmap_reve_flag) - // ReferenceViewingEnvironment tmap_reve; - // if (tmap_ndwt_flag) - // NominalDiffuseWhite tmap_ndwt; - // } - return AVIF_RESULT_NOT_IMPLEMENTED; +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + AVIF_CHECKRES(avifRWStreamWriteBits(s, hasGainmap, 1)); // bit(1) gainmap_flag; + if (hasGainmap) { + const avifImage * tmap = encoder->data->altImageMetadata; + const avifImage * gainmap = image->gainMap->image; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->width - 1, smallDimensionsFlag ? 7 : 15)); // unsigned int(small_dimensions_flag ? 7 : 15) gainmap_width_minus1; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->height - 1, smallDimensionsFlag ? 7 : 15)); // unsigned int(small_dimensions_flag ? 7 : 15) gainmap_height_minus1; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->matrixCoefficients, 8)); // bit(8) gainmap_matrix_coefficients; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->yuvRange == AVIF_RANGE_FULL, 1)); // bit(1) gainmap_full_range_flag; + const uint32_t gainmapChromaSubsampling = gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV400 ? 0 + : gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 ? 1 + : gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV422 ? 2 + : 3; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmapChromaSubsampling, 2)); // bit(1) gainmap_chroma_subsampling; + if (gainmapChromaSubsampling == 1 || gainmapChromaSubsampling == 2) { + AVIF_CHECKRES(avifRWStreamWriteBits(s, + gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 && + gainmap->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_VERTICAL && + gainmap->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_COLOCATED, + 1)); // bit(1) gainmap_chroma_is_horizontally_centered; + } + if (gainmapChromaSubsampling == 1) { + AVIF_CHECKRES(avifRWStreamWriteBits(s, + gainmap->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 && + gainmap->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_COLOCATED, + 1)); // bit(1) gainmap_chroma_is_vertically_centered; + } + + const avifBool gainmapFloatFlag = AVIF_FALSE; + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmapFloatFlag, 1)); // bit(1) gainmap_float_flag; + if (gainmapFloatFlag) { + // bit(2) gainmap_bit_depth_log2_minus4; + AVIF_ASSERT_OR_RETURN(AVIF_FALSE); + } else { + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->depth > 8, 1)); // bit(1) gainmap_high_bit_depth_flag; + if (gainmap->depth > 8) { + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmap->depth - 9, 3)); // bit(3) gainmap_bit_depth_minus9; + } + } + + tmapIccSize = encoder->data->altImageMetadata->icc.size; + AVIF_CHECKRES(avifRWStreamWriteBits(s, tmapIccSize != 0, 1)); // bit(1) tmap_icc_flag; + const avifBool tmapHasExplicitCicp = tmap->colorPrimaries != AVIF_COLOR_PRIMARIES_BT709 || + tmap->transferCharacteristics != AVIF_TRANSFER_CHARACTERISTICS_SRGB || + tmap->matrixCoefficients != AVIF_MATRIX_COEFFICIENTS_BT601 || + tmap->yuvRange != AVIF_RANGE_FULL; + AVIF_CHECKRES(avifRWStreamWriteBits(s, tmapHasExplicitCicp, 1)); // bit(1) tmap_explicit_cicp_flag; + if (tmapHasExplicitCicp) { + AVIF_CHECKRES(avifRWStreamWriteBits(s, tmap->colorPrimaries, 8)); // bit(8) tmap_colour_primaries; + AVIF_CHECKRES(avifRWStreamWriteBits(s, tmap->transferCharacteristics, 8)); // bit(8) tmap_transfer_characteristics; + AVIF_CHECKRES(avifRWStreamWriteBits(s, tmap->matrixCoefficients, 8)); // bit(8) tmap_matrix_coefficients; + AVIF_CHECKRES(avifRWStreamWriteBits(s, tmap->yuvRange == AVIF_RANGE_FULL, 1)); // bit(8) tmap_full_range_flag; + } + // gainmap->icc is ignored. + } + + AVIF_CHECKRES(avifEncoderWriteMiniHDRProperties(s, image)); + if (hasGainmap) { + AVIF_CHECKRES(avifEncoderWriteMiniHDRProperties(s, encoder->data->altImageMetadata)); + } +#endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP } // Chunk sizes @@ -2638,15 +2755,21 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * if (hasIcc) { AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)image->icc.size - 1, fewMetadataBytesFlag ? 10 : 20)); // unsigned int(few_metadata_bytes_flag ? 10 : 20) icc_data_size_minus1; } - // if (hdr_flag && gainmap_flag && tmap_icc_flag) - // unsigned int(few_metadata_bytes_flag ? 10 : 20) tmap_icc_data_size_minus1; +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + if (hasHdr && hasGainmap && tmapIccSize != 0) { + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)tmapIccSize - 1, fewMetadataBytesFlag ? 10 : 20)); // unsigned int(few_metadata_bytes_flag ? 10 : 20) tmap_icc_data_size_minus1; + } - // if (hdr_flag && gainmap_flag) - // unsigned int(few_metadata_bytes_flag ? 10 : 20) gainmap_metadata_size; - // if (hdr_flag && gainmap_flag) - // unsigned int(few_item_data_bytes_flag ? 15 : 28) gainmap_item_data_size; - // if (hdr_flag && gainmap_flag && gainmap_item_data_size > 0) - // unsigned int(few_codec_config_bytes_flag ? 3 : 12) gainmap_item_codec_config_size; + if (hasHdr && hasGainmap) { + AVIF_CHECKRES(avifRWStreamWriteBits(s, gainmapMetadataSize, fewMetadataBytesFlag ? 10 : 20)); // unsigned int(few_metadata_bytes_flag ? 10 : 20) gainmap_metadata_size; + } + if (hasHdr && hasGainmap) { + AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)gainmapData->size, fewItemDataBytesFlag ? 15 : 28)); // unsigned int(few_item_data_bytes_flag ? 15 : 28) gainmap_item_data_size; + } + if (hasHdr && hasGainmap && gainmapData->size != 0) { + AVIF_CHECKRES(avifRWStreamWriteBits(s, codecConfigSize, fewCodecConfigBytesFlag ? 3 : 12)); // unsigned int(few_codec_config_bytes_flag ? 3 : 12) gainmap_item_codec_config_size; + } +#endif AVIF_CHECKRES(avifRWStreamWriteBits(s, codecConfigSize, fewCodecConfigBytesFlag ? 3 : 12)); // unsigned int(few_codec_config_bytes_flag ? 3 : 12) main_item_codec_config_size; AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)colorData->size - 1, fewItemDataBytesFlag ? 15 : 28)); // unsigned int(few_item_data_bytes_flag ? 15 : 28) main_item_data_size_minus1; @@ -2669,14 +2792,15 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * if (s->numUsedBitsInPartialByte != 0) { AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 8 - s->numUsedBitsInPartialByte)); } - const size_t headerSize = avifRWStreamOffset(s); + const size_t headerBytes = avifRWStreamOffset(s); // Chunks if (hasAlpha && alphaData->size != 0 && codecConfigSize != 0) { AVIF_CHECKRES(writeCodecConfig(s, &alphaItem->av1C)); // unsigned int(8) alpha_item_codec_config[alpha_item_codec_config_size]; } - // if (hdr_flag && gainmap_flag && gainmap_item_codec_config_size > 0) - // unsigned int(8) gainmap_item_codec_config[gainmap_item_codec_config_size]; + if (hasHdr && hasGainmap && codecConfigSize != 0) { + AVIF_CHECKRES(writeCodecConfig(s, &gainmapItem->av1C)); // unsigned int(8) gainmap_item_codec_config[gainmap_item_codec_config_size]; + } if (codecConfigSize > 0) { AVIF_CHECKRES(writeCodecConfig(s, &colorItem->av1C)); // unsigned int(8) main_item_codec_config[main_item_codec_config_size]; } @@ -2684,16 +2808,21 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * if (hasIcc) { AVIF_CHECKRES(avifRWStreamWrite(s, image->icc.data, image->icc.size)); // unsigned int(8) icc_data[icc_data_size_minus1 + 1]; } - // if (hdr_flag && gainmap_flag && tmap_icc_flag) - // unsigned int(8) tmap_icc_data[tmap_icc_data_size_minus1 + 1]; - // if (hdr_flag && gainmap_flag && gainmap_metadata_size > 0) - // unsigned int(8) gainmap_metadata[gainmap_metadata_size]; +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + if (hasHdr && hasGainmap && tmapIccSize != 0) { + AVIF_CHECKRES(avifRWStreamWrite(s, encoder->data->altImageMetadata->icc.data, tmapIccSize)); // unsigned int(8) tmap_icc_data[tmap_icc_data_size_minus1 + 1]; + } + if (hasHdr && hasGainmap && gainmapMetadataSize != 0) { + AVIF_CHECKRES(avifWriteGainmapMetadata(s, image->gainMap, &encoder->diag)); // unsigned int(8) gainmap_metadata[gainmap_metadata_size]; + } +#endif if (hasAlpha && alphaData->size != 0) { AVIF_CHECKRES(avifRWStreamWrite(s, alphaData->data, alphaData->size)); // unsigned int(8) alpha_item_data[alpha_item_data_size]; } - // if (hdr_flag && gainmap_flag && gainmap_item_data_size > 0) - // unsigned int(8) gainmap_item_data[gainmap_item_data_size]; + if (hasHdr && hasGainmap && gainmapData->size != 0) { + AVIF_CHECKRES(avifRWStreamWrite(s, gainmapData->data, gainmapData->size)); // unsigned int(8) gainmap_item_data[gainmap_item_data_size]; + } AVIF_CHECKRES(avifRWStreamWrite(s, colorData->data, colorData->size)); // unsigned int(8) main_item_data[main_item_data_size_minus1 + 1]; @@ -2704,9 +2833,11 @@ static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * AVIF_CHECKRES(avifRWStreamWrite(s, image->xmp.data, image->xmp.size)); // unsigned int(8) xmp_data[xmp_data_size_minus1 + 1]; } - AVIF_ASSERT_OR_RETURN(avifRWStreamOffset(s) - headerSize == (hasAlpha ? codecConfigSize : 0) + codecConfigSize + - image->icc.size + (hasAlpha ? alphaData->size : 0) + - colorData->size + image->exif.size + image->xmp.size); + const size_t expectedChunkBytes = (hasAlpha ? codecConfigSize : 0) + (hasGainmap ? codecConfigSize : 0) + codecConfigSize + + image->icc.size + (hasGainmap ? tmapIccSize : 0) + (hasGainmap ? gainmapMetadataSize : 0) + + (hasAlpha ? alphaData->size : 0) + (hasGainmap ? gainmapData->size : 0) + colorData->size + + image->exif.size + image->xmp.size; + AVIF_ASSERT_OR_RETURN(avifRWStreamOffset(s) == headerBytes + expectedChunkBytes); avifRWStreamFinishBox(s, mini); return AVIF_RESULT_OK; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c77b329452..702739c099 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -131,7 +131,7 @@ if(AVIF_ENABLE_GTEST) add_avif_gtest_with_data(aviflosslesstest) add_avif_gtest_with_data(avifmetadatatest) - if(AVIF_ENABLE_EXPERIMENTAL_MINI) + if(AVIF_ENABLE_EXPERIMENTAL_MINI AND AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) add_avif_gtest(avifminitest) endif() @@ -368,7 +368,7 @@ if(AVIF_CODEC_AVM_ENABLED) PROPERTIES DISABLED True ) - if(AVIF_ENABLE_EXPERIMENTAL_MINI) + if(AVIF_ENABLE_EXPERIMENTAL_MINI AND AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) set_tests_properties(avifminitest PROPERTIES DISABLED True) endif() if(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) diff --git a/tests/gtest/avifminitest.cc b/tests/gtest/avifminitest.cc index 1bddea3e72..4d9f3feb4d 100644 --- a/tests/gtest/avifminitest.cc +++ b/tests/gtest/avifminitest.cc @@ -17,9 +17,9 @@ class AvifMinimizedImageBoxTest : public testing::TestWithParam> {}; + /*create_xmp=*/bool, avifTransformFlags, /*create_hdr=*/bool>> {}; -TEST_P(AvifMinimizedImageBoxTest, SimpleOpaque) { +TEST_P(AvifMinimizedImageBoxTest, All) { const int width = std::get<0>(GetParam()); const int height = std::get<1>(GetParam()); const int depth = std::get<2>(GetParam()); @@ -30,6 +30,7 @@ TEST_P(AvifMinimizedImageBoxTest, SimpleOpaque) { const bool create_exif = std::get<7>(GetParam()); const bool create_xmp = std::get<8>(GetParam()); const avifTransformFlags create_transform_flags = std::get<9>(GetParam()); + const bool create_hdr = std::get<10>(GetParam()); ImagePtr image = testutil::CreateImage(width, height, depth, format, planes, range); @@ -64,6 +65,17 @@ TEST_P(AvifMinimizedImageBoxTest, SimpleOpaque) { if (create_transform_flags & AVIF_TRANSFORM_IMIR) { image->imir.axis = 0; } + if (create_hdr) { + image->gainMap = avifGainMapCreate(); + ASSERT_NE(image->gainMap, nullptr); + image->gainMap->image = + testutil::CreateImage(width, height, /*depth=*/8, + AVIF_PIXEL_FORMAT_YUV400, AVIF_PLANES_YUV, + AVIF_RANGE_FULL) + .release(); + ASSERT_NE(image->gainMap->image, nullptr); + testutil::FillImageGradient(image->gainMap->image); + } // Encode. testutil::AvifRwData encoded_mini; @@ -75,9 +87,15 @@ TEST_P(AvifMinimizedImageBoxTest, SimpleOpaque) { AVIF_RESULT_OK); // Decode. - const ImagePtr decoded_mini = - testutil::Decode(encoded_mini.data, encoded_mini.size); + ImagePtr decoded_mini(avifImageCreateEmpty()); ASSERT_NE(decoded_mini, nullptr); + DecoderPtr decoder_mini(avifDecoderCreate()); + ASSERT_NE(decoder_mini, nullptr); + decoder_mini->enableParsingGainMapMetadata = AVIF_TRUE; + decoder_mini->enableDecodingGainMap = AVIF_TRUE; + ASSERT_EQ(avifDecoderReadMemory(decoder_mini.get(), decoded_mini.get(), + encoded_mini.data, encoded_mini.size), + AVIF_RESULT_OK); // Compare. testutil::AvifRwData encoded_meta = @@ -86,14 +104,30 @@ TEST_P(AvifMinimizedImageBoxTest, SimpleOpaque) { // At least 200 bytes should be saved. EXPECT_LT(encoded_mini.size, encoded_meta.size - 200); - const ImagePtr decoded_meta = - testutil::Decode(encoded_meta.data, encoded_meta.size); + ImagePtr decoded_meta(avifImageCreateEmpty()); ASSERT_NE(decoded_meta, nullptr); + DecoderPtr decoder_meta(avifDecoderCreate()); + ASSERT_NE(decoder_meta, nullptr); + decoder_meta->enableParsingGainMapMetadata = AVIF_TRUE; + decoder_meta->enableDecodingGainMap = AVIF_TRUE; + ASSERT_EQ(avifDecoderReadMemory(decoder_meta.get(), decoded_meta.get(), + encoded_meta.data, encoded_meta.size), + AVIF_RESULT_OK); + EXPECT_EQ(decoder_meta->gainMapPresent, decoder_mini->gainMapPresent); // Only the container changed. The pixels, features and metadata should be // identical. EXPECT_TRUE( testutil::AreImagesEqual(*decoded_meta.get(), *decoded_mini.get())); + EXPECT_EQ(decoded_meta->gainMap != nullptr, decoded_mini->gainMap != nullptr); + if (create_hdr) { + ASSERT_NE(decoded_meta->gainMap, nullptr); + ASSERT_NE(decoded_mini->gainMap, nullptr); + ASSERT_NE(decoded_meta->gainMap->image, nullptr); + ASSERT_NE(decoded_mini->gainMap->image, nullptr); + EXPECT_TRUE(testutil::AreImagesEqual(*decoded_meta->gainMap->image, + *decoded_mini->gainMap->image)); + } } INSTANTIATE_TEST_SUITE_P(OnePixel, AvifMinimizedImageBoxTest, @@ -105,7 +139,8 @@ INSTANTIATE_TEST_SUITE_P(OnePixel, AvifMinimizedImageBoxTest, /*create_icc=*/Values(false, true), /*create_exif=*/Values(false, true), /*create_xmp=*/Values(false, true), - Values(AVIF_TRANSFORM_NONE))); + Values(AVIF_TRANSFORM_NONE), + /*create_hdr=*/Values(false))); INSTANTIATE_TEST_SUITE_P( DepthsSubsamplings, AvifMinimizedImageBoxTest, @@ -115,7 +150,8 @@ INSTANTIATE_TEST_SUITE_P( AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400), Values(AVIF_PLANES_ALL), Values(AVIF_RANGE_FULL), /*create_icc=*/Values(false), /*create_exif=*/Values(false), - /*create_xmp=*/Values(false), Values(AVIF_TRANSFORM_NONE))); + /*create_xmp=*/Values(false), Values(AVIF_TRANSFORM_NONE), + /*create_hdr=*/Values(false))); INSTANTIATE_TEST_SUITE_P( Dimensions, AvifMinimizedImageBoxTest, @@ -123,7 +159,7 @@ INSTANTIATE_TEST_SUITE_P( Values(AVIF_PIXEL_FORMAT_YUV444), Values(AVIF_PLANES_ALL), Values(AVIF_RANGE_FULL), /*create_icc=*/Values(true), /*create_exif=*/Values(true), /*create_xmp=*/Values(true), - Values(AVIF_TRANSFORM_NONE))); + Values(AVIF_TRANSFORM_NONE), /*create_hdr=*/Values(false))); INSTANTIATE_TEST_SUITE_P( Orientation, AvifMinimizedImageBoxTest, @@ -133,7 +169,17 @@ INSTANTIATE_TEST_SUITE_P( /*create_exif=*/Values(true), /*create_xmp=*/Values(true), Values(AVIF_TRANSFORM_NONE, AVIF_TRANSFORM_IROT, AVIF_TRANSFORM_IMIR, - AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR))); + AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR), + /*create_hdr=*/Values(false))); + +INSTANTIATE_TEST_SUITE_P( + Hdr, AvifMinimizedImageBoxTest, + Combine(/*width=*/Values(8), /*height=*/Values(10), /*depth=*/Values(10), + Values(AVIF_PIXEL_FORMAT_YUV420), + Values(AVIF_PLANES_YUV, AVIF_PLANES_ALL), Values(AVIF_RANGE_FULL), + /*create_icc=*/Values(false), + /*create_exif=*/Values(false), /*create_xmp=*/Values(false), + Values(AVIF_TRANSFORM_NONE), /*create_hdr=*/Values(true))); //------------------------------------------------------------------------------