Parse the gain map version before parsing the gain map item. (#2800)
Also ignore the gain map if it points to an unsupported item.
These changes make sure unsupported gain maps are ignored instead of
causing errors.
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 5c8fa1f..1620cec 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -808,6 +808,9 @@
// ---------------------------------------------------------------------------
// gain maps
+// Initializes avifGainMap to default values.
+void avifGainMapSetDefaults(avifGainMap * gainMap);
+
// Finds the approximate min/max values from the given gain map values, excluding outliers.
// Uses a histogram, with outliers defined as having at least one empty bucket between them
// and the rest of the distribution. Discards at most 0.1% of values.
diff --git a/src/avif.c b/src/avif.c
index 984ca1a..a87710b 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -1310,6 +1310,17 @@
if (!gainMap) {
return NULL;
}
+ avifGainMapSetDefaults(gainMap);
+ // Note that some functions like avifDecoderFindGainMapItem() allocate avifGainMap directly on
+ // the stack instead of calling avifGainMapCreate() to simplify error handling. This works under
+ // the assumption that no complex initialization (such as dynamic allocation of fields) takes
+ // place here. If this function becomes more complex than one alloc + setDefaults, such code
+ // might need to be changed.
+ return gainMap;
+}
+
+void avifGainMapSetDefaults(avifGainMap * gainMap)
+{
memset(gainMap, 0, sizeof(avifGainMap));
gainMap->altColorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
gainMap->altTransferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
@@ -1327,7 +1338,6 @@
}
gainMap->baseHdrHeadroom.d = 1;
gainMap->alternateHdrHeadroom.d = 1;
- return gainMap;
}
void avifGainMapDestroy(avifGainMap * gainMap)
diff --git a/src/gainmap.c b/src/gainmap.c
index e02087b..b8585bf 100644
--- a/src/gainmap.c
+++ b/src/gainmap.c
@@ -7,7 +7,7 @@
#include <math.h>
#include <string.h>
-static void avifGainMapSetDefaults(avifGainMap * gainMap)
+static void avifGainMapSetEncodingDefaults(avifGainMap * gainMap)
{
for (int i = 0; i < 3; ++i) {
gainMap->gainMapMin[i] = (avifSignedFraction) { 1, 1 };
@@ -569,7 +569,7 @@
}
}
- avifGainMapSetDefaults(gainMap);
+ avifGainMapSetEncodingDefaults(gainMap);
gainMap->useBaseColorSpace = (gainMapMathPrimaries == baseColorPrimaries);
float (*baseGammaToLinear)(float) = avifTransferCharacteristicsGetGammaToLinearFunction(baseTransferCharacteristics);
diff --git a/src/read.c b/src/read.c
index 07c24c5..c87ee18 100644
--- a/src/read.c
+++ b/src/read.c
@@ -2098,53 +2098,56 @@
return AVIF_RESULT_OK;
}
-static avifBool avifParseImageGridBox(avifImageGrid * grid,
- const uint8_t * raw,
- size_t rawLen,
- uint32_t imageSizeLimit,
- uint32_t imageDimensionLimit,
- avifDiagnostics * diag)
+static avifResult avifParseImageGridBox(avifImageGrid * grid,
+ const uint8_t * raw,
+ size_t rawLen,
+ uint32_t imageSizeLimit,
+ uint32_t imageDimensionLimit,
+ avifDiagnostics * diag)
{
BEGIN_STREAM(s, raw, rawLen, diag, "Box[grid]");
uint8_t version, flags;
- AVIF_CHECK(avifROStreamRead(&s, &version, 1)); // unsigned int(8) version = 0;
+ AVIF_CHECKERR(avifROStreamRead(&s, &version, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) version = 0;
if (version != 0) {
avifDiagnosticsPrintf(diag, "Box[grid] has unsupported version [%u]", version);
- return AVIF_FALSE;
+ return AVIF_RESULT_NOT_IMPLEMENTED;
}
uint8_t rowsMinusOne, columnsMinusOne;
- AVIF_CHECK(avifROStreamRead(&s, &flags, 1)); // unsigned int(8) flags;
- AVIF_CHECK(avifROStreamRead(&s, &rowsMinusOne, 1)); // unsigned int(8) rows_minus_one;
- AVIF_CHECK(avifROStreamRead(&s, &columnsMinusOne, 1)); // unsigned int(8) columns_minus_one;
+ AVIF_CHECKERR(avifROStreamRead(&s, &flags, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) flags;
+ AVIF_CHECKERR(avifROStreamRead(&s, &rowsMinusOne, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) rows_minus_one;
+ AVIF_CHECKERR(avifROStreamRead(&s, &columnsMinusOne, 1), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(8) columns_minus_one;
grid->rows = (uint32_t)rowsMinusOne + 1;
grid->columns = (uint32_t)columnsMinusOne + 1;
uint32_t fieldLength = ((flags & 1) + 1) * 16;
if (fieldLength == 16) {
uint16_t outputWidth16, outputHeight16;
- AVIF_CHECK(avifROStreamReadU16(&s, &outputWidth16)); // unsigned int(FieldLength) output_width;
- AVIF_CHECK(avifROStreamReadU16(&s, &outputHeight16)); // unsigned int(FieldLength) output_height;
+ AVIF_CHECKERR(avifROStreamReadU16(&s, &outputWidth16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(FieldLength) output_width;
+ AVIF_CHECKERR(avifROStreamReadU16(&s, &outputHeight16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(FieldLength) output_height;
grid->outputWidth = outputWidth16;
grid->outputHeight = outputHeight16;
} else {
if (fieldLength != 32) {
// This should be impossible
avifDiagnosticsPrintf(diag, "Grid box contains illegal field length: [%u]", fieldLength);
- return AVIF_FALSE;
+ return AVIF_RESULT_INVALID_IMAGE_GRID;
}
- AVIF_CHECK(avifROStreamReadU32(&s, &grid->outputWidth)); // unsigned int(FieldLength) output_width;
- AVIF_CHECK(avifROStreamReadU32(&s, &grid->outputHeight)); // unsigned int(FieldLength) output_height;
+ AVIF_CHECKERR(avifROStreamReadU32(&s, &grid->outputWidth), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(FieldLength) output_width;
+ AVIF_CHECKERR(avifROStreamReadU32(&s, &grid->outputHeight), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(FieldLength) output_height;
}
if ((grid->outputWidth == 0) || (grid->outputHeight == 0)) {
avifDiagnosticsPrintf(diag, "Grid box contains illegal dimensions: [%u x %u]", grid->outputWidth, grid->outputHeight);
- return AVIF_FALSE;
+ return AVIF_RESULT_INVALID_IMAGE_GRID;
}
if (avifDimensionsTooLarge(grid->outputWidth, grid->outputHeight, imageSizeLimit, imageDimensionLimit)) {
avifDiagnosticsPrintf(diag, "Grid box dimensions are too large: [%u x %u]", grid->outputWidth, grid->outputHeight);
- return AVIF_FALSE;
+ return AVIF_RESULT_NOT_IMPLEMENTED;
}
- return avifROStreamRemainingBytes(&s) == 0;
+ if (avifROStreamRemainingBytes(&s) != 0) {
+ return AVIF_RESULT_BMFF_PARSE_FAILED;
+ }
+ return AVIF_RESULT_OK;
}
static avifBool avifParseGainMapMetadata(avifGainMap * gainMap, avifROStream * s)
@@ -2354,13 +2357,12 @@
if (isItemInInput) {
avifROData readData;
AVIF_CHECKRES(avifDecoderItemRead(item, decoder->io, &readData, 0, 0, decoder->data->diag));
- AVIF_CHECKERR(avifParseImageGridBox(grid,
+ AVIF_CHECKRES(avifParseImageGridBox(grid,
readData.data,
readData.size,
decoder->imageSizeLimit,
decoder->imageDimensionLimit,
- decoder->data->diag),
- AVIF_RESULT_INVALID_IMAGE_GRID);
+ decoder->data->diag));
// Validate that there are exactly the same number of dimg items to form the grid.
uint32_t dimgItemCount = 0;
for (uint32_t i = 0; i < item->meta->items.count; ++i) {
@@ -5638,27 +5640,26 @@
// Finds a 'tmap' (tone mapped image item) box associated with the given 'colorItem',
// then finds the associated gain map image.
-// If found, fills 'toneMappedImageItem', 'gainMapItem' and 'gainMapCodecType', and
-// allocates and fills metadata in decoder->image->gainMap.
-// Otherwise, sets 'toneMappedImageItem' and 'gainMapItem' to NULL.
+// If found, fills 'gainMapItem' and 'gainMapCodecType', and allocates and fills in
+// decoder->image->gainMap.
+// Otherwise, sets 'gainMapItem' to NULL and gainMapCodecType to AVIF_CODEC_TYPE_UNKNOWN.
// Returns AVIF_RESULT_OK if no errors were encountered (whether or not a gain map was found).
// Assumes that there is a single tmap item, and not, e.g., a grid of tmap items.
static avifResult avifDecoderFindGainMapItem(const avifDecoder * decoder,
const avifDecoderItem * colorItem,
- avifDecoderItem ** toneMappedImageItem,
avifDecoderItem ** gainMapItem,
avifCodecType * gainMapCodecType)
{
- *toneMappedImageItem = NULL;
*gainMapItem = NULL;
*gainMapCodecType = AVIF_CODEC_TYPE_UNKNOWN;
avifDecoderData * data = decoder->data;
+ // Find tmap and gain map item ids.
uint32_t gainMapItemID;
avifDecoderItem * toneMappedImageItemTmp;
AVIF_CHECKRES(avifDecoderDataFindToneMappedImageItem(data, colorItem, &toneMappedImageItemTmp, &gainMapItemID));
- if (!toneMappedImageItemTmp) {
+ if (!toneMappedImageItemTmp || !gainMapItemID) {
return AVIF_RESULT_OK;
}
@@ -5666,42 +5667,53 @@
return AVIF_RESULT_OK;
}
- AVIF_ASSERT_OR_RETURN(gainMapItemID != 0);
+ // Parse tmap item data (containing the gain map metadata).
+ avifROData tmapData;
+ AVIF_CHECKRES(avifDecoderItemRead(toneMappedImageItemTmp, decoder->io, &tmapData, 0, 0, data->diag));
+ // Allocate avifGainMap on the stack instead of using avifGainMapCreate() to simplify error handling.
+ avifGainMap gainMapTmp;
+ avifGainMapSetDefaults(&gainMapTmp);
+ const avifResult tmapParsingRes = avifParseToneMappedImageBox(&gainMapTmp, tmapData.data, tmapData.size, data->diag);
+ if (tmapParsingRes == AVIF_RESULT_NOT_IMPLEMENTED) {
+ // Unsupported gain map version. Simply ignore the gain map.
+ return AVIF_RESULT_OK;
+ }
+ AVIF_CHECKRES(tmapParsingRes);
+
avifDecoderItem * gainMapItemTmp;
AVIF_CHECKRES(avifMetaFindOrCreateItem(data->meta, gainMapItemID, &gainMapItemTmp));
if (avifDecoderItemShouldBeSkipped(gainMapItemTmp)) {
- avifDiagnosticsPrintf(data->diag, "Box[tmap] gain map item %d is not a supported image type", gainMapItemID);
- return AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE;
+ return AVIF_RESULT_NOT_IMPLEMENTED;
}
- AVIF_CHECKRES(avifDecoderItemReadAndParse(decoder,
- gainMapItemTmp,
- /*isItemInInput=*/AVIF_TRUE,
- &data->tileInfos[AVIF_ITEM_GAIN_MAP].grid,
- gainMapCodecType));
+ const avifResult gainMapParsingRes = avifDecoderItemReadAndParse(decoder,
+ gainMapItemTmp,
+ /*isItemInInput=*/AVIF_TRUE,
+ &data->tileInfos[AVIF_ITEM_GAIN_MAP].grid,
+ gainMapCodecType);
+ if (gainMapParsingRes == AVIF_RESULT_NOT_IMPLEMENTED) {
+ return AVIF_RESULT_OK;
+ }
+ AVIF_CHECKRES(gainMapParsingRes);
- decoder->image->gainMap = avifGainMapCreate();
- AVIF_CHECKERR(decoder->image->gainMap, AVIF_RESULT_OUT_OF_MEMORY);
-
- avifGainMap * const gainMap = decoder->image->gainMap;
AVIF_CHECKRES(avifReadColorProperties(decoder->io,
&toneMappedImageItemTmp->properties,
- &gainMap->altICC,
- &gainMap->altColorPrimaries,
- &gainMap->altTransferCharacteristics,
- &gainMap->altMatrixCoefficients,
- &gainMap->altYUVRange,
+ &gainMapTmp.altICC,
+ &gainMapTmp.altColorPrimaries,
+ &gainMapTmp.altTransferCharacteristics,
+ &gainMapTmp.altMatrixCoefficients,
+ &gainMapTmp.altYUVRange,
/*cicpSet=*/NULL));
const avifProperty * clliProp = avifPropertyArrayFind(&toneMappedImageItemTmp->properties, "clli");
if (clliProp) {
- gainMap->altCLLI = clliProp->u.clli;
+ gainMapTmp.altCLLI = clliProp->u.clli;
}
const avifProperty * pixiProp = avifPropertyArrayFind(&toneMappedImageItemTmp->properties, "pixi");
if (pixiProp) {
- gainMap->altPlaneCount = pixiProp->u.pixi.planeCount;
- gainMap->altDepth = pixiProp->u.pixi.planeDepths[0];
+ gainMapTmp.altPlaneCount = pixiProp->u.pixi.planeCount;
+ gainMapTmp.altDepth = pixiProp->u.pixi.planeDepths[0];
}
const avifProperty * ispeProp = avifPropertyArrayFind(&toneMappedImageItemTmp->properties, "ispe");
@@ -5731,21 +5743,34 @@
return AVIF_RESULT_INVALID_TONE_MAPPED_IMAGE;
}
- if (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) {
- gainMap->image = avifImageCreateEmpty();
- AVIF_CHECKERR(gainMap->image, AVIF_RESULT_OUT_OF_MEMORY);
+ avifColorPrimaries colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
+ avifTransferCharacteristics transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
+ avifMatrixCoefficients matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;
+ avifRange yuvRange = AVIF_RANGE_FULL;
+ avifBool cicpSet;
+ // Look for a colr nclx box. Other colr box types (e.g. ICC) are not supported.
+ AVIF_CHECKRES(
+ avifReadColorNclxProperty(&gainMapItemTmp->properties, &colorPrimaries, &transferCharacteristics, &matrixCoefficients, &yuvRange, &cicpSet));
- // Look for a colr nclx box. Other colr box types (e.g. ICC) are not supported.
- AVIF_CHECKRES(avifReadColorNclxProperty(&gainMapItemTmp->properties,
- &gainMap->image->colorPrimaries,
- &gainMap->image->transferCharacteristics,
- &gainMap->image->matrixCoefficients,
- &gainMap->image->yuvRange,
- /*cicpSet=*/NULL));
+ // -- Everything is valid, do memory allocations and fill in output data. --
+
+ decoder->image->gainMap = avifGainMapCreate();
+ AVIF_CHECKERR(decoder->image->gainMap, AVIF_RESULT_OUT_OF_MEMORY);
+ *decoder->image->gainMap = gainMapTmp;
+
+ if (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) {
+ decoder->image->gainMap->image = avifImageCreateEmpty();
+ avifImage * image = decoder->image->gainMap->image;
+ AVIF_CHECKERR(image, AVIF_RESULT_OUT_OF_MEMORY);
+ if (cicpSet) {
+ image->colorPrimaries = colorPrimaries;
+ image->transferCharacteristics = transferCharacteristics;
+ image->matrixCoefficients = matrixCoefficients;
+ image->yuvRange = yuvRange;
+ }
}
- // Only set the output parameters after everything has been validated.
- *toneMappedImageItem = toneMappedImageItemTmp;
+ // Only set the output pointer after everything has been validated.
*gainMapItem = gainMapItemTmp;
return AVIF_RESULT_OK;
}
@@ -6114,29 +6139,12 @@
// need to remove the early exit in avifParse() to check if a 'tmap' item might be present
// further down the file. Instead, we simply ignore tmap items in files that lack the 'tmap' brand.
if (avifBrandArrayHasBrand(&data->compatibleBrands, "tmap")) {
- avifDecoderItem * toneMappedImageItem;
avifDecoderItem * gainMapItem;
avifCodecType gainMapCodecType;
- AVIF_CHECKRES(
- avifDecoderFindGainMapItem(decoder, mainItems[AVIF_ITEM_COLOR], &toneMappedImageItem, &gainMapItem, &gainMapCodecType));
- if (toneMappedImageItem != NULL) {
- // Read the gain map's metadata.
- avifROData tmapData;
- AVIF_CHECKRES(avifDecoderItemRead(toneMappedImageItem, decoder->io, &tmapData, 0, 0, data->diag));
- AVIF_ASSERT_OR_RETURN(decoder->image->gainMap != NULL);
- const avifResult tmapParsingRes =
- avifParseToneMappedImageBox(decoder->image->gainMap, tmapData.data, tmapData.size, data->diag);
- if (tmapParsingRes == AVIF_RESULT_NOT_IMPLEMENTED) {
- // Unsupported gain map version. Simply ignore the gain map.
- avifGainMapDestroy(decoder->image->gainMap);
- decoder->image->gainMap = NULL;
- } else {
- AVIF_CHECKRES(tmapParsingRes);
- if (decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) {
- mainItems[AVIF_ITEM_GAIN_MAP] = gainMapItem;
- codecType[AVIF_ITEM_GAIN_MAP] = gainMapCodecType;
- }
- }
+ AVIF_CHECKRES(avifDecoderFindGainMapItem(decoder, mainItems[AVIF_ITEM_COLOR], &gainMapItem, &gainMapCodecType));
+ if (gainMapItem != NULL && decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_GAIN_MAP) {
+ mainItems[AVIF_ITEM_GAIN_MAP] = gainMapItem;
+ codecType[AVIF_ITEM_GAIN_MAP] = gainMapCodecType;
}
}
diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc
index 0c2cded..c076528 100644
--- a/tests/gtest/avifgainmaptest.cc
+++ b/tests/gtest/avifgainmaptest.cc
@@ -67,6 +67,8 @@
if (image == nullptr) {
return nullptr;
}
+ image->colorPrimaries = AVIF_COLOR_PRIMARIES_BT2020;
+ image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
image->transferCharacteristics =
(avifTransferCharacteristics)(base_rendition_is_hdr
? AVIF_TRANSFER_CHARACTERISTICS_PQ
@@ -78,6 +80,9 @@
if (gain_map == nullptr) {
return nullptr;
}
+ gain_map->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
+ gain_map->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT709;
+ gain_map->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
testutil::FillImageGradient(gain_map.get());
image->gainMap = avifGainMapCreate();
if (image->gainMap == nullptr) {
@@ -152,6 +157,13 @@
EXPECT_EQ(decoded->gainMap->image->width, image->gainMap->image->width);
EXPECT_EQ(decoded->gainMap->image->height, image->gainMap->image->height);
EXPECT_EQ(decoded->gainMap->image->depth, image->gainMap->image->depth);
+ EXPECT_EQ(decoded->gainMap->image->colorPrimaries,
+ image->gainMap->image->colorPrimaries);
+ EXPECT_EQ(decoded->gainMap->image->transferCharacteristics,
+ image->gainMap->image->transferCharacteristics);
+ EXPECT_EQ(decoded->gainMap->image->matrixCoefficients,
+ image->gainMap->image->matrixCoefficients);
+ EXPECT_EQ(decoded->gainMap->image->yuvRange, image->gainMap->image->yuvRange);
CheckGainMapMetadataMatches(*decoded->gainMap, *image->gainMap);
// Decode the image.