blob: 205df62f9d661796c12b6ab9e8a0f2e4acf52f97 [file] [log] [blame]
// Copyright 2019 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/internal.h"
#include <string.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;
}
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(avifImage * image);
static void fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha);
static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg);
// ---------------------------------------------------------------------------
// avifEncoderItem
// one "item" worth for encoder
typedef struct avifEncoderItem
{
uint16_t id;
uint8_t type[4];
avifImage * image; // avifImage* to use when encoding or populating ipma for this item (unowned)
avifCodec * codec; // only present on type==av01
avifRWData content; // OBU data on av01, metadata payload for Exif/XMP
avifBool alpha;
const char * infeName;
size_t infeNameSize;
const char * infeContentType;
size_t infeContentTypeSize;
size_t infeOffsetOffset; // Stream offset where infe offset was written, so it can be properly set after mdat is written
uint16_t irefToID; // if non-zero, make an iref from this id -> irefToID
const char * irefType;
struct ipmaArray ipma;
} avifEncoderItem;
AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item);
// ---------------------------------------------------------------------------
// avifEncoderData
typedef struct avifEncoderData
{
avifEncoderItemArray items;
uint16_t lastItemID;
uint16_t primaryItemID;
} avifEncoderData;
static avifEncoderData * avifEncoderDataCreate()
{
avifEncoderData * data = (avifEncoderData *)avifAlloc(sizeof(avifEncoderData));
memset(data, 0, sizeof(avifEncoderData));
avifArrayCreate(&data->items, sizeof(avifEncoderItem), 8);
return data;
}
static avifEncoderItem * avifEncoderDataCreateItem(avifEncoderData * data, const char * type, const char * infeName, size_t infeNameSize)
{
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;
return item;
}
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);
}
avifRWDataFree(&item->content);
}
avifArrayDestroy(&data->items);
avifFree(data);
}
// ---------------------------------------------------------------------------
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->data = avifEncoderDataCreate();
return encoder;
}
void avifEncoderDestroy(avifEncoder * encoder)
{
avifEncoderDataDestroy(encoder->data);
avifFree(encoder);
}
avifResult avifEncoderWrite(avifEncoder * encoder, avifImage * image, avifRWData * output)
{
if ((image->depth != 8) && (image->depth != 10) && (image->depth != 12)) {
return AVIF_RESULT_UNSUPPORTED_DEPTH;
}
avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
avifEncoderItem * colorItem = avifEncoderDataCreateItem(encoder->data, "av01", "Color", 6);
colorItem->image = image;
colorItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
if (!colorItem->codec) {
// Just bail out early, we're not surviving this function without an encoder compiled in
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
encoder->data->primaryItemID = colorItem->id;
avifBool imageIsOpaque = avifImageIsOpaque(image);
if (!imageIsOpaque) {
avifEncoderItem * alphaItem = avifEncoderDataCreateItem(encoder->data, "av01", "Alpha", 6);
alphaItem->image = image;
alphaItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
if (!alphaItem->codec) {
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
alphaItem->alpha = AVIF_TRUE;
alphaItem->irefToID = encoder->data->primaryItemID;
alphaItem->irefType = "auxl";
}
// -----------------------------------------------------------------------
// Create metadata items (Exif, XMP)
if (image->exif.size > 0) {
// Validate Exif payload (if any) and find TIFF header offset
uint32_t exifTiffHeaderOffset = 0;
if (image->exif.size > 0) {
if (image->exif.size < 4) {
// Can't even fit the TIFF header, something is wrong
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 < (image->exif.size - 4); ++exifTiffHeaderOffset) {
if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderBE, sizeof(tiffHeaderBE))) {
break;
}
if (!memcmp(&image->exif.data[exifTiffHeaderOffset], tiffHeaderLE, sizeof(tiffHeaderLE))) {
break;
}
}
if (exifTiffHeaderOffset >= image->exif.size - 4) {
// Couldn't find the TIFF header
return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
}
}
avifEncoderItem * exifItem = avifEncoderDataCreateItem(encoder->data, "Exif", "Exif", 5);
exifItem->irefToID = encoder->data->primaryItemID;
exifItem->irefType = "cdsc";
avifRWDataRealloc(&exifItem->content, sizeof(uint32_t) + image->exif.size);
exifTiffHeaderOffset = avifHTONL(exifTiffHeaderOffset);
memcpy(exifItem->content.data, &exifTiffHeaderOffset, sizeof(uint32_t));
memcpy(exifItem->content.data + sizeof(uint32_t), image->exif.data, image->exif.size);
}
if (image->xmp.size > 0) {
avifEncoderItem * xmpItem = avifEncoderDataCreateItem(encoder->data, "mime", "XMP", 4);
xmpItem->irefToID = encoder->data->primaryItemID;
xmpItem->irefType = "cdsc";
xmpItem->infeContentType = xmpContentType;
xmpItem->infeContentTypeSize = xmpContentTypeSize;
avifRWDataSet(&xmpItem->content, image->xmp.data, image->xmp.size);
}
// -----------------------------------------------------------------------
// Pre-fill config boxes based on image (codec can query/update later)
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->codec && item->image) {
fillConfigBox(item->codec, item->image, item->alpha);
}
}
// -----------------------------------------------------------------------
// Begin write stream
avifRWStream s;
avifRWStreamStart(&s, output);
// -----------------------------------------------------------------------
// Validate image
if (!image->width || !image->height || !image->yuvPlanes[AVIF_CHAN_Y]) {
result = AVIF_RESULT_NO_CONTENT;
goto writeCleanup;
}
if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
result = AVIF_RESULT_NO_YUV_FORMAT_SELECTED;
goto writeCleanup;
}
// -----------------------------------------------------------------------
// Encode AV1 OBUs
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->codec && item->image) {
if (!item->codec->encodeImage(item->codec, item->image, encoder, &item->content, item->alpha)) {
result = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
goto writeCleanup;
}
// TODO: rethink this if/when image grid encoding support is added
if (item->alpha) {
encoder->ioStats.alphaOBUSize = item->content.size;
} else {
encoder->ioStats.colorOBUSize = item->content.size;
}
}
}
// -----------------------------------------------------------------------
// Write ftyp
avifBoxMarker ftyp = avifRWStreamWriteBox(&s, "ftyp", -1, 0);
avifRWStreamWriteChars(&s, "avif", 4); // unsigned int(32) major_brand;
avifRWStreamWriteU32(&s, 0); // unsigned int(32) minor_version;
avifRWStreamWriteChars(&s, "avif", 4); // unsigned int(32) compatible_brands[];
avifRWStreamWriteChars(&s, "mif1", 4); // ... compatible_brands[]
avifRWStreamWriteChars(&s, "miaf", 4); // ... compatible_brands[]
if ((image->depth == 8) || (image->depth == 10)) { //
if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { //
avifRWStreamWriteChars(&s, "MA1B", 4); // ... compatible_brands[]
} else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) { //
avifRWStreamWriteChars(&s, "MA1A", 4); // ... compatible_brands[]
}
}
avifRWStreamFinishBox(&s, ftyp);
// -----------------------------------------------------------------------
// Start meta
avifBoxMarker meta = avifRWStreamWriteBox(&s, "meta", 0, 0);
// -----------------------------------------------------------------------
// Write hdlr
avifBoxMarker hdlr = avifRWStreamWriteBox(&s, "hdlr", 0, 0);
avifRWStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0;
avifRWStreamWriteChars(&s, "pict", 4); // unsigned int(32) handler_type;
avifRWStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0;
avifRWStreamWriteChars(&s, "libavif", 8); // string name; (writing null terminator)
avifRWStreamFinishBox(&s, hdlr);
// -----------------------------------------------------------------------
// Write pitm
if (encoder->data->primaryItemID != 0) {
avifRWStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t));
avifRWStreamWriteU16(&s, encoder->data->primaryItemID); // unsigned int(16) item_ID;
}
// -----------------------------------------------------------------------
// Write iloc
avifBoxMarker iloc = avifRWStreamWriteBox(&s, "iloc", 0, 0);
uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size;
// unsigned int(4) length_size;
avifRWStreamWrite(&s, &offsetSizeAndLengthSize, 1); //
avifRWStreamWriteZeros(&s, 1); // unsigned int(4) base_offset_size;
// unsigned int(4) reserved;
avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); // unsigned int(16) item_count;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID;
avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
item->infeOffsetOffset = avifRWStreamOffset(&s); //
avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset;
avifRWStreamWriteU32(&s, (uint32_t)item->content.size); // unsigned int(length_size*8) extent_length;
}
avifRWStreamFinishBox(&s, iloc);
// -----------------------------------------------------------------------
// Write iinf
avifBoxMarker iinf = avifRWStreamWriteBox(&s, "iinf", 0, 0);
avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); // unsigned int(16) entry_count;
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
avifBoxMarker infe = avifRWStreamWriteBox(&s, "infe", 2, 0);
avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID;
avifRWStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index;
avifRWStreamWrite(&s, item->type, 4); // unsigned int(32) item_type;
avifRWStreamWriteChars(&s, item->infeName, item->infeNameSize); // string item_name; (writing null terminator)
if (item->infeContentType && item->infeContentTypeSize) { // string content_type; (writing null terminator)
avifRWStreamWriteChars(&s, item->infeContentType, item->infeContentTypeSize);
}
avifRWStreamFinishBox(&s, infe);
}
avifRWStreamFinishBox(&s, iinf);
// -----------------------------------------------------------------------
// Write iref boxes
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->irefToID != 0) {
avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0);
avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, -1, 0);
avifRWStreamWriteU16(&s, item->id); // unsigned int(16) from_item_ID;
avifRWStreamWriteU16(&s, 1); // unsigned int(16) reference_count;
avifRWStreamWriteU16(&s, item->irefToID); // unsigned int(16) to_item_ID;
avifRWStreamFinishBox(&s, refType);
avifRWStreamFinishBox(&s, iref);
}
}
// -----------------------------------------------------------------------
// Write iprp -> ipco/ipma
avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", -1, 0);
uint8_t itemPropertyIndex = 0;
avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", -1, 0);
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
memset(&item->ipma, 0, sizeof(item->ipma));
if (!item->image || !item->codec) {
// No ipma to write for this item
continue;
}
// Properties all av01 items need
avifBoxMarker ispe = avifRWStreamWriteBox(&s, "ispe", 0, 0);
avifRWStreamWriteU32(&s, item->image->width); // unsigned int(32) image_width;
avifRWStreamWriteU32(&s, item->image->height); // unsigned int(32) image_height;
avifRWStreamFinishBox(&s, ispe);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE); // ipma is 1-indexed, doing this afterwards is correct
uint8_t channelCount = item->alpha ? 1 : 3; // TODO: write the correct value here when adding monochrome support
avifBoxMarker pixi = avifRWStreamWriteBox(&s, "pixi", 0, 0);
avifRWStreamWriteU8(&s, channelCount); // unsigned int (8) num_channels;
for (uint8_t chan = 0; chan < channelCount; ++chan) {
avifRWStreamWriteU8(&s, (uint8_t)item->image->depth); // unsigned int (8) bits_per_channel;
}
avifRWStreamFinishBox(&s, pixi);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
writeConfigBox(&s, &item->codec->configBox);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
if (item->alpha) {
// Alpha specific properties
avifBoxMarker auxC = avifRWStreamWriteBox(&s, "auxC", 0, 0);
avifRWStreamWriteChars(&s, alphaURN, alphaURNSize); // string aux_type;
avifRWStreamFinishBox(&s, auxC);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
} else {
// Color specific properties
if (item->image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) {
avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0);
avifRWStreamWriteChars(&s, "nclx", 4); // unsigned int(32) colour_type;
avifRWStreamWriteU16(&s, (uint16_t)item->image->nclx.colourPrimaries); // unsigned int(16) colour_primaries;
avifRWStreamWriteU16(&s, (uint16_t)item->image->nclx.transferCharacteristics); // unsigned int(16) transfer_characteristics;
avifRWStreamWriteU16(&s, (uint16_t)item->image->nclx.matrixCoefficients); // unsigned int(16) matrix_coefficients;
avifRWStreamWriteU8(&s, item->image->nclx.range & 0x80); // unsigned int(1) full_range_flag;
// unsigned int(7) reserved = 0;
avifRWStreamFinishBox(&s, colr);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
} else if ((item->image->profileFormat == AVIF_PROFILE_FORMAT_ICC) && item->image->icc.data && (item->image->icc.size > 0)) {
avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0);
avifRWStreamWriteChars(&s, "prof", 4); // unsigned int(32) colour_type;
avifRWStreamWrite(&s, item->image->icc.data, item->image->icc.size);
avifRWStreamFinishBox(&s, colr);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
}
// Write (Optional) Transformations
if (item->image->transformFlags & AVIF_TRANSFORM_PASP) {
avifBoxMarker pasp = avifRWStreamWriteBox(&s, "pasp", -1, 0);
avifRWStreamWriteU32(&s, item->image->pasp.hSpacing); // unsigned int(32) hSpacing;
avifRWStreamWriteU32(&s, item->image->pasp.vSpacing); // unsigned int(32) vSpacing;
avifRWStreamFinishBox(&s, pasp);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
}
if (item->image->transformFlags & AVIF_TRANSFORM_CLAP) {
avifBoxMarker clap = avifRWStreamWriteBox(&s, "clap", -1, 0);
avifRWStreamWriteU32(&s, item->image->clap.widthN); // unsigned int(32) cleanApertureWidthN;
avifRWStreamWriteU32(&s, item->image->clap.widthD); // unsigned int(32) cleanApertureWidthD;
avifRWStreamWriteU32(&s, item->image->clap.heightN); // unsigned int(32) cleanApertureHeightN;
avifRWStreamWriteU32(&s, item->image->clap.heightD); // unsigned int(32) cleanApertureHeightD;
avifRWStreamWriteU32(&s, item->image->clap.horizOffN); // unsigned int(32) horizOffN;
avifRWStreamWriteU32(&s, item->image->clap.horizOffD); // unsigned int(32) horizOffD;
avifRWStreamWriteU32(&s, item->image->clap.vertOffN); // unsigned int(32) vertOffN;
avifRWStreamWriteU32(&s, item->image->clap.vertOffD); // unsigned int(32) vertOffD;
avifRWStreamFinishBox(&s, clap);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
}
if (item->image->transformFlags & AVIF_TRANSFORM_IROT) {
avifBoxMarker irot = avifRWStreamWriteBox(&s, "irot", -1, 0);
uint8_t angle = item->image->irot.angle & 0x3;
avifRWStreamWrite(&s, &angle, 1); // unsigned int (6) reserved = 0; unsigned int (2) angle;
avifRWStreamFinishBox(&s, irot);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
}
if (item->image->transformFlags & AVIF_TRANSFORM_IMIR) {
avifBoxMarker imir = avifRWStreamWriteBox(&s, "imir", -1, 0);
uint8_t axis = item->image->imir.axis & 0x1;
avifRWStreamWrite(&s, &axis, 1); // unsigned int (7) reserved = 0; unsigned int (1) axis;
avifRWStreamFinishBox(&s, imir);
ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
}
}
}
avifRWStreamFinishBox(&s, ipco);
avifBoxMarker ipma = avifRWStreamWriteBox(&s, "ipma", 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); // unsigned int(32) entry_count;
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); // unsigned int(16) item_ID;
avifRWStreamWriteU8(&s, item->ipma.count); // unsigned int(8) association_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); // bit(1) essential; unsigned int(7) property_index;
}
}
}
avifRWStreamFinishBox(&s, ipma);
avifRWStreamFinishBox(&s, iprp);
// -----------------------------------------------------------------------
// Finish meta box
avifRWStreamFinishBox(&s, meta);
// -----------------------------------------------------------------------
// Write mdat
avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", -1, 0);
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->content.size == 0) {
continue;
}
uint32_t infeOffset = (uint32_t)s.offset;
avifRWStreamWrite(&s, item->content.data, item->content.size);
if (item->infeOffsetOffset != 0) {
size_t prevOffset = avifRWStreamOffset(&s);
avifRWStreamSetOffset(&s, item->infeOffsetOffset);
avifRWStreamWriteU32(&s, infeOffset);
avifRWStreamSetOffset(&s, prevOffset);
}
}
avifRWStreamFinishBox(&s, mdat);
// -----------------------------------------------------------------------
// Finish up stream
avifRWStreamFinishWrite(&s);
// -----------------------------------------------------------------------
// Set result and cleanup
result = AVIF_RESULT_OK;
writeCleanup:
return result;
}
static avifBool avifImageIsOpaque(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 fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha)
{
avifPixelFormatInfo formatInfo;
avifGetPixelFormatInfo(image->yuvFormat, &formatInfo);
// Profile 0. 8-bit and 10-bit 4:2:0 and 4:0:0 only.
// Profile 1. 8-bit and 10-bit 4:4:4
// Profile 2. 8-bit and 10-bit 4:2:2
// 12-bit 4:0:0, 4:2:2 and 4:4:4
uint8_t seqProfile = 0;
if (image->depth == 12) {
// Only seqProfile 2 can handle 12 bit
seqProfile = 2;
} else {
// 8-bit or 10-bit
if (alpha) {
seqProfile = 0;
} else {
switch (image->yuvFormat) {
case AVIF_PIXEL_FORMAT_YUV444:
seqProfile = 1;
break;
case AVIF_PIXEL_FORMAT_YUV422:
seqProfile = 2;
break;
case AVIF_PIXEL_FORMAT_YUV420:
seqProfile = 0;
break;
case AVIF_PIXEL_FORMAT_YV12:
seqProfile = 0;
break;
case AVIF_PIXEL_FORMAT_NONE:
default:
break;
}
}
}
// TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf
uint8_t seqLevelIdx0 = 31;
if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) {
// Image is 5.1 compatible
seqLevelIdx0 = 13; // 5.1
}
memset(&codec->configBox, 0, sizeof(avifCodecConfigurationBox));
codec->configBox.seqProfile = seqProfile;
codec->configBox.seqLevelIdx0 = seqLevelIdx0;
codec->configBox.seqTier0 = 0;
codec->configBox.highBitdepth = (image->depth > 8) ? 1 : 0;
codec->configBox.twelveBit = (image->depth == 12) ? 1 : 0;
codec->configBox.monochrome = alpha ? 1 : 0;
codec->configBox.chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX;
codec->configBox.chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY;
// TODO: choose the correct one from below:
// * 0 - CSP_UNKNOWN Unknown (in this case the source video transfer function must be signaled outside the AV1 bitstream)
// * 1 - CSP_VERTICAL Horizontally co-located with (0, 0) luma sample, vertical position in the middle between two luma samples
// * 2 - CSP_COLOCATED co-located with (0, 0) luma sample
// * 3 - CSP_RESERVED
codec->configBox.chromaSamplePosition = 0;
}
static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg)
{
avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", -1, 0);
// unsigned int (1) marker = 1;
// unsigned int (7) version = 1;
avifRWStreamWriteU8(s, 0x80 | 0x1);
// unsigned int (3) seq_profile;
// unsigned int (5) seq_level_idx_0;
avifRWStreamWriteU8(s, (uint8_t)((cfg->seqProfile & 0x7) << 5) | (uint8_t)(cfg->seqLevelIdx0 & 0x1f));
uint8_t bits = 0;
bits |= (cfg->seqTier0 & 0x1) << 7; // unsigned int (1) seq_tier_0;
bits |= (cfg->highBitdepth & 0x1) << 6; // unsigned int (1) high_bitdepth;
bits |= (cfg->twelveBit & 0x1) << 5; // unsigned int (1) twelve_bit;
bits |= (cfg->monochrome & 0x1) << 4; // unsigned int (1) monochrome;
bits |= (cfg->chromaSubsamplingX & 0x1) << 3; // unsigned int (1) chroma_subsampling_x;
bits |= (cfg->chromaSubsamplingY & 0x1) << 2; // unsigned int (1) chroma_subsampling_y;
bits |= (cfg->chromaSamplePosition & 0x3); // unsigned int (2) chroma_sample_position;
avifRWStreamWriteU8(s, bits);
// unsigned int (3) reserved = 0;
// unsigned int (1) initial_presentation_delay_present;
// if (initial_presentation_delay_present) {
// unsigned int (4) initial_presentation_delay_minus_one;
// } else {
// unsigned int (4) reserved = 0;
// }
avifRWStreamWriteU8(s, 0);
avifRWStreamFinishBox(s, av1C);
}