From aae7ae06b646b9ce54a47aa45065fd03e5a7bcf1 Mon Sep 17 00:00:00 2001 From: Maryla Date: Thu, 21 Sep 2023 16:52:33 +0200 Subject: [PATCH] Allow creating images with a different grid for the gain map. The main image can have a different number of cols/rows than the gain map, or one can be a grid and the other be a single image. Also allow setting differnet values for gain map metadata version fields. Add code in gainmaptest.cc to generate test images. These images are used in tests and as seeds by the fuzzer. --- include/avif/internal.h | 33 +++- src/write.c | 147 ++++++++++++++---- tests/data/README.md | 44 ++++-- .../color_grid_alpha_grid_gainmap_nogrid.avif | Bin 2870 -> 2870 bytes .../color_grid_gainmap_different_grid.avif | Bin 3101 -> 3101 bytes tests/data/seine_hdr_gainmap_small_srgb.avif | Bin 102018 -> 102004 bytes tests/gtest/avifgainmaptest.cc | 128 +++++++++++++++ 7 files changed, 301 insertions(+), 51 deletions(-) diff --git a/include/avif/internal.h b/include/avif/internal.h index 36c373790f..0dc0541b16 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -774,7 +774,7 @@ typedef struct avifSequenceHeader AVIF_NODISCARD avifBool avifSequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample, avifCodecType codecType); // --------------------------------------------------------------------------- -// gain maps +// Gain maps #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) @@ -786,6 +786,37 @@ avifResult avifFindMinMaxWithoutOutliers(const float * gainMapF, int numPixels, #endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP +// --------------------------------------------------------------------------- +// Internal encode +// +// These functions/options give extra flexibility to create non standard images for use in testing. + +typedef struct avifEncoderInternalOptions +{ +#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) + // Options related to the 'tmap' (tone mapped image) box. + uint8_t tmapVersion; // Value that should be written for the 'version' field (use default if 0) + uint16_t tmapMinimumVersion; // Value that should be written for the 'minimum_version' field (use default if 0) + uint16_t tmapWriterVersion; // Value that should be written for the 'writerVersion' field (use default if 0) + avifBool tmapAddExtraBytes; // Add arbitrary bytes at the end of the box +#endif + char dummy; // Avoid emptry struct error when AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP is off +} avifEncoderInternalOptions; + +// Sets extra encoding options. +void avifEncoderSetInternalOptions(avifEncoder * encoder, const avifEncoderInternalOptions * internalOptions); + +// Variant of avifEncoderAddImageGrid() that allows creating images where 'gridCols' and 'gridRows' differ for +// the base image and the gain map image. +avifResult avifEncoderAddImageGridInternal(avifEncoder * encoder, + uint32_t gridCols, + uint32_t gridRows, + const avifImage * const * cellImages, + uint32_t gainMapGridCols, + uint32_t gainMapGridRows, + const avifImage * const * gainMapCellImages, + avifAddImageFlags addImageFlags); + #define AVIF_INDEFINITE_DURATION64 UINT64_MAX #define AVIF_INDEFINITE_DURATION32 UINT32_MAX diff --git a/src/write.c b/src/write.c index e434490a15..2bd698a05e 100644 --- a/src/write.c +++ b/src/write.c @@ -243,6 +243,9 @@ typedef struct avifEncoderData // Fields specific to AV1/AV2 const char * imageItemType; // "av01" for AV1 ("av02" for AV2 if AVIF_CODEC_AVM) const char * configPropName; // "av1C" for AV1 ("av2C" for AV2 if AVIF_CODEC_AVM) + + // Extra encoder options not exposed in the public API. + avifEncoderInternalOptions internalOptions; } avifEncoderData; static void avifEncoderDataDestroy(avifEncoderData * data); @@ -281,6 +284,11 @@ static avifEncoderData * avifEncoderDataCreate(void) return NULL; } +void avifEncoderSetInternalOptions(avifEncoder * encoder, const avifEncoderInternalOptions * internalOptions) +{ + encoder->data->internalOptions = *internalOptions; +} + static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize, uint32_t cellIndex) { avifEncoderItem * item = (avifEncoderItem *)avifArrayPush(&data->items); @@ -885,17 +893,19 @@ static avifResult avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uin #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) -static avifBool avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMapMetadata * metadata) +static avifBool avifWriteToneMappedImagePayload(avifRWData * data, + const avifGainMapMetadata * metadata, + const avifEncoderInternalOptions * internalOptions) { avifRWStream s; avifRWStreamStart(&s, data); const uint8_t version = 0; - AVIF_CHECKRES(avifRWStreamWriteU8(&s, version)); + AVIF_CHECKRES(avifRWStreamWriteU8(&s, internalOptions->tmapVersion > 0 ? internalOptions->tmapVersion : version)); const uint16_t minimumVersion = 0; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, minimumVersion)); + AVIF_CHECKRES(avifRWStreamWriteU16(&s, internalOptions->tmapMinimumVersion > 0 ? internalOptions->tmapMinimumVersion : minimumVersion)); const uint16_t writerVersion = 0; - AVIF_CHECKRES(avifRWStreamWriteU16(&s, writerVersion)); + AVIF_CHECKRES(avifRWStreamWriteU16(&s, internalOptions->tmapWriterVersion > 0 ? internalOptions->tmapWriterVersion : writerVersion)); uint8_t flags = 0u; // Always write three channels for now for simplicity. @@ -942,6 +952,10 @@ static avifBool avifWriteToneMappedImagePayload(avifRWData * data, const avifGai AVIF_CHECKRES(avifRWStreamWriteU32(&s, metadata->alternateOffsetD[c])); } + if (internalOptions->tmapAddExtraBytes) { + AVIF_CHECKRES(avifRWStreamWriteU32(&s, 42)); // Arbitrary bytes. + } + avifRWStreamFinishWrite(&s); return AVIF_TRUE; } @@ -952,18 +966,21 @@ size_t avifEncoderGetGainMapSizeBytes(avifEncoder * encoder) } // Sets altImageMetadata's metadata values to represent the "alternate" image as if applying the gain map to the base image. -static avifResult avifImageCopyAltImageMetadata(avifImage * altImageMetadata, const avifImage * imageWithGainMap) -{ - altImageMetadata->width = imageWithGainMap->width; - altImageMetadata->height = imageWithGainMap->height; +static avifResult avifImageCopyAltImageMetadata(avifImage * altImageMetadata, + const avifImage * imageWithGainMap, + const avifImage * gainMapImage, + uint32_t gridWidth, + uint32_t gridHeight) +{ + altImageMetadata->width = gridWidth; + altImageMetadata->height = gridHeight; AVIF_CHECKRES(avifRWDataSet(&altImageMetadata->icc, imageWithGainMap->gainMap->altICC.data, imageWithGainMap->gainMap->altICC.size)); altImageMetadata->colorPrimaries = imageWithGainMap->gainMap->altColorPrimaries; altImageMetadata->transferCharacteristics = imageWithGainMap->gainMap->altTransferCharacteristics; altImageMetadata->matrixCoefficients = imageWithGainMap->gainMap->altMatrixCoefficients; altImageMetadata->yuvRange = imageWithGainMap->gainMap->altYUVRange; - altImageMetadata->depth = imageWithGainMap->gainMap->altDepth - ? imageWithGainMap->gainMap->altDepth - : AVIF_MAX(imageWithGainMap->depth, imageWithGainMap->gainMap->image->depth); + altImageMetadata->depth = imageWithGainMap->gainMap->altDepth ? imageWithGainMap->gainMap->altDepth + : AVIF_MAX(imageWithGainMap->depth, gainMapImage->depth); altImageMetadata->yuvFormat = (imageWithGainMap->gainMap->altPlaneCount == 1) ? AVIF_PIXEL_FORMAT_YUV400 : AVIF_PIXEL_FORMAT_YUV444; altImageMetadata->clli = imageWithGainMap->gainMap->altCLLI; return AVIF_RESULT_OK; @@ -1596,6 +1613,9 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * const * cellImages, + uint32_t gainMapGridCols, + uint32_t gainMapGridRows, + const avifImage * const * gainMapCellImages, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) { @@ -1636,18 +1656,18 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_FALSE, &encoder->diag)); #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - const avifBool hasGainMap = (firstCell->gainMap && firstCell->gainMap->image != NULL); + const avifBool firstCellHasGainMap = (firstCell->gainMap && firstCell->gainMap->image != NULL); // Check that either all cells have a gain map, or none of them do. // If a gain map is present, check that they all have the same gain map metadata. for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) { const avifImage * cellImage = cellImages[cellIndex]; const avifBool cellHasGainMap = (cellImage->gainMap && cellImage->gainMap->image); - if (cellHasGainMap != hasGainMap) { + if (cellHasGainMap != firstCellHasGainMap) { avifDiagnosticsPrintf(&encoder->diag, "cells should either all have a gain map image, or none of them should, found a mix"); return AVIF_RESULT_INVALID_IMAGE_GRID; } - if (hasGainMap) { + if (firstCellHasGainMap) { const avifGainMap * firstGainMap = firstCell->gainMap; const avifGainMap * cellGainMap = cellImage->gainMap; if (cellGainMap->altICC.size != firstGainMap->altICC.size || @@ -1688,7 +1708,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } } - if (hasGainMap) { + if (firstCellHasGainMap) { // AVIF supports 16-bit images through sample transforms used as bit depth extensions, // but this is not implemented for gain maps for now. Stick to at most 12 bits. // TODO(yguyon): Implement 16-bit gain maps. @@ -1703,6 +1723,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, return AVIF_RESULT_INVALID_ARGUMENT; } } +#else + (void)gainMapGridCols, (void)gainMapGridRows, (void)gainMapCellImages; #endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP // ----------------------------------------------------------------------- @@ -1784,18 +1806,31 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } if (encoder->data->items.count == 0) { + const uint32_t gridWidth = avifGridWidth(gridCols, firstCell, bottomRightCell); + const uint32_t gridHeight = avifGridHeight(gridRows, firstCell, bottomRightCell); + // Make a copy of the first image's metadata (sans pixels) for future writing/validation AVIF_CHECKRES(avifImageCopy(encoder->data->imageMetadata, firstCell, 0)); #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - if (hasGainMap) { - AVIF_CHECKRES(avifImageCopyAltImageMetadata(encoder->data->altImageMetadata, encoder->data->imageMetadata)); + if (firstCellHasGainMap) { + AVIF_CHECKRES(avifImageCopyAltImageMetadata(encoder->data->altImageMetadata, + encoder->data->imageMetadata, + encoder->data->imageMetadata->gainMap->image, + gridWidth, + gridHeight)); + } else if (gainMapCellImages) { + AVIF_CHECKRES(avifImageCopyAltImageMetadata(encoder->data->altImageMetadata, + encoder->data->imageMetadata, + gainMapCellImages[0], + gridWidth, + gridHeight)); + encoder->data->imageMetadata->gainMap->image = avifImageCreateEmpty(); + AVIF_CHECKRES(avifImageCopy(encoder->data->imageMetadata->gainMap->image, gainMapCellImages[0], 0)); } #endif // Prepare all AV1 items uint16_t colorItemID; - const uint32_t gridWidth = avifGridWidth(gridCols, firstCell, bottomRightCell); - const uint32_t gridHeight = avifGridHeight(gridRows, firstCell, bottomRightCell); AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_COLOR, &colorItemID)); encoder->data->primaryItemID = colorItemID; @@ -1836,13 +1871,13 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - if (firstCell->gainMap && firstCell->gainMap->image) { + if (firstCellHasGainMap || gainMapCellImages != NULL) { avifEncoderItem * toneMappedItem = avifEncoderDataCreateItem(encoder->data, "tmap", infeNameGainMap, /*infeNameSize=*/strlen(infeNameGainMap) + 1, /*cellIndex=*/0); - if (!avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata)) { + if (!avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata, &encoder->data->internalOptions)) { avifDiagnosticsPrintf(&encoder->diag, "failed to write gain map metadata, some values may be negative or too large"); return AVIF_RESULT_ENCODE_GAIN_MAP_FAILED; } @@ -1859,14 +1894,16 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY); *alternativeItemID = colorItemID; - const uint32_t gainMapGridWidth = - avifGridWidth(gridCols, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image); - const uint32_t gainMapGridHeight = - avifGridHeight(gridRows, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image); + const avifImage * const topLeftGainMapCell = gainMapCellImages ? gainMapCellImages[0] : firstCell->gainMap->image; + const avifImage * const bottomRightGainMapCell = gainMapCellImages + ? gainMapCellImages[gainMapGridCols * gainMapGridRows - 1] + : cellImages[gridCols * gridRows - 1]->gainMap->image; + const uint32_t gainMapGridWidth = avifGridWidth(gainMapGridCols, topLeftGainMapCell, bottomRightGainMapCell); + const uint32_t gainMapGridHeight = avifGridHeight(gainMapGridRows, topLeftGainMapCell, bottomRightGainMapCell); uint16_t gainMapItemID; AVIF_CHECKRES( - avifEncoderAddImageItems(encoder, gridCols, gridRows, gainMapGridWidth, gainMapGridHeight, AVIF_ITEM_GAIN_MAP, &gainMapItemID)); + avifEncoderAddImageItems(encoder, gainMapGridCols, gainMapGridRows, gainMapGridWidth, gainMapGridHeight, AVIF_ITEM_GAIN_MAP, &gainMapItemID)); avifEncoderItem * gainMapItem = avifEncoderDataFindItemByID(encoder->data, gainMapItemID); AVIF_ASSERT_OR_RETURN(gainMapItem); gainMapItem->hiddenImage = AVIF_TRUE; @@ -1919,7 +1956,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, // Another frame in an image sequence, or layer in a layered image #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) - if (hasGainMap) { + if (firstCellHasGainMap) { avifDiagnosticsPrintf(&encoder->diag, "gain maps are not supported for image sequences or layered images"); return AVIF_RESULT_NOT_IMPLEMENTED; } @@ -1970,10 +2007,17 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) if (item->itemCategory == AVIF_ITEM_GAIN_MAP) { - AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image); - cellImage = cellImage->gainMap->image; - AVIF_ASSERT_OR_RETURN(firstCell->gainMap && firstCell->gainMap->image); - firstCellImage = firstCell->gainMap->image; + if (gainMapCellImages) { + AVIF_ASSERT_OR_RETURN(gainMapCellImages[item->cellIndex]); + cellImage = gainMapCellImages[item->cellIndex]; + AVIF_ASSERT_OR_RETURN(gainMapCellImages[0]); + firstCellImage = gainMapCellImages[0]; + } else { + AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image); + cellImage = cellImage->gainMap->image; + AVIF_ASSERT_OR_RETURN(firstCell->gainMap && firstCell->gainMap->image); + firstCellImage = firstCell->gainMap->image; + } } #endif @@ -2077,7 +2121,15 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags); + return avifEncoderAddImageInternal(encoder, + /*gridCols=*/1, + /*gridRows=*/1, + &image, + /*gainMapGridCols=*/1, + /*gainMapGridRows=*/1, + /*gainMapCellImages=*/NULL, + durationInTimescales, + addImageFlags); } avifResult avifEncoderAddImageGrid(avifEncoder * encoder, @@ -2093,7 +2145,36 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, if (encoder->extraLayerCount == 0) { addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; // image grids cannot be image sequences } - return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags); + const avifResult res = avifEncoderAddImageInternal(encoder, + gridCols, + gridRows, + cellImages, + /*gainMapGridCols=*/gridCols, + /*gainMapGridRows=*/gridRows, + /*gainMapCellImages=*/NULL, + 1, + addImageFlags); + return res; +} + +avifResult avifEncoderAddImageGridInternal(avifEncoder * encoder, + uint32_t gridCols, + uint32_t gridRows, + const avifImage * const * cellImages, + uint32_t gainMapGridCols, + uint32_t gainMapGridRows, + const avifImage * const * gainMapCellImages, + avifAddImageFlags addImageFlags) +{ + avifDiagnosticsClearError(&encoder->diag); + if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256) || (gainMapGridCols == 0) || + (gainMapGridCols > 256) || (gainMapGridRows == 0) || (gainMapGridRows > 256)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + if (encoder->extraLayerCount == 0) { + addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; // image grids cannot be image sequences + } + return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, gainMapGridCols, gainMapGridRows, gainMapCellImages, 1, addImageFlags); } static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size) diff --git a/tests/data/README.md b/tests/data/README.md index d1a693946c..10de88ddfb 100644 --- a/tests/data/README.md +++ b/tests/data/README.md @@ -500,8 +500,9 @@ exiftool "-icc_profile<=p3.icc" paris_exif_xmp_icc_gainmap_bigendian.jpg License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) -Source: generated with a modified libavif at https://github.com/maryla-uc/libavif/tree/weirdgainmaps -by running `./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateGainMapImages ../tests/data/`  +Source: generated by `avifgainmaptest.cc`. To update, set `kUpdateTestImages` to true +in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` Contains a 4x3 color grid, a 4x3 alpha grid, and a 2x2 gain map grid. @@ -511,8 +512,9 @@ Contains a 4x3 color grid, a 4x3 alpha grid, and a 2x2 gain map grid. License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) -Source: generated with a modified libavif at https://github.com/maryla-uc/libavif/tree/weirdgainmaps -by running `./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateGainMapImages ../tests/data/`  +Source: generated by `avifgainmaptest.cc`. To update, set `kUpdateTestImages` to true +in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` Contains a single color image, single alpha image, and a 2x2 gain map grid. @@ -522,8 +524,9 @@ Contains a single color image, single alpha image, and a 2x2 gain map grid. License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) -Source: generated with a modified libavif at https://github.com/maryla-uc/libavif/tree/weirdgainmaps -by running `./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateGainMapImages ../tests/data/`  +Source: generated by `avifgainmaptest.cc`. To update, set `kUpdateTestImages` to true +in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` Contains a 4x3 color grid, a 4x3 alpha grid, and a single gain map image. @@ -533,8 +536,9 @@ Contains a 4x3 color grid, a 4x3 alpha grid, and a single gain map image. License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) -Source: generated with a modified libavif at https://github.com/maryla-uc/libavif/tree/weirdgainmaps -by running `./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateGainMapImages ../tests/data/`  +Source: generated by `avifgainmaptest.cc`. To update, set `kUpdateTestImages` to true +in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` Contains a gain map with the `version` field set to 99 in the tmap box. `minimum_version` and `writer_version` are 0. @@ -545,8 +549,9 @@ Contains a gain map with the `version` field set to 99 in the tmap box. License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) -Source: generated with a modified libavif at https://github.com/maryla-uc/libavif/tree/weirdgainmaps -by running `./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateGainMapImages ../tests/data/`  +Source: generated by `avifgainmaptest.cc`. To update, set `kUpdateTestImages` to true +in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` Contains a gain map with the `minimum_version` field set to 99 in the tmap box. `version` and `writer_version` are 0. @@ -557,8 +562,9 @@ Contains a gain map with the `minimum_version` field set to 99 in the tmap box. License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) -Source: generated with a modified libavif at https://github.com/maryla-uc/libavif/tree/weirdgainmaps -by running `./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateGainMapImages ../tests/data/`  +Source: generated by `avifgainmaptest.cc`. To update, set `kUpdateTestImages` to true +in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` Contains a gain map with the `writer_version` field set to 99 in the tmap box, and some extra unexpected bytes at the end of the gain map metadata. @@ -570,8 +576,9 @@ and some extra unexpected bytes at the end of the gain map metadata. License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) -Source: generated with a modified libavif at https://github.com/maryla-uc/libavif/tree/weirdgainmaps -by running `./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateGainMapImages ../tests/data/`  +Source: generated by `avifgainmaptest.cc`. To update, set `kUpdateTestImages` to true +in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` Contains a gain map with some extra unexpected bytes at the end of the gain map metadata. `version`, `minimum_version` and `writer_version` are 0. @@ -631,7 +638,8 @@ SDR image with a gain map to allow tone mapping to HDR. ![](seine_sdr_gainmap_big_srgb.avif) Source : modified version of `seine_sdr_gainmap_srgb.avif` with an upscaled gain map, generated using libavif's API. -See `CreateTestImages` in `avifgainmaptest.cc` (set kUpdateTestImages to update images). +To update, set `kUpdateTestImages` to true in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` SDR image with a gain map to allow tone mapping to HDR. The gain map's width and height are doubled compared to the base image. This is an atypical image just for testing. Typically, the gain map would be either the same size or smaller as the base image. @@ -643,7 +651,8 @@ This is an atypical image just for testing. Typically, the gain map would be eit License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) Source : created from `seine_hdr_srgb.avif` (for the base image) and `seine_sdr_gainmap_srgb.avif` (for the gain map) with libavif's API. -See `CreateTestImages` in `avifgainmaptest.cc` (set kUpdateTestImages to update images). +To update, set `kUpdateTestImages` to true in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` HDR image with a gain map to allow tone mapping to SDR. @@ -654,7 +663,8 @@ HDR image with a gain map to allow tone mapping to SDR. License: [same as libavif](https://github.com/AOMediaCodec/libavif/blob/main/LICENSE) Source : modified version of `seine_hdr_gainmap_srgb.avif` with a downscaled gain map, generated using libavif's API. -See `CreateTestImages` in `avifgainmaptest.cc` (set kUpdateTestImages to update images). +To update, set `kUpdateTestImages` to true in `avifgainmaptest.cc` and run the test: +`./tests/avifgainmaptest --gtest_filter=GainMapTest.CreateTestImages ../tests/data/` SDR image with a gain map to allow tone mapping to HDR. The gain map's width and height are halved compared to the base image. diff --git a/tests/data/color_grid_alpha_grid_gainmap_nogrid.avif b/tests/data/color_grid_alpha_grid_gainmap_nogrid.avif index 0dd4265e2cceae16730c9575638563bdb1e6c418..98c988510c3ebb052ecc724fc8889cac57579e4b 100644 GIT binary patch delta 14 VcmdlcwoPn<7CR&3W^HzE762iW18@KU delta 14 VcmdlcwoPn<7CR%$W^HzE762io19JcX diff --git a/tests/data/color_grid_gainmap_different_grid.avif b/tests/data/color_grid_gainmap_different_grid.avif index 17d14dab82d0e00db6c073463fccc59a4ab9cd60..d7474c01d2396f8f2bf5c46125a79b634b23b8c2 100644 GIT binary patch delta 14 VcmbO$F;`;4J9b9K&F|UuSO6(=1nmF- delta 14 VcmbO$F;`;4J9b8v&F|UuSO6)71n>X= diff --git a/tests/data/seine_hdr_gainmap_small_srgb.avif b/tests/data/seine_hdr_gainmap_small_srgb.avif index da4983d4b0fec3d0bbafdb5892a8af027b404913..929f4e4fda01efa4744e25003f6f12aed4d8286c 100644 GIT binary patch delta 12795 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 Nj-8+~;Inq#46tW#m3sgH delta 12809 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 bpOy3Xh~PK05DA&v?{cx@*0Y?mXx(encoded.data), encoded.size); } +void MakeTestImage(const std::string& path, uint32_t grid_cols, + uint32_t grid_rows, uint32_t cell_width, + uint32_t cell_height, uint32_t gain_map_grid_cols, + uint32_t gain_map_grid_rows, uint32_t gain_map_cell_width, + uint32_t gain_map_cell_height, uint8_t tmap_version = 0, + uint16_t minimum_version = 0, uint16_t writer_version = 0, + bool add_extra_bytes = false) { + std::vector cells; + std::vector cell_ptrs; + std::vector gain_map_cells; + std::vector gain_map_ptrs; + + avifGainMapMetadata gain_map_metadata = + GetTestGainMapMetadata(/*base_rendition_is_hdr=*/true); + for (uint32_t i = 0; i < grid_cols * grid_rows; ++i) { + ImagePtr image = + testutil::CreateImage(cell_width, cell_height, /*depth=*/10, + AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL); + ASSERT_NE(image, nullptr); + image->gainMap = avifGainMapCreate(); + image->gainMap->metadata = gain_map_metadata; + image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_PQ; + image->gainMap->altDepth = 8; + image->gainMap->altPlaneCount = 3; + image->gainMap->altColorPrimaries = AVIF_COLOR_PRIMARIES_SRGB; + image->gainMap->altTransferCharacteristics = + AVIF_TRANSFER_CHARACTERISTICS_SRGB; + image->gainMap->altMatrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; + testutil::FillImageGradient(image.get()); + + cell_ptrs.push_back(image.get()); + cells.push_back(std::move(image)); + } + for (uint32_t i = 0; i < gain_map_grid_cols * gain_map_grid_rows; ++i) { + ImagePtr gain_map = testutil::CreateImage( + gain_map_cell_width, gain_map_cell_height, /*depth=*/8, + AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV); + ASSERT_NE(gain_map, nullptr); + gain_map->gainMap = avifGainMapCreate(); + gain_map->gainMap->metadata = gain_map_metadata; + testutil::FillImageGradient(gain_map.get()); + gain_map_ptrs.push_back(gain_map.get()); + gain_map_cells.push_back(std::move(gain_map)); + } + + EncoderPtr encoder(avifEncoderCreate()); + ASSERT_NE(encoder, nullptr); + encoder->speed = 10; + avifEncoderInternalOptions internalOptions; + internalOptions.tmapVersion = tmap_version; + internalOptions.tmapMinimumVersion = minimum_version; + internalOptions.tmapWriterVersion = writer_version; + internalOptions.tmapAddExtraBytes = add_extra_bytes; + avifEncoderSetInternalOptions(encoder.get(), &internalOptions); + testutil::AvifRwData encoded; + avifResult result = avifEncoderAddImageGridInternal( + encoder.get(), grid_cols, grid_rows, cell_ptrs.data(), gain_map_grid_cols, + gain_map_grid_rows, gain_map_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE); + ASSERT_EQ(result, AVIF_RESULT_OK) + << avifResultToString(result) << " " << encoder->diag.error; + result = avifEncoderFinish(encoder.get(), &encoded); + ASSERT_EQ(result, AVIF_RESULT_OK) + << avifResultToString(result) << " " << encoder->diag.error; + + std::ofstream(path, std::ios::binary) + .write(reinterpret_cast(encoded.data), encoded.size); +} + TEST(GainMapTest, InvalidGrid) { std::vector cells; std::vector cell_ptrs; @@ -1162,6 +1230,66 @@ TEST(GainMapTest, CreateTestImages) { encoded_small_gainmap.size); } } + + if (kUpdateTestImages) { + MakeTestImage( + std::string(data_path) + "color_grid_gainmap_different_grid.avif", + /*grid_cols=*/4, + /*grid_rows=*/3, + /*cell_width=*/128, /*cell_heigh=*/200, + /*gain_map_grid_cols=*/2, /*gain_map_grid_rows=*/2, + /*gain_map_cell_width=*/64, /*gain_map_cell_height=*/80); + MakeTestImage( + std::string(data_path) + "color_grid_alpha_grid_gainmap_nogrid.avif", + /*grid_cols=*/4, + /*grid_rows=*/3, + /*cell_width=*/128, /*cell_heigh=*/200, + /*gain_map_grid_cols=*/1, /*gain_map_grid_rows=*/1, + /*gain_map_cell_width=*/64, /*gain_map_cell_height=*/80); + MakeTestImage( + std::string(data_path) + "color_nogrid_alpha_nogrid_gainmap_grid.avif", + /*grid_cols=*/1, + /*grid_rows=*/1, + /*cell_width=*/128, /*cell_heigh=*/200, + /*gain_map_grid_cols=*/2, /*gain_map_grid_rows=*/2, + /*gain_map_cell_width=*/64, /*gain_map_cell_height=*/80); + MakeTestImage(std::string(data_path) + "unsupported_gainmap_version.avif", + /*grid_cols=*/1, + /*grid_rows=*/1, + /*cell_width=*/100, /*cell_heigh=*/100, + /*gain_map_grid_cols=*/1, /*gain_map_grid_rows=*/1, + /*gain_map_cell_width=*/50, /*gain_map_cell_height=*/50, + /*tmap_version=*/99, /*minimum_version=*/0, + /*writer_version=*/0); + MakeTestImage( + std::string(data_path) + "unsupported_gainmap_minimum_version.avif", + /*grid_cols=*/1, + /*grid_rows=*/1, + /*cell_width=*/100, /*cell_heigh=*/100, + /*gain_map_grid_cols=*/1, /*gain_map_grid_rows=*/1, + /*gain_map_cell_width=*/50, /*gain_map_cell_height=*/50, + /*tmap_version=*/0, /*minimum_version=*/99, /*writer_version=*/99); + MakeTestImage( + std::string(data_path) + + "unsupported_gainmap_writer_version_with_extra_bytes.avif", + /*grid_cols=*/1, + /*grid_rows=*/1, + /*cell_width=*/100, /*cell_heigh=*/100, + /*gain_map_grid_cols=*/1, /*gain_map_grid_rows=*/1, + /*gain_map_cell_width=*/50, /*gain_map_cell_height=*/50, + /*tmap_version=*/0, /*minimum_version=*/0, /*writer_version=*/99, + /*add_extra_bytes=*/1); + MakeTestImage(std::string(data_path) + + "supported_gainmap_writer_version_with_extra_bytes.avif", + /*grid_cols=*/1, + /*grid_rows=*/1, + /*cell_width=*/100, /*cell_heigh=*/100, + /*gain_map_grid_cols=*/1, /*gain_map_grid_rows=*/1, + /*gain_map_cell_width=*/50, /*gain_map_cell_height=*/50, + /*tmap_version=*/0, /*minimum_version=*/0, + /*writer_version=*/0, + /*add_extra_bytes=*/1); + } } class ToneMapTest