#include "avif/internal.h"
#include <assert.h>
#include <string.h>
#include <time.h>
#define MAX_ASSOCIATIONS …
struct ipmaArray
{ … };
avifOffsetFixup;
AVIF_ARRAY_DECLARE(avifOffsetFixupArray, avifOffsetFixup, … } ;
static const char alphaURN[] = …;
static const size_t alphaURNSize = …;
static const char xmpContentType[] = …;
static const size_t xmpContentTypeSize = …;
static avifResult writeCodecConfig(avifRWStream * s, const avifCodecConfigurationBox * cfg);
static avifResult writeConfigBox(avifRWStream * s, const avifCodecConfigurationBox * cfg, const char * configPropName);
static int floorLog2(uint32_t n)
{ … }
static void splitTilesLog2(uint32_t dim1, uint32_t dim2, int tilesLog2, int * tileDim1Log2, int * tileDim2Log2)
{ … }
void avifSetTileConfiguration(int threads, uint32_t width, uint32_t height, int * tileRowsLog2, int * tileColsLog2)
{ … }
avifCodecEncodeOutput * avifCodecEncodeOutputCreate(void)
{ … }
avifResult avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync)
{ … }
void avifCodecEncodeOutputDestroy(avifCodecEncodeOutput * encodeOutput)
{ … }
avifEncoderItem;
AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, … } ;
avifEncoderItemReference;
AVIF_ARRAY_DECLARE(avifEncoderItemReferenceArray, avifEncoderItemReference, … } ;
avifEncoderFrame;
AVIF_ARRAY_DECLARE(avifEncoderFrameArray, avifEncoderFrame, … } ;
AVIF_ARRAY_DECLARE(avifEncoderItemIdArray, uint16_t, … } ;
avifEncoderData;
static void avifEncoderDataDestroy(avifEncoderData * data);
static avifEncoderData * avifEncoderDataCreate(void)
{ … }
static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize, uint32_t cellIndex)
{ … }
static avifEncoderItem * avifEncoderDataFindItemByID(avifEncoderData * data, uint16_t id)
{ … }
static void avifEncoderDataDestroy(avifEncoderData * data)
{ … }
static avifResult avifEncoderItemAddMdatFixup(avifEncoderItem * item, const avifRWStream * s)
{ … }
avifItemProperty;
AVIF_ARRAY_DECLARE(avifItemPropertyArray, avifItemProperty, … } ;
avifItemPropertyDedup;
static avifItemPropertyDedup * avifItemPropertyDedupCreate(void)
{ … }
static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup)
{ … }
static void avifItemPropertyDedupStart(avifItemPropertyDedup * dedup)
{ … }
static avifResult avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup, avifRWStream * outputStream, struct ipmaArray * ipma, avifBool essential)
{ … }
static const avifScalingMode noScaling = …;
avifEncoder * avifEncoderCreate(void)
{ … }
void avifEncoderDestroy(avifEncoder * encoder)
{ … }
avifResult avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
{ … }
static void avifEncoderBackupSettings(avifEncoder * encoder)
{ … }
static avifBool avifEncoderDetectChanges(const avifEncoder * encoder, avifEncoderChanges * encoderChanges)
{ … }
static avifResult avifEncoderWriteNclxProperty(avifRWStream * dedupStream,
avifRWStream * outputStream,
const avifImage * imageMetadata,
struct ipmaArray * ipma,
avifItemPropertyDedup * dedup)
{ … }
static avifResult avifEncoderWriteExtendedColorProperties(avifRWStream * dedupStream,
avifRWStream * outputStream,
const avifImage * imageMetadata,
struct ipmaArray * ipma,
avifItemPropertyDedup * dedup);
static avifResult avifEncoderWriteColorProperties(avifRWStream * outputStream,
const avifImage * imageMetadata,
struct ipmaArray * ipma,
avifItemPropertyDedup * dedup)
{ … }
static avifResult avifEncoderWriteHDRProperties(avifRWStream * dedupStream,
avifRWStream * outputStream,
const avifImage * imageMetadata,
struct ipmaArray * ipma,
avifItemPropertyDedup * dedup)
{ … }
static avifResult avifEncoderWriteExtendedColorProperties(avifRWStream * dedupStream,
avifRWStream * outputStream,
const avifImage * imageMetadata,
struct ipmaArray * ipma,
avifItemPropertyDedup * dedup)
{ … }
static avifResult avifRWStreamWriteHandlerBox(avifRWStream * s, const char handlerType[4])
{ … }
static avifResult avifEncoderWriteTrackMetaBox(avifEncoder * encoder, avifRWStream * s)
{ … }
static avifResult avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uint32_t gridRows, uint32_t gridWidth, uint32_t gridHeight)
{ … }
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
static avifBool avifWriteToneMappedImagePayload(avifRWData * data, const avifGainMapMetadata * metadata)
{ … }
size_t avifEncoderGetGainMapSizeBytes(avifEncoder * encoder)
{ … }
static avifResult avifImageCopyAltImageMetadata(avifImage * altImageMetadata, const avifImage * imageWithGainMap)
{ … }
#endif
#if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM)
static avifResult avifEncoderWriteSampleTransformTokens(avifRWStream * s, const avifSampleTransformExpression * expression)
{
AVIF_ASSERT_OR_RETURN(expression->count <= 256);
AVIF_CHECKRES(avifRWStreamWriteU8(s, (uint8_t)expression->count));
for (uint32_t t = 0; t < expression->count; ++t) {
const avifSampleTransformToken * token = &expression->tokens[t];
AVIF_CHECKRES(avifRWStreamWriteU8(s, token->type));
if (token->type == AVIF_SAMPLE_TRANSFORM_CONSTANT) {
const uint32_t constant = *(const uint32_t *)&token->constant;
AVIF_CHECKRES(avifRWStreamWriteU32(s, constant));
} else if (token->type == AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX) {
AVIF_CHECKRES(avifRWStreamWriteU8(s, token->inputImageItemIndex));
}
}
return AVIF_RESULT_OK;
}
static avifResult avifEncoderWriteSampleTransformPayload(avifEncoder * encoder, avifRWData * data)
{
avifRWStream s;
avifRWStreamStart(&s, data);
AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, 6));
AVIF_CHECKRES(avifRWStreamWriteBits(&s, AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, 2));
avifSampleTransformExpression expression = { 0 };
AVIF_CHECKRES(avifSampleTransformRecipeToExpression(encoder->sampleTransformRecipe, &expression));
const avifResult result = avifEncoderWriteSampleTransformTokens(&s, &expression);
avifArrayDestroy(&expression);
if (result != AVIF_RESULT_OK) {
avifDiagnosticsPrintf(&encoder->diag, "Failed to write sample transform metadata for recipe %d", (int)encoder->sampleTransformRecipe);
return result;
}
avifRWStreamFinishWrite(&s);
return AVIF_RESULT_OK;
}
#endif
static avifResult avifEncoderDataCreateExifItem(avifEncoderData * data, const avifRWData * exif)
{ … }
static avifResult avifEncoderDataCreateXMPItem(avifEncoderData * data, const avifRWData * xmp)
{ … }
static avifResult avifImageCopyAndPad(avifImage * const dstImage, const avifImage * srcImage, uint32_t dstWidth, uint32_t dstHeight)
{ … }
static int avifQualityToQuantizer(int quality, int minQuantizer, int maxQuantizer)
{ … }
static const char infeNameColor[] = …;
static const char infeNameAlpha[] = …;
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
static const char infeNameGainMap[] = …;
#endif
#if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM)
static const char infeNameSampleTransform[] = "SampleTransform";
#endif
static const char * getInfeName(avifItemCategory itemCategory)
{ … }
static avifResult avifEncoderAddImageItems(avifEncoder * encoder,
uint32_t gridCols,
uint32_t gridRows,
uint32_t gridWidth,
uint32_t gridHeight,
avifItemCategory itemCategory,
uint16_t * topLevelItemID)
{ … }
#if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM)
static avifResult avifEncoderCreateBitDepthExtensionItems(avifEncoder * encoder,
uint32_t gridCols,
uint32_t gridRows,
uint32_t gridWidth,
uint32_t gridHeight,
uint16_t colorItemID)
{
AVIF_ASSERT_OR_RETURN(encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B ||
encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B ||
encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_8B_OVERLAP_4B);
avifEncoderItem * sampleTransformItem = avifEncoderDataCreateItem(encoder->data,
"sato",
infeNameSampleTransform,
strlen(infeNameSampleTransform) + 1,
0);
AVIF_CHECKRES(avifEncoderWriteSampleTransformPayload(encoder, &sampleTransformItem->metadataPayload));
sampleTransformItem->itemCategory = AVIF_ITEM_SAMPLE_TRANSFORM;
uint16_t sampleTransformItemID = sampleTransformItem->id;
uint16_t * alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
*alternativeItemID = sampleTransformItem->id;
alternativeItemID = (uint16_t *)avifArrayPush(&encoder->data->alternativeItemIDs);
AVIF_CHECKERR(alternativeItemID != NULL, AVIF_RESULT_OUT_OF_MEMORY);
*alternativeItemID = colorItemID;
uint16_t bitDepthExtensionColorItemId;
AVIF_CHECKRES(
avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_COLOR, &bitDepthExtensionColorItemId));
avifEncoderItem * bitDepthExtensionColorItem = avifEncoderDataFindItemByID(encoder->data, bitDepthExtensionColorItemId);
assert(bitDepthExtensionColorItem);
bitDepthExtensionColorItem->hiddenImage = AVIF_TRUE;
AVIF_ASSERT_OR_RETURN(colorItemID < bitDepthExtensionColorItemId);
avifEncoderItem * colorItem = avifEncoderDataFindItemByID(encoder->data, colorItemID);
AVIF_ASSERT_OR_RETURN(colorItem != NULL);
AVIF_ASSERT_OR_RETURN(colorItem->dimgFromID == 0);
colorItem->dimgFromID = sampleTransformItemID;
bitDepthExtensionColorItem->dimgFromID = sampleTransformItemID;
if (encoder->data->alphaPresent) {
uint16_t bitDepthExtensionAlphaItemId;
AVIF_CHECKRES(
avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_SAMPLE_TRANSFORM_INPUT_0_ALPHA, &bitDepthExtensionAlphaItemId));
avifEncoderItem * bitDepthExtensionAlphaItem = avifEncoderDataFindItemByID(encoder->data, bitDepthExtensionAlphaItemId);
assert(bitDepthExtensionAlphaItem);
bitDepthExtensionAlphaItem->irefType = "auxl";
bitDepthExtensionAlphaItem->irefToID = bitDepthExtensionColorItemId;
if (encoder->data->imageMetadata->alphaPremultiplied) {
bitDepthExtensionColorItem = avifEncoderDataFindItemByID(encoder->data, bitDepthExtensionColorItemId);
assert(bitDepthExtensionColorItem);
bitDepthExtensionColorItem->irefType = "prem";
bitDepthExtensionColorItem->irefToID = bitDepthExtensionAlphaItemId;
}
}
return AVIF_RESULT_OK;
}
static avifResult avifImageApplyImgOpConst(avifImage * result,
const avifImage * inputImageItem,
avifSampleTransformTokenType op,
int32_t constant,
avifPlanesFlags planes)
{
const avifSampleTransformToken tokens[] = { { AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX, 0, 1 },
{ AVIF_SAMPLE_TRANSFORM_CONSTANT, constant, 0 },
{ (uint8_t)op, 0, 0 } };
return avifImageApplyOperations(result, AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, 3, tokens, 1, &inputImageItem, planes);
}
static avifResult avifImageCreateAllocate(avifImage ** sampleTransformedImage, const avifImage * reference, uint32_t numBits, avifPlanesFlag planes)
{
*sampleTransformedImage = avifImageCreate(reference->width, reference->height, numBits, reference->yuvFormat);
AVIF_CHECKERR(*sampleTransformedImage != NULL, AVIF_RESULT_OUT_OF_MEMORY);
return avifImageAllocatePlanes(*sampleTransformedImage, planes);
}
static avifResult avifEncoderDecodeSatoBaseImage(avifEncoder * encoder,
const avifImage * original,
uint32_t numBits,
avifPlanesFlag planes,
avifCodec ** codec,
avifImage ** decodedBaseImage)
{
avifDecodeSample sample;
memset(&sample, 0, sizeof(sample));
sample.spatialID = AVIF_SPATIAL_ID_UNSET;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if ((item->itemCategory != AVIF_ITEM_COLOR || planes != AVIF_PLANES_YUV) &&
(item->itemCategory != AVIF_ITEM_ALPHA || planes != AVIF_PLANES_A)) {
continue;
}
AVIF_ASSERT_OR_RETURN(item->encodeOutput != NULL);
AVIF_ASSERT_OR_RETURN(item->encodeOutput->samples.count == 1);
AVIF_ASSERT_OR_RETURN(item->encodeOutput->samples.sample[0].data.size != 0);
AVIF_ASSERT_OR_RETURN(sample.data.size == 0);
sample.data.data = item->encodeOutput->samples.sample[0].data.data;
sample.data.size = item->encodeOutput->samples.sample[0].data.size;
}
AVIF_ASSERT_OR_RETURN(sample.data.size != 0);
AVIF_CHECKRES(avifCodecCreate(AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_FLAG_CAN_DECODE, codec));
(*codec)->diag = &encoder->diag;
(*codec)->maxThreads = encoder->maxThreads;
(*codec)->imageSizeLimit = AVIF_DEFAULT_IMAGE_SIZE_LIMIT;
AVIF_CHECKRES(avifImageCreateAllocate(decodedBaseImage, original, numBits, planes));
avifBool isLimitedRangeAlpha = AVIF_FALSE;
AVIF_CHECKERR((*codec)->getNextImage(*codec, &sample, planes == AVIF_PLANES_A, &isLimitedRangeAlpha, *decodedBaseImage),
AVIF_RESULT_ENCODE_SAMPLE_TRANSFORM_FAILED);
return AVIF_RESULT_OK;
}
static avifResult avifEncoderCreateSatoImage(avifEncoder * encoder,
const avifEncoderItem * item,
avifBool itemWillBeEncodedLosslessly,
const avifImage * image,
avifImage ** sampleTransformedImage)
{
const avifPlanesFlag planes = avifIsAlpha(item->itemCategory) ? AVIF_PLANES_A : AVIF_PLANES_YUV;
avifBool isBase = item->itemCategory == AVIF_ITEM_COLOR || item->itemCategory == AVIF_ITEM_ALPHA;
if (!isBase) {
AVIF_ASSERT_OR_RETURN(item->itemCategory >= AVIF_SAMPLE_TRANSFORM_MIN_CATEGORY &&
item->itemCategory <= AVIF_SAMPLE_TRANSFORM_MAX_CATEGORY);
}
if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B) {
if (isBase) {
AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes));
AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_DIVIDE, 256, planes));
} else {
AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes));
AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_AND, 255, planes));
}
} else if (encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B) {
if (isBase) {
AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 12, planes));
AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_DIVIDE, 16, planes));
} else {
AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes));
AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_AND, 15, planes));
AVIF_CHECKRES(
avifImageApplyImgOpConst(*sampleTransformedImage, *sampleTransformedImage, AVIF_SAMPLE_TRANSFORM_PRODUCT, 16, planes));
if (!itemWillBeEncodedLosslessly) {
AVIF_CHECKRES(
avifImageApplyImgOpConst(*sampleTransformedImage, *sampleTransformedImage, AVIF_SAMPLE_TRANSFORM_SUM, 7, planes));
}
}
} else {
AVIF_CHECKERR(encoder->sampleTransformRecipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_8B_OVERLAP_4B,
AVIF_RESULT_NOT_IMPLEMENTED);
if (isBase) {
AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 12, planes));
AVIF_CHECKRES(avifImageApplyImgOpConst(*sampleTransformedImage, image, AVIF_SAMPLE_TRANSFORM_DIVIDE, 16, planes));
} else {
AVIF_CHECKRES(avifImageCreateAllocate(sampleTransformedImage, image, 8, planes));
avifCodec * codec = NULL;
avifImage * decodedBaseImage = NULL;
avifResult result = avifEncoderDecodeSatoBaseImage(encoder, image, 12, planes, &codec, &decodedBaseImage);
if (result == AVIF_RESULT_OK) {
const avifSampleTransformToken tokens[] = { { AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX, 0, 1 },
{ AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX, 0, 2 },
{ AVIF_SAMPLE_TRANSFORM_CONSTANT, 16, 0 },
{ AVIF_SAMPLE_TRANSFORM_PRODUCT, 0, 0 },
{ AVIF_SAMPLE_TRANSFORM_DIFFERENCE, 0, 0 },
{ AVIF_SAMPLE_TRANSFORM_CONSTANT, 128, 0 },
{ AVIF_SAMPLE_TRANSFORM_SUM, 0, 0 } };
const avifImage * inputImageItems[] = { image, decodedBaseImage };
result = avifImageApplyOperations(*sampleTransformedImage,
AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32,
7,
tokens,
2,
inputImageItems,
planes);
}
if (decodedBaseImage) {
avifImageDestroy(decodedBaseImage);
}
if (codec) {
avifCodecDestroy(codec);
}
AVIF_CHECKRES(result);
}
}
return AVIF_RESULT_OK;
}
static avifResult avifEncoderCreateBitDepthExtensionImage(avifEncoder * encoder,
const avifEncoderItem * item,
avifBool itemWillBeEncodedLosslessly,
const avifImage * image,
avifImage ** sampleTransformedImage)
{
AVIF_ASSERT_OR_RETURN(image->depth == 16);
*sampleTransformedImage = NULL;
const avifResult result = avifEncoderCreateSatoImage(encoder, item, itemWillBeEncodedLosslessly, image, sampleTransformedImage);
if (result != AVIF_RESULT_OK && *sampleTransformedImage != NULL) {
avifImageDestroy(*sampleTransformedImage);
}
return result;
}
#endif
static avifCodecType avifEncoderGetCodecType(const avifEncoder * encoder)
{ … }
static avifBool avifEncoderDataShouldForceKeyframeForAlpha(const avifEncoderData * data,
const avifEncoderItem * colorItem,
avifAddImageFlags addImageFlags)
{ … }
static avifResult avifGetErrorForItemCategory(avifItemCategory itemCategory)
{ … }
static uint32_t avifGridWidth(uint32_t gridCols, const avifImage * firstCell, const avifImage * bottomRightCell)
{ … }
static uint32_t avifGridHeight(uint32_t gridRows, const avifImage * firstCell, const avifImage * bottomRightCell)
{ … }
static avifResult avifValidateGrid(uint32_t gridCols,
uint32_t gridRows,
const avifImage * const * cellImages,
avifBool validateGainMap,
avifDiagnostics * diag)
{ … }
static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
uint32_t gridCols,
uint32_t gridRows,
const avifImage * const * cellImages,
uint64_t durationInTimescales,
avifAddImageFlags addImageFlags)
{ … }
avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags)
{ … }
avifResult avifEncoderAddImageGrid(avifEncoder * encoder,
uint32_t gridCols,
uint32_t gridRows,
const avifImage * const * cellImages,
avifAddImageFlags addImageFlags)
{ … }
static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size)
{ … }
static avifResult avifEncoderWriteMediaDataBox(avifEncoder * encoder,
avifRWStream * s,
avifEncoderItemReferenceArray * layeredColorItems,
avifEncoderItemReferenceArray * layeredAlphaItems)
{ … }
static avifResult avifWriteAltrGroup(avifRWStream * s, uint32_t groupID, const avifEncoderItemIdArray * itemIDs)
{ … }
#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI)
static avifBool avifEncoderIsMiniCompatible(const avifEncoder * encoder)
{
if (encoder->extraLayerCount || (encoder->data->frames.count != 1)) {
return AVIF_FALSE;
}
#if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM)
if (encoder->sampleTransformRecipe != AVIF_SAMPLE_TRANSFORM_NONE) {
return AVIF_FALSE;
}
#endif
if (encoder->data->imageMetadata->width > (1 << 15) || encoder->data->imageMetadata->height > (1 << 15)) {
return AVIF_FALSE;
}
if (encoder->data->imageMetadata->icc.size > (1 << 20) || encoder->data->imageMetadata->exif.size > (1 << 20) ||
encoder->data->imageMetadata->xmp.size > (1 << 20)) {
return AVIF_FALSE;
}
if (encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV444 &&
encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV422 &&
encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV420 &&
encoder->data->imageMetadata->yuvFormat != AVIF_PIXEL_FORMAT_YUV400) {
return AVIF_FALSE;
}
if (encoder->data->imageMetadata->colorPrimaries > 255 || encoder->data->imageMetadata->transferCharacteristics > 255 ||
encoder->data->imageMetadata->matrixCoefficients > 255) {
return AVIF_FALSE;
}
const avifEncoderItem * colorItem = NULL;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->gridCols || item->gridRows) {
return AVIF_FALSE;
}
if (item->id == encoder->data->primaryItemID) {
assert(!colorItem);
colorItem = item;
if (item->encodeOutput->samples.count != 1 || item->encodeOutput->samples.sample[0].data.size > (1 << 28)) {
return AVIF_FALSE;
}
continue;
}
if (item->itemCategory == AVIF_ITEM_ALPHA && item->irefToID == encoder->data->primaryItemID) {
if (item->encodeOutput->samples.count != 1 || item->encodeOutput->samples.sample[0].data.size >= (1 << 28)) {
return AVIF_FALSE;
}
continue;
}
if (!memcmp(item->type, "mime", 4) && !memcmp(item->infeName, "XMP", item->infeNameSize)) {
assert(item->metadataPayload.size == encoder->data->imageMetadata->xmp.size);
continue;
}
if (!memcmp(item->type, "Exif", 4) && !memcmp(item->infeName, "Exif", item->infeNameSize)) {
assert(item->metadataPayload.size == encoder->data->imageMetadata->exif.size + 4);
const uint32_t exif_tiff_header_offset = *(uint32_t *)item->metadataPayload.data;
if (exif_tiff_header_offset != 0) {
return AVIF_FALSE;
}
continue;
}
return AVIF_FALSE;
}
if (!colorItem) {
return AVIF_FALSE;
}
return AVIF_TRUE;
}
static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * s);
static avifResult avifEncoderWriteFileTypeBoxAndMetaBoxV1(avifEncoder * encoder, avifRWData * output)
{
avifRWStream s;
avifRWStreamStart(&s, output);
avifBoxMarker ftyp;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "ftyp", AVIF_BOX_SIZE_TBD, &ftyp));
AVIF_CHECKRES(avifRWStreamWriteChars(&s, "mif3", 4));
AVIF_CHECKRES(avifRWStreamWriteChars(&s, "avif", 4));
avifRWStreamFinishBox(&s, ftyp);
AVIF_CHECKRES(avifEncoderWriteMiniBox(encoder, &s));
avifRWStreamFinishWrite(&s);
return AVIF_RESULT_OK;
}
static avifResult avifEncoderWriteMiniBox(avifEncoder * encoder, avifRWStream * s)
{
const avifEncoderItem * colorItem = NULL;
const avifEncoderItem * alphaItem = NULL;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->id == encoder->data->primaryItemID) {
AVIF_ASSERT_OR_RETURN(!colorItem);
colorItem = item;
} else if (item->itemCategory == AVIF_ITEM_ALPHA && item->irefToID == encoder->data->primaryItemID) {
AVIF_ASSERT_OR_RETURN(!alphaItem);
alphaItem = item;
}
}
AVIF_ASSERT_OR_RETURN(colorItem);
const avifRWData * colorData = &colorItem->encodeOutput->samples.sample[0].data;
const avifRWData * alphaData = alphaItem ? &alphaItem->encodeOutput->samples.sample[0].data : NULL;
const avifImage * const image = encoder->data->imageMetadata;
const avifBool hasAlpha = alphaItem != NULL;
const avifBool alphaIsPremultiplied = encoder->data->imageMetadata->alphaPremultiplied;
const avifBool hasHdr = AVIF_FALSE;
const avifBool hasGainmap = AVIF_FALSE;
const avifBool hasIcc = image->icc.size != 0;
const uint32_t chromaSubsampling = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400 ? 0
: image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 ? 1
: image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422 ? 2
: 3;
const avifColorPrimaries defaultColorPrimaries = hasIcc ? AVIF_COLOR_PRIMARIES_UNSPECIFIED : AVIF_COLOR_PRIMARIES_BT709;
const avifTransferCharacteristics defaultTransferCharacteristics = hasIcc ? AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED
: AVIF_TRANSFER_CHARACTERISTICS_SRGB;
const avifMatrixCoefficients defaultMatrixCoefficients = chromaSubsampling == 0 ? AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED
: AVIF_MATRIX_COEFFICIENTS_BT601;
const avifBool hasExplicitCicp = image->colorPrimaries != defaultColorPrimaries ||
image->transferCharacteristics != defaultTransferCharacteristics ||
image->matrixCoefficients != defaultMatrixCoefficients;
const avifBool floatFlag = AVIF_FALSE;
const avifBool fullRange = image->yuvRange == AVIF_RANGE_FULL;
if (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV420 && image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN) {
avifDiagnosticsPrintf(&encoder->diag,
"YUV chroma sample position %d is only supported with 4:2:0 YUV format in AV1",
image->yuvChromaSamplePosition);
return AVIF_RESULT_INVALID_ARGUMENT;
}
const avifBool chromaIsHorizontallyCentered = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_VERTICAL &&
image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_COLOCATED;
const avifBool chromaIsVerticallyCentered = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_COLOCATED;
const uint32_t orientationMinus1 = avifImageIrotImirToExifOrientation(image) - 1;
const avifBool hasExplicitCodecTypes = AVIF_FALSE;
const uint32_t smallDimensionsFlag = image->width <= (1 << 7) && image->height <= (1 << 7);
const uint32_t codecConfigSize = 4;
const uint32_t fewCodecConfigBytesFlag = codecConfigSize < (1 << 3);
const uint32_t fewItemDataBytesFlag = colorData->size <= (1 << 15) && (!alphaData || alphaData->size < (1 << 15));
const uint32_t fewMetadataBytesFlag = image->icc.size <= (1 << 10) && image->exif.size <= (1 << 10) && image->xmp.size <= (1 << 10);
avifBoxMarker mini;
AVIF_CHECKRES(avifRWStreamWriteBox(s, "mini", AVIF_BOX_SIZE_TBD, &mini));
AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 2));
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasExplicitCodecTypes, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, floatFlag, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, fullRange, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, alphaItem != 0, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasExplicitCicp, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasHdr, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, hasIcc, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->exif.size != 0, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->xmp.size != 0, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, chromaSubsampling, 2));
AVIF_CHECKRES(avifRWStreamWriteBits(s, orientationMinus1, 3));
AVIF_CHECKRES(avifRWStreamWriteBits(s, smallDimensionsFlag, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->width - 1, smallDimensionsFlag ? 7 : 15));
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->height - 1, smallDimensionsFlag ? 7 : 15));
if (chromaSubsampling == 1 || chromaSubsampling == 2) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, chromaIsHorizontallyCentered, 1));
}
if (chromaSubsampling == 1) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, chromaIsVerticallyCentered, 1));
}
if (floatFlag) {
AVIF_ASSERT_OR_RETURN(AVIF_FALSE);
} else {
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->depth > 8, 1));
if (image->depth > 8) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->depth - 9, 3));
}
}
if (alphaItem) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, alphaIsPremultiplied, 1));
}
if (hasExplicitCicp) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->colorPrimaries, 8));
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->transferCharacteristics, 8));
if (chromaSubsampling != 0) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, image->matrixCoefficients, 8));
} else {
AVIF_CHECKERR(image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED, AVIF_RESULT_ENCODE_COLOR_FAILED);
}
}
if (hasExplicitCodecTypes) {
AVIF_ASSERT_OR_RETURN(AVIF_FALSE);
}
if (hasHdr) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}
if (hasIcc || image->exif.size || image->xmp.size || (hasHdr && hasGainmap)) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, fewMetadataBytesFlag, 1));
}
AVIF_CHECKRES(avifRWStreamWriteBits(s, fewCodecConfigBytesFlag, 1));
AVIF_CHECKRES(avifRWStreamWriteBits(s, fewItemDataBytesFlag, 1));
if (hasIcc) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)image->icc.size - 1, fewMetadataBytesFlag ? 10 : 20));
}
AVIF_CHECKRES(avifRWStreamWriteBits(s, codecConfigSize, fewCodecConfigBytesFlag ? 3 : 12));
AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)colorData->size - 1, fewItemDataBytesFlag ? 15 : 28));
if (hasAlpha) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)alphaData->size, fewItemDataBytesFlag ? 15 : 28));
}
if (hasAlpha && alphaData->size != 0) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, codecConfigSize, fewCodecConfigBytesFlag ? 3 : 12));
}
if (image->exif.size) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)image->exif.size - 1, fewMetadataBytesFlag ? 10 : 20));
}
if (image->xmp.size) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, (uint32_t)image->xmp.size - 1, fewMetadataBytesFlag ? 10 : 20));
}
if (s->numUsedBitsInPartialByte != 0) {
AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 8 - s->numUsedBitsInPartialByte));
}
const size_t headerSize = avifRWStreamOffset(s);
if (hasAlpha && alphaData->size != 0 && codecConfigSize != 0) {
AVIF_CHECKRES(writeCodecConfig(s, &alphaItem->av1C));
}
if (codecConfigSize > 0) {
AVIF_CHECKRES(writeCodecConfig(s, &colorItem->av1C));
}
if (hasIcc) {
AVIF_CHECKRES(avifRWStreamWrite(s, image->icc.data, image->icc.size));
}
if (hasAlpha && alphaData->size != 0) {
AVIF_CHECKRES(avifRWStreamWrite(s, alphaData->data, alphaData->size));
}
AVIF_CHECKRES(avifRWStreamWrite(s, colorData->data, colorData->size));
if (image->exif.size) {
AVIF_CHECKRES(avifRWStreamWrite(s, image->exif.data, image->exif.size));
}
if (image->xmp.size) {
AVIF_CHECKRES(avifRWStreamWrite(s, image->xmp.data, image->xmp.size));
}
AVIF_ASSERT_OR_RETURN(avifRWStreamOffset(s) - headerSize == (hasAlpha ? codecConfigSize : 0) + codecConfigSize +
image->icc.size + (hasAlpha ? alphaData->size : 0) +
colorData->size + image->exif.size + image->xmp.size);
avifRWStreamFinishBox(s, mini);
return AVIF_RESULT_OK;
}
#endif
static avifResult avifRWStreamWriteProperties(avifItemPropertyDedup * const dedup,
avifRWStream * const s,
const avifEncoder * const encoder,
const avifImage * const imageMetadata,
const avifImage * const altImageMetadata)
{ … }
avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
{ … }
avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output)
{ … }
static avifResult writeCodecConfig(avifRWStream * s, const avifCodecConfigurationBox * cfg)
{ … }
static avifResult writeConfigBox(avifRWStream * s, const avifCodecConfigurationBox * cfg, const char * configPropName)
{ … }