| // 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)avifLimitedToFullY(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)avifLimitedToFullY(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; | |
| } |