#include "avif/internal.h"
#include <assert.h>
#include <string.h>
#include <time.h>
#define MAX_ASSOCIATIONS 16
struct ipmaArray
{
uint8_t associations[MAX_ASSOCIATIONS];
avifBool essential[MAX_ASSOCIATIONS];
uint8_t count;
};
typedef struct avifOffsetFixup
{
size_t offset;
} avifOffsetFixup;
AVIF_ARRAY_DECLARE(avifOffsetFixupArray, avifOffsetFixup, fixup);
static const char alphaURN[] = AVIF_URN_ALPHA0;
static const size_t alphaURNSize = sizeof(alphaURN);
static const char xmpContentType[] = AVIF_CONTENT_TYPE_XMP;
static const size_t xmpContentTypeSize = sizeof(xmpContentType);
static avifResult writeConfigBox(avifRWStream * s, const avifCodecConfigurationBox * cfg, const char * configPropName);
static int floorLog2(uint32_t n)
{
assert(n > 0);
int count = 0;
while (n != 0) {
++count;
n >>= 1;
}
return count - 1;
}
static void splitTilesLog2(uint32_t dim1, uint32_t dim2, int tilesLog2, int * tileDim1Log2, int * tileDim2Log2)
{
assert(dim1 >= dim2);
uint32_t ratio = dim1 / dim2;
int diffLog2 = floorLog2(ratio);
int subtract = tilesLog2 - diffLog2;
if (subtract < 0) {
subtract = 0;
}
*tileDim2Log2 = subtract / 2;
*tileDim1Log2 = tilesLog2 - *tileDim2Log2;
assert(*tileDim1Log2 >= *tileDim2Log2);
}
void avifSetTileConfiguration(int threads, uint32_t width, uint32_t height, int * tileRowsLog2, int * tileColsLog2)
{
*tileRowsLog2 = 0;
*tileColsLog2 = 0;
if (threads > 1) {
const uint32_t kMinTileArea = 512 * 512;
const uint32_t kMaxTiles = 32;
uint32_t imageArea = width * height;
uint32_t tiles = (imageArea + kMinTileArea - 1) / kMinTileArea;
if (tiles > kMaxTiles) {
tiles = kMaxTiles;
}
if (tiles > (uint32_t)threads) {
tiles = threads;
}
int tilesLog2 = floorLog2(tiles);
if (width >= height) {
splitTilesLog2(width, height, tilesLog2, tileColsLog2, tileRowsLog2);
} else {
splitTilesLog2(height, width, tilesLog2, tileRowsLog2, tileColsLog2);
}
}
}
avifCodecEncodeOutput * avifCodecEncodeOutputCreate(void)
{
avifCodecEncodeOutput * encodeOutput = (avifCodecEncodeOutput *)avifAlloc(sizeof(avifCodecEncodeOutput));
memset(encodeOutput, 0, sizeof(avifCodecEncodeOutput));
if (!avifArrayCreate(&encodeOutput->samples, sizeof(avifEncodeSample), 1)) {
goto error;
}
return encodeOutput;
error:
avifCodecEncodeOutputDestroy(encodeOutput);
return NULL;
}
avifResult avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync)
{
avifEncodeSample * sample = (avifEncodeSample *)avifArrayPushPtr(&encodeOutput->samples);
AVIF_CHECKERR(sample, AVIF_RESULT_OUT_OF_MEMORY);
const avifResult result = avifRWDataSet(&sample->data, data, len);
if (result != AVIF_RESULT_OK) {
avifArrayPop(&encodeOutput->samples);
return result;
}
sample->sync = sync;
return AVIF_RESULT_OK;
}
void avifCodecEncodeOutputDestroy(avifCodecEncodeOutput * encodeOutput)
{
for (uint32_t sampleIndex = 0; sampleIndex < encodeOutput->samples.count; ++sampleIndex) {
avifRWDataFree(&encodeOutput->samples.sample[sampleIndex].data);
}
avifArrayDestroy(&encodeOutput->samples);
avifFree(encodeOutput);
}
typedef struct avifEncoderItem
{
uint16_t id;
uint8_t type[4]; avifCodec * codec; avifCodecEncodeOutput * encodeOutput; avifRWData metadataPayload; avifCodecConfigurationBox av1C; uint32_t cellIndex; avifItemCategory itemCategory; avifBool hiddenImage;
const char * infeName;
size_t infeNameSize;
const char * infeContentType;
size_t infeContentTypeSize;
avifOffsetFixupArray mdatFixups;
uint16_t irefToID; const char * irefType;
uint32_t gridCols; uint32_t gridRows;
uint32_t gridWidth;
uint32_t gridHeight;
uint32_t extraLayerCount;
uint16_t dimgFromID;
struct ipmaArray ipma;
} avifEncoderItem;
AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item);
typedef avifEncoderItem * avifEncoderItemReference;
AVIF_ARRAY_DECLARE(avifEncoderItemReferenceArray, avifEncoderItemReference, ref);
typedef struct avifEncoderFrame
{
uint64_t durationInTimescales;
} avifEncoderFrame;
AVIF_ARRAY_DECLARE(avifEncoderFrameArray, avifEncoderFrame, frame);
typedef struct avifEncoderData
{
avifEncoderItemArray items;
avifEncoderFrameArray frames;
int quantizer;
int quantizerAlpha;
int tileRowsLog2;
int tileColsLog2;
avifEncoder lastEncoder;
int lastQuantizer;
int lastQuantizerAlpha;
int lastTileRowsLog2;
int lastTileColsLog2;
avifImage * imageMetadata;
uint16_t lastItemID;
uint16_t primaryItemID;
avifBool singleImage; avifBool alphaPresent;
const char * imageItemType; const char * configPropName; } avifEncoderData;
static void avifEncoderDataDestroy(avifEncoderData * data);
static avifEncoderData * avifEncoderDataCreate()
{
avifEncoderData * data = (avifEncoderData *)avifAlloc(sizeof(avifEncoderData));
if (!data) {
return NULL;
}
memset(data, 0, sizeof(avifEncoderData));
data->imageMetadata = avifImageCreateEmpty();
if (!data->imageMetadata) {
goto error;
}
if (!avifArrayCreate(&data->items, sizeof(avifEncoderItem), 8)) {
goto error;
}
if (!avifArrayCreate(&data->frames, sizeof(avifEncoderFrame), 1)) {
goto error;
}
return data;
error:
avifEncoderDataDestroy(data);
return NULL;
}
static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize, uint32_t cellIndex)
{
avifEncoderItem * item = (avifEncoderItem *)avifArrayPushPtr(&data->items);
++data->lastItemID;
item->id = data->lastItemID;
memcpy(item->type, type, sizeof(item->type));
item->infeName = infeName;
item->infeNameSize = infeNameSize;
item->encodeOutput = avifCodecEncodeOutputCreate();
item->cellIndex = cellIndex;
if (!avifArrayCreate(&item->mdatFixups, sizeof(avifOffsetFixup), 4)) {
goto error;
}
return item;
error:
avifCodecEncodeOutputDestroy(item->encodeOutput);
--data->lastItemID;
avifArrayPop(&data->items);
return NULL;
}
static avifEncoderItem * avifEncoderDataFindItemByID(avifEncoderData * data, uint16_t id)
{
for (uint32_t itemIndex = 0; itemIndex < data->items.count; ++itemIndex) {
avifEncoderItem * item = &data->items.item[itemIndex];
if (item->id == id) {
return item;
}
}
return NULL;
}
static void avifEncoderDataDestroy(avifEncoderData * data)
{
for (uint32_t i = 0; i < data->items.count; ++i) {
avifEncoderItem * item = &data->items.item[i];
if (item->codec) {
avifCodecDestroy(item->codec);
}
avifCodecEncodeOutputDestroy(item->encodeOutput);
avifRWDataFree(&item->metadataPayload);
avifArrayDestroy(&item->mdatFixups);
}
if (data->imageMetadata) {
avifImageDestroy(data->imageMetadata);
}
avifArrayDestroy(&data->items);
avifArrayDestroy(&data->frames);
avifFree(data);
}
static void avifEncoderItemAddMdatFixup(avifEncoderItem * item, const avifRWStream * s)
{
avifOffsetFixup * fixup = (avifOffsetFixup *)avifArrayPushPtr(&item->mdatFixups);
fixup->offset = avifRWStreamOffset(s);
}
typedef struct avifItemProperty
{
uint8_t index;
size_t offset;
size_t size;
} avifItemProperty;
AVIF_ARRAY_DECLARE(avifItemPropertyArray, avifItemProperty, property);
typedef struct avifItemPropertyDedup
{
avifItemPropertyArray properties;
avifRWStream s; avifRWData buffer; uint8_t nextIndex; } avifItemPropertyDedup;
static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup);
static avifItemPropertyDedup * avifItemPropertyDedupCreate(void)
{
avifItemPropertyDedup * dedup = (avifItemPropertyDedup *)avifAlloc(sizeof(avifItemPropertyDedup));
memset(dedup, 0, sizeof(avifItemPropertyDedup));
if (!avifArrayCreate(&dedup->properties, sizeof(avifItemProperty), 8)) {
goto error;
}
if (avifRWDataRealloc(&dedup->buffer, 2048) != AVIF_RESULT_OK) {
goto error;
}
return dedup;
error:
avifItemPropertyDedupDestroy(dedup);
return NULL;
}
static void avifItemPropertyDedupDestroy(avifItemPropertyDedup * dedup)
{
avifArrayDestroy(&dedup->properties);
avifRWDataFree(&dedup->buffer);
avifFree(dedup);
}
static void avifItemPropertyDedupStart(avifItemPropertyDedup * dedup)
{
avifRWStreamStart(&dedup->s, &dedup->buffer);
}
static avifResult avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup, avifRWStream * outputStream, struct ipmaArray * ipma, avifBool essential)
{
uint8_t propertyIndex = 0;
const size_t newPropertySize = avifRWStreamOffset(&dedup->s);
for (size_t i = 0; i < dedup->properties.count; ++i) {
avifItemProperty * property = &dedup->properties.property[i];
if ((property->size == newPropertySize) &&
!memcmp(&outputStream->raw->data[property->offset], dedup->buffer.data, newPropertySize)) {
propertyIndex = property->index;
assert(propertyIndex != 0);
break;
}
}
if (propertyIndex == 0) {
avifItemProperty * property = (avifItemProperty *)avifArrayPushPtr(&dedup->properties);
property->index = ++dedup->nextIndex; property->size = newPropertySize;
property->offset = avifRWStreamOffset(outputStream);
AVIF_CHECKRES(avifRWStreamWrite(outputStream, dedup->buffer.data, newPropertySize));
propertyIndex = property->index;
}
AVIF_CHECKERR(ipma->count < MAX_ASSOCIATIONS, AVIF_RESULT_UNKNOWN_ERROR);
ipma->associations[ipma->count] = propertyIndex;
ipma->essential[ipma->count] = essential;
++ipma->count;
return AVIF_RESULT_OK;
}
static const avifScalingMode noScaling = { { 1, 1 }, { 1, 1 } };
avifEncoder * avifEncoderCreate(void)
{
avifEncoder * encoder = (avifEncoder *)avifAlloc(sizeof(avifEncoder));
if (!encoder) {
return NULL;
}
memset(encoder, 0, sizeof(avifEncoder));
encoder->codecChoice = AVIF_CODEC_CHOICE_AUTO;
encoder->maxThreads = 1;
encoder->speed = AVIF_SPEED_DEFAULT;
encoder->keyframeInterval = 0;
encoder->timescale = 1;
encoder->repetitionCount = AVIF_REPETITION_COUNT_INFINITE;
encoder->quality = AVIF_QUALITY_DEFAULT;
encoder->qualityAlpha = AVIF_QUALITY_DEFAULT;
encoder->minQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
encoder->maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY;
encoder->minQuantizerAlpha = AVIF_QUANTIZER_BEST_QUALITY;
encoder->maxQuantizerAlpha = AVIF_QUANTIZER_WORST_QUALITY;
encoder->tileRowsLog2 = 0;
encoder->tileColsLog2 = 0;
encoder->autoTiling = AVIF_FALSE;
encoder->scalingMode = noScaling;
encoder->data = avifEncoderDataCreate();
encoder->csOptions = avifCodecSpecificOptionsCreate();
if (!encoder->data || !encoder->csOptions) {
avifEncoderDestroy(encoder);
return NULL;
}
return encoder;
}
void avifEncoderDestroy(avifEncoder * encoder)
{
if (encoder->csOptions) {
avifCodecSpecificOptionsDestroy(encoder->csOptions);
}
if (encoder->data) {
avifEncoderDataDestroy(encoder->data);
}
avifFree(encoder);
}
avifResult avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
{
return avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
}
static void avifEncoderBackupSettings(avifEncoder * encoder)
{
avifEncoder * lastEncoder = &encoder->data->lastEncoder;
lastEncoder->data = encoder->data;
lastEncoder->codecChoice = encoder->codecChoice;
lastEncoder->maxThreads = encoder->maxThreads;
lastEncoder->speed = encoder->speed;
lastEncoder->keyframeInterval = encoder->keyframeInterval;
lastEncoder->timescale = encoder->timescale;
lastEncoder->repetitionCount = encoder->repetitionCount;
lastEncoder->extraLayerCount = encoder->extraLayerCount;
lastEncoder->minQuantizer = encoder->minQuantizer;
lastEncoder->maxQuantizer = encoder->maxQuantizer;
lastEncoder->minQuantizerAlpha = encoder->minQuantizerAlpha;
lastEncoder->maxQuantizerAlpha = encoder->maxQuantizerAlpha;
encoder->data->lastQuantizer = encoder->data->quantizer;
encoder->data->lastQuantizerAlpha = encoder->data->quantizerAlpha;
encoder->data->lastTileRowsLog2 = encoder->data->tileRowsLog2;
encoder->data->lastTileColsLog2 = encoder->data->tileColsLog2;
lastEncoder->scalingMode = encoder->scalingMode;
}
static avifBool avifEncoderDetectChanges(const avifEncoder * encoder, avifEncoderChanges * encoderChanges)
{
const avifEncoder * lastEncoder = &encoder->data->lastEncoder;
*encoderChanges = 0;
if (!lastEncoder->data) {
return AVIF_TRUE;
}
if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->maxThreads != encoder->maxThreads) ||
(lastEncoder->speed != encoder->speed) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) ||
(lastEncoder->timescale != encoder->timescale) || (lastEncoder->repetitionCount != encoder->repetitionCount) ||
(lastEncoder->extraLayerCount != encoder->extraLayerCount)) {
return AVIF_FALSE;
}
if (encoder->data->lastQuantizer != encoder->data->quantizer) {
*encoderChanges |= AVIF_ENCODER_CHANGE_QUANTIZER;
}
if (encoder->data->lastQuantizerAlpha != encoder->data->quantizerAlpha) {
*encoderChanges |= AVIF_ENCODER_CHANGE_QUANTIZER_ALPHA;
}
if (lastEncoder->minQuantizer != encoder->minQuantizer) {
*encoderChanges |= AVIF_ENCODER_CHANGE_MIN_QUANTIZER;
}
if (lastEncoder->maxQuantizer != encoder->maxQuantizer) {
*encoderChanges |= AVIF_ENCODER_CHANGE_MAX_QUANTIZER;
}
if (lastEncoder->minQuantizerAlpha != encoder->minQuantizerAlpha) {
*encoderChanges |= AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA;
}
if (lastEncoder->maxQuantizerAlpha != encoder->maxQuantizerAlpha) {
*encoderChanges |= AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA;
}
if (encoder->data->lastTileRowsLog2 != encoder->data->tileRowsLog2) {
*encoderChanges |= AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2;
}
if (encoder->data->lastTileColsLog2 != encoder->data->tileColsLog2) {
*encoderChanges |= AVIF_ENCODER_CHANGE_TILE_COLS_LOG2;
}
if (memcmp(&lastEncoder->scalingMode, &encoder->scalingMode, sizeof(avifScalingMode)) != 0) {
*encoderChanges |= AVIF_ENCODER_CHANGE_SCALING_MODE;
}
if (encoder->csOptions->count > 0) {
*encoderChanges |= AVIF_ENCODER_CHANGE_CODEC_SPECIFIC;
}
return AVIF_TRUE;
}
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)
{
avifRWStream * dedupStream = outputStream;
if (dedup) {
assert(ipma);
dedupStream = &dedup->s;
}
if (imageMetadata->icc.size > 0) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker colr;
AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "colr", AVIF_BOX_SIZE_TBD, &colr));
AVIF_CHECKRES(avifRWStreamWriteChars(dedupStream, "prof", 4)); AVIF_CHECKRES(avifRWStreamWrite(dedupStream, imageMetadata->icc.data, imageMetadata->icc.size));
avifRWStreamFinishBox(dedupStream, colr);
if (dedup) {
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, ipma, AVIF_FALSE));
}
}
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker colr;
AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "colr", AVIF_BOX_SIZE_TBD, &colr));
AVIF_CHECKRES(avifRWStreamWriteChars(dedupStream, "nclx", 4)); AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->colorPrimaries)); AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->transferCharacteristics)); AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->matrixCoefficients)); AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, (imageMetadata->yuvRange == AVIF_RANGE_FULL) ? 1 : 0, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, 0, 7)); avifRWStreamFinishBox(dedupStream, colr);
if (dedup) {
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, ipma, AVIF_FALSE));
}
return avifEncoderWriteExtendedColorProperties(dedupStream, outputStream, imageMetadata, ipma, dedup);
}
static avifResult avifEncoderWriteExtendedColorProperties(avifRWStream * dedupStream,
avifRWStream * outputStream,
const avifImage * imageMetadata,
struct ipmaArray * ipma,
avifItemPropertyDedup * dedup)
{
if (imageMetadata->clli.maxCLL || imageMetadata->clli.maxPALL) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker clli;
AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "clli", AVIF_BOX_SIZE_TBD, &clli));
AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->clli.maxCLL)); AVIF_CHECKRES(avifRWStreamWriteU16(dedupStream, imageMetadata->clli.maxPALL)); avifRWStreamFinishBox(dedupStream, clli);
if (dedup) {
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, ipma, AVIF_FALSE));
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_PASP) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker pasp;
AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "pasp", AVIF_BOX_SIZE_TBD, &pasp));
AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->pasp.hSpacing)); AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->pasp.vSpacing)); avifRWStreamFinishBox(dedupStream, pasp);
if (dedup) {
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, ipma, AVIF_FALSE));
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker clap;
AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "clap", AVIF_BOX_SIZE_TBD, &clap));
AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.widthN)); AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.widthD)); AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.heightN)); AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.heightD)); AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.horizOffN)); AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.horizOffD)); AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.vertOffN)); AVIF_CHECKRES(avifRWStreamWriteU32(dedupStream, imageMetadata->clap.vertOffD)); avifRWStreamFinishBox(dedupStream, clap);
if (dedup) {
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, ipma, AVIF_TRUE));
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker irot;
AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "irot", AVIF_BOX_SIZE_TBD, &irot));
AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, 0, 6)); AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, imageMetadata->irot.angle & 0x3, 2)); avifRWStreamFinishBox(dedupStream, irot);
if (dedup) {
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, ipma, AVIF_TRUE));
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker imir;
AVIF_CHECKRES(avifRWStreamWriteBox(dedupStream, "imir", AVIF_BOX_SIZE_TBD, &imir));
AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, 0, 7)); AVIF_CHECKRES(avifRWStreamWriteBits(dedupStream, imageMetadata->imir.axis ? 1 : 0, 1)); avifRWStreamFinishBox(dedupStream, imir);
if (dedup) {
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, outputStream, ipma, AVIF_TRUE));
}
}
return AVIF_RESULT_OK;
}
static avifResult avifEncoderWriteTrackMetaBox(avifEncoder * encoder, avifRWStream * s)
{
uint32_t metadataItemCount = 0;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (memcmp(item->type, encoder->data->imageItemType, 4) != 0) {
++metadataItemCount;
}
}
if (metadataItemCount == 0) {
return AVIF_RESULT_OK;
}
avifBoxMarker meta;
AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "meta", AVIF_BOX_SIZE_TBD, 0, 0, &meta));
avifBoxMarker hdlr;
AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0, &hdlr));
AVIF_CHECKRES(avifRWStreamWriteU32(s, 0)); AVIF_CHECKRES(avifRWStreamWriteChars(s, "pict", 4)); AVIF_CHECKRES(avifRWStreamWriteZeros(s, 12)); AVIF_CHECKRES(avifRWStreamWriteChars(s, "libavif", 8)); avifRWStreamFinishBox(s, hdlr);
avifBoxMarker iloc;
AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0, &iloc));
AVIF_CHECKRES(avifRWStreamWriteBits(s, 4, 4)); AVIF_CHECKRES(avifRWStreamWriteBits(s, 4, 4)); AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 4)); AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 4)); AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)metadataItemCount)); for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
if (memcmp(item->type, encoder->data->imageItemType, 4) == 0) {
continue;
}
AVIF_CHECKRES(avifRWStreamWriteU16(s, item->id)); AVIF_CHECKRES(avifRWStreamWriteU16(s, 0)); AVIF_CHECKRES(avifRWStreamWriteU16(s, 1)); avifEncoderItemAddMdatFixup(item, s); AVIF_CHECKRES(avifRWStreamWriteU32(s, 0 )); AVIF_CHECKRES(avifRWStreamWriteU32(s, (uint32_t)item->metadataPayload.size)); }
avifRWStreamFinishBox(s, iloc);
avifBoxMarker iinf;
AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0, &iinf));
AVIF_CHECKRES(avifRWStreamWriteU16(s, (uint16_t)metadataItemCount)); for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
if (memcmp(item->type, encoder->data->imageItemType, 4) == 0) {
continue;
}
assert(!item->hiddenImage);
avifBoxMarker infe;
AVIF_CHECKRES(avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, 2, 0, &infe));
AVIF_CHECKRES(avifRWStreamWriteU16(s, item->id)); AVIF_CHECKRES(avifRWStreamWriteU16(s, 0)); AVIF_CHECKRES(avifRWStreamWrite(s, item->type, 4)); AVIF_CHECKRES(avifRWStreamWriteChars(s, item->infeName, item->infeNameSize)); if (item->infeContentType && item->infeContentTypeSize) { AVIF_CHECKRES(avifRWStreamWriteChars(s, item->infeContentType, item->infeContentTypeSize));
}
avifRWStreamFinishBox(s, infe);
}
avifRWStreamFinishBox(s, iinf);
avifRWStreamFinishBox(s, meta);
return AVIF_RESULT_OK;
}
static avifResult avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uint32_t gridRows, uint32_t gridWidth, uint32_t gridHeight)
{
uint8_t gridFlags = ((gridWidth > 65535) || (gridHeight > 65535)) ? 1 : 0;
avifRWStream s;
avifRWStreamStart(&s, data);
AVIF_CHECKRES(avifRWStreamWriteU8(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU8(&s, gridFlags)); AVIF_CHECKRES(avifRWStreamWriteU8(&s, (uint8_t)(gridRows - 1))); AVIF_CHECKRES(avifRWStreamWriteU8(&s, (uint8_t)(gridCols - 1))); if (gridFlags & 1) {
AVIF_CHECKRES(avifRWStreamWriteU32(&s, gridWidth)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, gridHeight)); } else {
uint16_t tmpWidth = (uint16_t)gridWidth;
uint16_t tmpHeight = (uint16_t)gridHeight;
AVIF_CHECKRES(avifRWStreamWriteU16(&s, tmpWidth)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, tmpHeight)); }
avifRWStreamFinishWrite(&s);
return AVIF_RESULT_OK;
}
static avifResult avifEncoderDataCreateExifItem(avifEncoderData * data, const avifRWData * exif)
{
size_t exifTiffHeaderOffset;
const avifResult result = avifGetExifTiffHeaderOffset(exif->data, exif->size, &exifTiffHeaderOffset);
if (result != AVIF_RESULT_OK) {
return result;
}
avifEncoderItem * exifItem = avifEncoderDataCreateItem(data, "Exif", "Exif", 5, 0);
if (!exifItem) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
exifItem->irefToID = data->primaryItemID;
exifItem->irefType = "cdsc";
const uint32_t offset32bit = avifHTONL((uint32_t)exifTiffHeaderOffset);
AVIF_CHECKRES(avifRWDataRealloc(&exifItem->metadataPayload, sizeof(offset32bit) + exif->size));
memcpy(exifItem->metadataPayload.data, &offset32bit, sizeof(offset32bit));
memcpy(exifItem->metadataPayload.data + sizeof(offset32bit), exif->data, exif->size);
return AVIF_RESULT_OK;
}
static avifResult avifEncoderDataCreateXMPItem(avifEncoderData * data, const avifRWData * xmp)
{
avifEncoderItem * xmpItem = avifEncoderDataCreateItem(data, "mime", "XMP", 4, 0);
if (!xmpItem) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
xmpItem->irefToID = data->primaryItemID;
xmpItem->irefType = "cdsc";
xmpItem->infeContentType = xmpContentType;
xmpItem->infeContentTypeSize = xmpContentTypeSize;
AVIF_CHECKRES(avifRWDataSet(&xmpItem->metadataPayload, xmp->data, xmp->size));
return AVIF_RESULT_OK;
}
static avifImage * avifImageCopyAndPad(const avifImage * srcImage, uint32_t dstWidth, uint32_t dstHeight)
{
avifImage * dstImage = avifImageCreateEmpty();
if (!dstImage) {
return NULL;
}
if (avifImageCopy(dstImage, srcImage, (avifPlanesFlag)0) != AVIF_RESULT_OK) {
avifImageDestroy(dstImage);
return NULL;
}
assert(dstWidth >= srcImage->width);
assert(dstHeight >= srcImage->height);
dstImage->width = dstWidth;
dstImage->height = dstHeight;
if (srcImage->yuvPlanes[AVIF_CHAN_Y]) {
const avifResult allocationResult = avifImageAllocatePlanes(dstImage, AVIF_PLANES_YUV);
if (allocationResult != AVIF_RESULT_OK) {
avifImageDestroy(dstImage);
return NULL;
}
}
if (srcImage->alphaPlane) {
const avifResult allocationResult = avifImageAllocatePlanes(dstImage, AVIF_PLANES_A);
if (allocationResult != AVIF_RESULT_OK) {
avifImageDestroy(dstImage);
return NULL;
}
}
const avifBool usesU16 = avifImageUsesU16(srcImage);
for (int plane = AVIF_CHAN_Y; plane <= AVIF_CHAN_A; ++plane) {
const uint8_t * srcRow = avifImagePlane(srcImage, plane);
const uint32_t srcRowBytes = avifImagePlaneRowBytes(srcImage, plane);
const uint32_t srcPlaneWidth = avifImagePlaneWidth(srcImage, plane);
const uint32_t srcPlaneHeight = avifImagePlaneHeight(srcImage, plane); const size_t srcPlaneWidthBytes = (size_t)srcPlaneWidth << usesU16;
uint8_t * dstRow = avifImagePlane(dstImage, plane);
const uint32_t dstRowBytes = avifImagePlaneRowBytes(dstImage, plane);
const uint32_t dstPlaneWidth = avifImagePlaneWidth(dstImage, plane);
const uint32_t dstPlaneHeight = avifImagePlaneHeight(dstImage, plane); const size_t dstPlaneWidthBytes = (size_t)dstPlaneWidth << usesU16;
for (uint32_t j = 0; j < srcPlaneHeight; ++j) {
memcpy(dstRow, srcRow, srcPlaneWidthBytes);
if (dstPlaneWidth > srcPlaneWidth) {
if (usesU16) {
uint16_t * dstRow16 = (uint16_t *)dstRow;
for (uint32_t x = srcPlaneWidth; x < dstPlaneWidth; ++x) {
dstRow16[x] = dstRow16[srcPlaneWidth - 1];
}
} else {
memset(&dstRow[srcPlaneWidth], dstRow[srcPlaneWidth - 1], dstPlaneWidth - srcPlaneWidth);
}
}
srcRow += srcRowBytes;
dstRow += dstRowBytes;
}
for (uint32_t j = srcPlaneHeight; j < dstPlaneHeight; ++j) {
memcpy(dstRow, dstRow - dstRowBytes, dstPlaneWidthBytes);
dstRow += dstRowBytes;
}
}
return dstImage;
}
static int avifQualityToQuantizer(int quality, int minQuantizer, int maxQuantizer)
{
int quantizer;
if (quality == AVIF_QUALITY_DEFAULT) {
quantizer = (minQuantizer + maxQuantizer) / 2;
quantizer = AVIF_CLAMP(quantizer, 0, 63);
} else {
quality = AVIF_CLAMP(quality, 0, 100);
quantizer = ((100 - quality) * 63 + 50) / 100;
}
return quantizer;
}
static avifResult avifEncoderAddImageItems(avifEncoder * encoder,
uint32_t gridCols,
uint32_t gridRows,
uint32_t gridWidth,
uint32_t gridHeight,
avifItemCategory itemCategory,
uint16_t * topLevelItemID)
{
const uint32_t cellCount = gridCols * gridRows;
const char * infeName = (itemCategory == AVIF_ITEM_ALPHA) ? "Alpha" : "Color";
const size_t infeNameSize = 6;
if (cellCount > 1) {
avifEncoderItem * gridItem = avifEncoderDataCreateItem(encoder->data, "grid", infeName, infeNameSize, 0);
AVIF_CHECKRES(avifWriteGridPayload(&gridItem->metadataPayload, gridCols, gridRows, gridWidth, gridHeight));
gridItem->itemCategory = itemCategory;
gridItem->gridCols = gridCols;
gridItem->gridRows = gridRows;
gridItem->gridWidth = gridWidth;
gridItem->gridHeight = gridHeight;
*topLevelItemID = gridItem->id;
}
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
avifEncoderItem * item =
avifEncoderDataCreateItem(encoder->data, encoder->data->imageItemType, infeName, infeNameSize, cellIndex);
AVIF_CHECKERR(item, AVIF_RESULT_OUT_OF_MEMORY);
item->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
AVIF_CHECKERR(item->codec, AVIF_RESULT_NO_CODEC_AVAILABLE);
item->codec->csOptions = encoder->csOptions;
item->codec->diag = &encoder->diag;
item->itemCategory = itemCategory;
item->extraLayerCount = encoder->extraLayerCount;
if (cellCount > 1) {
item->dimgFromID = *topLevelItemID;
item->hiddenImage = AVIF_TRUE;
} else {
*topLevelItemID = item->id;
}
}
return AVIF_RESULT_OK;
}
static avifCodecType avifEncoderGetCodecType(const avifEncoder * encoder)
{
assert((encoder->codecChoice != AVIF_CODEC_CHOICE_AUTO) ||
(strcmp(avifCodecName(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE), "avm") != 0));
return avifCodecTypeFromChoice(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
}
static avifBool avifEncoderDataShouldForceKeyframeForAlpha(const avifEncoderData * data,
const avifEncoderItem * colorItem,
avifAddImageFlags addImageFlags)
{
if (!data->alphaPresent) {
return AVIF_FALSE;
}
if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
return AVIF_FALSE;
}
if (data->frames.count == 0) {
return AVIF_FALSE;
}
const uint32_t colorFramesOutputSoFar = colorItem->encodeOutput->samples.count;
const avifBool isLaggedOutput = (data->frames.count + 1) != colorFramesOutputSoFar;
if (isLaggedOutput) {
return AVIF_FALSE;
}
return colorItem->encodeOutput->samples.sample[colorFramesOutputSoFar - 1].sync;
}
static avifResult avifGetErrorForItemCategory(avifItemCategory itemCategory)
{
return (itemCategory == AVIF_ITEM_ALPHA) ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
uint32_t gridCols,
uint32_t gridRows,
const avifImage * const * cellImages,
uint64_t durationInTimescales,
avifAddImageFlags addImageFlags)
{
if (!avifCodecName(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE)) {
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
if (encoder->extraLayerCount >= AVIF_MAX_AV1_LAYER_COUNT) {
avifDiagnosticsPrintf(&encoder->diag, "extraLayerCount [%u] must be less than %d", encoder->extraLayerCount, AVIF_MAX_AV1_LAYER_COUNT);
return AVIF_RESULT_INVALID_ARGUMENT;
}
const uint32_t cellCount = gridCols * gridRows;
if (cellCount == 0) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
const avifImage * firstCell = cellImages[0];
const avifImage * bottomRightCell = cellImages[cellCount - 1];
if ((firstCell->depth != 8) && (firstCell->depth != 10) && (firstCell->depth != 12)) {
return AVIF_RESULT_UNSUPPORTED_DEPTH;
}
if (!firstCell->width || !firstCell->height || !bottomRightCell->width || !bottomRightCell->height) {
return AVIF_RESULT_NO_CONTENT;
}
const uint32_t tileWidth = firstCell->width;
const uint32_t tileHeight = firstCell->height;
const uint32_t gridWidth = (gridCols - 1) * tileWidth + bottomRightCell->width;
const uint32_t gridHeight = (gridRows - 1) * tileHeight + bottomRightCell->height;
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
const avifImage * cellImage = cellImages[cellIndex];
const uint32_t expectedCellWidth = ((cellIndex + 1) % gridCols) ? tileWidth : bottomRightCell->width;
const uint32_t expectedCellHeight = (cellIndex < (cellCount - gridCols)) ? tileHeight : bottomRightCell->height;
if ((cellImage->width != expectedCellWidth) || (cellImage->height != expectedCellHeight)) {
return AVIF_RESULT_INVALID_IMAGE_GRID;
}
}
if ((bottomRightCell->width > tileWidth) || (bottomRightCell->height > tileHeight)) {
return AVIF_RESULT_INVALID_IMAGE_GRID;
}
if ((cellCount > 1) &&
!avifAreGridDimensionsValid(firstCell->yuvFormat, gridWidth, gridHeight, tileWidth, tileHeight, &encoder->diag)) {
return AVIF_RESULT_INVALID_IMAGE_GRID;
}
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
const avifImage * cellImage = cellImages[cellIndex];
if ((cellImage->depth != firstCell->depth) || (cellImage->yuvFormat != firstCell->yuvFormat) ||
(cellImage->yuvRange != firstCell->yuvRange) || (cellImage->colorPrimaries != firstCell->colorPrimaries) ||
(cellImage->transferCharacteristics != firstCell->transferCharacteristics) ||
(cellImage->matrixCoefficients != firstCell->matrixCoefficients) || (!!cellImage->alphaPlane != !!firstCell->alphaPlane) ||
(cellImage->alphaPremultiplied != firstCell->alphaPremultiplied)) {
return AVIF_RESULT_INVALID_IMAGE_GRID;
}
if (!cellImage->yuvPlanes[AVIF_CHAN_Y]) {
return AVIF_RESULT_NO_CONTENT;
}
if (cellImage->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
return AVIF_RESULT_NO_YUV_FORMAT_SELECTED;
}
}
if (encoder->data->singleImage) {
return AVIF_RESULT_ENCODE_COLOR_FAILED;
}
if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
encoder->data->singleImage = AVIF_TRUE;
if (encoder->extraLayerCount > 0) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
if (encoder->data->items.count > 0) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
}
const avifCodecType codecType = avifEncoderGetCodecType(encoder);
switch (codecType) {
case AVIF_CODEC_TYPE_AV1:
encoder->data->imageItemType = "av01";
encoder->data->configPropName = "av1C";
break;
#if defined(AVIF_CODEC_AVM)
case AVIF_CODEC_TYPE_AV2:
encoder->data->imageItemType = "av02";
encoder->data->configPropName = "av2C";
break;
#endif
default:
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
encoder->data->quantizer = avifQualityToQuantizer(encoder->quality, encoder->minQuantizer, encoder->maxQuantizer);
encoder->data->quantizerAlpha = avifQualityToQuantizer(encoder->qualityAlpha, encoder->minQuantizerAlpha, encoder->maxQuantizerAlpha);
encoder->data->tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
encoder->data->tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
if (encoder->autoTiling) {
const int threads = 8;
avifSetTileConfiguration(threads, tileWidth, tileHeight, &encoder->data->tileRowsLog2, &encoder->data->tileColsLog2);
}
avifEncoderChanges encoderChanges;
if (!avifEncoderDetectChanges(encoder, &encoderChanges)) {
return AVIF_RESULT_CANNOT_CHANGE_SETTING;
}
avifEncoderBackupSettings(encoder);
if (durationInTimescales == 0) {
durationInTimescales = 1;
}
if (encoder->data->items.count == 0) {
const avifResult copyResult = avifImageCopy(encoder->data->imageMetadata, firstCell, 0);
if (copyResult != AVIF_RESULT_OK) {
return copyResult;
}
uint16_t colorItemID;
AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_COLOR, &colorItemID));
encoder->data->primaryItemID = colorItemID;
encoder->data->alphaPresent = (firstCell->alphaPlane != NULL);
if (encoder->data->alphaPresent && (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE)) {
encoder->data->alphaPresent = AVIF_FALSE;
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
const avifImage * cellImage = cellImages[cellIndex];
if (!avifImageIsOpaque(cellImage)) {
encoder->data->alphaPresent = AVIF_TRUE;
break;
}
}
}
if (encoder->data->alphaPresent) {
uint16_t alphaItemID;
AVIF_CHECKRES(avifEncoderAddImageItems(encoder, gridCols, gridRows, gridWidth, gridHeight, AVIF_ITEM_ALPHA, &alphaItemID));
avifEncoderItem * alphaItem = avifEncoderDataFindItemByID(encoder->data, alphaItemID);
assert(alphaItem);
alphaItem->irefType = "auxl";
alphaItem->irefToID = colorItemID;
if (encoder->data->imageMetadata->alphaPremultiplied) {
avifEncoderItem * colorItem = avifEncoderDataFindItemByID(encoder->data, colorItemID);
assert(colorItem);
colorItem->irefType = "prem";
colorItem->irefToID = alphaItemID;
}
}
if (firstCell->exif.size > 0) {
const avifResult result = avifEncoderDataCreateExifItem(encoder->data, &firstCell->exif);
if (result != AVIF_RESULT_OK) {
return result;
}
}
if (firstCell->xmp.size > 0) {
const avifResult result = avifEncoderDataCreateXMPItem(encoder->data, &firstCell->xmp);
if (result != AVIF_RESULT_OK) {
return result;
}
}
} else {
const avifImage * imageMetadata = encoder->data->imageMetadata;
if ((imageMetadata->depth != firstCell->depth) || (imageMetadata->yuvFormat != firstCell->yuvFormat) ||
(imageMetadata->yuvRange != firstCell->yuvRange) ||
(imageMetadata->yuvChromaSamplePosition != firstCell->yuvChromaSamplePosition) ||
(imageMetadata->colorPrimaries != firstCell->colorPrimaries) ||
(imageMetadata->transferCharacteristics != firstCell->transferCharacteristics) ||
(imageMetadata->matrixCoefficients != firstCell->matrixCoefficients) ||
(imageMetadata->alphaPremultiplied != firstCell->alphaPremultiplied) ||
(encoder->data->alphaPresent && !firstCell->alphaPlane)) {
return AVIF_RESULT_INCOMPATIBLE_IMAGE;
}
}
if (encoder->data->frames.count == 1) {
assert(encoder->data->items.count > 0);
const avifImage * imageMetadata = encoder->data->imageMetadata;
if ((imageMetadata->width > 65535) || (imageMetadata->height > 65535)) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
}
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->codec) {
const avifImage * cellImage = cellImages[item->cellIndex];
avifImage * paddedCellImage = NULL;
if ((cellImage->width != tileWidth) || (cellImage->height != tileHeight)) {
paddedCellImage = avifImageCopyAndPad(cellImage, tileWidth, tileHeight);
if (!paddedCellImage) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
cellImage = paddedCellImage;
}
const int quantizer = (item->itemCategory == AVIF_ITEM_ALPHA) ? encoder->data->quantizerAlpha : encoder->data->quantizer;
avifResult encodeResult = item->codec->encodeImage(item->codec,
encoder,
cellImage,
item->itemCategory == AVIF_ITEM_ALPHA,
encoder->data->tileRowsLog2,
encoder->data->tileColsLog2,
quantizer,
encoderChanges,
encoder->data->alphaPresent,
addImageFlags,
item->encodeOutput);
if (paddedCellImage) {
avifImageDestroy(paddedCellImage);
}
if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) {
encodeResult = avifGetErrorForItemCategory(item->itemCategory);
}
if (encodeResult != AVIF_RESULT_OK) {
return encodeResult;
}
if (itemIndex == 0 && avifEncoderDataShouldForceKeyframeForAlpha(encoder->data, item, addImageFlags)) {
addImageFlags |= AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
}
}
}
avifCodecSpecificOptionsClear(encoder->csOptions);
avifEncoderFrame * frame = (avifEncoderFrame *)avifArrayPushPtr(&encoder->data->frames);
frame->durationInTimescales = durationInTimescales;
return AVIF_RESULT_OK;
}
avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags)
{
avifDiagnosticsClearError(&encoder->diag);
return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags);
}
avifResult avifEncoderAddImageGrid(avifEncoder * encoder,
uint32_t gridCols,
uint32_t gridRows,
const avifImage * const * cellImages,
avifAddImageFlags addImageFlags)
{
avifDiagnosticsClearError(&encoder->diag);
if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) {
return AVIF_RESULT_INVALID_IMAGE_GRID;
}
if (encoder->extraLayerCount == 0) {
addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; }
return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags);
}
static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size)
{
const size_t mdatCurrentOffset = avifRWStreamOffset(s);
const size_t mdatSearchSize = mdatCurrentOffset - mdatStartOffset;
if (mdatSearchSize < size) {
return 0;
}
const size_t mdatEndSearchOffset = mdatCurrentOffset - size;
for (size_t searchOffset = mdatStartOffset; searchOffset <= mdatEndSearchOffset; ++searchOffset) {
if (!memcmp(data, &s->raw->data[searchOffset], size)) {
return searchOffset;
}
}
return 0;
}
static avifResult avifEncoderWriteMediaDataBox(avifEncoder * encoder,
avifRWStream * s,
avifEncoderItemReferenceArray * layeredColorItems,
avifEncoderItemReferenceArray * layeredAlphaItems)
{
encoder->ioStats.colorOBUSize = 0;
encoder->ioStats.alphaOBUSize = 0;
avifBoxMarker mdat;
AVIF_CHECKRES(avifRWStreamWriteBox(s, "mdat", AVIF_BOX_SIZE_TBD, &mdat));
const size_t mdatStartOffset = avifRWStreamOffset(s);
for (uint32_t itemPasses = 0; itemPasses < 3; ++itemPasses) {
const avifBool metadataPass = (itemPasses == 0);
const avifBool alphaPass = (itemPasses == 1);
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
const avifBool isGrid = (item->gridCols > 0); if ((item->metadataPayload.size == 0) && (item->encodeOutput->samples.count == 0)) {
continue;
}
if (!isGrid && (metadataPass != (item->metadataPayload.size > 0))) {
continue;
}
if (alphaPass != (item->itemCategory == AVIF_ITEM_ALPHA)) {
continue;
}
if ((encoder->extraLayerCount > 0) && (item->encodeOutput->samples.count > 0)) {
assert(item->encodeOutput->samples.count == item->mdatFixups.count);
avifEncoderItemReference * ref = (item->itemCategory == AVIF_ITEM_ALPHA) ? avifArrayPushPtr(layeredAlphaItems)
: avifArrayPushPtr(layeredColorItems);
*ref = item;
continue;
}
size_t chunkOffset = 0;
if (item->encodeOutput->samples.count == 1) {
avifEncodeSample * sample = &item->encodeOutput->samples.sample[0];
chunkOffset = avifEncoderFindExistingChunk(s, mdatStartOffset, sample->data.data, sample->data.size);
} else if (item->encodeOutput->samples.count == 0) {
chunkOffset = avifEncoderFindExistingChunk(s, mdatStartOffset, item->metadataPayload.data, item->metadataPayload.size);
}
if (!chunkOffset) {
chunkOffset = avifRWStreamOffset(s);
if (item->encodeOutput->samples.count > 0) {
for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
AVIF_CHECKRES(avifRWStreamWrite(s, sample->data.data, sample->data.size));
if (item->itemCategory == AVIF_ITEM_ALPHA) {
encoder->ioStats.alphaOBUSize += sample->data.size;
} else {
encoder->ioStats.colorOBUSize += sample->data.size;
}
}
} else {
AVIF_CHECKRES(avifRWStreamWrite(s, item->metadataPayload.data, item->metadataPayload.size));
}
}
for (uint32_t fixupIndex = 0; fixupIndex < item->mdatFixups.count; ++fixupIndex) {
avifOffsetFixup * fixup = &item->mdatFixups.fixup[fixupIndex];
size_t prevOffset = avifRWStreamOffset(s);
avifRWStreamSetOffset(s, fixup->offset);
AVIF_CHECKRES(avifRWStreamWriteU32(s, (uint32_t)chunkOffset));
avifRWStreamSetOffset(s, prevOffset);
}
}
}
uint32_t layeredItemCount = AVIF_MAX(layeredColorItems->count, layeredAlphaItems->count);
if (layeredItemCount > 0) {
avifBool hasMoreSample;
uint32_t layerIndex = 0;
do {
hasMoreSample = AVIF_FALSE;
for (uint32_t itemIndex = 0; itemIndex < layeredItemCount; ++itemIndex) {
for (int samplePass = 0; samplePass < 2; ++samplePass) {
avifEncoderItemReferenceArray * currentItems = (samplePass == 0) ? layeredAlphaItems : layeredColorItems;
if (itemIndex >= currentItems->count) {
continue;
}
avifEncoderItem * item = currentItems->ref[itemIndex];
if (item->encodeOutput->samples.count <= layerIndex) {
continue;
} else if (item->encodeOutput->samples.count > layerIndex + 1) {
hasMoreSample = AVIF_TRUE;
}
avifRWData * data = &item->encodeOutput->samples.sample[layerIndex].data;
size_t chunkOffset = avifEncoderFindExistingChunk(s, mdatStartOffset, data->data, data->size);
if (!chunkOffset) {
chunkOffset = avifRWStreamOffset(s);
AVIF_CHECKRES(avifRWStreamWrite(s, data->data, data->size));
if (samplePass == 0) {
encoder->ioStats.alphaOBUSize += data->size;
} else {
encoder->ioStats.colorOBUSize += data->size;
}
}
size_t prevOffset = avifRWStreamOffset(s);
avifRWStreamSetOffset(s, item->mdatFixups.fixup[layerIndex].offset);
AVIF_CHECKRES(avifRWStreamWriteU32(s, (uint32_t)chunkOffset));
avifRWStreamSetOffset(s, prevOffset);
}
}
++layerIndex;
} while (hasMoreSample);
assert(layerIndex <= AVIF_MAX_AV1_LAYER_COUNT);
}
avifRWStreamFinishBox(s, mdat);
return AVIF_RESULT_OK;
}
avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
{
avifDiagnosticsClearError(&encoder->diag);
if (encoder->data->items.count == 0) {
return AVIF_RESULT_NO_CONTENT;
}
const avifCodecType codecType = avifEncoderGetCodecType(encoder);
if (codecType == AVIF_CODEC_TYPE_UNKNOWN) {
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->codec) {
if (!item->codec->encodeFinish(item->codec, item->encodeOutput)) {
return avifGetErrorForItemCategory(item->itemCategory);
}
if (item->encodeOutput->samples.count != encoder->data->frames.count) {
return avifGetErrorForItemCategory(item->itemCategory);
}
if ((item->extraLayerCount > 0) && (item->encodeOutput->samples.count != item->extraLayerCount + 1)) {
avifDiagnosticsPrintf(&encoder->diag,
"Expected %u frames given to avifEncoderAddImage() to encode this layered image according to extraLayerCount, but got %u frames.",
item->extraLayerCount + 1,
item->encodeOutput->samples.count);
return AVIF_RESULT_INVALID_ARGUMENT;
}
}
}
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->encodeOutput->samples.count > 0) {
const avifEncodeSample * firstSample = &item->encodeOutput->samples.sample[0];
avifSequenceHeader sequenceHeader;
AVIF_CHECKERR(avifSequenceHeaderParse(&sequenceHeader, (const avifROData *)&firstSample->data, codecType),
avifGetErrorForItemCategory(item->itemCategory));
item->av1C = sequenceHeader.av1C;
}
}
const avifImage * imageMetadata = encoder->data->imageMetadata;
uint64_t now = (uint64_t)time(NULL) + 2082844800;
avifRWStream s;
avifRWStreamStart(&s, output);
const avifBool isSequence = (encoder->extraLayerCount == 0) && (encoder->data->frames.count > 1);
const char * majorBrand = "avif";
if (isSequence) {
majorBrand = "avis";
}
uint32_t minorVersion = 0;
#if defined(AVIF_CODEC_AVM)
if (codecType == AVIF_CODEC_TYPE_AV2) {
minorVersion = 2;
}
#endif
avifBoxMarker ftyp;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "ftyp", AVIF_BOX_SIZE_TBD, &ftyp));
AVIF_CHECKRES(avifRWStreamWriteChars(&s, majorBrand, 4)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, minorVersion)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, "avif", 4)); if (isSequence) { AVIF_CHECKRES(avifRWStreamWriteChars(&s, "avis", 4)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, "msf1", 4)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, "iso8", 4)); } AVIF_CHECKRES(avifRWStreamWriteChars(&s, "mif1", 4)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, "miaf", 4)); if ((imageMetadata->depth == 8) || (imageMetadata->depth == 10)) { if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { AVIF_CHECKRES(avifRWStreamWriteChars(&s, "MA1B", 4)); } else if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) { AVIF_CHECKRES(avifRWStreamWriteChars(&s, "MA1A", 4)); }
}
avifRWStreamFinishBox(&s, ftyp);
avifBoxMarker meta;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "meta", AVIF_BOX_SIZE_TBD, 0, 0, &meta));
avifBoxMarker hdlr;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0, &hdlr));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, "pict", 4)); AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 12)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, "libavif", 8)); avifRWStreamFinishBox(&s, hdlr);
if (encoder->data->primaryItemID != 0) {
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint16_t), 0, 0, NULL));
AVIF_CHECKRES(avifRWStreamWriteU16(&s, encoder->data->primaryItemID)); }
avifBoxMarker iloc;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0, &iloc));
AVIF_CHECKRES(avifRWStreamWriteBits(&s, 4, 4)); AVIF_CHECKRES(avifRWStreamWriteBits(&s, 4, 4)); AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, 4)); AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, 4)); avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count);
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0));
if (item->extraLayerCount > 0) {
uint32_t layerCount = item->extraLayerCount + 1;
AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)layerCount)); for (uint32_t i = 0; i < layerCount; ++i) {
avifEncoderItemAddMdatFixup(item, &s);
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0 )); AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)item->encodeOutput->samples.sample[i].data.size)); }
continue;
}
uint32_t contentSize = (uint32_t)item->metadataPayload.size;
if (item->encodeOutput->samples.count > 0) {
contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size;
}
AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); avifEncoderItemAddMdatFixup(item, &s); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0 )); AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)contentSize)); }
avifRWStreamFinishBox(&s, iloc);
avifBoxMarker iinf;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0, &iinf));
AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count));
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
uint32_t flags = item->hiddenImage ? 1 : 0;
avifBoxMarker infe;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "infe", AVIF_BOX_SIZE_TBD, 2, flags, &infe));
AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWrite(&s, item->type, 4)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, item->infeName, item->infeNameSize)); if (item->infeContentType && item->infeContentTypeSize) { AVIF_CHECKRES(avifRWStreamWriteChars(&s, item->infeContentType, item->infeContentTypeSize));
}
avifRWStreamFinishBox(&s, infe);
}
avifRWStreamFinishBox(&s, iinf);
avifBoxMarker iref = 0;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
uint16_t dimgCount = 0;
for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) {
avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex];
if (dimgItem->dimgFromID == item->id) {
++dimgCount;
}
}
if (dimgCount > 0) {
if (!iref) {
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0, &iref));
}
avifBoxMarker refType;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "dimg", AVIF_BOX_SIZE_TBD, &refType));
AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, dimgCount)); for (uint32_t dimgIndex = 0; dimgIndex < encoder->data->items.count; ++dimgIndex) {
avifEncoderItem * dimgItem = &encoder->data->items.item[dimgIndex];
if (dimgItem->dimgFromID == item->id) {
AVIF_CHECKRES(avifRWStreamWriteU16(&s, dimgItem->id)); }
}
avifRWStreamFinishBox(&s, refType);
}
if (item->irefToID != 0) {
if (!iref) {
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0, &iref));
}
avifBoxMarker refType;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD, &refType));
AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->irefToID)); avifRWStreamFinishBox(&s, refType);
}
}
if (iref) {
avifRWStreamFinishBox(&s, iref);
}
avifBoxMarker iprp;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "iprp", AVIF_BOX_SIZE_TBD, &iprp));
avifItemPropertyDedup * dedup = avifItemPropertyDedupCreate();
avifBoxMarker ipco;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "ipco", AVIF_BOX_SIZE_TBD, &ipco));
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
const avifBool isGrid = (item->gridCols > 0);
memset(&item->ipma, 0, sizeof(item->ipma));
if (!item->codec && !isGrid) {
continue;
}
if (item->dimgFromID && (item->extraLayerCount == 0)) {
avifBool foundPreviousCell = AVIF_FALSE;
for (uint32_t dedupIndex = 0; dedupIndex < itemIndex; ++dedupIndex) {
avifEncoderItem * dedupItem = &encoder->data->items.item[dedupIndex];
if ((item->dimgFromID == dedupItem->dimgFromID) && (dedupItem->extraLayerCount == 0)) {
item->ipma = dedupItem->ipma;
foundPreviousCell = AVIF_TRUE;
break;
}
}
if (foundPreviousCell) {
continue;
}
}
uint32_t imageWidth = imageMetadata->width;
uint32_t imageHeight = imageMetadata->height;
if (isGrid) {
imageWidth = item->gridWidth;
imageHeight = item->gridHeight;
}
avifItemPropertyDedupStart(dedup);
avifBoxMarker ispe;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&dedup->s, "ispe", AVIF_BOX_SIZE_TBD, 0, 0, &ispe));
AVIF_CHECKRES(avifRWStreamWriteU32(&dedup->s, imageWidth)); AVIF_CHECKRES(avifRWStreamWriteU32(&dedup->s, imageHeight)); avifRWStreamFinishBox(&dedup->s, ispe);
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, &s, &item->ipma, AVIF_FALSE));
avifItemPropertyDedupStart(dedup);
uint8_t channelCount =
((item->itemCategory == AVIF_ITEM_ALPHA) || (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) ? 1 : 3;
avifBoxMarker pixi;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&dedup->s, "pixi", AVIF_BOX_SIZE_TBD, 0, 0, &pixi));
AVIF_CHECKRES(avifRWStreamWriteU8(&dedup->s, channelCount)); for (uint8_t chan = 0; chan < channelCount; ++chan) {
AVIF_CHECKRES(avifRWStreamWriteU8(&dedup->s, (uint8_t)imageMetadata->depth)); }
avifRWStreamFinishBox(&dedup->s, pixi);
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, &s, &item->ipma, AVIF_FALSE));
if (item->codec) {
avifItemPropertyDedupStart(dedup);
AVIF_CHECKRES(writeConfigBox(&dedup->s, &item->av1C, encoder->data->configPropName));
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, &s, &item->ipma, AVIF_TRUE));
}
if (item->itemCategory == AVIF_ITEM_ALPHA) {
avifItemPropertyDedupStart(dedup);
avifBoxMarker auxC;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&dedup->s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0, &auxC));
AVIF_CHECKRES(avifRWStreamWriteChars(&dedup->s, alphaURN, alphaURNSize)); avifRWStreamFinishBox(&dedup->s, auxC);
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, &s, &item->ipma, AVIF_FALSE));
} else {
AVIF_CHECKRES(avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, dedup));
}
if (item->extraLayerCount > 0) {
avifItemPropertyDedupStart(dedup);
avifBoxMarker a1lx;
AVIF_CHECKRES(avifRWStreamWriteBox(&dedup->s, "a1lx", AVIF_BOX_SIZE_TBD, &a1lx));
uint32_t layerSize[AVIF_MAX_AV1_LAYER_COUNT - 1] = { 0 };
avifBool largeSize = AVIF_FALSE;
for (uint32_t validLayer = 0; validLayer < item->extraLayerCount; ++validLayer) {
uint32_t size = (uint32_t)item->encodeOutput->samples.sample[validLayer].data.size;
layerSize[validLayer] = size;
if (size > 0xffff) {
largeSize = AVIF_TRUE;
}
}
AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, 0, 7)); AVIF_CHECKRES(avifRWStreamWriteBits(&dedup->s, largeSize ? 1 : 0, 1));
for (uint32_t layer = 0; layer < AVIF_MAX_AV1_LAYER_COUNT - 1; ++layer) {
if (largeSize) {
AVIF_CHECKRES(avifRWStreamWriteU32(&dedup->s, layerSize[layer]));
} else {
AVIF_CHECKRES(avifRWStreamWriteU16(&dedup->s, (uint16_t)layerSize[layer]));
}
}
avifRWStreamFinishBox(&dedup->s, a1lx);
AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, &s, &item->ipma, AVIF_FALSE));
}
}
avifRWStreamFinishBox(&s, ipco);
avifItemPropertyDedupDestroy(dedup);
dedup = NULL;
avifBoxMarker ipma;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, 0, 0, &ipma));
{
int ipmaCount = 0;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->ipma.count > 0) {
++ipmaCount;
}
}
AVIF_CHECKRES(avifRWStreamWriteU32(&s, ipmaCount));
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->ipma.count == 0) {
continue;
}
AVIF_CHECKRES(avifRWStreamWriteU16(&s, item->id)); AVIF_CHECKRES(avifRWStreamWriteU8(&s, item->ipma.count)); for (int i = 0; i < item->ipma.count; ++i) {
AVIF_CHECKRES(avifRWStreamWriteBits(&s, item->ipma.essential[i] ? 1 : 0, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(&s, item->ipma.associations[i], 7)); }
}
}
avifRWStreamFinishBox(&s, ipma);
avifRWStreamFinishBox(&s, iprp);
avifRWStreamFinishBox(&s, meta);
if (isSequence) {
static const uint8_t unityMatrix[9][4] = {
{ 0x00, 0x01, 0x00, 0x00 },
{ 0 },
{ 0 },
{ 0 },
{ 0x00, 0x01, 0x00, 0x00 },
{ 0 },
{ 0 },
{ 0 },
{ 0x40, 0x00, 0x00, 0x00 }
};
if (encoder->repetitionCount < 0 && encoder->repetitionCount != AVIF_REPETITION_COUNT_INFINITE) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
uint64_t framesDurationInTimescales = 0;
for (uint32_t frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
const avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
framesDurationInTimescales += frame->durationInTimescales;
}
uint64_t durationInTimescales;
if (encoder->repetitionCount == AVIF_REPETITION_COUNT_INFINITE) {
durationInTimescales = AVIF_INDEFINITE_DURATION64;
} else {
uint64_t loopCount = encoder->repetitionCount + 1;
assert(framesDurationInTimescales != 0);
if (loopCount > UINT64_MAX / framesDurationInTimescales) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
durationInTimescales = framesDurationInTimescales * loopCount;
}
avifBoxMarker moov;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "moov", AVIF_BOX_SIZE_TBD, &moov));
avifBoxMarker mvhd;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "mvhd", AVIF_BOX_SIZE_TBD, 1, 0, &mvhd));
AVIF_CHECKRES(avifRWStreamWriteU64(&s, now)); AVIF_CHECKRES(avifRWStreamWriteU64(&s, now)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale)); AVIF_CHECKRES(avifRWStreamWriteU64(&s, durationInTimescales)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00010000)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0x0100)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 8)); AVIF_CHECKRES(avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix)));
AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 24)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, encoder->data->items.count)); avifRWStreamFinishBox(&s, mvhd);
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->encodeOutput->samples.count == 0) {
continue;
}
uint32_t syncSamplesCount = 0;
for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
if (sample->sync) {
++syncSamplesCount;
}
}
avifBoxMarker trak;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "trak", AVIF_BOX_SIZE_TBD, &trak));
avifBoxMarker tkhd;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "tkhd", AVIF_BOX_SIZE_TBD, 1, 1, &tkhd));
AVIF_CHECKRES(avifRWStreamWriteU64(&s, now)); AVIF_CHECKRES(avifRWStreamWriteU64(&s, now)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, itemIndex + 1)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU64(&s, durationInTimescales)); AVIF_CHECKRES(avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 2)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix))); AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageMetadata->width << 16)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, imageMetadata->height << 16)); avifRWStreamFinishBox(&s, tkhd);
if (item->irefToID != 0) {
avifBoxMarker tref;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "tref", AVIF_BOX_SIZE_TBD, &tref));
avifBoxMarker refType;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD, &refType));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)item->irefToID));
avifRWStreamFinishBox(&s, refType);
avifRWStreamFinishBox(&s, tref);
}
avifBoxMarker edts;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "edts", AVIF_BOX_SIZE_TBD, &edts));
uint32_t elstFlags = (encoder->repetitionCount != 0);
avifBoxMarker elst;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "elst", AVIF_BOX_SIZE_TBD, 1, elstFlags, &elst));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); AVIF_CHECKRES(avifRWStreamWriteU64(&s, framesDurationInTimescales)); AVIF_CHECKRES(avifRWStreamWriteU64(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); avifRWStreamFinishBox(&s, elst);
avifRWStreamFinishBox(&s, edts);
if (item->itemCategory != AVIF_ITEM_ALPHA) {
AVIF_CHECKRES(avifEncoderWriteTrackMetaBox(encoder, &s));
}
avifBoxMarker mdia;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "mdia", AVIF_BOX_SIZE_TBD, &mdia));
avifBoxMarker mdhd;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "mdhd", AVIF_BOX_SIZE_TBD, 1, 0, &mdhd));
AVIF_CHECKRES(avifRWStreamWriteU64(&s, now)); AVIF_CHECKRES(avifRWStreamWriteU64(&s, now)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale)); AVIF_CHECKRES(avifRWStreamWriteU64(&s, framesDurationInTimescales)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 21956)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); avifRWStreamFinishBox(&s, mdhd);
avifBoxMarker hdlrTrak;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0, &hdlrTrak));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, (item->itemCategory == AVIF_ITEM_ALPHA) ? "auxv" : "pict", 4)); AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 12)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, "libavif", 8)); avifRWStreamFinishBox(&s, hdlrTrak);
avifBoxMarker minf;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "minf", AVIF_BOX_SIZE_TBD, &minf));
avifBoxMarker vmhd;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "vmhd", AVIF_BOX_SIZE_TBD, 0, 1, &vmhd));
AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 6)); avifRWStreamFinishBox(&s, vmhd);
avifBoxMarker dinf;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "dinf", AVIF_BOX_SIZE_TBD, &dinf));
avifBoxMarker dref;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "dref", AVIF_BOX_SIZE_TBD, 0, 0, &dref));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "url ", 0, 0, 1, NULL)); avifRWStreamFinishBox(&s, dref);
avifRWStreamFinishBox(&s, dinf);
avifBoxMarker stbl;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, "stbl", AVIF_BOX_SIZE_TBD, &stbl));
avifBoxMarker stsd;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stsd", AVIF_BOX_SIZE_TBD, 0, 0, &stsd));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); avifBoxMarker imageItem;
AVIF_CHECKRES(avifRWStreamWriteBox(&s, encoder->data->imageItemType, AVIF_BOX_SIZE_TBD, &imageItem));
AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 6)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0x00480000)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 1)); AVIF_CHECKRES(avifRWStreamWriteChars(&s, "\012AOM Coding", 11)); AVIF_CHECKRES(avifRWStreamWriteZeros(&s, 32 - 11)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, 0x0018)); AVIF_CHECKRES(avifRWStreamWriteU16(&s, (uint16_t)0xffff)); AVIF_CHECKRES(writeConfigBox(&s, &item->av1C, encoder->data->configPropName));
if (item->itemCategory == AVIF_ITEM_COLOR) {
AVIF_CHECKRES(avifEncoderWriteColorProperties(&s, imageMetadata, NULL, NULL));
}
avifBoxMarker ccst;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "ccst", AVIF_BOX_SIZE_TBD, 0, 0, &ccst));
AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(&s, 1, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(&s, 15, 4)); AVIF_CHECKRES(avifRWStreamWriteBits(&s, 0, 26)); avifRWStreamFinishBox(&s, ccst);
if (item->itemCategory == AVIF_ITEM_ALPHA) {
avifBoxMarker auxi;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "auxi", AVIF_BOX_SIZE_TBD, 0, 0, &auxi));
AVIF_CHECKRES(avifRWStreamWriteChars(&s, alphaURN, alphaURNSize)); avifRWStreamFinishBox(&s, auxi);
}
avifRWStreamFinishBox(&s, imageItem);
avifRWStreamFinishBox(&s, stsd);
avifBoxMarker stts;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stts", AVIF_BOX_SIZE_TBD, 0, 0, &stts));
size_t sttsEntryCountOffset = avifRWStreamOffset(&s);
uint32_t sttsEntryCount = 0;
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); for (uint32_t sampleCount = 0, frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
++sampleCount;
if (frameIndex < (encoder->data->frames.count - 1)) {
avifEncoderFrame * nextFrame = &encoder->data->frames.frame[frameIndex + 1];
if (frame->durationInTimescales == nextFrame->durationInTimescales) {
continue;
}
}
AVIF_CHECKRES(avifRWStreamWriteU32(&s, sampleCount)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)frame->durationInTimescales)); sampleCount = 0;
++sttsEntryCount;
}
size_t prevOffset = avifRWStreamOffset(&s);
avifRWStreamSetOffset(&s, sttsEntryCountOffset);
AVIF_CHECKRES(avifRWStreamWriteU32(&s, sttsEntryCount));
avifRWStreamSetOffset(&s, prevOffset);
avifRWStreamFinishBox(&s, stts);
avifBoxMarker stsc;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stsc", AVIF_BOX_SIZE_TBD, 0, 0, &stsc));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->encodeOutput->samples.count)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); avifRWStreamFinishBox(&s, stsc);
avifBoxMarker stsz;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stsz", AVIF_BOX_SIZE_TBD, 0, 0, &stsz));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 0)); AVIF_CHECKRES(avifRWStreamWriteU32(&s, item->encodeOutput->samples.count)); for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
AVIF_CHECKRES(avifRWStreamWriteU32(&s, (uint32_t)sample->data.size)); }
avifRWStreamFinishBox(&s, stsz);
avifBoxMarker stco;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stco", AVIF_BOX_SIZE_TBD, 0, 0, &stco));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); avifEncoderItemAddMdatFixup(item, &s); AVIF_CHECKRES(avifRWStreamWriteU32(&s, 1)); avifRWStreamFinishBox(&s, stco);
avifBoxMarker stss;
AVIF_CHECKRES(avifRWStreamWriteFullBox(&s, "stss", AVIF_BOX_SIZE_TBD, 0, 0, &stss));
AVIF_CHECKRES(avifRWStreamWriteU32(&s, syncSamplesCount)); for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
if (sample->sync) {
AVIF_CHECKRES(avifRWStreamWriteU32(&s, sampleIndex + 1)); }
}
avifRWStreamFinishBox(&s, stss);
avifRWStreamFinishBox(&s, stbl);
avifRWStreamFinishBox(&s, minf);
avifRWStreamFinishBox(&s, mdia);
avifRWStreamFinishBox(&s, trak);
}
avifRWStreamFinishBox(&s, moov);
}
avifEncoderItemReferenceArray layeredColorItems;
avifEncoderItemReferenceArray layeredAlphaItems;
avifResult result = AVIF_RESULT_OK;
if (!avifArrayCreate(&layeredColorItems, sizeof(avifEncoderItemReference), 1)) {
result = AVIF_RESULT_OUT_OF_MEMORY;
}
if (!avifArrayCreate(&layeredAlphaItems, sizeof(avifEncoderItemReference), 1)) {
result = AVIF_RESULT_OUT_OF_MEMORY;
}
if (result == AVIF_RESULT_OK) {
result = avifEncoderWriteMediaDataBox(encoder, &s, &layeredColorItems, &layeredAlphaItems);
}
avifArrayDestroy(&layeredColorItems);
avifArrayDestroy(&layeredAlphaItems);
AVIF_CHECKRES(result);
avifRWStreamFinishWrite(&s);
return AVIF_RESULT_OK;
}
avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output)
{
avifResult addImageResult = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
if (addImageResult != AVIF_RESULT_OK) {
return addImageResult;
}
return avifEncoderFinish(encoder, output);
}
static avifResult writeCodecConfig(avifRWStream * s, const avifCodecConfigurationBox * cfg)
{
AVIF_CHECKRES(avifRWStreamWriteBits(s, 1, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(s, 1, 7));
AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->seqProfile, 3)); AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->seqLevelIdx0, 5));
AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->seqTier0, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->highBitdepth, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->twelveBit, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->monochrome, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->chromaSubsamplingX, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->chromaSubsamplingY, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(s, cfg->chromaSamplePosition, 2));
AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 3)); AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 1)); AVIF_CHECKRES(avifRWStreamWriteBits(s, 0, 4));
return AVIF_RESULT_OK;
}
static avifResult writeConfigBox(avifRWStream * s, const avifCodecConfigurationBox * cfg, const char * configPropName)
{
avifBoxMarker configBox;
AVIF_CHECKRES(avifRWStreamWriteBox(s, configPropName, AVIF_BOX_SIZE_TBD, &configBox));
AVIF_CHECKRES(writeCodecConfig(s, cfg));
avifRWStreamFinishBox(s, configBox);
return AVIF_RESULT_OK;
}