#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;
};
static void ipmaPush(struct ipmaArray * ipma, uint8_t assoc, avifBool essential)
{
ipma->associations[ipma->count] = assoc;
ipma->essential[ipma->count] = essential;
++ipma->count;
}
typedef struct avifOffsetFixup
{
size_t offset;
} avifOffsetFixup;
AVIF_ARRAY_DECLARE(avifOffsetFixupArray, avifOffsetFixup, fixup);
static const char alphaURN[] = URN_ALPHA0;
static const size_t alphaURNSize = sizeof(alphaURN);
static const char xmpContentType[] = CONTENT_TYPE_XMP;
static const size_t xmpContentTypeSize = sizeof(xmpContentType);
static avifBool avifImageIsOpaque(const avifImage * image);
static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg);
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;
}
void avifCodecEncodeOutputAddSample(avifCodecEncodeOutput * encodeOutput, const uint8_t * data, size_t len, avifBool sync)
{
avifEncodeSample * sample = (avifEncodeSample *)avifArrayPushPtr(&encodeOutput->samples);
avifRWDataSet(&sample->data, data, len);
sample->sync = sync;
}
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;
avifBool alpha;
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;
uint16_t dimgFromID;
struct ipmaArray ipma;
} avifEncoderItem;
AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item);
typedef struct avifEncoderFrame
{
uint64_t durationInTimescales;
} avifEncoderFrame;
AVIF_ARRAY_DECLARE(avifEncoderFrameArray, avifEncoderFrame, frame);
typedef struct avifEncoderData
{
avifEncoderItemArray items;
avifEncoderFrameArray frames;
avifImage * imageMetadata;
uint16_t lastItemID;
uint16_t primaryItemID;
avifBool singleImage;
avifBool alphaPresent;
} avifEncoderData;
static void avifEncoderDataDestroy(avifEncoderData * data);
static avifEncoderData * avifEncoderDataCreate()
{
avifEncoderData * data = (avifEncoderData *)avifAlloc(sizeof(avifEncoderData));
memset(data, 0, sizeof(avifEncoderData));
data->imageMetadata = avifImageCreateEmpty();
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);
}
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;
}
avifRWDataRealloc(&dedup->buffer, 2048);
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 uint8_t avifItemPropertyDedupFinish(avifItemPropertyDedup * dedup, avifRWStream * outputStream)
{
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)) {
return property->index;
}
}
avifItemProperty * property = (avifItemProperty *)avifArrayPushPtr(&dedup->properties);
property->index = ++dedup->nextIndex;
property->size = newPropertySize;
property->offset = avifRWStreamOffset(outputStream);
avifRWStreamWrite(outputStream, dedup->buffer.data, newPropertySize);
return property->index;
}
avifEncoder * avifEncoderCreate(void)
{
avifEncoder * encoder = (avifEncoder *)avifAlloc(sizeof(avifEncoder));
memset(encoder, 0, sizeof(avifEncoder));
encoder->maxThreads = 1;
encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
encoder->tileRowsLog2 = 0;
encoder->tileColsLog2 = 0;
encoder->speed = AVIF_SPEED_DEFAULT;
encoder->keyframeInterval = 0;
encoder->timescale = 1;
encoder->data = avifEncoderDataCreate();
encoder->csOptions = avifCodecSpecificOptionsCreate();
return encoder;
}
void avifEncoderDestroy(avifEncoder * encoder)
{
avifCodecSpecificOptionsDestroy(encoder->csOptions);
avifEncoderDataDestroy(encoder->data);
avifFree(encoder);
}
void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
{
avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
}
static void avifEncoderWriteColorProperties(avifRWStream * outputStream,
const avifImage * imageMetadata,
struct ipmaArray * ipma,
avifItemPropertyDedup * dedup)
{
avifRWStream * s = outputStream;
if (dedup) {
assert(ipma);
s = &dedup->s;
}
if (imageMetadata->icc.size > 0) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker colr = avifRWStreamWriteBox(s, "colr", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteChars(s, "prof", 4);
avifRWStreamWrite(s, imageMetadata->icc.data, imageMetadata->icc.size);
avifRWStreamFinishBox(s, colr);
if (dedup) {
ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
}
}
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker colr = avifRWStreamWriteBox(s, "colr", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteChars(s, "nclx", 4);
avifRWStreamWriteU16(s, imageMetadata->colorPrimaries);
avifRWStreamWriteU16(s, imageMetadata->transferCharacteristics);
avifRWStreamWriteU16(s, imageMetadata->matrixCoefficients);
avifRWStreamWriteU8(s, (imageMetadata->yuvRange == AVIF_RANGE_FULL) ? 0x80 : 0);
avifRWStreamFinishBox(s, colr);
if (dedup) {
ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_PASP) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker pasp = avifRWStreamWriteBox(s, "pasp", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteU32(s, imageMetadata->pasp.hSpacing);
avifRWStreamWriteU32(s, imageMetadata->pasp.vSpacing);
avifRWStreamFinishBox(s, pasp);
if (dedup) {
ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_FALSE);
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker clap = avifRWStreamWriteBox(s, "clap", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteU32(s, imageMetadata->clap.widthN);
avifRWStreamWriteU32(s, imageMetadata->clap.widthD);
avifRWStreamWriteU32(s, imageMetadata->clap.heightN);
avifRWStreamWriteU32(s, imageMetadata->clap.heightD);
avifRWStreamWriteU32(s, imageMetadata->clap.horizOffN);
avifRWStreamWriteU32(s, imageMetadata->clap.horizOffD);
avifRWStreamWriteU32(s, imageMetadata->clap.vertOffN);
avifRWStreamWriteU32(s, imageMetadata->clap.vertOffD);
avifRWStreamFinishBox(s, clap);
if (dedup) {
ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker irot = avifRWStreamWriteBox(s, "irot", AVIF_BOX_SIZE_TBD);
uint8_t angle = imageMetadata->irot.angle & 0x3;
avifRWStreamWrite(s, &angle, 1);
avifRWStreamFinishBox(s, irot);
if (dedup) {
ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
}
}
if (imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) {
if (dedup) {
avifItemPropertyDedupStart(dedup);
}
avifBoxMarker imir = avifRWStreamWriteBox(s, "imir", AVIF_BOX_SIZE_TBD);
uint8_t mode = imageMetadata->imir.mode & 0x1;
avifRWStreamWrite(s, &mode, 1);
avifRWStreamFinishBox(s, imir);
if (dedup) {
ipmaPush(ipma, avifItemPropertyDedupFinish(dedup, outputStream), AVIF_TRUE);
}
}
}
static void 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, "av01", 4) != 0) {
++metadataItemCount;
}
}
if (metadataItemCount == 0) {
return;
}
avifBoxMarker meta = avifRWStreamWriteFullBox(s, "meta", AVIF_BOX_SIZE_TBD, 0, 0);
avifBoxMarker hdlr = avifRWStreamWriteFullBox(s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(s, 0);
avifRWStreamWriteChars(s, "pict", 4);
avifRWStreamWriteZeros(s, 12);
avifRWStreamWriteChars(s, "libavif", 8);
avifRWStreamFinishBox(s, hdlr);
avifBoxMarker iloc = avifRWStreamWriteFullBox(s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0);
uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0);
avifRWStreamWrite(s, &offsetSizeAndLengthSize, 1);
avifRWStreamWriteZeros(s, 1);
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, "av01", 4) == 0) {
continue;
}
avifRWStreamWriteU16(s, item->id);
avifRWStreamWriteU16(s, 0);
avifRWStreamWriteU16(s, 1);
avifEncoderItemAddMdatFixup(item, s);
avifRWStreamWriteU32(s, 0 );
avifRWStreamWriteU32(s, (uint32_t)item->metadataPayload.size);
}
avifRWStreamFinishBox(s, iloc);
avifBoxMarker iinf = avifRWStreamWriteFullBox(s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0);
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, "av01", 4) == 0) {
continue;
}
avifBoxMarker infe = avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, 2, 0);
avifRWStreamWriteU16(s, item->id);
avifRWStreamWriteU16(s, 0);
avifRWStreamWrite(s, item->type, 4);
avifRWStreamWriteChars(s, item->infeName, item->infeNameSize);
if (item->infeContentType && item->infeContentTypeSize) {
avifRWStreamWriteChars(s, item->infeContentType, item->infeContentTypeSize);
}
avifRWStreamFinishBox(s, infe);
}
avifRWStreamFinishBox(s, iinf);
avifRWStreamFinishBox(s, meta);
}
static void avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uint32_t gridRows, const avifImage * firstCell)
{
uint32_t gridWidth = firstCell->width * gridCols;
uint32_t gridHeight = firstCell->height * gridRows;
uint8_t gridFlags = ((gridWidth > 65535) || (gridHeight > 65535)) ? 1 : 0;
avifRWStream s;
avifRWStreamStart(&s, data);
avifRWStreamWriteU8(&s, 0);
avifRWStreamWriteU8(&s, gridFlags);
avifRWStreamWriteU8(&s, (uint8_t)(gridRows - 1));
avifRWStreamWriteU8(&s, (uint8_t)(gridCols - 1));
if (gridFlags & 1) {
avifRWStreamWriteU32(&s, gridWidth);
avifRWStreamWriteU32(&s, gridHeight);
} else {
uint16_t tmpWidth = (uint16_t)gridWidth;
uint16_t tmpHeight = (uint16_t)gridHeight;
avifRWStreamWriteU16(&s, tmpWidth);
avifRWStreamWriteU16(&s, tmpHeight);
}
avifRWStreamFinishWrite(&s);
}
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;
}
const uint32_t cellCount = gridCols * gridRows;
if (cellCount == 0) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
const avifImage * firstCell = cellImages[0];
if ((firstCell->depth != 8) && (firstCell->depth != 10) && (firstCell->depth != 12)) {
return AVIF_RESULT_UNSUPPORTED_DEPTH;
}
if (!firstCell->width || !firstCell->height) {
return AVIF_RESULT_NO_CONTENT;
}
if ((cellCount > 1) && ((firstCell->width < 64) || (firstCell->height < 64))) {
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->width != firstCell->width) ||
(cellImage->height != firstCell->height) || (!!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->data->items.count > 0) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
}
if (durationInTimescales == 0) {
durationInTimescales = 1;
}
if (encoder->data->items.count == 0) {
avifImageCopy(encoder->data->imageMetadata, firstCell, 0);
uint16_t gridColorID = 0;
if (cellCount > 1) {
avifEncoderItem * gridColorItem = avifEncoderDataCreateItem(encoder->data, "grid", "Color", 6, 0);
avifWriteGridPayload(&gridColorItem->metadataPayload, gridCols, gridRows, firstCell);
gridColorItem->gridCols = gridCols;
gridColorItem->gridRows = gridRows;
gridColorID = gridColorItem->id;
encoder->data->primaryItemID = gridColorID;
}
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
avifEncoderItem * item = avifEncoderDataCreateItem(encoder->data, "av01", "Color", 6, cellIndex);
item->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
if (!item->codec) {
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
item->codec->csOptions = encoder->csOptions;
item->codec->diag = &encoder->diag;
if (cellCount > 1) {
item->dimgFromID = gridColorID;
} else {
encoder->data->primaryItemID = item->id;
}
}
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 gridAlphaID = 0;
if (cellCount > 1) {
avifEncoderItem * gridAlphaItem = avifEncoderDataCreateItem(encoder->data, "grid", "Alpha", 6, 0);
avifWriteGridPayload(&gridAlphaItem->metadataPayload, gridCols, gridRows, firstCell);
gridAlphaItem->alpha = AVIF_TRUE;
gridAlphaItem->irefToID = encoder->data->primaryItemID;
gridAlphaItem->irefType = "auxl";
gridAlphaItem->gridCols = gridCols;
gridAlphaItem->gridRows = gridRows;
gridAlphaID = gridAlphaItem->id;
if (encoder->data->imageMetadata->alphaPremultiplied) {
avifEncoderItem * primaryItem = avifEncoderDataFindItemByID(encoder->data, encoder->data->primaryItemID);
assert(primaryItem);
primaryItem->irefType = "prem";
primaryItem->irefToID = gridAlphaID;
}
}
for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) {
avifEncoderItem * item = avifEncoderDataCreateItem(encoder->data, "av01", "Alpha", 6, cellIndex);
item->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
if (!item->codec) {
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
item->codec->csOptions = encoder->csOptions;
item->codec->diag = &encoder->diag;
item->alpha = AVIF_TRUE;
if (cellCount > 1) {
item->dimgFromID = gridAlphaID;
} else {
item->irefToID = encoder->data->primaryItemID;
item->irefType = "auxl";
if (encoder->data->imageMetadata->alphaPremultiplied) {
avifEncoderItem * primaryItem = avifEncoderDataFindItemByID(encoder->data, encoder->data->primaryItemID);
assert(primaryItem);
primaryItem->irefType = "prem";
primaryItem->irefToID = item->id;
}
}
}
}
if (firstCell->exif.size > 0) {
uint32_t exifTiffHeaderOffset = 0;
if (firstCell->exif.size < 4) {
return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
}
const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
for (; exifTiffHeaderOffset < (firstCell->exif.size - 4); ++exifTiffHeaderOffset) {
if (!memcmp(&firstCell->exif.data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) {
break;
}
if (!memcmp(&firstCell->exif.data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) {
break;
}
}
if (exifTiffHeaderOffset >= firstCell->exif.size - 4) {
return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
}
avifEncoderItem * exifItem = avifEncoderDataCreateItem(encoder->data, "Exif", "Exif", 5, 0);
exifItem->irefToID = encoder->data->primaryItemID;
exifItem->irefType = "cdsc";
avifRWDataRealloc(&exifItem->metadataPayload, sizeof(uint32_t) + firstCell->exif.size);
exifTiffHeaderOffset = avifHTONL(exifTiffHeaderOffset);
memcpy(exifItem->metadataPayload.data, &exifTiffHeaderOffset, sizeof(uint32_t));
memcpy(exifItem->metadataPayload.data + sizeof(uint32_t), firstCell->exif.data, firstCell->exif.size);
}
if (firstCell->xmp.size > 0) {
avifEncoderItem * xmpItem = avifEncoderDataCreateItem(encoder->data, "mime", "XMP", 4, 0);
xmpItem->irefToID = encoder->data->primaryItemID;
xmpItem->irefType = "cdsc";
xmpItem->infeContentType = xmpContentType;
xmpItem->infeContentTypeSize = xmpContentTypeSize;
avifRWDataSet(&xmpItem->metadataPayload, firstCell->xmp.data, firstCell->xmp.size);
}
} else {
if (encoder->data->alphaPresent && !firstCell->alphaPlane) {
return AVIF_RESULT_ENCODE_ALPHA_FAILED;
}
}
if (encoder->keyframeInterval && ((encoder->data->frames.count % encoder->keyframeInterval) == 0)) {
addImageFlags |= AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
}
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];
avifResult encodeResult =
item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, addImageFlags, item->encodeOutput);
if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) {
encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
if (encodeResult != AVIF_RESULT_OK) {
return encodeResult;
}
}
}
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;
}
return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE);
}
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;
}
avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
{
avifDiagnosticsClearError(&encoder->diag);
if (encoder->data->items.count == 0) {
return AVIF_RESULT_NO_CONTENT;
}
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 item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
if (item->encodeOutput->samples.count != encoder->data->frames.count) {
return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
}
}
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;
if (avifSequenceHeaderParse(&sequenceHeader, (const avifROData *)&firstSample->data)) {
memcpy(&item->av1C, &sequenceHeader.av1C, sizeof(avifCodecConfigurationBox));
} else {
return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
}
}
const avifImage * imageMetadata = encoder->data->imageMetadata;
uint64_t now = (uint64_t)time(NULL) + 2082844800;
avifRWStream s;
avifRWStreamStart(&s, output);
const char * majorBrand = "avif";
if (encoder->data->frames.count > 1) {
majorBrand = "avis";
}
avifBoxMarker ftyp = avifRWStreamWriteBox(&s, "ftyp", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteChars(&s, majorBrand, 4);
avifRWStreamWriteU32(&s, 0);
avifRWStreamWriteChars(&s, "avif", 4);
if (encoder->data->frames.count > 1) {
avifRWStreamWriteChars(&s, "avis", 4);
avifRWStreamWriteChars(&s, "msf1", 4);
}
avifRWStreamWriteChars(&s, "mif1", 4);
avifRWStreamWriteChars(&s, "miaf", 4);
if ((imageMetadata->depth == 8) || (imageMetadata->depth == 10)) {
if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) {
avifRWStreamWriteChars(&s, "MA1B", 4);
} else if (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) {
avifRWStreamWriteChars(&s, "MA1A", 4);
}
}
avifRWStreamFinishBox(&s, ftyp);
avifBoxMarker meta = avifRWStreamWriteFullBox(&s, "meta", AVIF_BOX_SIZE_TBD, 0, 0);
avifBoxMarker hdlr = avifRWStreamWriteFullBox(&s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&s, 0);
avifRWStreamWriteChars(&s, "pict", 4);
avifRWStreamWriteZeros(&s, 12);
avifRWStreamWriteChars(&s, "libavif", 8);
avifRWStreamFinishBox(&s, hdlr);
if (encoder->data->primaryItemID != 0) {
avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint16_t), 0, 0);
avifRWStreamWriteU16(&s, encoder->data->primaryItemID);
}
avifBoxMarker iloc = avifRWStreamWriteFullBox(&s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0);
uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0);
avifRWStreamWrite(&s, &offsetSizeAndLengthSize, 1);
avifRWStreamWriteZeros(&s, 1);
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 contentSize = (uint32_t)item->metadataPayload.size;
if (item->encodeOutput->samples.count > 0) {
contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size;
}
avifRWStreamWriteU16(&s, item->id);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWriteU16(&s, 1);
avifEncoderItemAddMdatFixup(item, &s);
avifRWStreamWriteU32(&s, 0 );
avifRWStreamWriteU32(&s, (uint32_t)contentSize);
}
avifRWStreamFinishBox(&s, iloc);
avifBoxMarker iinf = avifRWStreamWriteFullBox(&s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0);
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];
avifBoxMarker infe = avifRWStreamWriteFullBox(&s, "infe", AVIF_BOX_SIZE_TBD, 2, 0);
avifRWStreamWriteU16(&s, item->id);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWrite(&s, item->type, 4);
avifRWStreamWriteChars(&s, item->infeName, item->infeNameSize);
if (item->infeContentType && item->infeContentTypeSize) {
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) {
iref = avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0);
}
avifBoxMarker refType = avifRWStreamWriteBox(&s, "dimg", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteU16(&s, item->id);
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) {
avifRWStreamWriteU16(&s, dimgItem->id);
}
}
avifRWStreamFinishBox(&s, refType);
}
if (item->irefToID != 0) {
if (!iref) {
iref = avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0);
}
avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD);
avifRWStreamWriteU16(&s, item->id);
avifRWStreamWriteU16(&s, 1);
avifRWStreamWriteU16(&s, item->irefToID);
avifRWStreamFinishBox(&s, refType);
}
}
if (iref) {
avifRWStreamFinishBox(&s, iref);
}
avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", AVIF_BOX_SIZE_TBD);
avifItemPropertyDedup * dedup = avifItemPropertyDedupCreate();
avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", AVIF_BOX_SIZE_TBD);
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) {
avifBool foundPreviousCell = AVIF_FALSE;
for (uint32_t dedupIndex = 0; dedupIndex < itemIndex; ++dedupIndex) {
avifEncoderItem * dedupItem = &encoder->data->items.item[dedupIndex];
if (item->dimgFromID == dedupItem->dimgFromID) {
memcpy(&item->ipma, &dedupItem->ipma, sizeof(struct ipmaArray));
foundPreviousCell = AVIF_TRUE;
break;
}
}
if (foundPreviousCell) {
continue;
}
}
uint32_t imageWidth = imageMetadata->width;
uint32_t imageHeight = imageMetadata->height;
if (isGrid) {
imageWidth = imageMetadata->width * item->gridCols;
imageHeight = imageMetadata->height * item->gridRows;
}
avifItemPropertyDedupStart(dedup);
avifBoxMarker ispe = avifRWStreamWriteFullBox(&dedup->s, "ispe", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&dedup->s, imageWidth);
avifRWStreamWriteU32(&dedup->s, imageHeight);
avifRWStreamFinishBox(&dedup->s, ispe);
ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
avifItemPropertyDedupStart(dedup);
uint8_t channelCount = (item->alpha || (imageMetadata->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) ? 1 : 3;
avifBoxMarker pixi = avifRWStreamWriteFullBox(&dedup->s, "pixi", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU8(&dedup->s, channelCount);
for (uint8_t chan = 0; chan < channelCount; ++chan) {
avifRWStreamWriteU8(&dedup->s, (uint8_t)imageMetadata->depth);
}
avifRWStreamFinishBox(&dedup->s, pixi);
ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
if (item->codec) {
avifItemPropertyDedupStart(dedup);
writeConfigBox(&dedup->s, &item->av1C);
ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_TRUE);
}
if (item->alpha) {
avifItemPropertyDedupStart(dedup);
avifBoxMarker auxC = avifRWStreamWriteFullBox(&dedup->s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteChars(&dedup->s, alphaURN, alphaURNSize);
avifRWStreamFinishBox(&dedup->s, auxC);
ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE);
} else {
avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, dedup);
}
}
avifRWStreamFinishBox(&s, ipco);
avifItemPropertyDedupDestroy(dedup);
dedup = NULL;
avifBoxMarker ipma = avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, 0, 0);
{
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;
}
}
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;
}
avifRWStreamWriteU16(&s, item->id);
avifRWStreamWriteU8(&s, item->ipma.count);
for (int i = 0; i < item->ipma.count; ++i) {
uint8_t essentialAndIndex = item->ipma.associations[i];
if (item->ipma.essential[i]) {
essentialAndIndex |= 0x80;
}
avifRWStreamWriteU8(&s, essentialAndIndex);
}
}
}
avifRWStreamFinishBox(&s, ipma);
avifRWStreamFinishBox(&s, iprp);
avifRWStreamFinishBox(&s, meta);
if (encoder->data->frames.count > 1) {
static const uint32_t unityMatrix[9] = { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 };
uint64_t durationInTimescales = 0;
for (uint32_t frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
const avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
durationInTimescales += frame->durationInTimescales;
}
avifBoxMarker moov = avifRWStreamWriteBox(&s, "moov", AVIF_BOX_SIZE_TBD);
avifBoxMarker mvhd = avifRWStreamWriteFullBox(&s, "mvhd", AVIF_BOX_SIZE_TBD, 1, 0);
avifRWStreamWriteU64(&s, now);
avifRWStreamWriteU64(&s, now);
avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale);
avifRWStreamWriteU64(&s, durationInTimescales);
avifRWStreamWriteU32(&s, 0x00010000);
avifRWStreamWriteU16(&s, 0x0100);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWriteZeros(&s, 8);
avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix));
avifRWStreamWriteZeros(&s, 24);
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 = avifRWStreamWriteBox(&s, "trak", AVIF_BOX_SIZE_TBD);
avifBoxMarker tkhd = avifRWStreamWriteFullBox(&s, "tkhd", AVIF_BOX_SIZE_TBD, 1, 1);
avifRWStreamWriteU64(&s, now);
avifRWStreamWriteU64(&s, now);
avifRWStreamWriteU32(&s, itemIndex + 1);
avifRWStreamWriteU32(&s, 0);
avifRWStreamWriteU64(&s, durationInTimescales);
avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 2);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix));
avifRWStreamWriteU32(&s, imageMetadata->width << 16);
avifRWStreamWriteU32(&s, imageMetadata->height << 16);
avifRWStreamFinishBox(&s, tkhd);
if (item->irefToID != 0) {
avifBoxMarker tref = avifRWStreamWriteBox(&s, "tref", AVIF_BOX_SIZE_TBD);
avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD);
avifRWStreamWriteU32(&s, (uint32_t)item->irefToID);
avifRWStreamFinishBox(&s, refType);
avifRWStreamFinishBox(&s, tref);
}
if (!item->alpha) {
avifEncoderWriteTrackMetaBox(encoder, &s);
}
avifBoxMarker mdia = avifRWStreamWriteBox(&s, "mdia", AVIF_BOX_SIZE_TBD);
avifBoxMarker mdhd = avifRWStreamWriteFullBox(&s, "mdhd", AVIF_BOX_SIZE_TBD, 1, 0);
avifRWStreamWriteU64(&s, now);
avifRWStreamWriteU64(&s, now);
avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale);
avifRWStreamWriteU64(&s, durationInTimescales);
avifRWStreamWriteU16(&s, 21956);
avifRWStreamWriteU16(&s, 0);
avifRWStreamFinishBox(&s, mdhd);
avifBoxMarker hdlrTrak = avifRWStreamWriteFullBox(&s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&s, 0);
avifRWStreamWriteChars(&s, "pict", 4);
avifRWStreamWriteZeros(&s, 12);
avifRWStreamWriteChars(&s, "libavif", 8);
avifRWStreamFinishBox(&s, hdlrTrak);
avifBoxMarker minf = avifRWStreamWriteBox(&s, "minf", AVIF_BOX_SIZE_TBD);
avifBoxMarker vmhd = avifRWStreamWriteFullBox(&s, "vmhd", AVIF_BOX_SIZE_TBD, 0, 1);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWriteZeros(&s, 6);
avifRWStreamFinishBox(&s, vmhd);
avifBoxMarker dinf = avifRWStreamWriteBox(&s, "dinf", AVIF_BOX_SIZE_TBD);
avifBoxMarker dref = avifRWStreamWriteFullBox(&s, "dref", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&s, 1);
avifRWStreamWriteFullBox(&s, "url ", 0, 0, 1);
avifRWStreamFinishBox(&s, dref);
avifRWStreamFinishBox(&s, dinf);
avifBoxMarker stbl = avifRWStreamWriteBox(&s, "stbl", AVIF_BOX_SIZE_TBD);
avifBoxMarker stco = avifRWStreamWriteFullBox(&s, "stco", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&s, 1);
avifEncoderItemAddMdatFixup(item, &s);
avifRWStreamWriteU32(&s, 1);
avifRWStreamFinishBox(&s, stco);
avifBoxMarker stsc = avifRWStreamWriteFullBox(&s, "stsc", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&s, 1);
avifRWStreamWriteU32(&s, 1);
avifRWStreamWriteU32(&s, item->encodeOutput->samples.count);
avifRWStreamWriteU32(&s, 1);
avifRWStreamFinishBox(&s, stsc);
avifBoxMarker stsz = avifRWStreamWriteFullBox(&s, "stsz", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&s, 0);
avifRWStreamWriteU32(&s, item->encodeOutput->samples.count);
for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
avifRWStreamWriteU32(&s, (uint32_t)sample->data.size);
}
avifRWStreamFinishBox(&s, stsz);
avifBoxMarker stss = avifRWStreamWriteFullBox(&s, "stss", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&s, syncSamplesCount);
for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
if (sample->sync) {
avifRWStreamWriteU32(&s, sampleIndex + 1);
}
}
avifRWStreamFinishBox(&s, stss);
avifBoxMarker stts = avifRWStreamWriteFullBox(&s, "stts", AVIF_BOX_SIZE_TBD, 0, 0);
size_t sttsEntryCountOffset = avifRWStreamOffset(&s);
uint32_t sttsEntryCount = 0;
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;
}
}
avifRWStreamWriteU32(&s, sampleCount);
avifRWStreamWriteU32(&s, (uint32_t)frame->durationInTimescales);
sampleCount = 0;
++sttsEntryCount;
}
size_t prevOffset = avifRWStreamOffset(&s);
avifRWStreamSetOffset(&s, sttsEntryCountOffset);
avifRWStreamWriteU32(&s, sttsEntryCount);
avifRWStreamSetOffset(&s, prevOffset);
avifRWStreamFinishBox(&s, stts);
avifBoxMarker stsd = avifRWStreamWriteFullBox(&s, "stsd", AVIF_BOX_SIZE_TBD, 0, 0);
avifRWStreamWriteU32(&s, 1);
avifBoxMarker av01 = avifRWStreamWriteBox(&s, "av01", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteZeros(&s, 6);
avifRWStreamWriteU16(&s, 1);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWriteU16(&s, 0);
avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3);
avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width);
avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height);
avifRWStreamWriteU32(&s, 0x00480000);
avifRWStreamWriteU32(&s, 0x00480000);
avifRWStreamWriteU32(&s, 0);
avifRWStreamWriteU16(&s, 1);
avifRWStreamWriteChars(&s, "\012AOM Coding", 11);
avifRWStreamWriteZeros(&s, 32 - 11);
avifRWStreamWriteU16(&s, 0x0018);
avifRWStreamWriteU16(&s, (uint16_t)0xffff);
writeConfigBox(&s, &item->av1C);
if (!item->alpha) {
avifEncoderWriteColorProperties(&s, imageMetadata, NULL, NULL);
}
avifRWStreamFinishBox(&s, av01);
avifRWStreamFinishBox(&s, stsd);
avifRWStreamFinishBox(&s, stbl);
avifRWStreamFinishBox(&s, minf);
avifRWStreamFinishBox(&s, mdia);
avifRWStreamFinishBox(&s, trak);
}
avifRWStreamFinishBox(&s, moov);
}
encoder->ioStats.colorOBUSize = 0;
encoder->ioStats.alphaOBUSize = 0;
avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", AVIF_BOX_SIZE_TBD);
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->alpha) {
continue;
}
size_t chunkOffset = 0;
if (item->encodeOutput->samples.count > 0) {
avifEncodeSample * sample = &item->encodeOutput->samples.sample[0];
chunkOffset = avifEncoderFindExistingChunk(&s, mdatStartOffset, sample->data.data, sample->data.size);
} else {
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];
avifRWStreamWrite(&s, sample->data.data, sample->data.size);
if (item->alpha) {
encoder->ioStats.alphaOBUSize += sample->data.size;
} else {
encoder->ioStats.colorOBUSize += sample->data.size;
}
}
} else {
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);
avifRWStreamWriteU32(&s, (uint32_t)chunkOffset);
avifRWStreamSetOffset(&s, prevOffset);
}
}
}
avifRWStreamFinishBox(&s, mdat);
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 avifBool avifImageIsOpaque(const avifImage * image)
{
if (!image->alphaPlane) {
return AVIF_TRUE;
}
int maxChannel = (1 << image->depth) - 1;
if (avifImageUsesU16(image)) {
for (uint32_t j = 0; j < image->height; ++j) {
for (uint32_t i = 0; i < image->width; ++i) {
uint16_t * p = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)];
if (*p != maxChannel) {
return AVIF_FALSE;
}
}
}
} else {
for (uint32_t j = 0; j < image->height; ++j) {
for (uint32_t i = 0; i < image->width; ++i) {
if (image->alphaPlane[i + (j * image->alphaRowBytes)] != maxChannel) {
return AVIF_FALSE;
}
}
}
}
return AVIF_TRUE;
}
static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg)
{
avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", AVIF_BOX_SIZE_TBD);
avifRWStreamWriteU8(s, 0x80 | 0x1);
avifRWStreamWriteU8(s, (uint8_t)((cfg->seqProfile & 0x7) << 5) | (uint8_t)(cfg->seqLevelIdx0 & 0x1f));
uint8_t bits = 0;
bits |= (cfg->seqTier0 & 0x1) << 7;
bits |= (cfg->highBitdepth & 0x1) << 6;
bits |= (cfg->twelveBit & 0x1) << 5;
bits |= (cfg->monochrome & 0x1) << 4;
bits |= (cfg->chromaSubsamplingX & 0x1) << 3;
bits |= (cfg->chromaSubsamplingY & 0x1) << 2;
bits |= (cfg->chromaSamplePosition & 0x3);
avifRWStreamWriteU8(s, bits);
avifRWStreamWriteU8(s, 0);
avifRWStreamFinishBox(s, av1C);
}