| // Copyright 2019 Joe Drago. All rights reserved. |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include "avif/internal.h" |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <math.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #define AUXTYPE_SIZE 64 |
| #define CONTENTTYPE_SIZE 64 |
| |
| // class VisualSampleEntry(codingname) extends SampleEntry(codingname) { |
| // unsigned int(16) pre_defined = 0; |
| // const unsigned int(16) reserved = 0; |
| // unsigned int(32)[3] pre_defined = 0; |
| // unsigned int(16) width; |
| // unsigned int(16) height; |
| // template unsigned int(32) horizresolution = 0x00480000; // 72 dpi |
| // template unsigned int(32) vertresolution = 0x00480000; // 72 dpi |
| // const unsigned int(32) reserved = 0; |
| // template unsigned int(16) frame_count = 1; |
| // string[32] compressorname; |
| // template unsigned int(16) depth = 0x0018; |
| // int(16) pre_defined = -1; |
| // // other boxes from derived specifications |
| // CleanApertureBox clap; // optional |
| // PixelAspectRatioBox pasp; // optional |
| // } |
| static const size_t VISUALSAMPLEENTRY_SIZE = 78; |
| |
| // The only supported ipma box values for both version and flags are [0,1], so there technically |
| // can't be more than 4 unique tuples right now. |
| #define MAX_IPMA_VERSION_AND_FLAGS_SEEN 4 |
| |
| // --------------------------------------------------------------------------- |
| // AVIF codec type (AV1 or AV2) |
| |
| static avifCodecType avifGetCodecType(const uint8_t * fourcc) |
| { |
| if (!memcmp(fourcc, "av01", 4)) { |
| return AVIF_CODEC_TYPE_AV1; |
| } |
| #if defined(AVIF_CODEC_AVM) |
| if (!memcmp(fourcc, "av02", 4)) { |
| return AVIF_CODEC_TYPE_AV2; |
| } |
| #endif |
| return AVIF_CODEC_TYPE_UNKNOWN; |
| } |
| |
| static const char * avifGetConfigurationPropertyName(avifCodecType codecType) |
| { |
| switch (codecType) { |
| case AVIF_CODEC_TYPE_AV1: |
| return "av1C"; |
| #if defined(AVIF_CODEC_AVM) |
| case AVIF_CODEC_TYPE_AV2: |
| return "av2C"; |
| #endif |
| default: |
| assert(AVIF_FALSE); |
| return NULL; |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Box data structures |
| |
| // ftyp |
| typedef struct avifFileType |
| { |
| uint8_t majorBrand[4]; |
| uint32_t minorVersion; |
| // If not null, points to a memory block of 4 * compatibleBrandsCount bytes. |
| const uint8_t * compatibleBrands; |
| int compatibleBrandsCount; |
| } avifFileType; |
| |
| // ispe |
| typedef struct avifImageSpatialExtents |
| { |
| uint32_t width; |
| uint32_t height; |
| } avifImageSpatialExtents; |
| |
| // auxC |
| typedef struct avifAuxiliaryType |
| { |
| char auxType[AUXTYPE_SIZE]; |
| } avifAuxiliaryType; |
| |
| // infe mime content_type |
| typedef struct avifContentType |
| { |
| char contentType[CONTENTTYPE_SIZE]; |
| } avifContentType; |
| |
| // colr |
| typedef struct avifColourInformationBox |
| { |
| avifBool hasICC; |
| uint64_t iccOffset; |
| size_t iccSize; |
| |
| avifBool hasNCLX; |
| avifColorPrimaries colorPrimaries; |
| avifTransferCharacteristics transferCharacteristics; |
| avifMatrixCoefficients matrixCoefficients; |
| avifRange range; |
| } avifColourInformationBox; |
| |
| #define MAX_PIXI_PLANE_DEPTHS 4 |
| typedef struct avifPixelInformationProperty |
| { |
| uint8_t planeDepths[MAX_PIXI_PLANE_DEPTHS]; |
| uint8_t planeCount; |
| } avifPixelInformationProperty; |
| |
| typedef struct avifOperatingPointSelectorProperty |
| { |
| uint8_t opIndex; |
| } avifOperatingPointSelectorProperty; |
| |
| typedef struct avifLayerSelectorProperty |
| { |
| uint16_t layerID; |
| } avifLayerSelectorProperty; |
| |
| typedef struct avifAV1LayeredImageIndexingProperty |
| { |
| uint32_t layerSize[3]; |
| } avifAV1LayeredImageIndexingProperty; |
| |
| // --------------------------------------------------------------------------- |
| // Top-level structures |
| |
| struct avifMeta; |
| |
| // Temporary storage for ipco/stsd contents until they can be associated and memcpy'd to an avifDecoderItem |
| typedef struct avifProperty |
| { |
| uint8_t type[4]; |
| union |
| { |
| avifImageSpatialExtents ispe; |
| avifAuxiliaryType auxC; |
| avifColourInformationBox colr; |
| avifCodecConfigurationBox av1C; // TODO(yguyon): Rename or add av2C |
| avifPixelAspectRatioBox pasp; |
| avifCleanApertureBox clap; |
| avifImageRotation irot; |
| avifImageMirror imir; |
| avifPixelInformationProperty pixi; |
| avifOperatingPointSelectorProperty a1op; |
| avifLayerSelectorProperty lsel; |
| avifAV1LayeredImageIndexingProperty a1lx; |
| avifContentLightLevelInformationBox clli; |
| } u; |
| } avifProperty; |
| AVIF_ARRAY_DECLARE(avifPropertyArray, avifProperty, prop); |
| |
| // Finds the first property of a given type. |
| static const avifProperty * avifPropertyArrayFind(const avifPropertyArray * properties, const char * type) |
| { |
| for (uint32_t propertyIndex = 0; propertyIndex < properties->count; ++propertyIndex) { |
| const avifProperty * prop = &properties->prop[propertyIndex]; |
| if (!memcmp(prop->type, type, 4)) { |
| return prop; |
| } |
| } |
| return NULL; |
| } |
| |
| AVIF_ARRAY_DECLARE(avifExtentArray, avifExtent, extent); |
| |
| // one "item" worth for decoding (all iref, iloc, iprp, etc refer to one of these) |
| typedef struct avifDecoderItem |
| { |
| uint32_t id; |
| struct avifMeta * meta; // Unowned; A back-pointer for convenience |
| uint8_t type[4]; |
| size_t size; |
| avifBool idatStored; // If true, offset is relative to the associated meta box's idat box (iloc construction_method==1) |
| uint32_t width; // Set from this item's ispe property, if present |
| uint32_t height; // Set from this item's ispe property, if present |
| avifContentType contentType; |
| avifPropertyArray properties; |
| avifExtentArray extents; // All extent offsets/sizes |
| avifRWData mergedExtents; // if set, is a single contiguous block of this item's extents (unused when extents.count == 1) |
| avifBool ownsMergedExtents; // if true, mergedExtents must be freed when this item is destroyed |
| avifBool partialMergedExtents; // If true, mergedExtents doesn't have all of the item data yet |
| uint32_t thumbnailForID; // if non-zero, this item is a thumbnail for Item #{thumbnailForID} |
| uint32_t auxForID; // if non-zero, this item is an auxC plane for Item #{auxForID} |
| uint32_t descForID; // if non-zero, this item is a content description for Item #{descForID} |
| uint32_t dimgForID; // if non-zero, this item is an input of derived Item #{dimgForID} |
| // If dimgForId is non-zero, this is the zero-based index of this item in the list of Item #{dimgForID}'s dimg |
| // Note that if the same item appears multiple times in the dimg box, this is the last index for this item. |
| uint32_t dimgIdx; |
| avifBool hasDimgFrom; // whether there is a 'dimg' box with this item's id as 'fromID' |
| uint32_t premByID; // if non-zero, this item is premultiplied by Item #{premByID} |
| avifBool hasUnsupportedEssentialProperty; // If true, this item cites a property flagged as 'essential' that libavif doesn't support (yet). Ignore the item, if so. |
| avifBool ipmaSeen; // if true, this item already received a property association |
| avifBool progressive; // if true, this item has progressive layers (a1lx), but does not select a specific layer (the layer_id value in lsel is set to 0xFFFF) |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) |
| avifPixelFormat miniPixelFormat; // Set from the MinimizedImageBox, if present (AVIF_PIXEL_FORMAT_NONE otherwise) |
| avifChromaSamplePosition miniChromaSamplePosition; // Set from the MinimizedImageBox, if present (AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN otherwise) |
| #endif |
| } avifDecoderItem; |
| AVIF_ARRAY_DECLARE(avifDecoderItemArray, avifDecoderItem *, item); |
| |
| // grid storage |
| typedef struct avifImageGrid |
| { |
| uint32_t rows; // Legal range: [1-256] |
| uint32_t columns; // Legal range: [1-256] |
| uint32_t outputWidth; |
| uint32_t outputHeight; |
| } avifImageGrid; |
| |
| // --------------------------------------------------------------------------- |
| // avifTrack |
| |
| typedef struct avifSampleTableChunk |
| { |
| uint64_t offset; |
| } avifSampleTableChunk; |
| AVIF_ARRAY_DECLARE(avifSampleTableChunkArray, avifSampleTableChunk, chunk); |
| |
| typedef struct avifSampleTableSampleToChunk |
| { |
| uint32_t firstChunk; |
| uint32_t samplesPerChunk; |
| uint32_t sampleDescriptionIndex; |
| } avifSampleTableSampleToChunk; |
| AVIF_ARRAY_DECLARE(avifSampleTableSampleToChunkArray, avifSampleTableSampleToChunk, sampleToChunk); |
| |
| typedef struct avifSampleTableSampleSize |
| { |
| uint32_t size; |
| } avifSampleTableSampleSize; |
| AVIF_ARRAY_DECLARE(avifSampleTableSampleSizeArray, avifSampleTableSampleSize, sampleSize); |
| |
| typedef struct avifSampleTableTimeToSample |
| { |
| uint32_t sampleCount; |
| uint32_t sampleDelta; |
| } avifSampleTableTimeToSample; |
| AVIF_ARRAY_DECLARE(avifSampleTableTimeToSampleArray, avifSampleTableTimeToSample, timeToSample); |
| |
| typedef struct avifSyncSample |
| { |
| uint32_t sampleNumber; |
| } avifSyncSample; |
| AVIF_ARRAY_DECLARE(avifSyncSampleArray, avifSyncSample, syncSample); |
| |
| typedef struct avifSampleDescription |
| { |
| uint8_t format[4]; |
| avifPropertyArray properties; |
| } avifSampleDescription; |
| AVIF_ARRAY_DECLARE(avifSampleDescriptionArray, avifSampleDescription, description); |
| |
| typedef struct avifSampleTable |
| { |
| avifSampleTableChunkArray chunks; |
| avifSampleDescriptionArray sampleDescriptions; |
| avifSampleTableSampleToChunkArray sampleToChunks; |
| avifSampleTableSampleSizeArray sampleSizes; |
| avifSampleTableTimeToSampleArray timeToSamples; |
| avifSyncSampleArray syncSamples; |
| uint32_t allSamplesSize; // If this is non-zero, sampleSizes will be empty and all samples will be this size |
| } avifSampleTable; |
| |
| static void avifSampleTableDestroy(avifSampleTable * sampleTable); |
| |
| static avifSampleTable * avifSampleTableCreate() |
| { |
| avifSampleTable * sampleTable = (avifSampleTable *)avifAlloc(sizeof(avifSampleTable)); |
| if (sampleTable == NULL) { |
| return NULL; |
| } |
| memset(sampleTable, 0, sizeof(avifSampleTable)); |
| if (!avifArrayCreate(&sampleTable->chunks, sizeof(avifSampleTableChunk), 16) || |
| !avifArrayCreate(&sampleTable->sampleDescriptions, sizeof(avifSampleDescription), 2) || |
| !avifArrayCreate(&sampleTable->sampleToChunks, sizeof(avifSampleTableSampleToChunk), 16) || |
| !avifArrayCreate(&sampleTable->sampleSizes, sizeof(avifSampleTableSampleSize), 16) || |
| !avifArrayCreate(&sampleTable->timeToSamples, sizeof(avifSampleTableTimeToSample), 16) || |
| !avifArrayCreate(&sampleTable->syncSamples, sizeof(avifSyncSample), 16)) { |
| avifSampleTableDestroy(sampleTable); |
| return NULL; |
| } |
| return sampleTable; |
| } |
| |
| static void avifSampleTableDestroy(avifSampleTable * sampleTable) |
| { |
| avifArrayDestroy(&sampleTable->chunks); |
| for (uint32_t i = 0; i < sampleTable->sampleDescriptions.count; ++i) { |
| avifSampleDescription * description = &sampleTable->sampleDescriptions.description[i]; |
| avifArrayDestroy(&description->properties); |
| } |
| avifArrayDestroy(&sampleTable->sampleDescriptions); |
| avifArrayDestroy(&sampleTable->sampleToChunks); |
| avifArrayDestroy(&sampleTable->sampleSizes); |
| avifArrayDestroy(&sampleTable->timeToSamples); |
| avifArrayDestroy(&sampleTable->syncSamples); |
| avifFree(sampleTable); |
| } |
| |
| static uint32_t avifSampleTableGetImageDelta(const avifSampleTable * sampleTable, int imageIndex) |
| { |
| int maxSampleIndex = 0; |
| for (uint32_t i = 0; i < sampleTable->timeToSamples.count; ++i) { |
| const avifSampleTableTimeToSample * timeToSample = &sampleTable->timeToSamples.timeToSample[i]; |
| maxSampleIndex += timeToSample->sampleCount; |
| if ((imageIndex < maxSampleIndex) || (i == (sampleTable->timeToSamples.count - 1))) { |
| return timeToSample->sampleDelta; |
| } |
| } |
| |
| // TODO: fail here? |
| return 1; |
| } |
| |
| static avifCodecType avifSampleTableGetCodecType(const avifSampleTable * sampleTable) |
| { |
| for (uint32_t i = 0; i < sampleTable->sampleDescriptions.count; ++i) { |
| const avifCodecType codecType = avifGetCodecType(sampleTable->sampleDescriptions.description[i].format); |
| if (codecType != AVIF_CODEC_TYPE_UNKNOWN) { |
| return codecType; |
| } |
| } |
| return AVIF_CODEC_TYPE_UNKNOWN; |
| } |
| |
| static uint32_t avifCodecConfigurationBoxGetDepth(const avifCodecConfigurationBox * av1C) |
| { |
| if (av1C->twelveBit) { |
| return 12; |
| } else if (av1C->highBitdepth) { |
| return 10; |
| } |
| return 8; |
| } |
| |
| // This is used as a hint to validating the clap box in avifDecoderItemValidateProperties. |
| static avifPixelFormat avifCodecConfigurationBoxGetFormat(const avifCodecConfigurationBox * av1C) |
| { |
| if (av1C->monochrome) { |
| return AVIF_PIXEL_FORMAT_YUV400; |
| } else if (av1C->chromaSubsamplingY == 1) { |
| return AVIF_PIXEL_FORMAT_YUV420; |
| } else if (av1C->chromaSubsamplingX == 1) { |
| return AVIF_PIXEL_FORMAT_YUV422; |
| } |
| return AVIF_PIXEL_FORMAT_YUV444; |
| } |
| |
| static const avifPropertyArray * avifSampleTableGetProperties(const avifSampleTable * sampleTable, avifCodecType codecType) |
| { |
| for (uint32_t i = 0; i < sampleTable->sampleDescriptions.count; ++i) { |
| const avifSampleDescription * description = &sampleTable->sampleDescriptions.description[i]; |
| if (avifGetCodecType(description->format) == codecType) { |
| return &description->properties; |
| } |
| } |
| return NULL; |
| } |
| |
| // one video track ("trak" contents) |
| typedef struct avifTrack |
| { |
| uint32_t id; |
| uint32_t auxForID; // if non-zero, this track is an auxC plane for Track #{auxForID} |
| uint32_t premByID; // if non-zero, this track is premultiplied by Track #{premByID} |
| uint32_t mediaTimescale; |
| uint64_t mediaDuration; |
| uint64_t trackDuration; |
| uint64_t segmentDuration; |
| avifBool isRepeating; |
| int repetitionCount; |
| uint32_t width; |
| uint32_t height; |
| avifSampleTable * sampleTable; |
| struct avifMeta * meta; |
| } avifTrack; |
| AVIF_ARRAY_DECLARE(avifTrackArray, avifTrack, track); |
| |
| // --------------------------------------------------------------------------- |
| // avifCodecDecodeInput |
| |
| avifCodecDecodeInput * avifCodecDecodeInputCreate(void) |
| { |
| avifCodecDecodeInput * decodeInput = (avifCodecDecodeInput *)avifAlloc(sizeof(avifCodecDecodeInput)); |
| if (decodeInput == NULL) { |
| return NULL; |
| } |
| memset(decodeInput, 0, sizeof(avifCodecDecodeInput)); |
| if (!avifArrayCreate(&decodeInput->samples, sizeof(avifDecodeSample), 1)) { |
| avifFree(decodeInput); |
| return NULL; |
| } |
| return decodeInput; |
| } |
| |
| void avifCodecDecodeInputDestroy(avifCodecDecodeInput * decodeInput) |
| { |
| for (uint32_t sampleIndex = 0; sampleIndex < decodeInput->samples.count; ++sampleIndex) { |
| avifDecodeSample * sample = &decodeInput->samples.sample[sampleIndex]; |
| if (sample->ownsData) { |
| avifRWDataFree((avifRWData *)&sample->data); |
| } |
| } |
| avifArrayDestroy(&decodeInput->samples); |
| avifFree(decodeInput); |
| } |
| |
| // Returns how many samples are in the chunk. |
| static uint32_t avifGetSampleCountOfChunk(const avifSampleTableSampleToChunkArray * sampleToChunks, uint32_t chunkIndex) |
| { |
| uint32_t sampleCount = 0; |
| for (int sampleToChunkIndex = sampleToChunks->count - 1; sampleToChunkIndex >= 0; --sampleToChunkIndex) { |
| const avifSampleTableSampleToChunk * sampleToChunk = &sampleToChunks->sampleToChunk[sampleToChunkIndex]; |
| if (sampleToChunk->firstChunk <= (chunkIndex + 1)) { |
| sampleCount = sampleToChunk->samplesPerChunk; |
| break; |
| } |
| } |
| return sampleCount; |
| } |
| |
| static avifResult avifCodecDecodeInputFillFromSampleTable(avifCodecDecodeInput * decodeInput, |
| avifSampleTable * sampleTable, |
| const uint32_t imageCountLimit, |
| const uint64_t sizeHint, |
| avifDiagnostics * diag) |
| { |
| if (imageCountLimit) { |
| // Verify that the we're not about to exceed the frame count limit. |
| |
| uint32_t imageCountLeft = imageCountLimit; |
| for (uint32_t chunkIndex = 0; chunkIndex < sampleTable->chunks.count; ++chunkIndex) { |
| // First, figure out how many samples are in this chunk |
| uint32_t sampleCount = avifGetSampleCountOfChunk(&sampleTable->sampleToChunks, chunkIndex); |
| if (sampleCount == 0) { |
| // chunks with 0 samples are invalid |
| avifDiagnosticsPrintf(diag, "Sample table contains a chunk with 0 samples"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| if (sampleCount > imageCountLeft) { |
| // This file exceeds the imageCountLimit, bail out |
| avifDiagnosticsPrintf(diag, "Exceeded avifDecoder's imageCountLimit"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| imageCountLeft -= sampleCount; |
| } |
| } |
| |
| uint32_t sampleSizeIndex = 0; |
| for (uint32_t chunkIndex = 0; chunkIndex < sampleTable->chunks.count; ++chunkIndex) { |
| avifSampleTableChunk * chunk = &sampleTable->chunks.chunk[chunkIndex]; |
| |
| // First, figure out how many samples are in this chunk |
| uint32_t sampleCount = avifGetSampleCountOfChunk(&sampleTable->sampleToChunks, chunkIndex); |
| if (sampleCount == 0) { |
| // chunks with 0 samples are invalid |
| avifDiagnosticsPrintf(diag, "Sample table contains a chunk with 0 samples"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| uint64_t sampleOffset = chunk->offset; |
| for (uint32_t sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) { |
| uint32_t sampleSize = sampleTable->allSamplesSize; |
| if (sampleSize == 0) { |
| if (sampleSizeIndex >= sampleTable->sampleSizes.count) { |
| // We've run out of samples to sum |
| avifDiagnosticsPrintf(diag, "Truncated sample table"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| avifSampleTableSampleSize * sampleSizePtr = &sampleTable->sampleSizes.sampleSize[sampleSizeIndex]; |
| sampleSize = sampleSizePtr->size; |
| } |
| |
| avifDecodeSample * sample = (avifDecodeSample *)avifArrayPush(&decodeInput->samples); |
| AVIF_CHECKERR(sample != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| sample->offset = sampleOffset; |
| sample->size = sampleSize; |
| sample->spatialID = AVIF_SPATIAL_ID_UNSET; // Not filtering by spatial_id |
| sample->sync = AVIF_FALSE; // to potentially be set to true following the outer loop |
| |
| if (sampleSize > UINT64_MAX - sampleOffset) { |
| avifDiagnosticsPrintf(diag, |
| "Sample table contains an offset/size pair which overflows: [%" PRIu64 " / %u]", |
| sampleOffset, |
| sampleSize); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| if (sizeHint && ((sampleOffset + sampleSize) > sizeHint)) { |
| avifDiagnosticsPrintf(diag, "Exceeded avifIO's sizeHint, possibly truncated data"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| sampleOffset += sampleSize; |
| ++sampleSizeIndex; |
| } |
| } |
| |
| // Mark appropriate samples as sync |
| for (uint32_t syncSampleIndex = 0; syncSampleIndex < sampleTable->syncSamples.count; ++syncSampleIndex) { |
| uint32_t frameIndex = sampleTable->syncSamples.syncSample[syncSampleIndex].sampleNumber - 1; // sampleNumber is 1-based |
| if (frameIndex < decodeInput->samples.count) { |
| decodeInput->samples.sample[frameIndex].sync = AVIF_TRUE; |
| } |
| } |
| |
| // Assume frame 0 is sync, just in case the stss box is absent in the BMFF. (Unnecessary?) |
| if (decodeInput->samples.count > 0) { |
| decodeInput->samples.sample[0].sync = AVIF_TRUE; |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| static avifResult avifCodecDecodeInputFillFromDecoderItem(avifCodecDecodeInput * decodeInput, |
| avifDecoderItem * item, |
| avifBool allowProgressive, |
| const uint32_t imageCountLimit, |
| const uint64_t sizeHint, |
| avifDiagnostics * diag) |
| { |
| if (sizeHint && (item->size > sizeHint)) { |
| avifDiagnosticsPrintf(diag, "Exceeded avifIO's sizeHint, possibly truncated data"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| uint8_t layerCount = 0; |
| size_t layerSizes[4] = { 0 }; |
| const avifProperty * a1lxProp = avifPropertyArrayFind(&item->properties, "a1lx"); |
| if (a1lxProp) { |
| // Calculate layer count and all layer sizes from the a1lx box, and then validate |
| |
| size_t remainingSize = item->size; |
| for (int i = 0; i < 3; ++i) { |
| ++layerCount; |
| |
| const size_t layerSize = (size_t)a1lxProp->u.a1lx.layerSize[i]; |
| if (layerSize) { |
| if (layerSize >= remainingSize) { // >= instead of > because there must be room for the last layer |
| avifDiagnosticsPrintf(diag, "a1lx layer index [%d] does not fit in item size", i); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| layerSizes[i] = layerSize; |
| remainingSize -= layerSize; |
| } else { |
| layerSizes[i] = remainingSize; |
| remainingSize = 0; |
| break; |
| } |
| } |
| if (remainingSize > 0) { |
| AVIF_ASSERT_OR_RETURN(layerCount == 3); |
| ++layerCount; |
| layerSizes[3] = remainingSize; |
| } |
| } |
| |
| const avifProperty * lselProp = avifPropertyArrayFind(&item->properties, "lsel"); |
| // Progressive images offer layers via the a1lxProp, but don't specify a layer selection with lsel. |
| // |
| // For backward compatibility with earlier drafts of AVIF spec v1.1.0, treat an absent lsel as |
| // equivalent to layer_id == 0xFFFF during the transitional period. Remove !lselProp when the test |
| // images have been updated to the v1.1.0 spec. |
| item->progressive = (a1lxProp && (!lselProp || (lselProp->u.lsel.layerID == 0xFFFF))); |
| if (lselProp && (lselProp->u.lsel.layerID != 0xFFFF)) { |
| // Layer selection. This requires that the underlying AV1 codec decodes all layers, |
| // and then only returns the requested layer as a single frame. To the user of libavif, |
| // this appears to be a single frame. |
| |
| decodeInput->allLayers = AVIF_TRUE; |
| |
| size_t sampleSize = 0; |
| if (layerCount > 0) { |
| // Optimization: If we're selecting a layer that doesn't require the entire image's payload (hinted via the a1lx box) |
| |
| if (lselProp->u.lsel.layerID >= layerCount) { |
| avifDiagnosticsPrintf(diag, |
| "lsel property requests layer index [%u] which isn't present in a1lx property ([%u] layers)", |
| lselProp->u.lsel.layerID, |
| layerCount); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| for (uint8_t i = 0; i <= lselProp->u.lsel.layerID; ++i) { |
| sampleSize += layerSizes[i]; |
| } |
| } else { |
| // This layer's payload subsection is unknown, just use the whole payload |
| sampleSize = item->size; |
| } |
| |
| avifDecodeSample * sample = (avifDecodeSample *)avifArrayPush(&decodeInput->samples); |
| AVIF_CHECKERR(sample != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| sample->itemID = item->id; |
| sample->offset = 0; |
| sample->size = sampleSize; |
| AVIF_ASSERT_OR_RETURN(lselProp->u.lsel.layerID < AVIF_MAX_AV1_LAYER_COUNT); |
| sample->spatialID = (uint8_t)lselProp->u.lsel.layerID; |
| sample->sync = AVIF_TRUE; |
| } else if (allowProgressive && item->progressive) { |
| // Progressive image. Decode all layers and expose them all to the user. |
| |
| if (imageCountLimit && (layerCount > imageCountLimit)) { |
| avifDiagnosticsPrintf(diag, "Exceeded avifDecoder's imageCountLimit (progressive)"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| decodeInput->allLayers = AVIF_TRUE; |
| |
| size_t offset = 0; |
| for (int i = 0; i < layerCount; ++i) { |
| avifDecodeSample * sample = (avifDecodeSample *)avifArrayPush(&decodeInput->samples); |
| AVIF_CHECKERR(sample != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| sample->itemID = item->id; |
| sample->offset = offset; |
| sample->size = layerSizes[i]; |
| sample->spatialID = AVIF_SPATIAL_ID_UNSET; |
| sample->sync = (i == 0); // Assume all layers depend on the first layer |
| |
| offset += layerSizes[i]; |
| } |
| } else { |
| // Typical case: Use the entire item's payload for a single frame output |
| |
| avifDecodeSample * sample = (avifDecodeSample *)avifArrayPush(&decodeInput->samples); |
| AVIF_CHECKERR(sample != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| sample->itemID = item->id; |
| sample->offset = 0; |
| sample->size = item->size; |
| sample->spatialID = AVIF_SPATIAL_ID_UNSET; |
| sample->sync = AVIF_TRUE; |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Helper macros / functions |
| |
| #define BEGIN_STREAM(VARNAME, PTR, SIZE, DIAG, CONTEXT) \ |
| avifROStream VARNAME; \ |
| avifROData VARNAME##_roData; \ |
| VARNAME##_roData.data = PTR; \ |
| VARNAME##_roData.size = SIZE; \ |
| avifROStreamStart(&VARNAME, &VARNAME##_roData, DIAG, CONTEXT) |
| |
| // Use this to keep track of whether or not a child box that must be unique (0 or 1 present) has |
| // been seen yet, when parsing a parent box. If the "seen" bit is already set for a given box when |
| // it is encountered during parse, an error is thrown. Which bit corresponds to which box is |
| // dictated entirely by the calling function. |
| static avifBool uniqueBoxSeen(uint32_t * uniqueBoxFlags, uint32_t whichFlag, const char * parentBoxType, const char * boxType, avifDiagnostics * diagnostics) |
| { |
| const uint32_t flag = 1 << whichFlag; |
| if (*uniqueBoxFlags & flag) { |
| // This box has already been seen. Error! |
| avifDiagnosticsPrintf(diagnostics, "Box[%s] contains a duplicate unique box of type '%s'", parentBoxType, boxType); |
| return AVIF_FALSE; |
| } |
| |
| // Mark this box as seen. |
| *uniqueBoxFlags |= flag; |
| return AVIF_TRUE; |
| } |
| |
| // --------------------------------------------------------------------------- |
| // avifDecoderData |
| |
| typedef struct avifTile |
| { |
| avifCodecDecodeInput * input; |
| avifCodecType codecType; |
| // This may point to a codec that it owns or point to a shared codec that it does not own. In the shared case, this will |
| // point to one of the avifCodec instances in avifDecoderData. |
| struct avifCodec * codec; |
| avifImage * image; |
| uint32_t width; // Either avifTrack.width or avifDecoderItem.width |
| uint32_t height; // Either avifTrack.height or avifDecoderItem.height |
| uint8_t operatingPoint; |
| } avifTile; |
| AVIF_ARRAY_DECLARE(avifTileArray, avifTile, tile); |
| |
| // This holds one "meta" box (from the BMFF and HEIF standards) worth of relevant-to-AVIF information. |
| // * If a meta box is parsed from the root level of the BMFF, it can contain the information about |
| // "items" which might be color planes, alpha planes, or EXIF or XMP metadata. |
| // * If a meta box is parsed from inside of a track ("trak") box, any metadata (EXIF/XMP) items inside |
| // of that box are implicitly associated with that track. |
| typedef struct avifMeta |
| { |
| // Items (from HEIF) are the generic storage for any data that does not require timed processing |
| // (single image color planes, alpha planes, EXIF, XMP, etc). Each item has a unique integer ID >1, |
| // and is defined by a series of child boxes in a meta box: |
| // * iloc - location: byte offset to item data, item size in bytes |
| // * iinf - information: type of item (color planes, alpha plane, EXIF, XMP) |
| // * ipco - properties: dimensions, aspect ratio, image transformations, references to other items |
| // * ipma - associations: Attaches an item in the properties list to a given item |
| // |
| // Items are lazily created in this array when any of the above boxes refer to one by a new (unseen) ID, |
| // and are then further modified/updated as new information for an item's ID is parsed. |
| avifDecoderItemArray items; |
| |
| // Any ipco boxes explained above are populated into this array as a staging area, which are |
| // then duplicated into the appropriate items upon encountering an item property association |
| // (ipma) box. |
| avifPropertyArray properties; |
| |
| // Filled with the contents of this meta box's "idat" box, which is raw data that an item can |
| // directly refer to in its item location box (iloc) instead of just giving an offset into the |
| // overall file. If all items' iloc boxes simply point at an offset/length in the file itself, |
| // this buffer will likely be empty. |
| avifRWData idat; |
| |
| // Ever-incrementing ID for uniquely identifying which 'meta' box contains an idat (when |
| // multiple meta boxes exist as BMFF siblings). Each time avifParseMetaBox() is called on an |
| // avifMeta struct, this value is incremented. Any time an additional meta box is detected at |
| // the same "level" (root level, trak level, etc), this ID helps distinguish which meta box's |
| // "idat" is which, as items implicitly reference idat boxes that exist in the same meta |
| // box. |
| uint32_t idatID; |
| |
| // Contents of a pitm box, which signal which of the items in this file is the main image. For |
| // AVIF, this should point at an image item containing color planes, and all other items |
| // are ignored unless they refer to this item in some way (alpha plane, EXIF/XMP metadata). |
| uint32_t primaryItemID; |
| |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) |
| // If true, the fields above were extracted from a MinimizedImageBox. It imposes some |
| // constraints on its optional extendedMeta field, such as forbidden item IDs, properties etc. |
| avifBool fromMini; |
| #endif |
| |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM) |
| // Parsed from Sample Transform metadata if present, otherwise empty. |
| avifSampleTransformExpression sampleTransformExpression; |
| // Bit depth extracted from the pixi property of the Sample Transform derived image item, if any. |
| uint32_t sampleTransformDepth; |
| #endif |
| } avifMeta; |
| |
| static void avifMetaDestroy(avifMeta * meta); |
| |
| static avifMeta * avifMetaCreate() |
| { |
| avifMeta * meta = (avifMeta *)avifAlloc(sizeof(avifMeta)); |
| if (meta == NULL) { |
| return NULL; |
| } |
| memset(meta, 0, sizeof(avifMeta)); |
| if (!avifArrayCreate(&meta->items, sizeof(avifDecoderItem *), 8) || !avifArrayCreate(&meta->properties, sizeof(avifProperty), 16)) { |
| avifMetaDestroy(meta); |
| return NULL; |
| } |
| return meta; |
| } |
| |
| static void avifMetaDestroy(avifMeta * meta) |
| { |
| for (uint32_t i = 0; i < meta->items.count; ++i) { |
| avifDecoderItem * item = meta->items.item[i]; |
| avifArrayDestroy(&item->properties); |
| avifArrayDestroy(&item->extents); |
| if (item->ownsMergedExtents) { |
| avifRWDataFree(&item->mergedExtents); |
| } |
| avifFree(item); |
| } |
| avifArrayDestroy(&meta->items); |
| avifArrayDestroy(&meta->properties); |
| avifRWDataFree(&meta->idat); |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM) |
| avifArrayDestroy(&meta->sampleTransformExpression); |
| #endif |
| avifFree(meta); |
| } |
| |
| static avifResult avifCheckItemID(const char * boxFourcc, uint32_t itemID, avifDiagnostics * diag) |
| { |
| if (itemID == 0) { |
| avifDiagnosticsPrintf(diag, "Box[%4s] has an invalid item ID [%u]", boxFourcc, itemID); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| static avifResult avifMetaFindOrCreateItem(avifMeta * meta, uint32_t itemID, avifDecoderItem ** item) |
| { |
| *item = NULL; |
| AVIF_ASSERT_OR_RETURN(itemID != 0); |
| |
| for (uint32_t i = 0; i < meta->items.count; ++i) { |
| if (meta->items.item[i]->id == itemID) { |
| *item = meta->items.item[i]; |
| return AVIF_RESULT_OK; |
| } |
| } |
| |
| avifDecoderItem ** itemPtr = (avifDecoderItem **)avifArrayPush(&meta->items); |
| AVIF_CHECKERR(itemPtr != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| *item = (avifDecoderItem *)avifAlloc(sizeof(avifDecoderItem)); |
| if (*item == NULL) { |
| avifArrayPop(&meta->items); |
| return AVIF_RESULT_OUT_OF_MEMORY; |
| } |
| memset(*item, 0, sizeof(avifDecoderItem)); |
| |
| *itemPtr = *item; |
| if (!avifArrayCreate(&(*item)->properties, sizeof(avifProperty), 16)) { |
| avifFree(*item); |
| *item = NULL; |
| avifArrayPop(&meta->items); |
| return AVIF_RESULT_OUT_OF_MEMORY; |
| } |
| if (!avifArrayCreate(&(*item)->extents, sizeof(avifExtent), 1)) { |
| avifArrayDestroy(&(*item)->properties); |
| avifFree(*item); |
| *item = NULL; |
| avifArrayPop(&meta->items); |
| return AVIF_RESULT_OUT_OF_MEMORY; |
| } |
| (*item)->id = itemID; |
| (*item)->meta = meta; |
| return AVIF_RESULT_OK; |
| } |
| |
| // A group of AVIF tiles in an image item, such as a single tile or a grid of multiple tiles. |
| typedef struct avifTileInfo |
| { |
| unsigned int tileCount; |
| unsigned int decodedTileCount; |
| unsigned int firstTileIndex; // Within avifDecoderData.tiles. |
| avifImageGrid grid; |
| } avifTileInfo; |
| |
| typedef struct avifDecoderData |
| { |
| avifMeta * meta; // The root-level meta box |
| avifTrackArray tracks; |
| avifTileArray tiles; |
| avifTileInfo tileInfos[AVIF_ITEM_CATEGORY_COUNT]; |
| avifDecoderSource source; |
| // When decoding AVIF images with grid, use a single decoder instance for all the tiles instead of creating a decoder instance |
| // for each tile. If that is the case, |codec| will be used by all the tiles. |
| // |
| // There are some edge cases where we will still need multiple decoder instances: |
| // * For animated AVIF with alpha, we will need two instances (one for the color planes and one for the alpha plane since they are both |
| // encoded as separate video sequences). In this case, |codec| will be used for the color planes and |codecAlpha| will be |
| // used for the alpha plane. |
| // * For grid images with multiple layers. In this case, each tile will need its own decoder instance since there would be |
| // multiple layers in each tile. In this case, |codec| and |codecAlpha| are not used and each tile will have its own |
| // decoder instance. |
| // * For grid images where the operating points of all the tiles are not the same. In this case, each tile needs its own |
| // decoder instance (same as above). |
| avifCodec * codec; |
| avifCodec * codecAlpha; |
| uint8_t majorBrand[4]; // From the file's ftyp, used by AVIF_DECODER_SOURCE_AUTO |
| avifDiagnostics * diag; // Shallow copy; owned by avifDecoder |
| const avifSampleTable * sourceSampleTable; // NULL unless (source == AVIF_DECODER_SOURCE_TRACKS), owned by an avifTrack |
| avifBool cicpSet; // True if avifDecoder's image has had its CICP set correctly yet. |
| // This allows nclx colr boxes to override AV1 CICP, as specified in the MIAF |
| // standard (ISO/IEC 23000-22:2019), section 7.3.6.4: |
| // The colour information property takes precedence over any colour information |
| // in the image bitstream, i.e. if the property is present, colour information in |
| // the bitstream shall be ignored. |
| |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM) |
| // Remember the dimg association order to the Sample Transform derived image item. |
| // Colour items only. The alpha items are implicit. |
| uint8_t sampleTransformNumInputImageItems; // At most AVIF_SAMPLE_TRANSFORM_MAX_NUM_INPUT_IMAGE_ITEMS. |
| avifItemCategory sampleTransformInputImageItems[AVIF_SAMPLE_TRANSFORM_MAX_NUM_INPUT_IMAGE_ITEMS]; |
| #endif |
| } avifDecoderData; |
| |
| static void avifDecoderDataDestroy(avifDecoderData * data); |
| |
| static avifDecoderData * avifDecoderDataCreate() |
| { |
| avifDecoderData * data = (avifDecoderData *)avifAlloc(sizeof(avifDecoderData)); |
| if (data == NULL) { |
| return NULL; |
| } |
| memset(data, 0, sizeof(avifDecoderData)); |
| data->meta = avifMetaCreate(); |
| if (data->meta == NULL || !avifArrayCreate(&data->tracks, sizeof(avifTrack), 2) || |
| !avifArrayCreate(&data->tiles, sizeof(avifTile), 8)) { |
| avifDecoderDataDestroy(data); |
| return NULL; |
| } |
| return data; |
| } |
| |
| static void avifDecoderDataResetCodec(avifDecoderData * data) |
| { |
| for (unsigned int i = 0; i < data->tiles.count; ++i) { |
| avifTile * tile = &data->tiles.tile[i]; |
| if (tile->image) { |
| avifImageFreePlanes(tile->image, AVIF_PLANES_ALL); // forget any pointers into codec image buffers |
| } |
| if (tile->codec) { |
| // Check if tile->codec was created separately and destroy it in that case. |
| if (tile->codec != data->codec && tile->codec != data->codecAlpha) { |
| avifCodecDestroy(tile->codec); |
| } |
| tile->codec = NULL; |
| } |
| } |
| for (int c = 0; c < AVIF_ITEM_CATEGORY_COUNT; ++c) { |
| data->tileInfos[c].decodedTileCount = 0; |
| } |
| if (data->codec) { |
| avifCodecDestroy(data->codec); |
| data->codec = NULL; |
| } |
| if (data->codecAlpha) { |
| avifCodecDestroy(data->codecAlpha); |
| data->codecAlpha = NULL; |
| } |
| } |
| |
| static avifTile * avifDecoderDataCreateTile(avifDecoderData * data, avifCodecType codecType, uint32_t width, uint32_t height, uint8_t operatingPoint) |
| { |
| avifTile * tile = (avifTile *)avifArrayPush(&data->tiles); |
| if (tile == NULL) { |
| return NULL; |
| } |
| tile->codecType = codecType; |
| tile->image = avifImageCreateEmpty(); |
| if (!tile->image) { |
| goto error; |
| } |
| tile->input = avifCodecDecodeInputCreate(); |
| if (!tile->input) { |
| goto error; |
| } |
| tile->width = width; |
| tile->height = height; |
| tile->operatingPoint = operatingPoint; |
| return tile; |
| |
| error: |
| if (tile->input) { |
| avifCodecDecodeInputDestroy(tile->input); |
| } |
| if (tile->image) { |
| avifImageDestroy(tile->image); |
| } |
| avifArrayPop(&data->tiles); |
| return NULL; |
| } |
| |
| static avifTrack * avifDecoderDataCreateTrack(avifDecoderData * data) |
| { |
| avifTrack * track = (avifTrack *)avifArrayPush(&data->tracks); |
| if (track == NULL) { |
| return NULL; |
| } |
| track->meta = avifMetaCreate(); |
| if (track->meta == NULL) { |
| avifArrayPop(&data->tracks); |
| return NULL; |
| } |
| return track; |
| } |
| |
| static void avifDecoderDataClearTiles(avifDecoderData * data) |
| { |
| for (unsigned int i = 0; i < data->tiles.count; ++i) { |
| avifTile * tile = &data->tiles.tile[i]; |
| if (tile->input) { |
| avifCodecDecodeInputDestroy(tile->input); |
| tile->input = NULL; |
| } |
| if (tile->codec) { |
| // Check if tile->codec was created separately and destroy it in that case. |
| if (tile->codec != data->codec && tile->codec != data->codecAlpha) { |
| avifCodecDestroy(tile->codec); |
| } |
| tile->codec = NULL; |
| } |
| if (tile->image) { |
| avifImageDestroy(tile->image); |
| tile->image = NULL; |
| } |
| } |
| data->tiles.count = 0; |
| for (int c = 0; c < AVIF_ITEM_CATEGORY_COUNT; ++c) { |
| data->tileInfos[c].tileCount = 0; |
| data->tileInfos[c].decodedTileCount = 0; |
| } |
| if (data->codec) { |
| avifCodecDestroy(data->codec); |
| data->codec = NULL; |
| } |
| if (data->codecAlpha) { |
| avifCodecDestroy(data->codecAlpha); |
| data->codecAlpha = NULL; |
| } |
| } |
| |
| static void avifDecoderDataDestroy(avifDecoderData * data) |
| { |
| if (data->meta) { |
| avifMetaDestroy(data->meta); |
| } |
| for (uint32_t i = 0; i < data->tracks.count; ++i) { |
| avifTrack * track = &data->tracks.track[i]; |
| if (track->sampleTable) { |
| avifSampleTableDestroy(track->sampleTable); |
| } |
| if (track->meta) { |
| avifMetaDestroy(track->meta); |
| } |
| } |
| avifArrayDestroy(&data->tracks); |
| avifDecoderDataClearTiles(data); |
| avifArrayDestroy(&data->tiles); |
| avifFree(data); |
| } |
| |
| // This returns the max extent that has to be read in order to decode this item. If |
| // the item is stored in an idat, the data has already been read during Parse() and |
| // this function will return AVIF_RESULT_OK with a 0-byte extent. |
| static avifResult avifDecoderItemMaxExtent(const avifDecoderItem * item, const avifDecodeSample * sample, avifExtent * outExtent) |
| { |
| if (item->extents.count == 0) { |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| |
| if (item->idatStored) { |
| // construction_method: idat(1) |
| |
| if (item->meta->idat.size > 0) { |
| // Already read from a meta box during Parse() |
| memset(outExtent, 0, sizeof(avifExtent)); |
| return AVIF_RESULT_OK; |
| } |
| |
| // no associated idat box was found in the meta box, bail out |
| return AVIF_RESULT_NO_CONTENT; |
| } |
| |
| // construction_method: file(0) |
| |
| if (sample->size == 0) { |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| uint64_t remainingOffset = sample->offset; |
| size_t remainingBytes = sample->size; // This may be smaller than item->size if the item is progressive |
| |
| // Assert that the for loop below will execute at least one iteration. |
| AVIF_ASSERT_OR_RETURN(item->extents.count != 0); |
| uint64_t minOffset = UINT64_MAX; |
| uint64_t maxOffset = 0; |
| for (uint32_t extentIter = 0; extentIter < item->extents.count; ++extentIter) { |
| avifExtent * extent = &item->extents.extent[extentIter]; |
| |
| // Make local copies of extent->offset and extent->size as they might need to be adjusted |
| // due to the sample's offset. |
| uint64_t startOffset = extent->offset; |
| size_t extentSize = extent->size; |
| if (remainingOffset) { |
| if (remainingOffset >= extentSize) { |
| remainingOffset -= extentSize; |
| continue; |
| } else { |
| if (remainingOffset > UINT64_MAX - startOffset) { |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| startOffset += remainingOffset; |
| extentSize -= (size_t)remainingOffset; |
| remainingOffset = 0; |
| } |
| } |
| |
| const size_t usedExtentSize = (extentSize < remainingBytes) ? extentSize : remainingBytes; |
| |
| if (usedExtentSize > UINT64_MAX - startOffset) { |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| const uint64_t endOffset = startOffset + usedExtentSize; |
| |
| if (minOffset > startOffset) { |
| minOffset = startOffset; |
| } |
| if (maxOffset < endOffset) { |
| maxOffset = endOffset; |
| } |
| |
| remainingBytes -= usedExtentSize; |
| if (remainingBytes == 0) { |
| // We've got enough bytes for this sample. |
| break; |
| } |
| } |
| |
| if (remainingBytes != 0) { |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| |
| outExtent->offset = minOffset; |
| const uint64_t extentLength = maxOffset - minOffset; |
| if (extentLength > SIZE_MAX) { |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| outExtent->size = (size_t)extentLength; |
| return AVIF_RESULT_OK; |
| } |
| |
| static uint8_t avifDecoderItemOperatingPoint(const avifDecoderItem * item) |
| { |
| const avifProperty * a1opProp = avifPropertyArrayFind(&item->properties, "a1op"); |
| if (a1opProp) { |
| return a1opProp->u.a1op.opIndex; |
| } |
| return 0; // default |
| } |
| |
| static avifResult avifDecoderItemValidateProperties(const avifDecoderItem * item, |
| const char * configPropName, |
| avifDiagnostics * diag, |
| const avifStrictFlags strictFlags) |
| { |
| const avifProperty * const configProp = avifPropertyArrayFind(&item->properties, configPropName); |
| if (!configProp) { |
| // An item configuration property box is mandatory in all valid AVIF configurations. Bail out. |
| avifDiagnosticsPrintf(diag, "Item ID %u of type '%.4s' is missing mandatory %s property", item->id, (const char *)item->type, configPropName); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| if (!memcmp(item->type, "grid", 4)) { |
| for (uint32_t i = 0; i < item->meta->items.count; ++i) { |
| avifDecoderItem * tile = item->meta->items.item[i]; |
| if (tile->dimgForID != item->id) { |
| continue; |
| } |
| // Tile item types were checked in avifDecoderGenerateImageTiles(), no need to do it here. |
| |
| // MIAF (ISO 23000-22:2019), Section 7.3.11.4.1: |
| // All input images of a grid image item shall use the same [...] chroma sampling format, |
| // and the same decoder configuration (see 7.3.6.2). |
| |
| // The chroma sampling format is part of the decoder configuration. |
| const avifProperty * tileConfigProp = avifPropertyArrayFind(&tile->properties, configPropName); |
| if (!tileConfigProp) { |
| avifDiagnosticsPrintf(diag, |
| "Tile item ID %u of type '%.4s' is missing mandatory %s property", |
| tile->id, |
| (const char *)tile->type, |
| configPropName); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| // configProp was copied from a tile item to the grid item. Comparing tileConfigProp with it |
| // is equivalent to comparing tileConfigProp with the configPropName from the first tile. |
| if ((tileConfigProp->u.av1C.seqProfile != configProp->u.av1C.seqProfile) || |
| (tileConfigProp->u.av1C.seqLevelIdx0 != configProp->u.av1C.seqLevelIdx0) || |
| (tileConfigProp->u.av1C.seqTier0 != configProp->u.av1C.seqTier0) || |
| (tileConfigProp->u.av1C.highBitdepth != configProp->u.av1C.highBitdepth) || |
| (tileConfigProp->u.av1C.twelveBit != configProp->u.av1C.twelveBit) || |
| (tileConfigProp->u.av1C.monochrome != configProp->u.av1C.monochrome) || |
| (tileConfigProp->u.av1C.chromaSubsamplingX != configProp->u.av1C.chromaSubsamplingX) || |
| (tileConfigProp->u.av1C.chromaSubsamplingY != configProp->u.av1C.chromaSubsamplingY) || |
| (tileConfigProp->u.av1C.chromaSamplePosition != configProp->u.av1C.chromaSamplePosition)) { |
| avifDiagnosticsPrintf(diag, |
| "The fields of the %s property of tile item ID %u of type '%.4s' differs from other tiles", |
| configPropName, |
| tile->id, |
| (const char *)tile->type); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| } |
| } |
| |
| const avifProperty * pixiProp = avifPropertyArrayFind(&item->properties, "pixi"); |
| if (!pixiProp && (strictFlags & AVIF_STRICT_PIXI_REQUIRED)) { |
| // A pixi box is mandatory in all valid AVIF configurations. Bail out. |
| avifDiagnosticsPrintf(diag, |
| "[Strict] Item ID %u of type '%.4s' is missing mandatory pixi property", |
| item->id, |
| (const char *)item->type); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| if (pixiProp) { |
| const uint32_t configDepth = avifCodecConfigurationBoxGetDepth(&configProp->u.av1C); |
| for (uint8_t i = 0; i < pixiProp->u.pixi.planeCount; ++i) { |
| if (pixiProp->u.pixi.planeDepths[i] != configDepth) { |
| // pixi depth must match configuration property depth |
| avifDiagnosticsPrintf(diag, |
| "Item ID %u depth specified by pixi property [%u] does not match %s property depth [%u]", |
| item->id, |
| pixiProp->u.pixi.planeDepths[i], |
| configPropName, |
| configDepth); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| } |
| } |
| |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) |
| if (item->miniPixelFormat != AVIF_PIXEL_FORMAT_NONE) { |
| // This is a 'mini' box. |
| |
| if (item->miniPixelFormat != avifCodecConfigurationBoxGetFormat(&configProp->u.av1C)) { |
| avifDiagnosticsPrintf(diag, |
| "Item ID %u format [%s] specified by mini box does not match %s property format [%s]", |
| item->id, |
| avifPixelFormatToString(item->miniPixelFormat), |
| configPropName, |
| avifPixelFormatToString(avifCodecConfigurationBoxGetFormat(&configProp->u.av1C))); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| if (configProp->u.av1C.chromaSamplePosition == /*CSP_UNKNOWN=*/0) { |
| // Section 6.4.2. Color config semantics of AV1 specification says: |
| // CSP_UNKNOWN - the source video transfer function must be signaled outside the AV1 bitstream |
| // See https://aomediacodec.github.io/av1-spec/#color-config-semantics |
| |
| // So item->miniChromaSamplePosition can differ and will override the AV1 value. |
| } else if ((uint8_t)item->miniChromaSamplePosition != configProp->u.av1C.chromaSamplePosition) { |
| avifDiagnosticsPrintf(diag, |
| "Item ID %u chroma sample position [%u] specified by mini box does not match %s property chroma sample position [%u]", |
| item->id, |
| (uint32_t)item->miniChromaSamplePosition, |
| configPropName, |
| configProp->u.av1C.chromaSamplePosition); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| } |
| #endif // AVIF_ENABLE_EXPERIMENTAL_MINI |
| |
| if (strictFlags & AVIF_STRICT_CLAP_VALID) { |
| const avifProperty * clapProp = avifPropertyArrayFind(&item->properties, "clap"); |
| if (clapProp) { |
| const avifProperty * ispeProp = avifPropertyArrayFind(&item->properties, "ispe"); |
| if (!ispeProp) { |
| avifDiagnosticsPrintf(diag, |
| "[Strict] Item ID %u is missing an ispe property, so its clap property cannot be validated", |
| item->id); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| avifCropRect cropRect; |
| const uint32_t imageW = ispeProp->u.ispe.width; |
| const uint32_t imageH = ispeProp->u.ispe.height; |
| const avifPixelFormat configFormat = avifCodecConfigurationBoxGetFormat(&configProp->u.av1C); |
| avifBool validClap = avifCropRectConvertCleanApertureBox(&cropRect, &clapProp->u.clap, imageW, imageH, configFormat, diag); |
| if (!validClap) { |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| } |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| static avifResult avifDecoderItemRead(avifDecoderItem * item, |
| avifIO * io, |
| avifROData * outData, |
| size_t offset, |
| size_t partialByteCount, |
| avifDiagnostics * diag) |
| { |
| if (item->mergedExtents.data && !item->partialMergedExtents) { |
| // Multiple extents have already been concatenated for this item, just return it |
| if (offset >= item->mergedExtents.size) { |
| avifDiagnosticsPrintf(diag, "Item ID %u read has overflowing offset", item->id); |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| outData->data = item->mergedExtents.data + offset; |
| outData->size = item->mergedExtents.size - offset; |
| return AVIF_RESULT_OK; |
| } |
| |
| if (item->extents.count == 0) { |
| avifDiagnosticsPrintf(diag, "Item ID %u has zero extents", item->id); |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| |
| // Find this item's source of all extents' data, based on the construction method |
| const avifRWData * idatBuffer = NULL; |
| if (item->idatStored) { |
| // construction_method: idat(1) |
| |
| if (item->meta->idat.size > 0) { |
| idatBuffer = &item->meta->idat; |
| } else { |
| // no associated idat box was found in the meta box, bail out |
| avifDiagnosticsPrintf(diag, "Item ID %u is stored in an idat, but no associated idat box was found", item->id); |
| return AVIF_RESULT_NO_CONTENT; |
| } |
| } |
| |
| // Merge extents into a single contiguous buffer |
| if ((io->sizeHint > 0) && (item->size > io->sizeHint)) { |
| // Sanity check: somehow the sum of extents exceeds the entire file or idat size! |
| avifDiagnosticsPrintf(diag, "Item ID %u reported size failed size hint sanity check. Truncated data?", item->id); |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| |
| if (offset >= item->size) { |
| avifDiagnosticsPrintf(diag, "Item ID %u read has overflowing offset", item->id); |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| const size_t maxOutputSize = item->size - offset; |
| const size_t readOutputSize = (partialByteCount && (partialByteCount < maxOutputSize)) ? partialByteCount : maxOutputSize; |
| const size_t totalBytesToRead = offset + readOutputSize; |
| |
| // If there is a single extent for this item and the source of the read buffer is going to be |
| // persistent for the lifetime of the avifDecoder (whether it comes from its own internal |
| // idatBuffer or from a known-persistent IO), we can avoid buffer duplication and just use the |
| // preexisting buffer. |
| avifBool singlePersistentBuffer = ((item->extents.count == 1) && (idatBuffer || io->persistent)); |
| if (!singlePersistentBuffer) { |
| // Always allocate the item's full size here, as progressive image decodes will do partial |
| // reads into this buffer and begin feeding the buffer to the underlying AV1 decoder, but |
| // will then write more into this buffer without flushing the AV1 decoder (which is still |
| // holding the address of the previous allocation of this buffer). This strategy avoids |
| // use-after-free issues in the AV1 decoder and unnecessary reallocs as a typical |
| // progressive decode use case will eventually decode the final layer anyway. |
| AVIF_CHECKRES(avifRWDataRealloc(&item->mergedExtents, item->size)); |
| item->ownsMergedExtents = AVIF_TRUE; |
| } |
| |
| // Set this until we manage to fill the entire mergedExtents buffer |
| item->partialMergedExtents = AVIF_TRUE; |
| |
| uint8_t * front = item->mergedExtents.data; |
| size_t remainingBytes = totalBytesToRead; |
| for (uint32_t extentIter = 0; extentIter < item->extents.count; ++extentIter) { |
| avifExtent * extent = &item->extents.extent[extentIter]; |
| |
| size_t bytesToRead = extent->size; |
| if (bytesToRead > remainingBytes) { |
| bytesToRead = remainingBytes; |
| } |
| |
| avifROData offsetBuffer; |
| if (idatBuffer) { |
| if (extent->offset > idatBuffer->size) { |
| avifDiagnosticsPrintf(diag, "Item ID %u has impossible extent offset in idat buffer", item->id); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| // Since extent->offset (a uint64_t) is not bigger than idatBuffer->size (a size_t), |
| // it is safe to cast extent->offset to size_t. |
| const size_t extentOffset = (size_t)extent->offset; |
| if (extent->size > idatBuffer->size - extentOffset) { |
| avifDiagnosticsPrintf(diag, "Item ID %u has impossible extent size in idat buffer", item->id); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| offsetBuffer.data = idatBuffer->data + extentOffset; |
| offsetBuffer.size = idatBuffer->size - extentOffset; |
| } else { |
| // construction_method: file(0) |
| |
| if ((io->sizeHint > 0) && (extent->offset > io->sizeHint)) { |
| avifDiagnosticsPrintf(diag, "Item ID %u extent offset failed size hint sanity check. Truncated data?", item->id); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| avifResult readResult = io->read(io, 0, extent->offset, bytesToRead, &offsetBuffer); |
| if (readResult != AVIF_RESULT_OK) { |
| return readResult; |
| } |
| if (bytesToRead != offsetBuffer.size) { |
| avifDiagnosticsPrintf(diag, |
| "Item ID %u tried to read %zu bytes, but only received %zu bytes", |
| item->id, |
| bytesToRead, |
| offsetBuffer.size); |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| } |
| |
| if (singlePersistentBuffer) { |
| memcpy(&item->mergedExtents, &offsetBuffer, sizeof(avifRWData)); |
| item->mergedExtents.size = bytesToRead; |
| } else { |
| AVIF_ASSERT_OR_RETURN(item->ownsMergedExtents); |
| AVIF_ASSERT_OR_RETURN(front); |
| memcpy(front, offsetBuffer.data, bytesToRead); |
| front += bytesToRead; |
| } |
| |
| remainingBytes -= bytesToRead; |
| if (remainingBytes == 0) { |
| // This happens when partialByteCount is set |
| break; |
| } |
| } |
| if (remainingBytes != 0) { |
| // This should be impossible? |
| avifDiagnosticsPrintf(diag, "Item ID %u has %zu unexpected trailing bytes", item->id, remainingBytes); |
| return AVIF_RESULT_TRUNCATED_DATA; |
| } |
| |
| outData->data = item->mergedExtents.data + offset; |
| outData->size = readOutputSize; |
| item->partialMergedExtents = (item->size != totalBytesToRead); |
| return AVIF_RESULT_OK; |
| } |
| |
| // Returns the avifCodecType of the first tile of the gridItem. |
| static avifCodecType avifDecoderItemGetGridCodecType(const avifDecoderItem * gridItem) |
| { |
| for (uint32_t i = 0; i < gridItem->meta->items.count; ++i) { |
| avifDecoderItem * item = gridItem->meta->items.item[i]; |
| const avifCodecType tileCodecType = avifGetCodecType(item->type); |
| if ((item->dimgForID == gridItem->id) && (tileCodecType != AVIF_CODEC_TYPE_UNKNOWN)) { |
| return tileCodecType; |
| } |
| } |
| return AVIF_CODEC_TYPE_UNKNOWN; |
| } |
| |
| static avifResult avifDecoderGenerateImageGridTiles(avifDecoder * decoder, avifImageGrid * grid, avifDecoderItem * gridItem, avifItemCategory itemCategory) |
| { |
| // Count number of dimg for this item, bail out if it doesn't match perfectly |
| unsigned int tilesAvailable = 0; |
| avifDecoderItem * firstTileItem = NULL; |
| avifBool progressive = AVIF_TRUE; |
| for (uint32_t i = 0; i < gridItem->meta->items.count; ++i) { |
| avifDecoderItem * item = gridItem->meta->items.item[i]; |
| if (item->dimgForID != gridItem->id) { |
| continue; |
| } |
| |
| // According to HEIF (ISO 14496-12), Section 6.6.2.3.1, the SingleItemTypeReferenceBox of type 'dimg' |
| // identifies the input images of the derived image item of type 'grid'. Since the reference_count |
| // shall be equal to rows*columns, unknown tile item types cannot be skipped but must be considered |
| // as errors. |
| const avifCodecType tileCodecType = avifGetCodecType(item->type); |
| if (tileCodecType == AVIF_CODEC_TYPE_UNKNOWN) { |
| char type[4]; |
| for (int j = 0; j < 4; j++) { |
| if (isprint((unsigned char)item->type[j])) { |
| type[j] = item->type[j]; |
| } else { |
| type[j] = '.'; |
| } |
| } |
| avifDiagnosticsPrintf(&decoder->diag, |
| "Tile item ID %u has an unknown item type '%.4s' (%02x%02x%02x%02x)", |
| item->id, |
| type, |
| item->type[0], |
| item->type[1], |
| item->type[2], |
| item->type[3]); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| |
| if (item->hasUnsupportedEssentialProperty) { |
| // An essential property isn't supported by libavif; can't |
| // decode a grid image if any tile in the grid isn't supported. |
| avifDiagnosticsPrintf(&decoder->diag, "Grid image contains tile with an unsupported property marked as essential"); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| |
| const avifTile * tile = |
| avifDecoderDataCreateTile(decoder->data, tileCodecType, item->width, item->height, avifDecoderItemOperatingPoint(item)); |
| AVIF_CHECKERR(tile != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| AVIF_CHECKRES(avifCodecDecodeInputFillFromDecoderItem(tile->input, |
| item, |
| decoder->allowProgressive, |
| decoder->imageCountLimit, |
| decoder->io->sizeHint, |
| &decoder->diag)); |
| tile->input->itemCategory = itemCategory; |
| |
| if (firstTileItem == NULL) { |
| firstTileItem = item; |
| |
| // Adopt the configuration property of the first image item tile, so that it can be queried from |
| // the top-level color/alpha item during avifDecoderReset(). |
| const char * configPropName = memcmp(item->type, "av02", 4) ? "av1C" : "av2C"; |
| const avifProperty * srcProp = avifPropertyArrayFind(&item->properties, configPropName); |
| if (!srcProp) { |
| avifDiagnosticsPrintf(&decoder->diag, "Grid image's first tile is missing an %s property", configPropName); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| avifProperty * dstProp = (avifProperty *)avifArrayPush(&gridItem->properties); |
| AVIF_CHECKERR(dstProp != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| *dstProp = *srcProp; |
| |
| } else if (memcmp(item->type, firstTileItem->type, 4)) { |
| // MIAF (ISO 23000-22:2019), Section 7.3.11.4.1: |
| // All input images of a grid image item shall use the same coding format [...] |
| // The coding format is defined by the item type. |
| avifDiagnosticsPrintf(&decoder->diag, |
| "Tile item ID %u of type '%.4s' differs from other tile type '%.4s'", |
| item->id, |
| (const char *)item->type, |
| (const char *)firstTileItem->type); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| if (!item->progressive) { |
| progressive = AVIF_FALSE; |
| } |
| ++tilesAvailable; |
| } |
| if (itemCategory == AVIF_ITEM_COLOR && progressive) { |
| // If all the items that make up the grid are progressive, then propagate that status to the top-level grid item. |
| gridItem->progressive = AVIF_TRUE; |
| } |
| |
| if (tilesAvailable != grid->rows * grid->columns) { |
| avifDiagnosticsPrintf(&decoder->diag, |
| "Grid image of dimensions %ux%u requires %u tiles, but %u were found", |
| grid->columns, |
| grid->rows, |
| grid->rows * grid->columns, |
| tilesAvailable); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| // Allocates the dstImage. Also verifies some spec compliance rules for grids, if relevant. |
| static avifResult avifDecoderDataAllocateImagePlanes(avifDecoderData * data, const avifTileInfo * info, avifImage * dstImage) |
| { |
| const avifTile * tile = &data->tiles.tile[info->firstTileIndex]; |
| uint32_t dstWidth; |
| uint32_t dstHeight; |
| |
| if (info->grid.rows > 0 && info->grid.columns > 0) { |
| const avifImageGrid * grid = &info->grid; |
| // Validate grid image size and tile size. |
| // |
| // HEIF (ISO/IEC 23008-12:2017), Section 6.6.2.3.1: |
| // The tiled input images shall completely "cover" the reconstructed image grid canvas, ... |
| if (((tile->image->width * grid->columns) < grid->outputWidth) || ((tile->image->height * grid->rows) < grid->outputHeight)) { |
| avifDiagnosticsPrintf(data->diag, |
| "Grid image tiles do not completely cover the image (HEIF (ISO/IEC 23008-12:2017), Section 6.6.2.3.1)"); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| // Tiles in the rightmost column and bottommost row must overlap the reconstructed image grid canvas. See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2, Figure 2. |
| if (((tile->image->width * (grid->columns - 1)) >= grid->outputWidth) || |
| ((tile->image->height * (grid->rows - 1)) >= grid->outputHeight)) { |
| avifDiagnosticsPrintf(data->diag, |
| "Grid image tiles in the rightmost column and bottommost row do not overlap the reconstructed image grid canvas. See MIAF (ISO/IEC 23000-22:2019), Section 7.3.11.4.2, Figure 2"); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| if (!avifAreGridDimensionsValid(tile->image->yuvFormat, |
| grid->outputWidth, |
| grid->outputHeight, |
| tile->image->width, |
| tile->image->height, |
| data->diag)) { |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| dstWidth = grid->outputWidth; |
| dstHeight = grid->outputHeight; |
| } else { |
| // Only one tile. Width and height are inherited from the 'ispe' property of the corresponding avifDecoderItem. |
| dstWidth = tile->width; |
| dstHeight = tile->height; |
| } |
| |
| const avifBool alpha = avifIsAlpha(tile->input->itemCategory); |
| if (alpha) { |
| // An alpha tile does not contain any YUV pixels. |
| AVIF_ASSERT_OR_RETURN(tile->image->yuvFormat == AVIF_PIXEL_FORMAT_NONE); |
| } |
| |
| const uint32_t dstDepth = tile->image->depth; |
| |
| // Lazily populate dstImage with the new frame's properties. |
| const avifBool dimsOrDepthIsDifferent = (dstImage->width != dstWidth) || (dstImage->height != dstHeight) || |
| (dstImage->depth != dstDepth); |
| const avifBool yuvFormatIsDifferent = !alpha && (dstImage->yuvFormat != tile->image->yuvFormat); |
| if (dimsOrDepthIsDifferent || yuvFormatIsDifferent) { |
| if (alpha) { |
| // Alpha doesn't match size, just bail out |
| avifDiagnosticsPrintf(data->diag, "Alpha plane dimensions do not match color plane dimensions"); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| |
| if (dimsOrDepthIsDifferent) { |
| avifImageFreePlanes(dstImage, AVIF_PLANES_ALL); |
| dstImage->width = dstWidth; |
| dstImage->height = dstHeight; |
| dstImage->depth = dstDepth; |
| } |
| if (yuvFormatIsDifferent) { |
| avifImageFreePlanes(dstImage, AVIF_PLANES_YUV); |
| dstImage->yuvFormat = tile->image->yuvFormat; |
| } |
| // Keep dstImage->yuvRange which is already set to its correct value |
| // (extracted from the 'colr' box if parsed or from a Sequence Header OBU otherwise). |
| |
| if (!data->cicpSet) { |
| data->cicpSet = AVIF_TRUE; |
| dstImage->colorPrimaries = tile->image->colorPrimaries; |
| dstImage->transferCharacteristics = tile->image->transferCharacteristics; |
| dstImage->matrixCoefficients = tile->image->matrixCoefficients; |
| } |
| } |
| |
| if (avifImageAllocatePlanes(dstImage, alpha ? AVIF_PLANES_A : AVIF_PLANES_YUV) != AVIF_RESULT_OK) { |
| avifDiagnosticsPrintf(data->diag, "Image allocation failure"); |
| return AVIF_RESULT_OUT_OF_MEMORY; |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| // Copies over the pixels from the tile into dstImage. |
| // Verifies that the relevant properties of the tile match those of the first tile in case of a grid. |
| static avifResult avifDecoderDataCopyTileToImage(avifDecoderData * data, |
| const avifTileInfo * info, |
| avifImage * dstImage, |
| const avifTile * tile, |
| unsigned int tileIndex) |
| { |
| const avifTile * firstTile = &data->tiles.tile[info->firstTileIndex]; |
| if (tile != firstTile) { |
| // Check for tile consistency. All tiles in a grid image should match the first tile in the properties checked below. |
| if ((tile->image->width != firstTile->image->width) || (tile->image->height != firstTile->image->height) || |
| (tile->image->depth != firstTile->image->depth) || (tile->image->yuvFormat != firstTile->image->yuvFormat) || |
| (tile->image->yuvRange != firstTile->image->yuvRange) || (tile->image->colorPrimaries != firstTile->image->colorPrimaries) || |
| (tile->image->transferCharacteristics != firstTile->image->transferCharacteristics) || |
| (tile->image->matrixCoefficients != firstTile->image->matrixCoefficients)) { |
| avifDiagnosticsPrintf(data->diag, "Grid image contains mismatched tiles"); |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| } |
| |
| avifImage srcView; |
| avifImageSetDefaults(&srcView); |
| avifImage dstView; |
| avifImageSetDefaults(&dstView); |
| avifCropRect dstViewRect = { 0, 0, firstTile->image->width, firstTile->image->height }; |
| if (info->grid.rows > 0 && info->grid.columns > 0) { |
| unsigned int rowIndex = tileIndex / info->grid.columns; |
| unsigned int colIndex = tileIndex % info->grid.columns; |
| dstViewRect.x = firstTile->image->width * colIndex; |
| dstViewRect.y = firstTile->image->height * rowIndex; |
| if (dstViewRect.x + dstViewRect.width > info->grid.outputWidth) { |
| dstViewRect.width = info->grid.outputWidth - dstViewRect.x; |
| } |
| if (dstViewRect.y + dstViewRect.height > info->grid.outputHeight) { |
| dstViewRect.height = info->grid.outputHeight - dstViewRect.y; |
| } |
| } |
| const avifCropRect srcViewRect = { 0, 0, dstViewRect.width, dstViewRect.height }; |
| AVIF_ASSERT_OR_RETURN(avifImageSetViewRect(&dstView, dstImage, &dstViewRect) == AVIF_RESULT_OK && |
| avifImageSetViewRect(&srcView, tile->image, &srcViewRect) == AVIF_RESULT_OK); |
| avifImageCopySamples(&dstView, &srcView, avifIsAlpha(tile->input->itemCategory) ? AVIF_PLANES_A : AVIF_PLANES_YUV); |
| return AVIF_RESULT_OK; |
| } |
| |
| // If colorId == 0 (a sentinel value as item IDs must be nonzero), accept any found EXIF/XMP metadata. Passing in 0 |
| // is used when finding metadata in a meta box embedded in a trak box, as any items inside of a meta box that is |
| // inside of a trak box are implicitly associated to the track. |
| static avifResult avifDecoderFindMetadata(avifDecoder * decoder, avifMeta * meta, avifImage * image, uint32_t colorId) |
| { |
| if (decoder->ignoreExif && decoder->ignoreXMP) { |
| // Nothing to do! |
| return AVIF_RESULT_OK; |
| } |
| |
| for (uint32_t itemIndex = 0; itemIndex < meta->items.count; ++itemIndex) { |
| avifDecoderItem * item = meta->items.item[itemIndex]; |
| if (!item->size) { |
| continue; |
| } |
| if (item->hasUnsupportedEssentialProperty) { |
| // An essential property isn't supported by libavif; ignore the item. |
| continue; |
| } |
| |
| if ((colorId > 0) && (item->descForID != colorId)) { |
| // Not a content description (metadata) for the colorOBU, skip it |
| continue; |
| } |
| |
| if (!decoder->ignoreExif && !memcmp(item->type, "Exif", 4)) { |
| avifROData exifContents; |
| avifResult readResult = avifDecoderItemRead(item, decoder->io, &exifContents, 0, 0, &decoder->diag); |
| if (readResult != AVIF_RESULT_OK) { |
| return readResult; |
| } |
| |
| // Advance past Annex A.2.1's header |
| BEGIN_STREAM(exifBoxStream, exifContents.data, exifContents.size, &decoder->diag, "Exif header"); |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) |
| // The MinimizedImageBox does not signal the exifTiffHeaderOffset. |
| if (!meta->fromMini) |
| #endif |
| { |
| uint32_t exifTiffHeaderOffset; |
| AVIF_CHECKERR(avifROStreamReadU32(&exifBoxStream, &exifTiffHeaderOffset), |
| AVIF_RESULT_INVALID_EXIF_PAYLOAD); // unsigned int(32) exif_tiff_header_offset; |
| size_t expectedExifTiffHeaderOffset; |
| AVIF_CHECKRES(avifGetExifTiffHeaderOffset(avifROStreamCurrent(&exifBoxStream), |
| avifROStreamRemainingBytes(&exifBoxStream), |
| &expectedExifTiffHeaderOffset)); |
| AVIF_CHECKERR(exifTiffHeaderOffset == expectedExifTiffHeaderOffset, AVIF_RESULT_INVALID_EXIF_PAYLOAD); |
| } |
| |
| AVIF_CHECKRES(avifRWDataSet(&image->exif, avifROStreamCurrent(&exifBoxStream), avifROStreamRemainingBytes(&exifBoxStream))); |
| } else if (!decoder->ignoreXMP && !memcmp(item->type, "mime", 4) && |
| !strcmp(item->contentType.contentType, AVIF_CONTENT_TYPE_XMP)) { |
| avifROData xmpContents; |
| avifResult readResult = avifDecoderItemRead(item, decoder->io, &xmpContents, 0, 0, &decoder->diag); |
| if (readResult != AVIF_RESULT_OK) { |
| return readResult; |
| } |
| |
| AVIF_CHECKRES(avifImageSetMetadataXMP(image, xmpContents.data, xmpContents.size)); |
| } |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| // --------------------------------------------------------------------------- |
| // URN |
| |
| static avifBool isAlphaURN(const char * urn) |
| { |
| return !strcmp(urn, AVIF_URN_ALPHA0) || !strcmp(urn, AVIF_URN_ALPHA1); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // BMFF Parsing |
| |
| static avifBool avifParseHandlerBox(const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[hdlr]"); |
| |
| AVIF_CHECK(avifROStreamReadAndEnforceVersion(&s, 0)); |
| |
| uint32_t predefined; |
| AVIF_CHECK(avifROStreamReadU32(&s, &predefined)); // unsigned int(32) pre_defined = 0; |
| if (predefined != 0) { |
| avifDiagnosticsPrintf(diag, "Box[hdlr] contains a pre_defined value that is nonzero"); |
| return AVIF_FALSE; |
| } |
| |
| uint8_t handlerType[4]; |
| AVIF_CHECK(avifROStreamRead(&s, handlerType, 4)); // unsigned int(32) handler_type; |
| if (memcmp(handlerType, "pict", 4) != 0) { |
| avifDiagnosticsPrintf(diag, "Box[hdlr] handler_type is not 'pict'"); |
| return AVIF_FALSE; |
| } |
| |
| for (int i = 0; i < 3; ++i) { |
| uint32_t reserved; |
| AVIF_CHECK(avifROStreamReadU32(&s, &reserved)); // const unsigned int(32)[3] reserved = 0; |
| } |
| |
| // Verify that a valid string is here, but don't bother to store it |
| AVIF_CHECK(avifROStreamReadString(&s, NULL, 0)); // string name; |
| return AVIF_TRUE; |
| } |
| |
| static avifResult avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[iloc]"); |
| |
| // Section 8.11.3.2 of ISO/IEC 14496-12. |
| uint8_t version; |
| AVIF_CHECKERR(avifROStreamReadVersionAndFlags(&s, &version, NULL), AVIF_RESULT_BMFF_PARSE_FAILED); |
| if (version > 2) { |
| avifDiagnosticsPrintf(diag, "Box[iloc] has an unsupported version [%u]", version); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| uint8_t offsetSize, lengthSize, baseOffsetSize, indexSize = 0; |
| uint32_t reserved; |
| AVIF_CHECKERR(avifROStreamReadBits8(&s, &offsetSize, /*bitCount=*/4), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(4) offset_size; |
| AVIF_CHECKERR(avifROStreamReadBits8(&s, &lengthSize, /*bitCount=*/4), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(4) length_size; |
| AVIF_CHECKERR(avifROStreamReadBits8(&s, &baseOffsetSize, /*bitCount=*/4), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(4) base_offset_size; |
| if (version == 1 || version == 2) { |
| AVIF_CHECKERR(avifROStreamReadBits8(&s, &indexSize, /*bitCount=*/4), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(4) index_size; |
| } else { |
| AVIF_CHECKERR(avifROStreamReadBits(&s, &reserved, /*bitCount=*/4), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(4) reserved; |
| } |
| |
| // Section 8.11.3.3 of ISO/IEC 14496-12. |
| if ((offsetSize != 0 && offsetSize != 4 && offsetSize != 8) || (lengthSize != 0 && lengthSize != 4 && lengthSize != 8) || |
| (baseOffsetSize != 0 && baseOffsetSize != 4 && baseOffsetSize != 8) || (indexSize != 0 && indexSize != 4 && indexSize != 8)) { |
| avifDiagnosticsPrintf(diag, "Box[iloc] has an invalid size"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| uint16_t tmp16; |
| uint32_t itemCount; |
| if (version < 2) { |
| AVIF_CHECKERR(avifROStreamReadU16(&s, &tmp16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) item_count; |
| itemCount = tmp16; |
| } else { |
| AVIF_CHECKERR(avifROStreamReadU32(&s, &itemCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) item_count; |
| } |
| for (uint32_t i = 0; i < itemCount; ++i) { |
| uint32_t itemID; |
| if (version < 2) { |
| AVIF_CHECKERR(avifROStreamReadU16(&s, &tmp16), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) item_ID; |
| itemID = tmp16; |
| } else { |
| AVIF_CHECKERR(avifROStreamReadU32(&s, &itemID), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(32) item_ID; |
| } |
| AVIF_CHECKRES(avifCheckItemID("iloc", itemID, diag)); |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) |
| if (meta->fromMini && itemID < 5) { |
| // This is the extendedMeta field of an enclosing MinimizedImageBox. |
| // Item IDs 1 to 4 are reserved and already defined, including their locations. |
| avifDiagnosticsPrintf(diag, "%s: Box[iloc] has a forbidden item ID [%u]", s.diagContext, itemID); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| #endif // AVIF_ENABLE_EXPERIMENTAL_MINI |
| |
| avifDecoderItem * item; |
| AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, itemID, &item)); |
| if (item->extents.count > 0) { |
| // This item has already been given extents via this iloc box. This is invalid. |
| avifDiagnosticsPrintf(diag, "Item ID [%u] contains duplicate sets of extents", itemID); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| if (version == 1 || version == 2) { |
| AVIF_CHECKERR(avifROStreamReadBits(&s, &reserved, /*bitCount=*/12), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(12) reserved = 0; |
| if (reserved) { |
| avifDiagnosticsPrintf(diag, "Box[iloc] has a non null reserved field [%u]", reserved); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| uint8_t constructionMethod; |
| AVIF_CHECKERR(avifROStreamReadBits8(&s, &constructionMethod, /*bitCount=*/4), |
| AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(4) construction_method; |
| if (constructionMethod != 0 /* file offset */ && constructionMethod != 1 /* idat offset */) { |
| // construction method 2 (item offset) unsupported |
| avifDiagnosticsPrintf(diag, "Box[iloc] has an unsupported construction method [%u]", constructionMethod); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| if (constructionMethod == 1) { |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) |
| if (meta->fromMini) { |
| // This is the extendedMeta field of an enclosing MinimizedImageBox. |
| // It may not contain an ItemDataBox. |
| avifDiagnosticsPrintf(diag, "%s: Box[iloc] has a forbidden construction method [%u]", s.diagContext, constructionMethod); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| #endif // AVIF_ENABLE_EXPERIMENTAL_MINI |
| item->idatStored = AVIF_TRUE; |
| } |
| } |
| |
| uint16_t dataReferenceIndex; |
| AVIF_CHECKERR(avifROStreamReadU16(&s, &dataReferenceIndex), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) data_reference_index; |
| uint64_t baseOffset; |
| AVIF_CHECKERR(avifROStreamReadUX8(&s, &baseOffset, baseOffsetSize), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(base_offset_size*8) base_offset; |
| uint16_t extentCount; |
| AVIF_CHECKERR(avifROStreamReadU16(&s, &extentCount), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(16) extent_count; |
| for (int extentIter = 0; extentIter < extentCount; ++extentIter) { |
| if ((version == 1 || version == 2) && indexSize > 0) { |
| // Section 8.11.3.1 of ISO/IEC 14496-12: |
| // The item_reference_index is only used for the method item_offset; it indicates the 1-based index |
| // of the item reference with referenceType 'iloc' linked from this item. If index_size is 0, then |
| // the value 1 is implied; the value 0 is reserved. |
| uint64_t itemReferenceIndex; // Ignored unless construction_method=2 which is unsupported, but still read it. |
| AVIF_CHECKERR(avifROStreamReadUX8(&s, &itemReferenceIndex, indexSize), |
| AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(index_size*8) item_reference_index; |
| } |
| |
| uint64_t extentOffset; |
| AVIF_CHECKERR(avifROStreamReadUX8(&s, &extentOffset, offsetSize), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(offset_size*8) extent_offset; |
| uint64_t extentLength; |
| AVIF_CHECKERR(avifROStreamReadUX8(&s, &extentLength, lengthSize), AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(length_size*8) extent_length; |
| |
| avifExtent * extent = (avifExtent *)avifArrayPush(&item->extents); |
| AVIF_CHECKERR(extent != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| if (extentOffset > UINT64_MAX - baseOffset) { |
| avifDiagnosticsPrintf(diag, |
| "Item ID [%u] contains an extent offset which overflows: [base: %" PRIu64 " offset:%" PRIu64 "]", |
| itemID, |
| baseOffset, |
| extentOffset); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| uint64_t offset = baseOffset + extentOffset; |
| extent->offset = offset; |
| if (extentLength > SIZE_MAX) { |
| avifDiagnosticsPrintf(diag, "Item ID [%u] contains an extent length which overflows: [%" PRIu64 "]", itemID, extentLength); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| extent->size = (size_t)extentLength; |
| if (extent->size > SIZE_MAX - item->size) { |
| avifDiagnosticsPrintf(diag, |
| "Item ID [%u] contains an extent length which overflows the item size: [%zu, %zu]", |
| itemID, |
| extent->size, |
| item->size); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| item->size += extent->size; |
| } |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| static avifBool avifParseImageGridBox(avifImageGrid * grid, |
| const uint8_t * raw, |
| size_t rawLen, |
| uint32_t imageSizeLimit, |
| uint32_t imageDimensionLimit, |
| avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[grid]"); |
| |
| uint8_t version, flags; |
| AVIF_CHECK(avifROStreamRead(&s, &version, 1)); // unsigned int(8) version = 0; |
| if (version != 0) { |
| avifDiagnosticsPrintf(diag, "Box[grid] has unsupported version [%u]", version); |
| return AVIF_FALSE; |
| } |
| uint8_t rowsMinusOne, columnsMinusOne; |
| AVIF_CHECK(avifROStreamRead(&s, &flags, 1)); // unsigned int(8) flags; |
| AVIF_CHECK(avifROStreamRead(&s, &rowsMinusOne, 1)); // unsigned int(8) rows_minus_one; |
| AVIF_CHECK(avifROStreamRead(&s, &columnsMinusOne, 1)); // unsigned int(8) columns_minus_one; |
| grid->rows = (uint32_t)rowsMinusOne + 1; |
| grid->columns = (uint32_t)columnsMinusOne + 1; |
| |
| uint32_t fieldLength = ((flags & 1) + 1) * 16; |
| if (fieldLength == 16) { |
| uint16_t outputWidth16, outputHeight16; |
| AVIF_CHECK(avifROStreamReadU16(&s, &outputWidth16)); // unsigned int(FieldLength) output_width; |
| AVIF_CHECK(avifROStreamReadU16(&s, &outputHeight16)); // unsigned int(FieldLength) output_height; |
| grid->outputWidth = outputWidth16; |
| grid->outputHeight = outputHeight16; |
| } else { |
| if (fieldLength != 32) { |
| // This should be impossible |
| avifDiagnosticsPrintf(diag, "Grid box contains illegal field length: [%u]", fieldLength); |
| return AVIF_FALSE; |
| } |
| AVIF_CHECK(avifROStreamReadU32(&s, &grid->outputWidth)); // unsigned int(FieldLength) output_width; |
| AVIF_CHECK(avifROStreamReadU32(&s, &grid->outputHeight)); // unsigned int(FieldLength) output_height; |
| } |
| if ((grid->outputWidth == 0) || (grid->outputHeight == 0)) { |
| avifDiagnosticsPrintf(diag, "Grid box contains illegal dimensions: [%u x %u]", grid->outputWidth, grid->outputHeight); |
| return AVIF_FALSE; |
| } |
| if (avifDimensionsTooLarge(grid->outputWidth, grid->outputHeight, imageSizeLimit, imageDimensionLimit)) { |
| avifDiagnosticsPrintf(diag, "Grid box dimensions are too large: [%u x %u]", grid->outputWidth, grid->outputHeight); |
| return AVIF_FALSE; |
| } |
| return avifROStreamRemainingBytes(&s) == 0; |
| } |
| |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP) |
| |
| static avifBool avifParseToneMappedImageBox(avifGainMapMetadata * metadata, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[tmap]"); |
| |
| uint8_t version; |
| AVIF_CHECK(avifROStreamRead(&s, &version, 1)); // unsigned int(8) version = 0; |
| if (version != 0) { |
| avifDiagnosticsPrintf(diag, "Box[tmap] has unsupported version [%u]", version); |
| return AVIF_FALSE; |
| } |
| |
| uint8_t flags; |
| AVIF_CHECK(avifROStreamRead(&s, &flags, 1)); // unsigned int(8) flags; |
| uint8_t channelCount = (flags & 1) * 2 + 1; |
| AVIF_ASSERT_OR_RETURN(channelCount == 1 || channelCount == 3); |
| metadata->useBaseColorSpace = (flags & 2) != 0; |
| metadata->backwardDirection = (flags & 4) != 0; |
| const avifBool useCommonDenominator = (flags & 8) != 0; |
| |
| if (useCommonDenominator) { |
| uint32_t commonDenominator; |
| AVIF_CHECK(avifROStreamReadU32(&s, &commonDenominator)); |
| |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->baseHdrHeadroomN)); |
| metadata->baseHdrHeadroomD = commonDenominator; |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->alternateHdrHeadroomN)); |
| metadata->alternateHdrHeadroomD = commonDenominator; |
| |
| for (int c = 0; c < channelCount; ++c) { |
| AVIF_CHECK(avifROStreamReadU32(&s, (uint32_t *)&metadata->gainMapMinN[c])); |
| metadata->gainMapMinD[c] = commonDenominator; |
| AVIF_CHECK(avifROStreamReadU32(&s, (uint32_t *)&metadata->gainMapMaxN[c])); |
| metadata->gainMapMaxD[c] = commonDenominator; |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->gainMapGammaN[c])); |
| metadata->gainMapGammaD[c] = commonDenominator; |
| AVIF_CHECK(avifROStreamReadU32(&s, (uint32_t *)&metadata->baseOffsetN[c])); |
| metadata->baseOffsetD[c] = commonDenominator; |
| AVIF_CHECK(avifROStreamReadU32(&s, (uint32_t *)&metadata->alternateOffsetN[c])); |
| metadata->alternateOffsetD[c] = commonDenominator; |
| } |
| } else { |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->baseHdrHeadroomN)); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->baseHdrHeadroomD)); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->alternateHdrHeadroomN)); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->alternateHdrHeadroomD)); |
| |
| for (int c = 0; c < channelCount; ++c) { |
| AVIF_CHECK(avifROStreamReadU32(&s, (uint32_t *)&metadata->gainMapMinN[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->gainMapMinD[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, (uint32_t *)&metadata->gainMapMaxN[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->gainMapMaxD[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->gainMapGammaN[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->gainMapGammaD[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, (uint32_t *)&metadata->baseOffsetN[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->baseOffsetD[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, (uint32_t *)&metadata->alternateOffsetN[c])); |
| AVIF_CHECK(avifROStreamReadU32(&s, &metadata->alternateOffsetD[c])); |
| } |
| } |
| |
| // Fill the remaining values by copying those from the first channel. |
| for (int c = channelCount; c < 3; ++c) { |
| metadata->gainMapMinN[c] = metadata->gainMapMinN[0]; |
| metadata->gainMapMinD[c] = metadata->gainMapMinD[0]; |
| metadata->gainMapMaxN[c] = metadata->gainMapMaxN[0]; |
| metadata->gainMapMaxD[c] = metadata->gainMapMaxD[0]; |
| metadata->gainMapGammaN[c] = metadata->gainMapGammaN[0]; |
| metadata->gainMapGammaD[c] = metadata->gainMapGammaD[0]; |
| metadata->baseOffsetN[c] = metadata->baseOffsetN[0]; |
| metadata->baseOffsetD[c] = metadata->baseOffsetD[0]; |
| metadata->alternateOffsetN[c] = metadata->alternateOffsetN[0]; |
| metadata->alternateOffsetD[c] = metadata->alternateOffsetD[0]; |
| } |
| |
| return avifROStreamRemainingBytes(&s) == 0; |
| } |
| #endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP |
| |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM) |
| // bit_depth is assumed to be 2 (32-bit). |
| static avifResult avifParseSampleTransformTokens(avifROStream * s, avifSampleTransformExpression * expression) |
| { |
| uint8_t tokenCount; |
| AVIF_CHECK(avifROStreamRead(s, &tokenCount, /*size=*/1)); // unsigned int(8) token_count; |
| AVIF_CHECKERR(tokenCount != 0, AVIF_RESULT_BMFF_PARSE_FAILED); |
| AVIF_CHECKERR(avifArrayCreate(expression, sizeof(expression->tokens[0]), tokenCount), AVIF_RESULT_OUT_OF_MEMORY); |
| |
| for (uint32_t t = 0; t < tokenCount; ++t) { |
| avifSampleTransformToken * token = (avifSampleTransformToken *)avifArrayPush(expression); |
| AVIF_CHECKERR(token != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| |
| AVIF_CHECK(avifROStreamRead(s, &token->type, /*size=*/1)); // unsigned int(8) token; |
| if (token->type == AVIF_SAMPLE_TRANSFORM_CONSTANT) { |
| // Two's complement representation is assumed here. |
| uint32_t constant; |
| AVIF_CHECK(avifROStreamReadU32(s, &constant)); // signed int(1<<(bit_depth+3)) constant; |
| token->constant = *(int32_t *)&constant; // maybe =(int32_t)constant; is enough |
| } else if (token->type == AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX) { |
| AVIF_CHECK(avifROStreamRead(s, &token->inputImageItemIndex, 1)); // unsigned int(8) input_image_item_index; |
| } |
| } |
| AVIF_CHECKERR(avifROStreamRemainingBytes(s) == 0, AVIF_RESULT_BMFF_PARSE_FAILED); |
| return AVIF_RESULT_OK; |
| } |
| |
| // Parses the raw bitstream of the 'sato' Sample Transform derived image item and extracts the expression. |
| static avifResult avifParseSampleTransformImageBox(const uint8_t * raw, |
| size_t rawLen, |
| uint32_t numInputImageItems, |
| avifSampleTransformExpression * expression, |
| avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[sato]"); |
| |
| uint8_t version, bitDepth; |
| AVIF_CHECK(avifROStreamReadBits8(&s, &version, /*bitCount=*/6)); // unsigned int(6) version = 0; |
| AVIF_CHECK(avifROStreamReadBits8(&s, &bitDepth, /*bitCount=*/2)); // unsigned int(2) bit_depth; |
| AVIF_CHECKERR(version == 0, AVIF_RESULT_NOT_IMPLEMENTED); |
| AVIF_CHECKERR(bitDepth == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, AVIF_RESULT_NOT_IMPLEMENTED); |
| |
| const avifResult result = avifParseSampleTransformTokens(&s, expression); |
| if (result != AVIF_RESULT_OK) { |
| avifArrayDestroy(expression); |
| return result; |
| } |
| if (!avifSampleTransformExpressionIsValid(expression, numInputImageItems)) { |
| avifArrayDestroy(expression); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| static avifResult avifDecoderSampleTransformItemValidateProperties(const avifDecoderItem * item, avifDiagnostics * diag) |
| { |
| const avifProperty * pixiProp = avifPropertyArrayFind(&item->properties, "pixi"); |
| if (!pixiProp) { |
| avifDiagnosticsPrintf(diag, "Item ID %u of type '%.4s' is missing mandatory pixi property", item->id, (const char *)item->type); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| for (uint8_t i = 0; i < pixiProp->u.pixi.planeCount; ++i) { |
| if (pixiProp->u.pixi.planeDepths[i] != pixiProp->u.pixi.planeDepths[0]) { |
| avifDiagnosticsPrintf(diag, |
| "Item ID %u of type '%.4s' has different depths specified by pixi property [%u, %u], this is not supported", |
| item->id, |
| (const char *)item->type, |
| pixiProp->u.pixi.planeDepths[0], |
| pixiProp->u.pixi.planeDepths[i]); |
| return AVIF_RESULT_NOT_IMPLEMENTED; |
| } |
| } |
| |
| const avifProperty * ispeProp = avifPropertyArrayFind(&item->properties, "ispe"); |
| if (!ispeProp) { |
| avifDiagnosticsPrintf(diag, "Item ID %u of type '%.4s' is missing mandatory ispe property", item->id, (const char *)item->type); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| for (uint32_t i = 0; i < item->meta->items.count; ++i) { |
| avifDecoderItem * inputImageItem = item->meta->items.item[i]; |
| if (inputImageItem->dimgForID != item->id) { |
| continue; |
| } |
| // Even if inputImageItem is a grid, the ispe property from its first tile should have been copied to the grid item. |
| const avifProperty * inputImageItemIspeProp = avifPropertyArrayFind(&inputImageItem->properties, "ispe"); |
| AVIF_ASSERT_OR_RETURN(inputImageItemIspeProp != NULL); |
| if (inputImageItemIspeProp->u.ispe.width != ispeProp->u.ispe.width || |
| inputImageItemIspeProp->u.ispe.height != ispeProp->u.ispe.height) { |
| avifDiagnosticsPrintf(diag, |
| "The fields of the ispe property of item ID %u of type '%.4s' differs from item ID %u", |
| inputImageItem->id, |
| (const char *)inputImageItem->type, |
| item->id); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| // TODO(yguyon): Check that all input image items share the same codec config (except for the bit depth value). |
| } |
| |
| AVIF_CHECKERR(avifPropertyArrayFind(&item->properties, "clap") == NULL, AVIF_RESULT_NOT_IMPLEMENTED); |
| return AVIF_RESULT_OK; |
| } |
| #endif // AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM |
| |
| // Extracts the codecType from the item type or from its children. |
| // Also parses and outputs grid information if the item is a grid. |
| // isItemInInput must be false if the item is a made-up structure |
| // (and thus not part of the parseable input bitstream). |
| static avifResult avifDecoderItemReadAndParse(const avifDecoder * decoder, |
| avifDecoderItem * item, |
| avifBool isItemInInput, |
| avifImageGrid * grid, |
| avifCodecType * codecType) |
| { |
| if (!memcmp(item->type, "grid", 4)) { |
| if (isItemInInput) { |
| avifROData readData; |
| AVIF_CHECKRES(avifDecoderItemRead(item, decoder->io, &readData, 0, 0, decoder->data->diag)); |
| AVIF_CHECKERR(avifParseImageGridBox(grid, |
| readData.data, |
| readData.size, |
| decoder->imageSizeLimit, |
| decoder->imageDimensionLimit, |
| decoder->data->diag), |
| AVIF_RESULT_INVALID_IMAGE_GRID); |
| // Validate that there are exactly the same number of dimg items to form the grid. |
| uint32_t dimgItemCount = 0; |
| for (uint32_t i = 0; i < item->meta->items.count; ++i) { |
| if (item->meta->items.item[i]->dimgForID == item->id) { |
| ++dimgItemCount; |
| } |
| } |
| if (dimgItemCount != grid->rows * grid->columns) { |
| return AVIF_RESULT_INVALID_IMAGE_GRID; |
| } |
| } else { |
| // item was generated for convenience and is not part of the bitstream. |
| // grid information should already be set. |
| AVIF_ASSERT_OR_RETURN(grid->rows > 0 && grid->columns > 0); |
| } |
| *codecType = avifDecoderItemGetGridCodecType(item); |
| AVIF_CHECKERR(*codecType != AVIF_CODEC_TYPE_UNKNOWN, AVIF_RESULT_INVALID_IMAGE_GRID); |
| } else { |
| *codecType = avifGetCodecType(item->type); |
| AVIF_ASSERT_OR_RETURN(*codecType != AVIF_CODEC_TYPE_UNKNOWN); |
| } |
| // TODO(yguyon): If AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM is defined, backward-incompatible |
| // files with a primary 'sato' Sample Transform derived image item could be |
| // handled here (compared to backward-compatible files with a 'sato' item in the |
| // same 'altr' group as the primary regular color item which are handled in |
| // avifDecoderDataFindSampleTransformImageItem() below). |
| return AVIF_RESULT_OK; |
| } |
| |
| static avifBool avifParseImageSpatialExtentsProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[ispe]"); |
| AVIF_CHECK(avifROStreamReadAndEnforceVersion(&s, 0)); |
| |
| avifImageSpatialExtents * ispe = &prop->u.ispe; |
| AVIF_CHECK(avifROStreamReadU32(&s, &ispe->width)); |
| AVIF_CHECK(avifROStreamReadU32(&s, &ispe->height)); |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseAuxiliaryTypeProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[auxC]"); |
| AVIF_CHECK(avifROStreamReadAndEnforceVersion(&s, 0)); |
| |
| AVIF_CHECK(avifROStreamReadString(&s, prop->u.auxC.auxType, AUXTYPE_SIZE)); |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseColourInformationBox(avifProperty * prop, uint64_t rawOffset, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[colr]"); |
| |
| avifColourInformationBox * colr = &prop->u.colr; |
| colr->hasICC = AVIF_FALSE; |
| colr->hasNCLX = AVIF_FALSE; |
| |
| uint8_t colorType[4]; // unsigned int(32) colour_type; |
| AVIF_CHECK(avifROStreamRead(&s, colorType, 4)); |
| if (!memcmp(colorType, "rICC", 4) || !memcmp(colorType, "prof", 4)) { |
| colr->hasICC = AVIF_TRUE; |
| // Remember the offset of the ICC payload relative to the beginning of the stream. A direct pointer cannot be stored |
| // because decoder->io->persistent could have been AVIF_FALSE when obtaining raw through decoder->io->read(). |
| // The bytes could be copied now instead of remembering the offset, but it is as invasive as passing rawOffset everywhere. |
| colr->iccOffset = rawOffset + avifROStreamOffset(&s); |
| colr->iccSize = avifROStreamRemainingBytes(&s); |
| } else if (!memcmp(colorType, "nclx", 4)) { |
| AVIF_CHECK(avifROStreamReadU16(&s, &colr->colorPrimaries)); // unsigned int(16) colour_primaries; |
| AVIF_CHECK(avifROStreamReadU16(&s, &colr->transferCharacteristics)); // unsigned int(16) transfer_characteristics; |
| AVIF_CHECK(avifROStreamReadU16(&s, &colr->matrixCoefficients)); // unsigned int(16) matrix_coefficients; |
| uint8_t full_range_flag; |
| AVIF_CHECK(avifROStreamReadBits8(&s, &full_range_flag, /*bitCount=*/1)); // unsigned int(1) full_range_flag; |
| colr->range = full_range_flag ? AVIF_RANGE_FULL : AVIF_RANGE_LIMITED; |
| uint8_t reserved; |
| AVIF_CHECK(avifROStreamReadBits8(&s, &reserved, /*bitCount=*/7)); // unsigned int(7) reserved = 0; |
| if (reserved) { |
| avifDiagnosticsPrintf(diag, "Box[colr] contains nonzero reserved bits [%u]", reserved); |
| return AVIF_FALSE; |
| } |
| colr->hasNCLX = AVIF_TRUE; |
| } |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseContentLightLevelInformationBox(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[clli]"); |
| |
| avifContentLightLevelInformationBox * clli = &prop->u.clli; |
| |
| AVIF_CHECK(avifROStreamReadU16(&s, &clli->maxCLL)); // unsigned int(16) max_content_light_level |
| AVIF_CHECK(avifROStreamReadU16(&s, &clli->maxPALL)); // unsigned int(16) max_pic_average_light_level |
| return AVIF_TRUE; |
| } |
| |
| // Implementation of section 2.3.3 of AV1 Codec ISO Media File Format Binding specification v1.2.0. |
| // See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax. |
| static avifBool avifParseCodecConfiguration(avifROStream * s, avifCodecConfigurationBox * config, const char * configPropName, avifDiagnostics * diag) |
| { |
| uint32_t marker, version; |
| AVIF_CHECK(avifROStreamReadBits(s, &marker, /*bitCount=*/1)); // unsigned int (1) marker = 1; |
| if (!marker) { |
| avifDiagnosticsPrintf(diag, "%s contains illegal marker: [%u]", configPropName, marker); |
| return AVIF_FALSE; |
| } |
| AVIF_CHECK(avifROStreamReadBits(s, &version, /*bitCount=*/7)); // unsigned int (7) version = 1; |
| if (version != 1) { |
| avifDiagnosticsPrintf(diag, "%s contains illegal version: [%u]", configPropName, version); |
| return AVIF_FALSE; |
| } |
| |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->seqProfile, /*bitCount=*/3)); // unsigned int (3) seq_profile; |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->seqLevelIdx0, /*bitCount=*/5)); // unsigned int (5) seq_level_idx_0; |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->seqTier0, /*bitCount=*/1)); // unsigned int (1) seq_tier_0; |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->highBitdepth, /*bitCount=*/1)); // unsigned int (1) high_bitdepth; |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->twelveBit, /*bitCount=*/1)); // unsigned int (1) twelve_bit; |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->monochrome, /*bitCount=*/1)); // unsigned int (1) monochrome; |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->chromaSubsamplingX, /*bitCount=*/1)); // unsigned int (1) chroma_subsampling_x; |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->chromaSubsamplingY, /*bitCount=*/1)); // unsigned int (1) chroma_subsampling_y; |
| AVIF_CHECK(avifROStreamReadBits8(s, &config->chromaSamplePosition, /*bitCount=*/2)); // unsigned int (2) chroma_sample_position; |
| |
| // 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; |
| // } |
| AVIF_CHECK(avifROStreamSkip(s, /*byteCount=*/1)); |
| |
| // According to section 2.2.1 of AV1 Image File Format specification v1.1.0: |
| // - Sequence Header OBUs should not be present in the AV1CodecConfigurationBox. |
| // - If a Sequence Header OBU is present in the AV1CodecConfigurationBox, |
| // it shall match the Sequence Header OBU in the AV1 Image Item Data. |
| // - Metadata OBUs, if present, shall match the values given in other item properties, |
| // such as the PixelInformationProperty or ColourInformationBox. |
| // See https://aomediacodec.github.io/av1-avif/v1.1.0.html#av1-configuration-item-property. |
| // For simplicity, the constraints above are not enforced. |
| // The following is skipped by avifParseItemPropertyContainerBox(). |
| // unsigned int (8) configOBUs[]; |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseCodecConfigurationBoxProperty(avifProperty * prop, |
| const uint8_t * raw, |
| size_t rawLen, |
| const char * configPropName, |
| avifDiagnostics * diag) |
| { |
| char diagContext[10]; |
| snprintf(diagContext, sizeof(diagContext), "Box[%.4s]", configPropName); // "Box[av1C]" or "Box[av2C]" |
| BEGIN_STREAM(s, raw, rawLen, diag, diagContext); |
| return avifParseCodecConfiguration(&s, &prop->u.av1C, configPropName, diag); |
| } |
| |
| static avifBool avifParsePixelAspectRatioBoxProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[pasp]"); |
| |
| avifPixelAspectRatioBox * pasp = &prop->u.pasp; |
| AVIF_CHECK(avifROStreamReadU32(&s, &pasp->hSpacing)); // unsigned int(32) hSpacing; |
| AVIF_CHECK(avifROStreamReadU32(&s, &pasp->vSpacing)); // unsigned int(32) vSpacing; |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseCleanApertureBoxProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[clap]"); |
| |
| avifCleanApertureBox * clap = &prop->u.clap; |
| AVIF_CHECK(avifROStreamReadU32(&s, &clap->widthN)); // unsigned int(32) cleanApertureWidthN; |
| AVIF_CHECK(avifROStreamReadU32(&s, &clap->widthD)); // unsigned int(32) cleanApertureWidthD; |
| AVIF_CHECK(avifROStreamReadU32(&s, &clap->heightN)); // unsigned int(32) cleanApertureHeightN; |
| AVIF_CHECK(avifROStreamReadU32(&s, &clap->heightD)); // unsigned int(32) cleanApertureHeightD; |
| AVIF_CHECK(avifROStreamReadU32(&s, &clap->horizOffN)); // unsigned int(32) horizOffN; |
| AVIF_CHECK(avifROStreamReadU32(&s, &clap->horizOffD)); // unsigned int(32) horizOffD; |
| AVIF_CHECK(avifROStreamReadU32(&s, &clap->vertOffN)); // unsigned int(32) vertOffN; |
| AVIF_CHECK(avifROStreamReadU32(&s, &clap->vertOffD)); // unsigned int(32) vertOffD; |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseImageRotationProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[irot]"); |
| |
| avifImageRotation * irot = &prop->u.irot; |
| uint8_t reserved; |
| AVIF_CHECK(avifROStreamReadBits8(&s, &reserved, /*bitCount=*/6)); // unsigned int (6) reserved = 0; |
| if (reserved) { |
| avifDiagnosticsPrintf(diag, "Box[irot] contains nonzero reserved bits [%u]", reserved); |
| return AVIF_FALSE; |
| } |
| AVIF_CHECK(avifROStreamReadBits8(&s, &irot->angle, /*bitCount=*/2)); // unsigned int (2) angle; |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseImageMirrorProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[imir]"); |
| |
| avifImageMirror * imir = &prop->u.imir; |
| uint8_t reserved; |
| AVIF_CHECK(avifROStreamReadBits8(&s, &reserved, /*bitCount=*/7)); // unsigned int(7) reserved = 0; |
| if (reserved) { |
| avifDiagnosticsPrintf(diag, "Box[imir] contains nonzero reserved bits [%u]", reserved); |
| return AVIF_FALSE; |
| } |
| AVIF_CHECK(avifROStreamReadBits8(&s, &imir->axis, /*bitCount=*/1)); // unsigned int(1) axis; |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParsePixelInformationProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[pixi]"); |
| AVIF_CHECK(avifROStreamReadAndEnforceVersion(&s, 0)); |
| |
| avifPixelInformationProperty * pixi = &prop->u.pixi; |
| AVIF_CHECK(avifROStreamRead(&s, &pixi->planeCount, 1)); // unsigned int (8) num_channels; |
| if (pixi->planeCount < 1 || pixi->planeCount > MAX_PIXI_PLANE_DEPTHS) { |
| avifDiagnosticsPrintf(diag, "Box[pixi] contains unsupported plane count [%u]", pixi->planeCount); |
| return AVIF_FALSE; |
| } |
| for (uint8_t i = 0; i < pixi->planeCount; ++i) { |
| AVIF_CHECK(avifROStreamRead(&s, &pixi->planeDepths[i], 1)); // unsigned int (8) bits_per_channel; |
| if (pixi->planeDepths[i] != pixi->planeDepths[0]) { |
| avifDiagnosticsPrintf(diag, |
| "Box[pixi] contains unsupported mismatched plane depths [%u != %u]", |
| pixi->planeDepths[i], |
| pixi->planeDepths[0]); |
| return AVIF_FALSE; |
| } |
| } |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseOperatingPointSelectorProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[a1op]"); |
| |
| avifOperatingPointSelectorProperty * a1op = &prop->u.a1op; |
| AVIF_CHECK(avifROStreamRead(&s, &a1op->opIndex, 1)); |
| if (a1op->opIndex > 31) { // 31 is AV1's max operating point value |
| avifDiagnosticsPrintf(diag, "Box[a1op] contains an unsupported operating point [%u]", a1op->opIndex); |
| return AVIF_FALSE; |
| } |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseLayerSelectorProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[lsel]"); |
| |
| avifLayerSelectorProperty * lsel = &prop->u.lsel; |
| AVIF_CHECK(avifROStreamReadU16(&s, &lsel->layerID)); |
| if ((lsel->layerID != 0xFFFF) && (lsel->layerID >= AVIF_MAX_AV1_LAYER_COUNT)) { |
| avifDiagnosticsPrintf(diag, "Box[lsel] contains an unsupported layer [%u]", lsel->layerID); |
| return AVIF_FALSE; |
| } |
| return AVIF_TRUE; |
| } |
| |
| static avifBool avifParseAV1LayeredImageIndexingProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[a1lx]"); |
| |
| avifAV1LayeredImageIndexingProperty * a1lx = &prop->u.a1lx; |
| |
| uint8_t largeSize = 0; |
| AVIF_CHECK(avifROStreamRead(&s, &largeSize, 1)); |
| if (largeSize & 0xFE) { |
| avifDiagnosticsPrintf(diag, "Box[a1lx] has bits set in the reserved section [%u]", largeSize); |
| return AVIF_FALSE; |
| } |
| |
| for (int i = 0; i < 3; ++i) { |
| if (largeSize) { |
| AVIF_CHECK(avifROStreamReadU32(&s, &a1lx->layerSize[i])); |
| } else { |
| uint16_t layerSize16; |
| AVIF_CHECK(avifROStreamReadU16(&s, &layerSize16)); |
| a1lx->layerSize[i] = (uint32_t)layerSize16; |
| } |
| } |
| |
| // Layer sizes will be validated later (when the item's size is known) |
| return AVIF_TRUE; |
| } |
| |
| static avifResult avifParseItemPropertyContainerBox(avifPropertyArray * properties, |
| uint64_t rawOffset, |
| const uint8_t * raw, |
| size_t rawLen, |
| avifDiagnostics * diag) |
| { |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[ipco]"); |
| |
| while (avifROStreamHasBytesLeft(&s, 1)) { |
| avifBoxHeader header; |
| AVIF_CHECKERR(avifROStreamReadBoxHeader(&s, &header), AVIF_RESULT_BMFF_PARSE_FAILED); |
| |
| avifProperty * prop = avifArrayPush(properties); |
| AVIF_CHECKERR(prop != NULL, AVIF_RESULT_OUT_OF_MEMORY); |
| memcpy(prop->type, header.type, 4); |
| if (!memcmp(header.type, "ispe", 4)) { |
| AVIF_CHECKERR(avifParseImageSpatialExtentsProperty(prop, avifROStreamCurrent(&s), header.size, diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "auxC", 4)) { |
| AVIF_CHECKERR(avifParseAuxiliaryTypeProperty(prop, avifROStreamCurrent(&s), header.size, diag), AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "colr", 4)) { |
| AVIF_CHECKERR(avifParseColourInformationBox(prop, rawOffset + avifROStreamOffset(&s), avifROStreamCurrent(&s), header.size, diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "av1C", 4)) { |
| AVIF_CHECKERR(avifParseCodecConfigurationBoxProperty(prop, avifROStreamCurrent(&s), header.size, "av1C", diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| #if defined(AVIF_CODEC_AVM) |
| } else if (!memcmp(header.type, "av2C", 4)) { |
| AVIF_CHECKERR(avifParseCodecConfigurationBoxProperty(prop, avifROStreamCurrent(&s), header.size, "av2C", diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| #endif |
| } else if (!memcmp(header.type, "pasp", 4)) { |
| AVIF_CHECKERR(avifParsePixelAspectRatioBoxProperty(prop, avifROStreamCurrent(&s), header.size, diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "clap", 4)) { |
| AVIF_CHECKERR(avifParseCleanApertureBoxProperty(prop, avifROStreamCurrent(&s), header.size, diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "irot", 4)) { |
| AVIF_CHECKERR(avifParseImageRotationProperty(prop, avifROStreamCurrent(&s), header.size, diag), AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "imir", 4)) { |
| AVIF_CHECKERR(avifParseImageMirrorProperty(prop, avifROStreamCurrent(&s), header.size, diag), AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "pixi", 4)) { |
| AVIF_CHECKERR(avifParsePixelInformationProperty(prop, avifROStreamCurrent(&s), header.size, diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "a1op", 4)) { |
| AVIF_CHECKERR(avifParseOperatingPointSelectorProperty(prop, avifROStreamCurrent(&s), header.size, diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "lsel", 4)) { |
| AVIF_CHECKERR(avifParseLayerSelectorProperty(prop, avifROStreamCurrent(&s), header.size, diag), AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "a1lx", 4)) { |
| AVIF_CHECKERR(avifParseAV1LayeredImageIndexingProperty(prop, avifROStreamCurrent(&s), header.size, diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| } else if (!memcmp(header.type, "clli", 4)) { |
| AVIF_CHECKERR(avifParseContentLightLevelInformationBox(prop, avifROStreamCurrent(&s), header.size, diag), |
| AVIF_RESULT_BMFF_PARSE_FAILED); |
| } |
| |
| AVIF_CHECKERR(avifROStreamSkip(&s, header.size), AVIF_RESULT_BMFF_PARSE_FAILED); |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| static avifResult avifParseItemPropertyAssociation(avifMeta * meta, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag, uint32_t * outVersionAndFlags) |
| { |
| #if defined(AVIF_ENABLE_EXPERIMENTAL_MINI) |
| if (meta->fromMini) { |
| // The MinimizedImageBox will always create 8 item properties, so to refer to the |
| // first property in the ItemPropertyContainerBox of its extendedMeta field, use index 9. |
| AVIF_ASSERT_OR_RETURN(meta->properties.count >= 8); |
| } |
| #endif // AVIF_ENABLE_EXPERIMENTAL_MINI |
| |
| // NOTE: If this function ever adds support for versions other than [0,1] or flags other than |
| // [0,1], please increase the value of MAX_IPMA_VERSION_AND_FLAGS_SEEN accordingly. |
| |
| BEGIN_STREAM(s, raw, rawLen, diag, "Box[ipma]"); |
| |
| uint8_t version; |
| uint32_t flags; |
| AVIF_CHECKERR(avifROStreamReadVersionAndFlags(&s, &version, &flags), AVIF_RESULT_BMFF_PARSE_FAILED); |
| avifBool propertyIndexIsU15 = ((flags & 0x1) != 0); |
| *outVersionAndFlags = ((uint32_t)version << 24) | flags; |
| |
| uint32_t entryCount; |
| AVIF_CHECKERR(avifROStreamReadU32(&s, &entryCount), AVIF_RESULT_BMFF_PARSE_FAILED); |
| unsigned int prevItemID = 0; |
| for (uint32_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) { |
| // ISO/IEC 14496-12, Seventh edition, 2022-01, Section 8.11.14.1: |
| // Each ItemPropertyAssociationBox shall be ordered by increasing item_ID, and there shall |
| // be at most one occurrence of a given item_ID, in the set of ItemPropertyAssociationBox |
| // boxes. |
| unsigned int itemID; |
| if (version < 1) { |
| uint16_t tmp; |
| AVIF_CHECKERR(avifROStreamReadU16(&s, &tmp), AVIF_RESULT_BMFF_PARSE_FAILED); |
| itemID = tmp; |
| } else { |
| AVIF_CHECKERR(avifROStreamReadU32(&s, &itemID), AVIF_RESULT_BMFF_PARSE_FAILED); |
| } |
| AVIF_CHECKRES(avifCheckItemID("ipma", itemID, diag)); |
| if (itemID <= prevItemID) { |
| avifDiagnosticsPrintf(diag, "Box[ipma] item IDs are not ordered by increasing ID"); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| prevItemID = itemID; |
| |
| avifDecoderItem * item; |
| AVIF_CHECKRES(avifMetaFindOrCreateItem(meta, itemID, &item)); |
| if (item->ipmaSeen) { |
| avifDiagnosticsPrintf(diag, "Duplicate Box[ipma] for item ID [%u]", itemID); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| item->ipmaSeen = AVIF_TRUE; |
| |
| uint8_t associationCount; |
| AVIF_CHECKERR(avifROStreamRead(&s, &associationCount, 1), AVIF_RESULT_BMFF_PARSE_FAILED); |
| for (uint8_t associationIndex = 0; associationIndex < associationCount; ++associationIndex) { |
| uint8_t essential; |
| AVIF_CHECKERR(avifROStreamReadBits8(&s, &essential, /*bitCount=*/1), AVIF_RESULT_BMFF_PARSE_FAILED); // bit(1) essential; |
| uint32_t propertyIndex; |
| AVIF_CHECKERR(avifROStreamReadBits(&s, &propertyIndex, /*bitCount=*/propertyIndexIsU15 ? 15 : 7), |
| AVIF_RESULT_BMFF_PARSE_FAILED); // unsigned int(7/15) property_index; |
| |
| if (propertyIndex == 0) { |
| // Not associated with any item |
| continue; |
| } |
| --propertyIndex; // 1-indexed |
| |
| if (propertyIndex >= meta->properties.count) { |
| avifDiagnosticsPrintf(diag, |
| "Box[ipma] for item ID [%u] contains an illegal property index [%u] (out of [%u] properties)", |
| itemID, |
| propertyIndex, |
| meta->properties.count); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| |
| // Copy property to item |
| const avifProperty * srcProp = &meta->properties.prop[propertyIndex]; |
| |
| static const char * supportedTypes[] = { |
| "ispe", |
| "auxC", |
| "colr", |
| "av1C", |
| #if defined(AVIF_CODEC_AVM) |
| "av2C", |
| #endif |
| "pasp", |
| "clap", |
| "irot", |
| "imir", |
| "pixi", |
| "a1op", |
| "lsel", |
| "a1lx", |
| "clli" |
| }; |
| size_t supportedTypesCount = sizeof(supportedTypes) / sizeof(supportedTypes[0]); |
| avifBool supportedType = AVIF_FALSE; |
| for (size_t i = 0; i < supportedTypesCount; ++i) { |
| if (!memcmp(srcProp->type, supportedTypes[i], 4)) { |
| supportedType = AVIF_TRUE; |
| break; |
| } |
| } |
| if (supportedType) { |
| if (essential) { |
| // Verify that it is legal for this property to be flagged as essential. Any |
| // types in this list are *required* in the spec to not be flagged as essential |
| // when associated with an item. |
| static const char * const nonessentialTypes[] = { |
| |
| // AVIF: Section 2.3.2.3.2: "If associated, it shall not be marked as essential." |
| "a1lx" |
| |
| }; |
| size_t nonessentialTypesCount = sizeof(nonessentialTypes) / sizeof(nonessentialTypes[0]); |
| for (size_t i = 0; i < nonessentialTypesCount; ++i) { |
| if (!memcmp(srcProp->type, nonessentialTypes[i], 4)) { |
| avifDiagnosticsPrintf(diag, |
| "Item ID [%u] has a %s property association which must not be marked essential, but is", |
| itemID, |
| nonessentialTypes[i]); |
| return AVIF_RESULT_BMFF_PARSE_FAILED; |
| } |
| } |
| } else { |
| // Verify that it is legal for this property to not be flagged as essential. Any |
| // types in this list are *required* in the spec to be flagged as essential when |
| // associated with an item. |
| static const char * const essentialTypes[] = { |
| |
| // AVIF: Section 2.3.2.1.1: "If associated, it shall be marked as essential." |
| "a1op", |
| |
| // HEIF: Section 6.5.11.1: "essential shall be equal to 1 for an 'lsel' item property." |
| "lsel" |
| |
| }; |
| size_t essentialTypesCount = sizeof(essentialTypes) / sizeof(essentialTypes[0]); |
| for (size_t i = 0; i < essentialTypesCount; ++i) { |
| if (!memcmp(srcProp->type, essentialTypes[i], 4)) { |
| avifDiagnosticsPrintf(diag, |
| "Item ID [%u] has a %s property association which must be marked essential, but is not", |
| itemID, |
| essentialTypes[i]); |
|