diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 506b0f454..f62035a6a 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -109,6 +109,7 @@ This snapshot pins or classifies the available references and failures; it does | `HeifDecoderCore` color-property parsing/association, `HeifItem` color profiles, and `Av1Decoder` effective color validation | ISO/IEC 14496-12 section 12.1.5 color information; HEIF section 6.5.5.1 color-information properties; AV1-ISOBMFF section 2.3.4 configuration semantics | libavif `src/read.c` functions `avifParseColourInformationBox`, `avifReadColorNclxProperty`, and `avifReadColorProperties`, plus `src/write.c` function `avifEncoderWriteColorProperties`, at `092276ce89098ead06db80975173191e5fee1826` | Associate at most one ICC and one `nclx` property with each presented color image item, validate ICC payloads and CICP reserved bits, expose them through ImageSharp's existing profile types, and inherit a grid's CICP description only for tiles that do not declare one. For AV1 items, require every specified sequence-header CICP field and the range flag to match `nclx`; use the item property only to supply sequence-header fields coded as unspecified before color conversion, then expose the effective CICP values even when no item property is present. Retain only the two image color profiles; do not add a reusable color-box, sample-entry, track, or media model. | | `HeifPixelAspectRatio`, `HeifItem.PixelAspectRatio`, and `HeifDecoderCore.ApplyItemPixelAspectRatioMetadata` | ISO/IEC 14496-12 section 12.1.4.3 pixel aspect ratio | libavif `src/read.c` function `avifParsePixelAspectRatioBox`, `src/write.c` function `avifEncoderWritePaspProperty`, and the presented-image property selection in `src/read.c` at `092276ce89098ead06db80975173191e5fee1826` | Preserve the two unsigned 32-bit relative spacings on the associated image item, reject zero or duplicate ratios, and map the displayed pixel width-to-height ratio into ImageSharp's existing unitless resolution metadata. Exchange the metadata axes after a quarter-turn presentation rotation and fall back from a derived grid to its first decodable tile only when the grid does not declare `pasp`. This remains one still-image presentation property and introduces no generic transform, sample-entry, or display model. | | `Av1CodecConfiguration`, `HeifItem.Av1CodecConfiguration`, `Av1HeifItemDecoder`, and AV1 grid configuration checks | AV1-ISOBMFF sections 2.3.3 and 2.3.4 codec-configuration record syntax and semantics; AVIF sections 2.1, 2.2.1, and 2.2.3 AV1 image-item, item-configuration, and HDR metadata constraints; AV1 sections 5.8.3, 5.8.4, 6.7.3, and 6.7.4 HDR metadata syntax and semantics; ISOBMFF mastering-display and content-light image properties; ITU-T H.274 section 8.9 mastering-display field semantics; MIAF section 7.3.11.4.1 grid input constraints | libavif `src/read.c` functions `avifParseCodecConfiguration`, `avifDecoderItemValidateProperties`, `avifReadCodecConfigProperty`, `avifParseContentLightLevelInformation`, and `avifSkipMasteringDisplayColourVolume` at `092276ce89098ead06db80975173191e5fee1826`; libaom `av1/decoder/obu.c` functions `read_metadata`, `read_metadata_hdr_cll`, and `read_metadata_hdr_mdcv` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Associate exactly one `av1C` property with each decoded `av01` image item, validate the fixed record and its bit depth/chroma fields against the item's AV1 sequence header and optional `pixi` channel depths, require matching configurations across grid tiles, and report the encoded image precision and monochrome shape through `HeifMetadata`. Validate low-overhead OBU framing, require exactly one sequence header in the image item, allow at most one first-position sequence header in `configOBUs`, and compare a repeated header's extension and payload exactly while ignoring only its legal size-field representation. Decode `clli` and `mdcv` as still-image item properties, validate matching HDR CLL and HDR MDCV metadata OBUs from the combined configuration/item sequence, and account for the different primary order and fixed-point precision of the ISOBMFF and AV1 MDCV representations. Expose the effective HDR values without adding sample groups, tracks, or media metadata. Related still-image HDR properties remain required. Consume but do not retain presentation-delay syntax, and introduce no sample entry, sample description, track, timing, or generic decoder-configuration model. | +| `HevcCodecConfiguration`, `HevcNalUnitArray`, and `HeifItem.HevcCodecConfiguration` | ISO/IEC 14496-15 `HEVCDecoderConfigurationRecord`, HEVC NAL-unit header syntax, and HEVC image-item configuration requirements | Android `libhevc` v1.6.0 decoder NAL and parameter-header paths at `c83a76b084498d55f252f48b2e3786804cdf24b7`; independently produced HEIC fixtures for `hvcC` record layout | Associate exactly one `hvcC` property with each `hvc1` image item; validate the fixed profile, level, chroma, bit-depth, reserved, length-size, array, and NAL-header fields within the property boundary; and retain only the image description plus bounded configuration NAL units required to decode that item. Consume but do not retain average-frame-rate or temporal-layer presentation fields. Do not add visual sample entries, sample descriptions, tracks, timing, access-unit timelines, or a generic decoder-configuration model. Apache-2.0 notice preservation and separate HEVC patent/release clearance remain final integration gates. | | `HeifContentColorVolume`, `HeifItem.ContentColorVolume`, and `HeifDecoderCore` content color-volume parsing and presentation | HEIF content color-volume item property; AVIF 1.2 content color-volume requirements; ITU-T H.274 (V4) content colour volume syntax and semantics | libavif `src/read.c` function `avifSkipContentColourVolume` at `092276ce89098ead06db80975173191e5fee1826`; official ITU-T H.274 (V4), January 2026 | Decode only the bounded per-image `cclv` property: require zero cancellation, persistence, and reserved bits; preserve optional signed G/B/R primary coordinates and normalized minimum, maximum, and average luminance values; and validate their registered ranges and ordering. Expose the effective grid-or-tile still-image value through `HeifMetadata`. Do not add SEI persistence, retained video state, tracks, samples, timing, or a generic ISO BMFF color-volume box model. | | `HeifAmbientViewingEnvironment`, `HeifReferenceViewingEnvironment`, `HeifNominalDiffuseWhite`, and their per-item presentation metadata | ISOBMFF ambient viewing environment; ITU-T H.274 (V4) section 8.13; HEIF Amendment 1 sections 6.5.44 and 6.5.45; AVIF 1.2 image-item box requirements | libavif `src/read.c` functions `avifSkipAmbientViewingEnvironment`, `avifSkipReferenceViewingEnvironment`, and `avifSkipNominalDiffuseWhite` at `092276ce89098ead06db80975173191e5fee1826`; official ITU-T H.274 (V4), January 2026 | Decode the fixed-size `amve`, version-zero `reve`, and version-zero `ndwt` properties only when associated with the presented still-image item. Preserve physical illuminance and luminance units, distinct surround/periphery chromaticities, and the coded zero that requests the standard nominal diffuse-white default. Validate registered coordinate ranges and duplicate associations. Retain no video-SEI persistence, visual sample entry, display pipeline, track, timing, or generic viewing-environment box model. | | `GridHeifItemDecoder` and `HeifDecoderCore` grid/thumbnail selection | ISO/IEC 23008-12 section 6.6.2.3 image-grid syntax and MIAF grid-cell constraints | libavif `src/read.c` functions `avifParseImageGridBox`, `avifDecoderDataAllocateImagePlanes`, and `avifDecoderDataCopyTileToImage` at `092276ce89098ead06db80975173191e5fee1826` | Parse version-zero 16-bit and 32-bit grid descriptors, preserve row-major `dimg` order, require the declared tile count and one coding format, validate canvas coverage and edge overlap, and crop only the rightmost column and bottom row while copying through ImageSharp pixel buffers. A primary grid whose tile codec is unavailable may use only a decodable thumbnail that explicitly references that grid. | diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 55dc93702..3c43774f2 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -172,3 +172,215 @@ Alliance for Open Media Patent License 1.0 2.12. Specification. "Specification" means the specification designated by the Alliance for Open Media as a Final Deliverable for which this License was issued. + + +License notice for the Ittiam libhevc Codec Library +----- + +Parts of the HEVC implementation are adapted from the Android fork of the +Ittiam libhevc Codec Library. +https://android.googlesource.com/platform/external/libhevc/ + +Copyright (C) 2012 Ittiam Systems Pvt Ltd, Bangalore + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs index fa354caad..94bfe685e 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs @@ -104,7 +104,7 @@ public enum Heif4CharCode : uint Ndwt = 0x6E647774U, /// - /// HVC configuration. + /// HEVC codec-configuration item property. /// HvcC = 0x68766343U, @@ -219,7 +219,7 @@ public enum Heif4CharCode : uint Avis = 0x61766973U, /// - /// High Efficiency Coding tile. + /// HEVC-coded image item. /// Hvc1 = 0x68766331U, diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 7c4e412af..ccc2e55e4 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -7,6 +7,7 @@ using System.Text; using SixLabors.ImageSharp.ColorProfiles; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Hevc; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -269,6 +270,19 @@ internal sealed class HeifDecoderCore : ImageDecoderCore meta.BitDepth = codecConfiguration.BitDepth; meta.IsMonochrome = codecConfiguration.IsMonochrome; } + else if (metadataItem.Type == Heif4CharCode.Hvc1) + { + HevcCodecConfiguration codecConfiguration = metadataItem.HevcCodecConfiguration + ?? throw new InvalidImageContentException($"HEVC image item {metadataItem.Id} has no codec configuration property."); + + if (metadataItem.ChannelBitDepths is not null) + { + codecConfiguration.ValidateChannelBitDepths(metadataItem.ChannelBitDepths); + } + + meta.BitDepth = codecConfiguration.BitDepth; + meta.IsMonochrome = codecConfiguration.IsMonochrome; + } else if (metadataItem.Type == Heif4CharCode.Jpeg) { compressionMethod = HeifCompressionMethod.LegacyJpeg; @@ -1267,6 +1281,13 @@ internal sealed class HeifDecoderCore : ImageDecoderCore Heif4CharCode.Av1C, new Av1CodecConfiguration(boxBuffer))); + break; + case Heif4CharCode.HvcC: + properties.Add( + new KeyValuePair( + Heif4CharCode.HvcC, + new HevcCodecConfiguration(boxBuffer))); + break; case Heif4CharCode.Clap: EnsureBufferRemaining(boxBuffer, 0, 32, "clean aperture"); @@ -1302,7 +1323,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore break; case Heif4CharCode.Altt: case Heif4CharCode.Iscl: - case Heif4CharCode.HvcC: case Heif4CharCode.Rloc: case Heif4CharCode.Udes: // These registered image properties are not arbitrary unknown boxes. Preserve their indices so @@ -1442,6 +1462,19 @@ internal sealed class HeifDecoderCore : ImageDecoderCore item.Av1CodecConfiguration = (Av1CodecConfiguration)prop.Value; break; + case Heif4CharCode.HvcC: + if (item.Type != Heif4CharCode.Hvc1) + { + throw new InvalidImageContentException($"Item {itemId} associates an HEVC codec configuration with non-HEVC item type '{PrettyPrint(item.Type)}'."); + } + + if (item.HevcCodecConfiguration is not null) + { + throw new InvalidImageContentException($"Item {itemId} associates more than one HEVC codec configuration property."); + } + + item.HevcCodecConfiguration = (HevcCodecConfiguration)prop.Value; + break; case Heif4CharCode.AuxC: if (item.AuxiliaryType is not null) { diff --git a/src/ImageSharp/Formats/Heif/HeifItem.cs b/src/ImageSharp/Formats/Heif/HeifItem.cs index 7dc8d5e7e..e26467cda 100644 --- a/src/ImageSharp/Formats/Heif/HeifItem.cs +++ b/src/ImageSharp/Formats/Heif/HeifItem.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Hevc; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -108,6 +109,12 @@ internal class HeifItem(Heif4CharCode type, uint id) /// public Av1CodecConfiguration? Av1CodecConfiguration { get; set; } + /// + /// Gets or sets the HEVC codec configuration associated with this coded image item, or + /// when the item has no HEVC codec-configuration property. + /// + public HevcCodecConfiguration? HevcCodecConfiguration { get; set; } + /// /// Gets or sets the relative pixel spacing associated with this image item, or when the /// item has no pixel-aspect-ratio property. diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs new file mode 100644 index 000000000..c3375549c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs @@ -0,0 +1,275 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Contains the image-description fields and parameter-set arrays stored in an HEVC codec-configuration item +/// property. +/// +internal sealed class HevcCodecConfiguration +{ + /// + /// The NAL-unit arrays carried by the codec-configuration property. + /// + private readonly HevcNalUnitArray[] nalUnitArrays; + + /// + /// Initializes a new instance of the class from an HEVC + /// codec-configuration item-property payload. + /// + /// The complete bounded configuration payload. + public HevcCodecConfiguration(ReadOnlySpan data) + { + const int fixedRecordLength = 23; + if (data.Length < fixedRecordLength) + { + throw new InvalidImageContentException("The HEVC codec configuration is truncated."); + } + + int offset = 0; + if (data[offset++] != 1) + { + throw new InvalidImageContentException("The HEVC codec configuration has an unsupported version."); + } + + byte profile = data[offset++]; + this.GeneralProfileSpace = (byte)(profile >> 6); + this.GeneralTierFlag = (profile & 0x20) != 0; + this.GeneralProfileIdc = (byte)(profile & 0x1F); + this.GeneralProfileCompatibilityFlags = BinaryPrimitives.ReadUInt32BigEndian(data[offset..]); + offset += 4; + this.GeneralConstraintIndicatorFlags = ((ulong)BinaryPrimitives.ReadUInt32BigEndian(data[offset..]) << 16) + | BinaryPrimitives.ReadUInt16BigEndian(data[(offset + 4)..]); + + offset += 6; + this.GeneralLevelIdc = data[offset++]; + + ushort spatialSegmentation = BinaryPrimitives.ReadUInt16BigEndian(data[offset..]); + offset += 2; + byte parallelism = data[offset++]; + byte chromaFormat = data[offset++]; + byte lumaBitDepth = data[offset++]; + byte chromaBitDepth = data[offset++]; + if ((spatialSegmentation & 0xF000) != 0xF000 + || (parallelism & 0xFC) != 0xFC + || (chromaFormat & 0xFC) != 0xFC + || (lumaBitDepth & 0xF8) != 0xF8 + || (chromaBitDepth & 0xF8) != 0xF8) + { + throw new InvalidImageContentException("The HEVC codec configuration has invalid reserved bits."); + } + + this.ChromaFormat = (byte)(chromaFormat & 3); + this.BitDepthLuma = 8 + (lumaBitDepth & 7); + this.BitDepthChroma = 8 + (chromaBitDepth & 7); + + // Average frame rate and temporal-layer signaling describe timed samples. Consume those fixed-record fields + // to reach the image item's NAL length width without retaining playback state in the still-image model. + offset += 2; + byte temporalAndLengthFields = data[offset++]; + this.NalUnitLengthSize = (temporalAndLengthFields & 3) + 1; + + int arrayCount = data[offset++]; + this.nalUnitArrays = new HevcNalUnitArray[arrayCount]; + Span seenNalUnitTypes = stackalloc bool[64]; + for (int arrayIndex = 0; arrayIndex < arrayCount; arrayIndex++) + { + if (data.Length - offset < 3) + { + throw new InvalidImageContentException("The HEVC codec configuration contains a truncated NAL-unit array header."); + } + + byte arrayHeader = data[offset++]; + if ((arrayHeader & 0x40) != 0) + { + throw new InvalidImageContentException("The HEVC codec configuration NAL-unit array has a nonzero reserved bit."); + } + + bool isComplete = (arrayHeader & 0x80) != 0; + byte nalUnitType = (byte)(arrayHeader & 0x3F); + if (seenNalUnitTypes[nalUnitType]) + { + throw new InvalidImageContentException($"The HEVC codec configuration contains more than one array for NAL-unit type {nalUnitType}."); + } + + seenNalUnitTypes[nalUnitType] = true; + int nalUnitCount = BinaryPrimitives.ReadUInt16BigEndian(data[offset..]); + offset += 2; + ReadOnlyMemory[] nalUnits = new ReadOnlyMemory[nalUnitCount]; + for (int nalUnitIndex = 0; nalUnitIndex < nalUnitCount; nalUnitIndex++) + { + if (data.Length - offset < 2) + { + throw new InvalidImageContentException("The HEVC codec configuration contains a truncated NAL-unit length."); + } + + int nalUnitLength = BinaryPrimitives.ReadUInt16BigEndian(data[offset..]); + offset += 2; + if (nalUnitLength < 2 || nalUnitLength > data.Length - offset) + { + throw new InvalidImageContentException("The HEVC codec configuration contains an invalid NAL-unit length."); + } + + ReadOnlySpan nalUnit = data.Slice(offset, nalUnitLength); + byte actualNalUnitType = (byte)((nalUnit[0] >> 1) & 0x3F); + + // The two-byte HEVC NAL header repeats the array's type. temporal_id_plus1 cannot be zero because + // zero is reserved to make header damage detectable before any parameter syntax is consumed. + if ((nalUnit[0] & 0x80) != 0 || (nalUnit[1] & 7) == 0 || actualNalUnitType != nalUnitType) + { + throw new InvalidImageContentException("The HEVC codec configuration contains an invalid NAL-unit header."); + } + + nalUnits[nalUnitIndex] = nalUnit.ToArray(); + offset += nalUnitLength; + } + + this.nalUnitArrays[arrayIndex] = new HevcNalUnitArray(nalUnitType, isComplete, nalUnits); + } + + if (offset != data.Length) + { + throw new InvalidImageContentException("The HEVC codec configuration contains unexpected trailing data."); + } + } + + /// + /// Gets the profile namespace declared by the coded image. + /// + public byte GeneralProfileSpace { get; } + + /// + /// Gets a value indicating whether the coded image uses the high tier. + /// + public bool GeneralTierFlag { get; } + + /// + /// Gets the profile identifier declared by the coded image. + /// + public byte GeneralProfileIdc { get; } + + /// + /// Gets the profile-compatibility flags declared by the coded image. + /// + public uint GeneralProfileCompatibilityFlags { get; } + + /// + /// Gets the 48-bit profile-constraint flags declared by the coded image. + /// + public ulong GeneralConstraintIndicatorFlags { get; } + + /// + /// Gets the level identifier declared by the coded image. + /// + public byte GeneralLevelIdc { get; } + + /// + /// Gets the coded chroma format, where zero denotes monochrome and one through three denote 4:2:0, 4:2:2, + /// and 4:4:4 respectively. + /// + public byte ChromaFormat { get; } + + /// + /// Gets the coded luma sample precision in bits. + /// + public int BitDepthLuma { get; } + + /// + /// Gets the coded chroma sample precision in bits. + /// + public int BitDepthChroma { get; } + + /// + /// Gets the maximum coded color-component precision in bits. + /// + public int BitDepth => this.IsMonochrome ? this.BitDepthLuma : Math.Max(this.BitDepthLuma, this.BitDepthChroma); + + /// + /// Gets a value indicating whether the coded image contains only a luma plane. + /// + public bool IsMonochrome => this.ChromaFormat == 0; + + /// + /// Gets the number of bytes used by each length-delimited NAL unit in the associated image item. + /// + public int NalUnitLengthSize { get; } + + /// + /// Gets the bounded NAL-unit arrays carried by the codec-configuration property. + /// + public IReadOnlyList NalUnitArrays => this.nalUnitArrays; + + /// + /// Validates the associated pixel-information property against the coded luma and chroma sample precisions. + /// + /// The per-channel precisions associated with the HEVC image item. + public void ValidateChannelBitDepths(ReadOnlySpan channelBitDepths) + { + int expectedChannelCount = this.IsMonochrome ? 1 : 3; + if (channelBitDepths.Length != expectedChannelCount || channelBitDepths[0] != this.BitDepthLuma) + { + throw new InvalidImageContentException("The HEVC item pixel information does not match its codec configuration."); + } + + for (int channel = 1; channel < channelBitDepths.Length; channel++) + { + if (channelBitDepths[channel] != this.BitDepthChroma) + { + throw new InvalidImageContentException("The HEVC item pixel information does not match its codec configuration."); + } + } + } + + /// + /// Determines whether another configuration describes the same coded-image sample layout. + /// + /// The configuration to compare. + /// when the profile, level, chroma format, and sample precisions match. + public bool HasMatchingImageConfiguration(HevcCodecConfiguration other) + => this.GeneralProfileSpace == other.GeneralProfileSpace + && this.GeneralTierFlag == other.GeneralTierFlag + && this.GeneralProfileIdc == other.GeneralProfileIdc + && this.GeneralProfileCompatibilityFlags == other.GeneralProfileCompatibilityFlags + && this.GeneralConstraintIndicatorFlags == other.GeneralConstraintIndicatorFlags + && this.GeneralLevelIdc == other.GeneralLevelIdc + && this.ChromaFormat == other.ChromaFormat + && this.BitDepthLuma == other.BitDepthLuma + && this.BitDepthChroma == other.BitDepthChroma; +} + +/// +/// Contains every configuration NAL unit declared for one HEVC NAL-unit type. +/// +internal sealed class HevcNalUnitArray +{ + /// + /// Initializes a new instance of the class. + /// + /// The six-bit HEVC NAL-unit type. + /// A value indicating whether the array contains every NAL unit of this type. + /// The complete bounded NAL units, including their two-byte headers. + public HevcNalUnitArray(byte nalUnitType, bool isComplete, ReadOnlyMemory[] nalUnits) + { + this.NalUnitType = nalUnitType; + this.IsComplete = isComplete; + this.NalUnits = nalUnits; + } + + /// + /// Gets the six-bit HEVC NAL-unit type shared by every entry in the array. + /// + public byte NalUnitType { get; } + + /// + /// Gets a value indicating whether the array contains every NAL unit of this type for the coded image. + /// + public bool IsComplete { get; } + + /// + /// Gets the complete NAL units, including their two-byte HEVC headers. + /// + public IReadOnlyList> NalUnits { get; } +}