blob: c87a079ffa713ad14f94da737f76ec32364bc088 [file] [log] [blame]
// Copyright 2019 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/internal.h"
#include <string.h>
// from the MIAF spec:
// ---
// Section 6.7
// "α is an alpha plane value, scaled into the range of 0 (fully transparent) to 1 (fully opaque), inclusive"
// ---
// Section 7.3.5.2
// "the sample values of the alpha plane divided by the maximum value (e.g. by 255 for 8-bit sample
// values) provides the multiplier to be used to obtain the intensity for the associated master image"
// ---
// The define AVIF_FIX_STUDIO_ALPHA detects when the alpha OBU is incorrectly using studio range
// and corrects it before returning the alpha pixels to the caller.
#define AVIF_FIX_STUDIO_ALPHA
#define AUXTYPE_SIZE 64
#define MAX_COMPATIBLE_BRANDS 32
#define MAX_ITEMS 8
#define MAX_PROPERTIES 24
// ---------------------------------------------------------------------------
// Box data structures
// ftyp
typedef struct avifFileType
{
uint8_t majorBrand[4];
uint32_t minorVersion;
uint8_t compatibleBrands[4 * MAX_COMPATIBLE_BRANDS];
int compatibleBrandsCount;
} avifFileType;
// ispe
typedef struct avifImageSpatialExtents
{
uint32_t width;
uint32_t height;
} avifImageSpatialExtents;
// auxC
typedef struct avifAuxiliaryType
{
char auxType[AUXTYPE_SIZE];
} avifAuxiliaryType;
// colr
typedef struct avifColourInformationBox
{
avifProfileFormat format;
uint8_t * icc;
size_t iccSize;
avifNclxColorProfile nclx;
} avifColourInformationBox;
// ---------------------------------------------------------------------------
// Top-level structures
// one "item" worth (all iref, iloc, iprp, etc refer to one of these)
typedef struct avifItem
{
int id;
uint8_t type[4];
uint32_t offset;
uint32_t size;
avifBool ispePresent;
avifImageSpatialExtents ispe;
avifBool auxCPresent;
avifAuxiliaryType auxC;
avifBool colrPresent;
avifColourInformationBox colr;
int thumbnailForID; // if non-zero, this item is a thumbnail for Item #{thumbnailForID}
int auxForID; // if non-zero, this item is an auxC plane for Item #{auxForID}
} avifItem;
// Temporary storage for ipco contents until they can be associated and memcpy'd to an avifItem
typedef struct avifProperty
{
uint8_t type[4];
avifImageSpatialExtents ispe;
avifAuxiliaryType auxC;
avifColourInformationBox colr;
} avifProperty;
typedef struct avifData
{
// TODO: Everything in here using a MAX_* constant is a bit lazy; it should all be dynamic
avifFileType ftyp;
avifItem items[MAX_ITEMS];
avifProperty properties[MAX_PROPERTIES];
int propertyCount;
} avifData;
int findItemID(avifData * data, int itemID)
{
if (itemID == 0) {
return -1;
}
for (int i = 0; i < MAX_ITEMS; ++i) {
if (data->items[i].id == itemID) {
return i;
}
}
for (int i = 0; i < MAX_ITEMS; ++i) {
if (data->items[i].id == 0) {
data->items[i].id = itemID;
return i;
}
}
return -1;
}
// ---------------------------------------------------------------------------
// URN
static avifBool isAlphaURN(char * urn)
{
if (!strcmp(urn, URN_ALPHA0)) return AVIF_TRUE;
if (!strcmp(urn, URN_ALPHA1)) return AVIF_TRUE;
return AVIF_FALSE;
}
// ---------------------------------------------------------------------------
// BMFF Parsing
#define BEGIN_STREAM(VARNAME, PTR, SIZE) \
avifStream VARNAME; \
avifRawData VARNAME ## _rawData = { PTR, SIZE }; \
avifStreamStart(&VARNAME, &VARNAME ## _rawData)
static avifBool avifParseItemLocationBox(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
CHECK(avifStreamReadAndEnforceVersion(&s, 0));
uint8_t offsetSizeAndLengthSize;
CHECK(avifStreamRead(&s, &offsetSizeAndLengthSize, 1));
uint8_t offsetSize = (offsetSizeAndLengthSize >> 4) & 0xf; // unsigned int(4) offset_size;
uint8_t lengthSize = (offsetSizeAndLengthSize >> 0) & 0xf; // unsigned int(4) length_size;
uint8_t baseOffsetSizeAndReserved;
CHECK(avifStreamRead(&s, &baseOffsetSizeAndReserved, 1));
uint8_t baseOffsetSize = (baseOffsetSizeAndReserved >> 4) & 0xf; // unsigned int(4) base_offset_size;
uint16_t itemCount;
CHECK(avifStreamReadU16(&s, &itemCount)); // unsigned int(16) item_count;
for (int i = 0; i < itemCount; ++i) {
uint16_t itemID; // unsigned int(16) item_ID;
CHECK(avifStreamReadU16(&s, &itemID)); //
uint16_t dataReferenceIndex; // unsigned int(16) data_reference_index;
CHECK(avifStreamReadU16(&s, &dataReferenceIndex)); //
uint64_t baseOffset; // unsigned int(base_offset_size*8) base_offset;
CHECK(avifStreamReadUX8(&s, &baseOffset, baseOffsetSize)); //
uint16_t extentCount; // unsigned int(16) extent_count;
CHECK(avifStreamReadU16(&s, &extentCount)); //
if (extentCount == 1) {
uint64_t extentOffset; // unsigned int(offset_size*8) extent_offset;
CHECK(avifStreamReadUX8(&s, &extentOffset, offsetSize));
uint64_t extentLength; // unsigned int(offset_size*8) extent_length;
CHECK(avifStreamReadUX8(&s, &extentLength, lengthSize));
int itemIndex = findItemID(data, itemID);
if (itemIndex == -1) {
return AVIF_FALSE;
}
data->items[itemIndex].id = itemID;
data->items[itemIndex].offset = (uint32_t)(baseOffset + extentOffset);
data->items[itemIndex].size = (uint32_t)extentLength;
} else {
// TODO: support more than one extent
return AVIF_FALSE;
}
}
return AVIF_TRUE;
}
static avifBool avifParseImageSpatialExtentsProperty(avifData * data, uint8_t * raw, size_t rawLen, int propertyIndex)
{
BEGIN_STREAM(s, raw, rawLen);
CHECK(avifStreamReadAndEnforceVersion(&s, 0));
CHECK(avifStreamReadU32(&s, &data->properties[propertyIndex].ispe.width));
CHECK(avifStreamReadU32(&s, &data->properties[propertyIndex].ispe.height));
return AVIF_TRUE;
}
static avifBool avifParseAuxiliaryTypeProperty(avifData * data, uint8_t * raw, size_t rawLen, int propertyIndex)
{
BEGIN_STREAM(s, raw, rawLen);
CHECK(avifStreamReadAndEnforceVersion(&s, 0));
CHECK(avifStreamReadString(&s, data->properties[propertyIndex].auxC.auxType, AUXTYPE_SIZE));
return AVIF_TRUE;
}
static avifBool avifParseColourInformationBox(avifData * data, uint8_t * raw, size_t rawLen, int propertyIndex)
{
BEGIN_STREAM(s, raw, rawLen);
data->properties[propertyIndex].colr.format = AVIF_PROFILE_FORMAT_NONE;
uint8_t colourType[4]; // unsigned int(32) colour_type;
CHECK(avifStreamRead(&s, colourType, 4));
if (!memcmp(colourType, "rICC", 4) || !memcmp(colourType, "prof", 4)) {
data->properties[propertyIndex].colr.format = AVIF_PROFILE_FORMAT_ICC;
data->properties[propertyIndex].colr.icc = avifStreamCurrent(&s);
data->properties[propertyIndex].colr.iccSize = avifStreamRemainingBytes(&s);
} else if (!memcmp(colourType, "nclx", 4)) {
CHECK(avifStreamReadU16(&s, &data->properties[propertyIndex].colr.nclx.colourPrimaries)); // unsigned int(16) colour_primaries;
CHECK(avifStreamReadU16(&s, &data->properties[propertyIndex].colr.nclx.transferCharacteristics)); // unsigned int(16) transfer_characteristics;
CHECK(avifStreamReadU16(&s, &data->properties[propertyIndex].colr.nclx.matrixCoefficients)); // unsigned int(16) matrix_coefficients;
CHECK(avifStreamRead(&s, &data->properties[propertyIndex].colr.nclx.fullRangeFlag, 1)); // unsigned int(1) full_range_flag; unsigned int(7) reserved = 0;
data->properties[propertyIndex].colr.nclx.fullRangeFlag |= 0x80;
data->properties[propertyIndex].colr.format = AVIF_PROFILE_FORMAT_NCLX;
}
return AVIF_TRUE;
}
static avifBool avifParseItemPropertyContainerBox(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
data->propertyCount = 0;
while (avifStreamHasBytesLeft(&s, 1)) {
avifBoxHeader header;
CHECK(avifStreamReadBoxHeader(&s, &header));
if (data->propertyCount >= MAX_PROPERTIES) {
return AVIF_FALSE;
}
int propertyIndex = data->propertyCount;
++data->propertyCount;
memcpy(data->properties[propertyIndex].type, header.type, 4);
if (!memcmp(header.type, "ispe", 4)) {
CHECK(avifParseImageSpatialExtentsProperty(data, avifStreamCurrent(&s), header.size, propertyIndex));
}
if (!memcmp(header.type, "auxC", 4)) {
CHECK(avifParseAuxiliaryTypeProperty(data, avifStreamCurrent(&s), header.size, propertyIndex));
}
if (!memcmp(header.type, "colr", 4)) {
CHECK(avifParseColourInformationBox(data, avifStreamCurrent(&s), header.size, propertyIndex));
}
CHECK(avifStreamSkip(&s, header.size));
}
return AVIF_TRUE;
}
static avifBool avifParseItemPropertyAssociation(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
uint8_t version;
uint8_t flags[3];
CHECK(avifStreamReadVersionAndFlags(&s, &version, flags));
avifBool propertyIndexIsU16 = (flags[2] & 0x1) ? AVIF_TRUE : AVIF_FALSE; // is flags[2] correct?
uint32_t entryCount;
CHECK(avifStreamReadU32(&s, &entryCount));
for (uint32_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) {
unsigned int itemID;
if (version < 1) {
uint16_t tmp;
CHECK(avifStreamReadU16(&s, &tmp));
itemID = tmp;
} else {
CHECK(avifStreamReadU32(&s, &itemID));
}
uint8_t associationCount;
CHECK(avifStreamRead(&s, &associationCount, 1));
for (uint8_t associationIndex = 0; associationIndex < associationCount; ++associationIndex) {
avifBool essential = AVIF_FALSE;
uint16_t propertyIndex = 0;
if (propertyIndexIsU16) {
CHECK(avifStreamReadU16(&s, &propertyIndex));
essential = (propertyIndex & 0x8000) ? AVIF_TRUE : AVIF_FALSE;
propertyIndex &= 0x7fff;
} else {
uint8_t tmp;
CHECK(avifStreamRead(&s, &tmp, 1));
essential = (tmp & 0x80) ? AVIF_TRUE : AVIF_FALSE;
propertyIndex = tmp & 0x7f;
}
if (propertyIndex == 0) {
// Not associated with any item
continue;
}
--propertyIndex; // 1-indexed
if (propertyIndex >= MAX_PROPERTIES) {
return AVIF_FALSE;
}
int itemIndex = findItemID(data, itemID);
if (itemIndex == -1) {
return AVIF_FALSE;
}
// Associate property with item
avifProperty * prop = &data->properties[propertyIndex];
if (!memcmp(prop->type, "ispe", 4)) {
data->items[itemIndex].ispePresent = AVIF_TRUE;
memcpy(&data->items[itemIndex].ispe, &prop->ispe, sizeof(avifImageSpatialExtents));
} else if (!memcmp(prop->type, "auxC", 4)) {
data->items[itemIndex].auxCPresent = AVIF_TRUE;
memcpy(&data->items[itemIndex].auxC, &prop->auxC, sizeof(avifAuxiliaryType));
} else if (!memcmp(prop->type, "colr", 4)) {
data->items[itemIndex].colrPresent = AVIF_TRUE;
memcpy(&data->items[itemIndex].colr, &prop->colr, sizeof(avifColourInformationBox));
}
}
}
return AVIF_TRUE;
}
static avifBool avifParseItemPropertiesBox(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
avifBoxHeader ipcoHeader;
CHECK(avifStreamReadBoxHeader(&s, &ipcoHeader));
if (memcmp(ipcoHeader.type, "ipco", 4) != 0) {
return AVIF_FALSE;
}
// Read all item properties inside of ItemPropertyContainerBox
CHECK(avifParseItemPropertyContainerBox(data, avifStreamCurrent(&s), ipcoHeader.size));
CHECK(avifStreamSkip(&s, ipcoHeader.size));
// Now read all ItemPropertyAssociation until the end of the box, and make associations
while (avifStreamHasBytesLeft(&s, 1)) {
avifBoxHeader ipmaHeader;
CHECK(avifStreamReadBoxHeader(&s, &ipmaHeader));
if (!memcmp(ipmaHeader.type, "ipma", 4)) {
CHECK(avifParseItemPropertyAssociation(data, avifStreamCurrent(&s), ipmaHeader.size));
} else {
// These must all be type ipma
return AVIF_FALSE;
}
CHECK(avifStreamSkip(&s, ipmaHeader.size));
}
return AVIF_TRUE;
}
static avifBool avifParseItemInfoEntry(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
CHECK(avifStreamReadAndEnforceVersion(&s, 2)); // TODO: support version > 2? 2+ is required for item_type
uint16_t itemID; // unsigned int(16) item_ID;
CHECK(avifStreamReadU16(&s, &itemID)); //
uint16_t itemProtectionIndex; // unsigned int(16) item_protection_index;
CHECK(avifStreamReadU16(&s, &itemProtectionIndex)); //
uint8_t itemType[4]; // unsigned int(32) item_type;
CHECK(avifStreamRead(&s, itemType, 4)); //
int itemIndex = findItemID(data, itemID);
if (itemIndex == -1) {
return AVIF_FALSE;
}
memcpy(data->items[itemIndex].type, itemType, sizeof(itemType));
return AVIF_TRUE;
}
static avifBool avifParseItemInfoBox(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
uint8_t version;
CHECK(avifStreamReadVersionAndFlags(&s, &version, NULL));
uint32_t entryCount;
if (version == 0) {
uint16_t tmp;
CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) entry_count;
entryCount = tmp;
} else if (version == 1) {
CHECK(avifStreamReadU32(&s, &entryCount)); // unsigned int(16) entry_count;
} else {
return AVIF_FALSE;
}
for (uint32_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) {
avifBoxHeader infeHeader;
CHECK(avifStreamReadBoxHeader(&s, &infeHeader));
if (!memcmp(infeHeader.type, "infe", 4)) {
CHECK(avifParseItemInfoEntry(data, avifStreamCurrent(&s), infeHeader.size));
} else {
// These must all be type ipma
return AVIF_FALSE;
}
CHECK(avifStreamSkip(&s, infeHeader.size));
}
return AVIF_TRUE;
}
static avifBool avifParseItemReferenceBox(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
uint8_t version;
CHECK(avifStreamReadVersionAndFlags(&s, &version, NULL));
while (avifStreamHasBytesLeft(&s, 1)) {
avifBoxHeader irefHeader;
CHECK(avifStreamReadBoxHeader(&s, &irefHeader));
uint32_t fromID = 0;
if (version == 0) {
uint16_t tmp;
CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) from_item_ID;
fromID = tmp;
} else if (version == 1) {
CHECK(avifStreamReadU32(&s, &fromID)); // unsigned int(32) from_item_ID;
} else {
// unsupported iref version, skip it
break;
}
uint16_t referenceCount = 0;
CHECK(avifStreamReadU16(&s, &referenceCount)); // unsigned int(16) reference_count;
for (uint16_t refIndex = 0; refIndex < referenceCount; ++refIndex) {
uint32_t toID = 0;
if (version == 0) {
uint16_t tmp;
CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) to_item_ID;
toID = tmp;
} else if (version == 1) {
CHECK(avifStreamReadU32(&s, &toID)); // unsigned int(32) to_item_ID;
} else {
// unsupported iref version, skip it
break;
}
// Read this reference as "{fromID} is a {irefType} for {toID}"
if (fromID && toID) {
int itemIndex = findItemID(data, fromID);
if (itemIndex == -1) {
return AVIF_FALSE;
}
if (!memcmp(irefHeader.type, "thmb", 4)) {
data->items[itemIndex].thumbnailForID = toID;
}
if (!memcmp(irefHeader.type, "auxl", 4)) {
data->items[itemIndex].auxForID = toID;
}
}
}
}
return AVIF_TRUE;
}
static avifBool avifParseMetaBox(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
CHECK(avifStreamReadAndEnforceVersion(&s, 0));
while (avifStreamHasBytesLeft(&s, 1)) {
avifBoxHeader header;
CHECK(avifStreamReadBoxHeader(&s, &header));
if (!memcmp(header.type, "iloc", 4)) {
CHECK(avifParseItemLocationBox(data, avifStreamCurrent(&s), header.size));
} else if (!memcmp(header.type, "iprp", 4)) {
CHECK(avifParseItemPropertiesBox(data, avifStreamCurrent(&s), header.size));
} else if (!memcmp(header.type, "iinf", 4)) {
CHECK(avifParseItemInfoBox(data, avifStreamCurrent(&s), header.size));
} else if (!memcmp(header.type, "iref", 4)) {
CHECK(avifParseItemReferenceBox(data, avifStreamCurrent(&s), header.size));
}
CHECK(avifStreamSkip(&s, header.size));
}
return AVIF_TRUE;
}
static avifBool avifParseFileTypeBox(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
CHECK(avifStreamRead(&s, data->ftyp.majorBrand, 4));
CHECK(avifStreamReadU32(&s, &data->ftyp.minorVersion));
size_t compatibleBrandsBytes = avifStreamRemainingBytes(&s);
if ((compatibleBrandsBytes % 4) != 0) {
return AVIF_FALSE;
}
if (compatibleBrandsBytes > (4 * MAX_COMPATIBLE_BRANDS)) {
// TODO: stop clamping and resize this
compatibleBrandsBytes = (4 * MAX_COMPATIBLE_BRANDS);
}
CHECK(avifStreamRead(&s, data->ftyp.compatibleBrands, compatibleBrandsBytes));
data->ftyp.compatibleBrandsCount = (int)compatibleBrandsBytes / 4;
return AVIF_TRUE;
}
static avifBool avifParse(avifData * data, uint8_t * raw, size_t rawLen)
{
BEGIN_STREAM(s, raw, rawLen);
while (avifStreamHasBytesLeft(&s, 1)) {
avifBoxHeader header;
CHECK(avifStreamReadBoxHeader(&s, &header));
if (!memcmp(header.type, "ftyp", 4)) {
CHECK(avifParseFileTypeBox(data, avifStreamCurrent(&s), header.size));
} else if (!memcmp(header.type, "meta", 4)) {
CHECK(avifParseMetaBox(data, avifStreamCurrent(&s), header.size));
}
CHECK(avifStreamSkip(&s, header.size));
}
return AVIF_TRUE;
}
// ---------------------------------------------------------------------------
avifResult avifImageRead(avifImage * image, avifRawData * input)
{
avifCodec * codec = NULL;
// -----------------------------------------------------------------------
// Parse BMFF boxes
avifData data;
memset(&data, 0, sizeof(data));
if (!avifParse(&data, input->data, input->size)) {
return AVIF_RESULT_BMFF_PARSE_FAILED;
}
avifBool avifCompatible = (memcmp(data.ftyp.majorBrand, "avif", 4) == 0) ? AVIF_TRUE : AVIF_FALSE;
if (!avifCompatible) {
for (int compatibleBrandIndex = 0; compatibleBrandIndex < data.ftyp.compatibleBrandsCount; ++compatibleBrandIndex) {
uint8_t * compatibleBrand = &data.ftyp.compatibleBrands[4 * compatibleBrandIndex];
if (!memcmp(compatibleBrand, "avif", 4)) {
avifCompatible = AVIF_TRUE;
break;
}
}
}
if (!avifCompatible) {
return AVIF_RESULT_INVALID_FTYP;
}
// -----------------------------------------------------------------------
avifRawData colorOBU = AVIF_RAW_DATA_EMPTY;
avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY;
avifItem * colorOBUItem = NULL;
avifItem * alphaOBUItem = NULL;
// Find the colorOBU item
for (int itemIndex = 0; itemIndex < MAX_ITEMS; ++itemIndex) {
avifItem * item = &data.items[itemIndex];
if (!item->id || !item->size) {
break;
}
if (item->offset > input->size) {
break;
}
if ((item->offset + item->size) > input->size) {
break;
}
if (memcmp(item->type, "av01", 4)) {
// probably exif or some other data
continue;
}
if (item->thumbnailForID != 0) {
// It's a thumbnail, skip it
continue;
}
colorOBUItem = item;
colorOBU.data = input->data + item->offset;
colorOBU.size = item->size;
break;
}
// Find the alphaOBU item, if any
if (colorOBUItem) {
for (int itemIndex = 0; itemIndex < MAX_ITEMS; ++itemIndex) {
avifItem * item = &data.items[itemIndex];
if (!item->id || !item->size) {
break;
}
if (item->offset > input->size) {
break;
}
if ((item->offset + item->size) > input->size) {
break;
}
if (memcmp(item->type, "av01", 4)) {
// probably exif or some other data
continue;
}
if (item->thumbnailForID != 0) {
// It's a thumbnail, skip it
continue;
}
if (isAlphaURN(item->auxC.auxType) && (item->auxForID == colorOBUItem->id)) {
alphaOBUItem = item;
alphaOBU.data = input->data + item->offset;
alphaOBU.size = item->size;
break;
}
}
}
if (colorOBU.size == 0) {
return AVIF_RESULT_NO_AV1_ITEMS_FOUND;
}
avifBool hasAlpha = (alphaOBU.size > 0) ? AVIF_TRUE : AVIF_FALSE;
codec = avifCodecCreate();
if (!avifCodecDecode(codec, AVIF_CODEC_PLANES_COLOR, &colorOBU)) {
avifCodecDestroy(codec);
return AVIF_RESULT_DECODE_COLOR_FAILED;
}
avifCodecImageSize colorPlanesSize = avifCodecGetImageSize(codec, AVIF_CODEC_PLANES_COLOR);
avifCodecImageSize alphaPlanesSize;
memset(&alphaPlanesSize, 0, sizeof(alphaPlanesSize));
if (hasAlpha) {
if (!avifCodecDecode(codec, AVIF_CODEC_PLANES_ALPHA, &alphaOBU)) {
avifCodecDestroy(codec);
return AVIF_RESULT_DECODE_ALPHA_FAILED;
}
alphaPlanesSize = avifCodecGetImageSize(codec, AVIF_CODEC_PLANES_ALPHA);
if ((colorPlanesSize.width != alphaPlanesSize.width) || (colorPlanesSize.height != alphaPlanesSize.height)) {
avifCodecDestroy(codec);
return AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH;
}
}
if ((colorOBUItem && colorOBUItem->ispePresent && ((colorOBUItem->ispe.width != colorPlanesSize.width) || (colorOBUItem->ispe.height != colorPlanesSize.height))) ||
(alphaOBUItem && alphaOBUItem->ispePresent && ((alphaOBUItem->ispe.width != alphaPlanesSize.width) || (alphaOBUItem->ispe.height != alphaPlanesSize.height))))
{
avifCodecDestroy(codec);
return AVIF_RESULT_ISPE_SIZE_MISMATCH;
}
if (colorOBUItem->colrPresent) {
if (colorOBUItem->colr.format == AVIF_PROFILE_FORMAT_ICC) {
avifImageSetProfileICC(image, colorOBUItem->colr.icc, colorOBUItem->colr.iccSize);
} else if (colorOBUItem->colr.format == AVIF_PROFILE_FORMAT_NCLX) {
avifImageSetProfileNCLX(image, &colorOBUItem->colr.nclx);
}
}
avifImageFreePlanes(image, AVIF_PLANES_ALL);
avifResult imageResult = avifCodecGetDecodedImage(codec, image);
if (imageResult != AVIF_RESULT_OK) {
avifCodecDestroy(codec);
return imageResult;
}
#if defined(AVIF_FIX_STUDIO_ALPHA)
if (hasAlpha && avifCodecAlphaLimitedRange(codec)) {
// Naughty! Alpha planes are supposed to be full range. Correct that here.
if (avifImageUsesU16(image)) {
for (int j = 0; j < image->height; ++j) {
for (int i = 0; i < image->height; ++i) {
uint16_t * alpha = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)];
*alpha = (uint16_t)avifLimitedToFull(image->depth, *alpha);
}
}
} else {
for (int j = 0; j < image->height; ++j) {
for (int i = 0; i < image->height; ++i) {
uint8_t * alpha = &image->alphaPlane[i + (j * image->alphaRowBytes)];
*alpha = (uint8_t)avifLimitedToFull(image->depth, *alpha);
}
}
}
}
#endif
// Make this optional?
avifImageYUVToRGB(image);
if (codec) {
avifCodecDestroy(codec);
}
image->ioStats.colorOBUSize = colorOBU.size;
image->ioStats.alphaOBUSize = alphaOBU.size;
return AVIF_RESULT_OK;
}