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 0dd4265e2c..98c988510c 100644 Binary files a/tests/data/color_grid_alpha_grid_gainmap_nogrid.avif and b/tests/data/color_grid_alpha_grid_gainmap_nogrid.avif differ diff --git a/tests/data/color_grid_gainmap_different_grid.avif b/tests/data/color_grid_gainmap_different_grid.avif index 17d14dab82..d7474c01d2 100644 Binary files a/tests/data/color_grid_gainmap_different_grid.avif and b/tests/data/color_grid_gainmap_different_grid.avif differ diff --git a/tests/data/seine_hdr_gainmap_small_srgb.avif b/tests/data/seine_hdr_gainmap_small_srgb.avif index da4983d4b0..929f4e4fda 100644 Binary files a/tests/data/seine_hdr_gainmap_small_srgb.avif and b/tests/data/seine_hdr_gainmap_small_srgb.avif differ diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc index 75c94660b5..a13b326a77 100644 --- a/tests/gtest/avifgainmaptest.cc +++ b/tests/gtest/avifgainmaptest.cc @@ -451,6 +451,74 @@ TEST(GainMapTest, EncodeDecodeGrid) { // .write(reinterpret_cast(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