Implement the HDR part of MinimizedImageBox (#2440)
Write and parse tone mapping and gain map elements in SlimHEIF.
diff --git a/src/read.c b/src/read.c
index d711cb9..5c8b68a 100644
--- a/src/read.c
+++ b/src/read.c
@@ -2252,6 +2252,109 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 1587586..7e48280 100644
--- a/src/write.c
+++ b/src/write.c
@@ -676,6 +676,14 @@
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,19 +698,57 @@
}
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,
const avifImage * imageMetadata,
@@ -1262,6 +1308,7 @@
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 @@
}
#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 @@
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 @@
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 @@
{
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 @@
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 @@
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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 c77b329..702739c 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -131,7 +131,7 @@
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 @@
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 1bddea3..4d9f3fe 100644
--- a/tests/gtest/avifminitest.cc
+++ b/tests/gtest/avifminitest.cc
@@ -17,9 +17,9 @@
: public testing::TestWithParam<std::tuple<
/*width=*/int, /*height=*/int, /*depth=*/int, avifPixelFormat,
avifPlanesFlags, avifRange, /*create_icc=*/bool, /*create_exif=*/bool,
- /*create_xmp=*/bool, avifTransformFlags>> {};
+ /*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 @@
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 @@
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 @@
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 @@
// 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 @@
/*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 @@
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 @@
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 @@
/*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)));
//------------------------------------------------------------------------------