@@ -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
248251static 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+
284292static 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,
20772111avifResult 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
20832125avifResult 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
20992170static size_t avifEncoderFindExistingChunk (avifRWStream * s , size_t mdatStartOffset , const uint8_t * data , size_t size )
0 commit comments