blob: 700867ee2284c122512d4c0cbfe87a1b61613eab [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];
uint8_t count;
};
static void ipmaPush(struct ipmaArray * ipma, uint8_t assoc)
{
ipma->associations[ipma->count] = assoc;
++ipma->count;
}
static const char alphaURN[] = URN_ALPHA0;
static const size_t alphaURNSize = sizeof(alphaURN);
static avifBool avifImageIsOpaque(avifImage * image);
static void writeConfigBox(avifStream * s, avifCodecConfigurationBox * cfg);
avifResult avifImageWrite(avifImage * image, avifRawData * output, int numThreads, int quality)
{
if ((image->depth != 8) && (image->depth != 10) && (image->depth != 12)) {
return AVIF_RESULT_UNSUPPORTED_DEPTH;
}
avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
avifRawData colorOBU = AVIF_RAW_DATA_EMPTY;
avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY;
avifCodec * codec = avifCodecCreate();
avifStream s;
avifStreamStart(&s, output);
// -----------------------------------------------------------------------
// Reformat pixels, if need be
if (!image->width || !image->height || !image->depth) {
result = AVIF_RESULT_NO_CONTENT;
goto writeCleanup;
}
if ((image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) || !image->yuvPlanes[AVIF_CHAN_Y] || !image->yuvPlanes[AVIF_CHAN_U] || !image->yuvPlanes[AVIF_CHAN_V]) {
if (!image->rgbPlanes[AVIF_CHAN_R] || !image->rgbPlanes[AVIF_CHAN_G] || !image->rgbPlanes[AVIF_CHAN_B]) {
result = AVIF_RESULT_NO_CONTENT;
goto writeCleanup;
}
avifImageFreePlanes(image, AVIF_PLANES_YUV);
if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
result = AVIF_RESULT_NO_YUV_FORMAT_SELECTED;
goto writeCleanup;
}
avifImageRGBToYUV(image);
}
// -----------------------------------------------------------------------
// Encode AV1 OBUs
avifRawData * alphaOBUPtr = &alphaOBU;
if (avifImageIsOpaque(image)) {
alphaOBUPtr = NULL;
}
avifResult encodeResult = avifCodecEncodeImage(codec, image, numThreads, quality, &colorOBU, alphaOBUPtr);
if (encodeResult != AVIF_RESULT_OK) {
result = encodeResult;
goto writeCleanup;
}
avifBool hasAlpha = (alphaOBU.size > 0) ? AVIF_TRUE : AVIF_FALSE;
// -----------------------------------------------------------------------
// Write ftyp
avifBoxMarker ftyp = avifStreamWriteBox(&s, "ftyp", -1, 0);
avifStreamWriteChars(&s, "mif1", 4); // unsigned int(32) major_brand;
avifStreamWriteU32(&s, 0); // unsigned int(32) minor_version;
avifStreamWriteChars(&s, "mif1", 4); // unsigned int(32) compatible_brands[];
avifStreamWriteChars(&s, "avif", 4); // ... compatible_brands[]
avifStreamWriteChars(&s, "miaf", 4); // ... compatible_brands[]
avifStreamFinishBox(&s, ftyp);
// -----------------------------------------------------------------------
// Write mdat
avifBoxMarker mdat = avifStreamWriteBox(&s, "mdat", -1, 0);
uint32_t colorOBUOffset = (uint32_t)s.offset;
avifStreamWrite(&s, colorOBU.data, colorOBU.size);
uint32_t alphaOBUOffset = (uint32_t)s.offset;
avifStreamWrite(&s, alphaOBU.data, alphaOBU.size);
avifStreamFinishBox(&s, mdat);
// -----------------------------------------------------------------------
// Start meta
avifBoxMarker meta = avifStreamWriteBox(&s, "meta", 0, 0);
// -----------------------------------------------------------------------
// Write hdlr
avifBoxMarker hdlr = avifStreamWriteBox(&s, "hdlr", 0, 0);
avifStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0;
avifStreamWriteChars(&s, "pict", 4); // unsigned int(32) handler_type;
avifStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0;
avifStreamWriteChars(&s, "libavif", 8); // string name; (writing null terminator)
avifStreamFinishBox(&s, hdlr);
// -----------------------------------------------------------------------
// Write pitm
avifStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t));
avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
// -----------------------------------------------------------------------
// Write iloc
avifBoxMarker iloc = avifStreamWriteBox(&s, "iloc", 0, 0);
// iloc header
uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size; unsigned int(4) length_size;
avifStreamWrite(&s, &offsetSizeAndLengthSize, 1); //
avifStreamWriteZeros(&s, 1); // unsigned int(4) base_offset_size; unsigned int(4) reserved;
avifStreamWriteU16(&s, hasAlpha ? 2 : 1); // unsigned int(16) item_count;
// Item ID #1 (Color OBU)
avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
avifStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
avifStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
avifStreamWriteU32(&s, colorOBUOffset); // unsigned int(offset_size*8) extent_offset;
avifStreamWriteU32(&s, (uint32_t)colorOBU.size); // unsigned int(length_size*8) extent_length;
if (hasAlpha) {
avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID;
avifStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
avifStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
avifStreamWriteU32(&s, alphaOBUOffset); // unsigned int(offset_size*8) extent_offset;
avifStreamWriteU32(&s, (uint32_t)alphaOBU.size); // unsigned int(length_size*8) extent_length;
}
avifStreamFinishBox(&s, iloc);
// -----------------------------------------------------------------------
// Write iinf
avifBoxMarker iinf = avifStreamWriteBox(&s, "iinf", 0, 0);
avifStreamWriteU16(&s, hasAlpha ? 2 : 1); // unsigned int(16) entry_count;
avifBoxMarker infe0 = avifStreamWriteBox(&s, "infe", 2, 0);
avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
avifStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index;
avifStreamWriteChars(&s, "av01", 4); // unsigned int(32) item_type;
avifStreamWriteChars(&s, "Color", 6); // string item_name; (writing null terminator)
avifStreamFinishBox(&s, infe0);
if (hasAlpha) {
avifBoxMarker infe1 = avifStreamWriteBox(&s, "infe", 2, 0);
avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID;
avifStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index;
avifStreamWriteChars(&s, "av01", 4); // unsigned int(32) item_type;
avifStreamWriteChars(&s, "Alpha", 6); // string item_name; (writing null terminator)
avifStreamFinishBox(&s, infe1);
}
avifStreamFinishBox(&s, iinf);
// -----------------------------------------------------------------------
// Write iref (auxl) for alpha, if any
if (hasAlpha) {
avifBoxMarker iref = avifStreamWriteBox(&s, "iref", 0, 0);
avifBoxMarker auxl = avifStreamWriteBox(&s, "auxl", -1, 0);
avifStreamWriteU16(&s, 2); // unsigned int(16) from_item_ID;
avifStreamWriteU16(&s, 1); // unsigned int(16) reference_count;
avifStreamWriteU16(&s, 1); // unsigned int(16) to_item_ID;
avifStreamFinishBox(&s, auxl);
avifStreamFinishBox(&s, iref);
}
// -----------------------------------------------------------------------
// Write iprp->ipco->ispe
avifBoxMarker iprp = avifStreamWriteBox(&s, "iprp", -1, 0);
{
uint8_t ipcoIndex = 0;
struct ipmaArray ipmaColor;
memset(&ipmaColor, 0, sizeof(ipmaColor));
struct ipmaArray ipmaAlpha;
memset(&ipmaAlpha, 0, sizeof(ipmaAlpha));
avifBoxMarker ipco = avifStreamWriteBox(&s, "ipco", -1, 0);
{
avifBoxMarker ispe = avifStreamWriteBox(&s, "ispe", 0, 0);
avifStreamWriteU32(&s, image->width); // unsigned int(32) image_width;
avifStreamWriteU32(&s, image->height); // unsigned int(32) image_height;
avifStreamFinishBox(&s, ispe);
++ipcoIndex;
ipmaPush(&ipmaColor, ipcoIndex); // ipma is 1-indexed, doing this afterwards is correct
ipmaPush(&ipmaAlpha, ipcoIndex); // Alpha shares the ispe prop
if (image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) {
avifBoxMarker colr = avifStreamWriteBox(&s, "colr", -1, 0);
avifStreamWriteChars(&s, "nclx", 4); // unsigned int(32) colour_type;
avifStreamWriteU16(&s, image->nclx.colourPrimaries); // unsigned int(16) colour_primaries;
avifStreamWriteU16(&s, image->nclx.transferCharacteristics); // unsigned int(16) transfer_characteristics;
avifStreamWriteU16(&s, image->nclx.matrixCoefficients); // unsigned int(16) matrix_coefficients;
avifStreamWriteU8(&s, image->nclx.fullRangeFlag & 0x80); // unsigned int(1) full_range_flag; unsigned int(7) reserved = 0;
avifStreamFinishBox(&s, colr);
++ipcoIndex;
ipmaPush(&ipmaColor, ipcoIndex);
} else if ((image->profileFormat == AVIF_PROFILE_FORMAT_ICC) && image->icc.data && (image->icc.data > 0)) {
avifBoxMarker colr = avifStreamWriteBox(&s, "colr", -1, 0);
avifStreamWriteChars(&s, "prof", 4); // unsigned int(32) colour_type;
avifStreamWrite(&s, image->icc.data, image->icc.size);
avifStreamFinishBox(&s, colr);
++ipcoIndex;
ipmaPush(&ipmaColor, ipcoIndex);
}
avifBoxMarker pixiC = avifStreamWriteBox(&s, "pixi", 0, 0);
avifStreamWriteU8(&s, 3); // unsigned int (8) num_channels;
avifStreamWriteU8(&s, image->depth); // unsigned int (8) bits_per_channel;
avifStreamWriteU8(&s, image->depth); // unsigned int (8) bits_per_channel;
avifStreamWriteU8(&s, image->depth); // unsigned int (8) bits_per_channel;
avifStreamFinishBox(&s, pixiC);
++ipcoIndex;
ipmaPush(&ipmaColor, ipcoIndex);
avifCodecConfigurationBox colorConfig;
avifCodecGetConfigurationBox(codec, AVIF_CODEC_PLANES_COLOR, &colorConfig);
writeConfigBox(&s, &colorConfig);
++ipcoIndex;
ipmaPush(&ipmaColor, ipcoIndex);
if (hasAlpha) {
avifBoxMarker pixiA = avifStreamWriteBox(&s, "pixi", 0, 0);
avifStreamWriteU8(&s, 1); // unsigned int (8) num_channels;
avifStreamWriteU8(&s, image->depth); // unsigned int (8) bits_per_channel;
avifStreamFinishBox(&s, pixiA);
++ipcoIndex;
ipmaPush(&ipmaAlpha, ipcoIndex);
avifCodecConfigurationBox alphaConfig;
avifCodecGetConfigurationBox(codec, AVIF_CODEC_PLANES_ALPHA, &alphaConfig);
writeConfigBox(&s, &alphaConfig);
++ipcoIndex;
ipmaPush(&ipmaAlpha, ipcoIndex);
avifBoxMarker auxC = avifStreamWriteBox(&s, "auxC", 0, 0);
avifStreamWriteChars(&s, alphaURN, alphaURNSize); // string aux_type;
avifStreamFinishBox(&s, auxC);
++ipcoIndex;
ipmaPush(&ipmaAlpha, ipcoIndex);
}
}
avifStreamFinishBox(&s, ipco);
avifBoxMarker ipma = avifStreamWriteBox(&s, "ipma", 0, 0);
{
int ipmaCount = hasAlpha ? 2 : 1;
avifStreamWriteU32(&s, ipmaCount); // unsigned int(32) entry_count;
avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
avifStreamWriteU8(&s, ipmaColor.count); // unsigned int(8) association_count;
for (int i = 0; i < ipmaColor.count; ++i) {
avifStreamWriteU8(&s, ipmaColor.associations[i]); // bit(1) essential; unsigned int(7) property_index;
}
if (hasAlpha) {
avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID;
avifStreamWriteU8(&s, ipmaAlpha.count); // unsigned int(8) association_count;
for (int i = 0; i < ipmaAlpha.count; ++i) {
avifStreamWriteU8(&s, ipmaAlpha.associations[i]); // bit(1) essential; unsigned int(7) property_index;
}
}
}
avifStreamFinishBox(&s, ipma);
}
avifStreamFinishBox(&s, iprp);
// -----------------------------------------------------------------------
// Finish up stream
avifStreamFinishBox(&s, meta);
avifStreamFinishWrite(&s);
// -----------------------------------------------------------------------
// IO stats
image->ioStats.colorOBUSize = colorOBU.size;
image->ioStats.alphaOBUSize = alphaOBU.size;
result = AVIF_RESULT_OK;
// -----------------------------------------------------------------------
// Cleanup
writeCleanup:
if (codec) {
avifCodecDestroy(codec);
}
avifRawDataFree(&colorOBU);
avifRawDataFree(&alphaOBU);
return result;
}
static avifBool avifImageIsOpaque(avifImage * image)
{
if (!image->alphaPlane) {
return AVIF_TRUE;
}
int maxChannel = (1 << image->depth) - 1;
if (avifImageUsesU16(image)) {
for (int j = 0; j < image->height; ++j) {
for (int 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 (int j = 0; j < image->height; ++j) {
for (int i = 0; i < image->width; ++i) {
if (image->alphaPlane[i + (j * image->alphaRowBytes)] != maxChannel) {
return AVIF_FALSE;
}
}
}
}
return AVIF_TRUE;
}
static void writeConfigBox(avifStream * s, avifCodecConfigurationBox * cfg)
{
avifBoxMarker av1C = avifStreamWriteBox(s, "av1C", -1, 0);
// unsigned int (1) marker = 1;
// unsigned int (7) version = 1;
avifStreamWriteU8(s, 0x80 | 0x1);
// unsigned int (3) seq_profile;
// unsigned int (5) seq_level_idx_0;
avifStreamWriteU8(s, ((cfg->seqProfile & 0x7) << 5) | (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;
avifStreamWriteU8(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;
// }
avifStreamWriteU8(s, 0);
avifStreamFinishBox(s, av1C);
}