Skip to content

Commit af5863a

Browse files
committed
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.
1 parent 14d8e3c commit af5863a

File tree

4 files changed

+286
-46
lines changed

4 files changed

+286
-46
lines changed

include/avif/internal.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ typedef struct avifSequenceHeader
774774
AVIF_NODISCARD avifBool avifSequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample, avifCodecType codecType);
775775

776776
// ---------------------------------------------------------------------------
777-
// gain maps
777+
// Gain maps
778778

779779
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
780780

@@ -786,6 +786,37 @@ avifResult avifFindMinMaxWithoutOutliers(const float * gainMapF, int numPixels,
786786

787787
#endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP
788788

789+
// ---------------------------------------------------------------------------
790+
// Internal encode
791+
//
792+
// These functions/options give extra flexibility to create non standard images for use in testing.
793+
794+
typedef struct avifEncoderInternalOptions
795+
{
796+
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
797+
// Options related to the 'tmap' (tone mapped image) box.
798+
uint8_t tmapVersion; // Value that should be written for the 'version' field (use default if 0)
799+
uint16_t tmapMinimumVersion; // Value that should be written for the 'minimum_version' field (use default if 0)
800+
uint16_t tmapWriterVersion; // Value that should be written for the 'writerVersion' field (use default if 0)
801+
avifBool tmapAddExtraBytes; // Add arbitrary bytes at the end of the box
802+
#endif
803+
char dummy; // Avoid emptry struct error when AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP is off
804+
} avifEncoderInternalOptions;
805+
806+
// Sets extra encoding options.
807+
void avifEncoderSetInternalOptions(avifEncoder * encoder, const avifEncoderInternalOptions * internalOptions);
808+
809+
// Variant of avifEncoderAddImageGrid() that allows creating images where 'gridCols' and 'gridRows' differ for
810+
// the base image and the gain map image.
811+
avifResult avifEncoderAddImageGridInternal(avifEncoder * encoder,
812+
uint32_t gridCols,
813+
uint32_t gridRows,
814+
const avifImage * const * cellImages,
815+
uint32_t gainMapGridCols,
816+
uint32_t gainMapGridRows,
817+
const avifImage * const * gainMapCellImages,
818+
avifAddImageFlags addImageFlags);
819+
789820
#define AVIF_INDEFINITE_DURATION64 UINT64_MAX
790821
#define AVIF_INDEFINITE_DURATION32 UINT32_MAX
791822

src/write.c

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ typedef struct avifEncoderData
243243
// Fields specific to AV1/AV2
244244
const char * imageItemType; // "av01" for AV1 ("av02" for AV2 if AVIF_CODEC_AVM)
245245
const char * configPropName; // "av1C" for AV1 ("av2C" for AV2 if AVIF_CODEC_AVM)
246+
247+
// Extra encoder options not exposed in the public API.
248+
avifEncoderInternalOptions internalOptions;
246249
} avifEncoderData;
247250

248251
static void avifEncoderDataDestroy(avifEncoderData * data);
@@ -281,6 +284,11 @@ static avifEncoderData * avifEncoderDataCreate(void)
281284
return NULL;
282285
}
283286

287+
void avifEncoderSetInternalOptions(avifEncoder * encoder, const avifEncoderInternalOptions * internalOptions)
288+
{
289+
encoder->data->internalOptions = *internalOptions;
290+
}
291+
284292
static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize, uint32_t cellIndex)
285293
{
286294
avifEncoderItem * item = (avifEncoderItem *)avifArrayPush(&data->items);
@@ -885,17 +893,19 @@ static avifResult avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uin
885893

886894
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
887895

888-
static avifBool avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMapMetadata * metadata)
896+
static avifBool avifWriteToneMappedImagePayload(avifRWData * data,
897+
const avifGainMapMetadata * metadata,
898+
const avifEncoderInternalOptions * internalOptions)
889899
{
890900
avifRWStream s;
891901
avifRWStreamStart(&s, data);
892902
const uint8_t version = 0;
893-
AVIF_CHECKRES(avifRWStreamWriteU8(&s, version));
903+
AVIF_CHECKRES(avifRWStreamWriteU8(&s, internalOptions->tmapVersion > 0 ? internalOptions->tmapVersion : version));
894904

895905
const uint16_t minimumVersion = 0;
896-
AVIF_CHECKRES(avifRWStreamWriteU16(&s, minimumVersion));
906+
AVIF_CHECKRES(avifRWStreamWriteU16(&s, internalOptions->tmapMinimumVersion > 0 ? internalOptions->tmapMinimumVersion : minimumVersion));
897907
const uint16_t writerVersion = 0;
898-
AVIF_CHECKRES(avifRWStreamWriteU16(&s, writerVersion));
908+
AVIF_CHECKRES(avifRWStreamWriteU16(&s, internalOptions->tmapWriterVersion > 0 ? internalOptions->tmapWriterVersion : writerVersion));
899909

900910
uint8_t flags = 0u;
901911
// Always write three channels for now for simplicity.
@@ -942,6 +952,10 @@ static avifBool avifWriteToneMappedImagePayload(avifRWData * data, const avifGai
942952
AVIF_CHECKRES(avifRWStreamWriteU32(&s, metadata->alternateOffsetD[c]));
943953
}
944954

955+
if (internalOptions->tmapAddExtraBytes) {
956+
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 42)); // Arbitrary bytes.
957+
}
958+
945959
avifRWStreamFinishWrite(&s);
946960
return AVIF_TRUE;
947961
}
@@ -952,7 +966,7 @@ size_t avifEncoderGetGainMapSizeBytes(avifEncoder * encoder)
952966
}
953967

954968
// Sets altImageMetadata's metadata values to represent the "alternate" image as if applying the gain map to the base image.
955-
static avifResult avifImageCopyAltImageMetadata(avifImage * altImageMetadata, const avifImage * imageWithGainMap)
969+
static avifResult avifImageCopyAltImageMetadata(avifImage * altImageMetadata, const avifImage * imageWithGainMap, const avifImage * gainMapImage)
956970
{
957971
altImageMetadata->width = imageWithGainMap->width;
958972
altImageMetadata->height = imageWithGainMap->height;
@@ -961,9 +975,8 @@ static avifResult avifImageCopyAltImageMetadata(avifImage * altImageMetadata, co
961975
altImageMetadata->transferCharacteristics = imageWithGainMap->gainMap->altTransferCharacteristics;
962976
altImageMetadata->matrixCoefficients = imageWithGainMap->gainMap->altMatrixCoefficients;
963977
altImageMetadata->yuvRange = imageWithGainMap->gainMap->altYUVRange;
964-
altImageMetadata->depth = imageWithGainMap->gainMap->altDepth
965-
? imageWithGainMap->gainMap->altDepth
966-
: AVIF_MAX(imageWithGainMap->depth, imageWithGainMap->gainMap->image->depth);
978+
altImageMetadata->depth = imageWithGainMap->gainMap->altDepth ? imageWithGainMap->gainMap->altDepth
979+
: AVIF_MAX(imageWithGainMap->depth, gainMapImage->depth);
967980
altImageMetadata->yuvFormat = (imageWithGainMap->gainMap->altPlaneCount == 1) ? AVIF_PIXEL_FORMAT_YUV400 : AVIF_PIXEL_FORMAT_YUV444;
968981
altImageMetadata->clli = imageWithGainMap->gainMap->altCLLI;
969982
return AVIF_RESULT_OK;
@@ -1596,6 +1609,9 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
15961609
uint32_t gridCols,
15971610
uint32_t gridRows,
15981611
const avifImage * const * cellImages,
1612+
uint32_t gainMapGridCols,
1613+
uint32_t gainMapGridRows,
1614+
const avifImage * const * gainMapCellImages,
15991615
uint64_t durationInTimescales,
16001616
avifAddImageFlags addImageFlags)
16011617
{
@@ -1636,18 +1652,18 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
16361652
AVIF_CHECKRES(avifValidateGrid(gridCols, gridRows, cellImages, /*validateGainMap=*/AVIF_FALSE, &encoder->diag));
16371653

16381654
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
1639-
const avifBool hasGainMap = (firstCell->gainMap && firstCell->gainMap->image != NULL);
1655+
const avifBool firstCellHasGainMap = (firstCell->gainMap && firstCell->gainMap->image != NULL);
16401656

16411657
// Check that either all cells have a gain map, or none of them do.
16421658
// If a gain map is present, check that they all have the same gain map metadata.
16431659
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
16441660
const avifImage * cellImage = cellImages[cellIndex];
16451661
const avifBool cellHasGainMap = (cellImage->gainMap && cellImage->gainMap->image);
1646-
if (cellHasGainMap != hasGainMap) {
1662+
if (cellHasGainMap != firstCellHasGainMap) {
16471663
avifDiagnosticsPrintf(&encoder->diag, "cells should either all have a gain map image, or none of them should, found a mix");
16481664
return AVIF_RESULT_INVALID_IMAGE_GRID;
16491665
}
1650-
if (hasGainMap) {
1666+
if (firstCellHasGainMap) {
16511667
const avifGainMap * firstGainMap = firstCell->gainMap;
16521668
const avifGainMap * cellGainMap = cellImage->gainMap;
16531669
if (cellGainMap->altICC.size != firstGainMap->altICC.size ||
@@ -1688,7 +1704,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
16881704
}
16891705
}
16901706

1691-
if (hasGainMap) {
1707+
if (firstCellHasGainMap) {
16921708
// AVIF supports 16-bit images through sample transforms used as bit depth extensions,
16931709
// but this is not implemented for gain maps for now. Stick to at most 12 bits.
16941710
// TODO(yguyon): Implement 16-bit gain maps.
@@ -1703,6 +1719,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
17031719
return AVIF_RESULT_INVALID_ARGUMENT;
17041720
}
17051721
}
1722+
#else
1723+
(void)gainMapGridCols, (void)gainMapGridRows, (void)gainMapCellImages;
17061724
#endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP
17071725

17081726
// -----------------------------------------------------------------------
@@ -1787,8 +1805,15 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
17871805
// Make a copy of the first image's metadata (sans pixels) for future writing/validation
17881806
AVIF_CHECKRES(avifImageCopy(encoder->data->imageMetadata, firstCell, 0));
17891807
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
1790-
if (hasGainMap) {
1791-
AVIF_CHECKRES(avifImageCopyAltImageMetadata(encoder->data->altImageMetadata, encoder->data->imageMetadata));
1808+
if (firstCellHasGainMap) {
1809+
AVIF_CHECKRES(avifImageCopyAltImageMetadata(encoder->data->altImageMetadata,
1810+
encoder->data->imageMetadata,
1811+
encoder->data->imageMetadata->gainMap->image));
1812+
} else if (gainMapCellImages) {
1813+
AVIF_CHECKRES(
1814+
avifImageCopyAltImageMetadata(encoder->data->altImageMetadata, encoder->data->imageMetadata, gainMapCellImages[0]));
1815+
encoder->data->imageMetadata->gainMap->image = avifImageCreateEmpty();
1816+
AVIF_CHECKRES(avifImageCopy(encoder->data->imageMetadata->gainMap->image, gainMapCellImages[0], 0));
17921817
}
17931818
#endif
17941819

@@ -1836,13 +1861,13 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
18361861
}
18371862

18381863
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
1839-
if (firstCell->gainMap && firstCell->gainMap->image) {
1864+
if (firstCellHasGainMap || gainMapCellImages != NULL) {
18401865
avifEncoderItem * toneMappedItem = avifEncoderDataCreateItem(encoder->data,
18411866
"tmap",
18421867
infeNameGainMap,
18431868
/*infeNameSize=*/strlen(infeNameGainMap) + 1,
18441869
/*cellIndex=*/0);
1845-
if (!avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata)) {
1870+
if (!avifWriteToneMappedImagePayload(&toneMappedItem->metadataPayload, &firstCell->gainMap->metadata, &encoder->data->internalOptions)) {
18461871
avifDiagnosticsPrintf(&encoder->diag, "failed to write gain map metadata, some values may be negative or too large");
18471872
return AVIF_RESULT_ENCODE_GAIN_MAP_FAILED;
18481873
}
@@ -1859,14 +1884,16 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
18591884
AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
18601885
*alternativeItemID = colorItemID;
18611886

1862-
const uint32_t gainMapGridWidth =
1863-
avifGridWidth(gridCols, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image);
1864-
const uint32_t gainMapGridHeight =
1865-
avifGridHeight(gridRows, cellImages[0]->gainMap->image, cellImages[gridCols * gridRows - 1]->gainMap->image);
1887+
const avifImage * const topLeftGainMapCell = gainMapCellImages ? gainMapCellImages[0] : firstCell->gainMap->image;
1888+
const avifImage * const bottomRightGainMapCell = gainMapCellImages
1889+
? gainMapCellImages[gainMapGridCols * gainMapGridRows - 1]
1890+
: cellImages[gridCols * gridRows - 1]->gainMap->image;
1891+
const uint32_t gainMapGridWidth = avifGridWidth(gainMapGridCols, topLeftGainMapCell, bottomRightGainMapCell);
1892+
const uint32_t gainMapGridHeight = avifGridHeight(gainMapGridRows, topLeftGainMapCell, bottomRightGainMapCell);
18661893

18671894
uint16_t gainMapItemID;
18681895
AVIF_CHECKRES(
1869-
avifEncoderAddImageItems(encoder, gridCols, gridRows, gainMapGridWidth, gainMapGridHeight, AVIF_ITEM_GAIN_MAP, &gainMapItemID));
1896+
avifEncoderAddImageItems(encoder, gainMapGridCols, gainMapGridRows, gainMapGridWidth, gainMapGridHeight, AVIF_ITEM_GAIN_MAP, &gainMapItemID));
18701897
avifEncoderItem * gainMapItem = avifEncoderDataFindItemByID(encoder->data, gainMapItemID);
18711898
AVIF_ASSERT_OR_RETURN(gainMapItem);
18721899
gainMapItem->hiddenImage = AVIF_TRUE;
@@ -1919,7 +1946,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
19191946
// Another frame in an image sequence, or layer in a layered image
19201947

19211948
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
1922-
if (hasGainMap) {
1949+
if (firstCellHasGainMap) {
19231950
avifDiagnosticsPrintf(&encoder->diag, "gain maps are not supported for image sequences or layered images");
19241951
return AVIF_RESULT_NOT_IMPLEMENTED;
19251952
}
@@ -1970,10 +1997,17 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
19701997

19711998
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
19721999
if (item->itemCategory == AVIF_ITEM_GAIN_MAP) {
1973-
AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image);
1974-
cellImage = cellImage->gainMap->image;
1975-
AVIF_ASSERT_OR_RETURN(firstCell->gainMap && firstCell->gainMap->image);
1976-
firstCellImage = firstCell->gainMap->image;
2000+
if (gainMapCellImages) {
2001+
AVIF_ASSERT_OR_RETURN(gainMapCellImages[item->cellIndex]);
2002+
cellImage = gainMapCellImages[item->cellIndex];
2003+
AVIF_ASSERT_OR_RETURN(gainMapCellImages[0]);
2004+
firstCellImage = gainMapCellImages[0];
2005+
} else {
2006+
AVIF_ASSERT_OR_RETURN(cellImage->gainMap && cellImage->gainMap->image);
2007+
cellImage = cellImage->gainMap->image;
2008+
AVIF_ASSERT_OR_RETURN(firstCell->gainMap && firstCell->gainMap->image);
2009+
firstCellImage = firstCell->gainMap->image;
2010+
}
19772011
}
19782012
#endif
19792013

@@ -2077,7 +2111,15 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
20772111
avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags)
20782112
{
20792113
avifDiagnosticsClearError(&encoder->diag);
2080-
return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags);
2114+
return avifEncoderAddImageInternal(encoder,
2115+
/*gridCols=*/1,
2116+
/*gridRows=*/1,
2117+
&image,
2118+
/*gainMapGridCols=*/1,
2119+
/*gainMapGridRows=*/1,
2120+
/*gainMapCellImages=*/NULL,
2121+
durationInTimescales,
2122+
addImageFlags);
20812123
}
20822124

20832125
avifResult avifEncoderAddImageGrid(avifEncoder * encoder,
@@ -2093,7 +2135,36 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder,
20932135
if (encoder->extraLayerCount == 0) {
20942136
addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; // image grids cannot be image sequences
20952137
}
2096-
return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags);
2138+
const avifResult res = avifEncoderAddImageInternal(encoder,
2139+
gridCols,
2140+
gridRows,
2141+
cellImages,
2142+
/*gainMapGridCols=*/gridCols,
2143+
/*gainMapGridRows=*/gridRows,
2144+
/*gainMapCellImages=*/NULL,
2145+
1,
2146+
addImageFlags);
2147+
return res;
2148+
}
2149+
2150+
avifResult avifEncoderAddImageGridInternal(avifEncoder * encoder,
2151+
uint32_t gridCols,
2152+
uint32_t gridRows,
2153+
const avifImage * const * cellImages,
2154+
uint32_t gainMapGridCols,
2155+
uint32_t gainMapGridRows,
2156+
const avifImage * const * gainMapCellImages,
2157+
avifAddImageFlags addImageFlags)
2158+
{
2159+
avifDiagnosticsClearError(&encoder->diag);
2160+
if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256) || (gainMapGridCols == 0) ||
2161+
(gainMapGridCols > 256) || (gainMapGridRows == 0) || (gainMapGridRows > 256)) {
2162+
return AVIF_RESULT_INVALID_IMAGE_GRID;
2163+
}
2164+
if (encoder->extraLayerCount == 0) {
2165+
addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; // image grids cannot be image sequences
2166+
}
2167+
return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, gainMapGridCols, gainMapGridRows, gainMapCellImages, 1, addImageFlags);
20972168
}
20982169

20992170
static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size)

0 commit comments

Comments
 (0)