diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index b711f456b..0644a3201 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -112,6 +112,7 @@ This snapshot pins or classifies the available references and failures; it does
| `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 | HM `source/Lib/TLibDecoder/NALread.cpp` and parameter-header paths at `9c1f298659ab0cee9dc13d23d0304221575410b9`; 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. BSD and Apache-2.0 notice preservation and separate HEVC patent/release clearance remain final integration gates. |
+| `HevcNalUnit`, `HevcNalUnitHeader`, `HevcRbspDecoder`, and `HevcBitReader` | HEVC sections 7.3.1.1 and 7.3.1.2 NAL-unit and header syntax, section 7.3.2.11 RBSP trailing bits, and section 9.1 Exp-Golomb parsing | HM `source/Lib/TLibDecoder/NALread.cpp` and `SyntaxElementParser.cpp` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` NAL extraction and bitstream paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Validate the fixed NAL header, remove only legal emulation-prevention bytes, and provide bounded fixed-width, flag, unsigned/signed Exp-Golomb, and RBSP-trailing-bit reads. Configuration arrays immediately use the shared parser. The primitives consume only NAL units delivered by a supported HEIF image item or its `hvcC` property and introduce no Annex B API, access-unit model, timeline, track, sample table, or generic ISO BMFF surface. |
| `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 3c43774f2..2d7aeee80 100644
--- a/THIRD-PARTY-NOTICES.TXT
+++ b/THIRD-PARTY-NOTICES.TXT
@@ -174,6 +174,46 @@ Alliance for Open Media Patent License 1.0
License was issued.
+License notice for the HM HEVC Reference Software
+-----
+
+Parts of the HEVC implementation are adapted from the HM HEVC Reference
+Software.
+https://vcgit.hhi.fraunhofer.de/jvet/HM
+
+The copyright in this software is being made available under the BSD
+License, included below. This software may be subject to other third party
+and contributor rights, including patent rights, and no such rights are
+granted under this license.
+
+Copyright (c) 2010-2026, ITU/ISO/IEC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+
License notice for the Ittiam libhevc Codec Library
-----
diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcBitReader.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcBitReader.cs
new file mode 100644
index 000000000..d0e5b6196
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Hevc/HevcBitReader.cs
@@ -0,0 +1,180 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
+
+///
+/// Reads fixed-width and Exp-Golomb HEVC syntax from a most-significant-bit-first byte span.
+///
+internal ref struct HevcBitReader
+{
+ ///
+ /// The complete raw byte sequence buffer.
+ ///
+ private readonly ReadOnlySpan data;
+
+ ///
+ /// The zero-based position of the next bit to read.
+ ///
+ private int bitPosition;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The bounded HEVC syntax bytes.
+ public HevcBitReader(ReadOnlySpan data)
+ {
+ this.data = data;
+ this.bitPosition = 0;
+ }
+
+ ///
+ /// Gets the zero-based position of the next bit to read.
+ ///
+ public readonly int BitPosition => this.bitPosition;
+
+ ///
+ /// Gets the number of unread bits in the bounded byte span.
+ ///
+ public readonly int BitsRemaining => (this.data.Length * 8) - this.bitPosition;
+
+ ///
+ /// Gets a value indicating whether the next bit begins a byte.
+ ///
+ public readonly bool IsByteAligned => (this.bitPosition & 7) == 0;
+
+ ///
+ /// Reads an unsigned fixed-width value in most-significant-bit-first order.
+ ///
+ /// The number of bits to read.
+ /// The decoded unsigned value.
+ ///
+ /// The requested value extends beyond the bounded HEVC syntax.
+ ///
+ public uint ReadBits(int bitCount)
+ {
+ DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount));
+ if (bitCount > this.BitsRemaining)
+ {
+ throw new InvalidImageContentException("The HEVC bitstream is truncated.");
+ }
+
+ uint value = 0;
+ int remaining = bitCount;
+ while (remaining > 0)
+ {
+ // HEVC fixed-width syntax is MSB-first. Reading only the available portion of each byte keeps the
+ // same operation valid for both aligned parameter fields and fields that straddle byte boundaries.
+ int byteOffset = this.bitPosition >> 3;
+ int bitOffset = this.bitPosition & 7;
+ int bitsFromByte = Math.Min(remaining, 8 - bitOffset);
+ int shift = 8 - bitOffset - bitsFromByte;
+ uint mask = (1U << bitsFromByte) - 1;
+
+ value = (value << bitsFromByte) | ((uint)(this.data[byteOffset] >> shift) & mask);
+ this.bitPosition += bitsFromByte;
+ remaining -= bitsFromByte;
+ }
+
+ return value;
+ }
+
+ ///
+ /// Reads a one-bit HEVC flag.
+ ///
+ /// when the coded flag is one; otherwise, .
+ /// The flag extends beyond the bounded HEVC syntax.
+ public bool ReadFlag() => this.ReadBits(1) != 0;
+
+ ///
+ /// Reads an unsigned exponential-Golomb value.
+ ///
+ /// The decoded unsigned value.
+ ///
+ /// The code is truncated or exceeds the range of a 32-bit unsigned integer.
+ ///
+ public uint ReadUnsignedExpGolomb()
+ {
+ int leadingZeroBits = 0;
+ while (!this.ReadFlag())
+ {
+ leadingZeroBits++;
+ if (leadingZeroBits > 32)
+ {
+ throw new InvalidImageContentException("The HEVC unsigned Exp-Golomb value exceeds 32 bits.");
+ }
+ }
+
+ // In ue(v), the zero-prefix length selects an all-one basis and the equally wide suffix selects the
+ // offset from that basis. Keeping those parts separate makes the 32-bit overflow boundary explicit.
+ uint suffix = this.ReadBits(leadingZeroBits);
+ if (leadingZeroBits == 32)
+ {
+ // Only an all-zero suffix fits after the 32-bit all-one basis.
+ if (suffix != 0)
+ {
+ throw new InvalidImageContentException("The HEVC unsigned Exp-Golomb value exceeds 32 bits.");
+ }
+
+ return uint.MaxValue;
+ }
+
+ return ((1U << leadingZeroBits) - 1) + suffix;
+ }
+
+ ///
+ /// Reads a signed exponential-Golomb value.
+ ///
+ /// The decoded signed value.
+ ///
+ /// The code is truncated or exceeds the range of a 32-bit signed integer.
+ ///
+ public int ReadSignedExpGolomb()
+ {
+ uint codeNumber = this.ReadUnsignedExpGolomb();
+
+ // HEVC's se(v) mapping alternates positive and negative magnitudes: 0, 1, -1, 2, -2, and so on.
+ if ((codeNumber & 1) == 0)
+ {
+ return -(int)(codeNumber >> 1);
+ }
+
+ ulong magnitude = ((ulong)codeNumber + 1) >> 1;
+ if (magnitude > int.MaxValue)
+ {
+ throw new InvalidImageContentException("The HEVC signed Exp-Golomb value exceeds 32 bits.");
+ }
+
+ return (int)magnitude;
+ }
+
+ ///
+ /// Reads and validates the stop bit and zero alignment bits that terminate an HEVC raw byte sequence payload.
+ ///
+ ///
+ /// The trailing-bit pattern is truncated, malformed, or followed by additional data.
+ ///
+ public void ReadRbspTrailingBits()
+ {
+ // An RBSP ends with one stop bit followed only by zero bits up to the next byte boundary.
+ if (!this.ReadFlag())
+ {
+ throw new InvalidImageContentException("The HEVC RBSP stop bit is not set.");
+ }
+
+ while (!this.IsByteAligned)
+ {
+ if (this.ReadFlag())
+ {
+ throw new InvalidImageContentException("The HEVC RBSP has a nonzero alignment bit.");
+ }
+ }
+
+ // Each reader is bounded to one RBSP, so reaching alignment before the buffer end means the caller left
+ // syntax unread or the NAL unit contains bytes beyond its normative terminator.
+ if (this.BitsRemaining != 0)
+ {
+ throw new InvalidImageContentException("The HEVC RBSP contains unexpected trailing data.");
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs
index c3375549c..c7551222d 100644
--- a/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs
+++ b/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs
@@ -98,7 +98,7 @@ internal sealed class HevcCodecConfiguration
seenNalUnitTypes[nalUnitType] = true;
int nalUnitCount = BinaryPrimitives.ReadUInt16BigEndian(data[offset..]);
offset += 2;
- ReadOnlyMemory[] nalUnits = new ReadOnlyMemory[nalUnitCount];
+ HevcNalUnit[] nalUnits = new HevcNalUnit[nalUnitCount];
for (int nalUnitIndex = 0; nalUnitIndex < nalUnitCount; nalUnitIndex++)
{
if (data.Length - offset < 2)
@@ -113,17 +113,16 @@ internal sealed class HevcCodecConfiguration
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);
+ HevcNalUnit nalUnit = new(data.Slice(offset, nalUnitLength));
- // 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)
+ // The array header repeats the type so a damaged or misrouted parameter set is rejected before
+ // its RBSP syntax can affect the image configuration.
+ if (nalUnit.Header.NalUnitType != nalUnitType)
{
- throw new InvalidImageContentException("The HEVC codec configuration contains an invalid NAL-unit header.");
+ throw new InvalidImageContentException("The HEVC codec configuration NAL-unit type does not match its array.");
}
- nalUnits[nalUnitIndex] = nalUnit.ToArray();
+ nalUnits[nalUnitIndex] = nalUnit;
offset += nalUnitLength;
}
@@ -250,8 +249,8 @@ internal sealed class HevcNalUnitArray
///
/// 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)
+ /// The decoded bounded NAL units.
+ public HevcNalUnitArray(byte nalUnitType, bool isComplete, HevcNalUnit[] nalUnits)
{
this.NalUnitType = nalUnitType;
this.IsComplete = isComplete;
@@ -269,7 +268,7 @@ internal sealed class HevcNalUnitArray
public bool IsComplete { get; }
///
- /// Gets the complete NAL units, including their two-byte HEVC headers.
+ /// Gets the decoded NAL units.
///
- public IReadOnlyList> NalUnits { get; }
+ public IReadOnlyList NalUnits { get; }
}
diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnit.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnit.cs
new file mode 100644
index 000000000..8f5d64ba3
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnit.cs
@@ -0,0 +1,93 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
+
+///
+/// Contains one decoded HEVC network abstraction layer unit.
+///
+internal sealed class HevcNalUnit
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The complete NAL unit, including its two-byte header.
+ /// The NAL header or encoded payload is malformed.
+ public HevcNalUnit(ReadOnlySpan data)
+ {
+ this.Header = HevcNalUnitHeader.Parse(data);
+
+ // Container and configuration NAL units carry EBSP bytes. Decode them once at the boundary so every
+ // parameter-set and slice parser observes the same validated RBSP representation.
+ ReadOnlySpan encodedPayload = data[2..];
+ byte[] rbspBuffer = new byte[encodedPayload.Length];
+ int rbspLength = HevcRbspDecoder.Decode(encodedPayload, rbspBuffer);
+ this.Rbsp = rbspBuffer.AsMemory(0, rbspLength);
+ }
+
+ ///
+ /// Gets the decoded two-byte NAL-unit header.
+ ///
+ public HevcNalUnitHeader Header { get; }
+
+ ///
+ /// Gets the raw byte sequence payload after removal of emulation-prevention bytes.
+ ///
+ public ReadOnlyMemory Rbsp { get; }
+}
+
+///
+/// Removes HEVC emulation-prevention bytes from an encoded raw byte sequence payload.
+///
+internal static class HevcRbspDecoder
+{
+ ///
+ /// Decodes an encoded byte sequence payload into a raw byte sequence payload.
+ ///
+ /// The NAL payload following the two-byte header.
+ /// A buffer at least as long as .
+ /// The number of decoded bytes written to .
+ ///
+ /// The payload contains a forbidden start-code-like byte sequence or an invalid emulation-prevention byte.
+ ///
+ public static int Decode(ReadOnlySpan encodedPayload, Span destination)
+ {
+ DebugGuard.MustBeGreaterThanOrEqualTo(destination.Length, encodedPayload.Length, nameof(destination));
+
+ int destinationOffset = 0;
+ int consecutiveZeroBytes = 0;
+ for (int sourceOffset = 0; sourceOffset < encodedPayload.Length; sourceOffset++)
+ {
+ byte value = encodedPayload[sourceOffset];
+
+ // HEVC section 7.3.1.1 forbids 00 00 00 through 00 00 02 in EBSP form. A 03 after two zeros is an
+ // emulation-prevention byte only when another byte in the range 00 through 03 follows it.
+ if (consecutiveZeroBytes == 2)
+ {
+ if (value < 3)
+ {
+ throw new InvalidImageContentException("The HEVC NAL unit contains a forbidden start-code-like byte sequence.");
+ }
+
+ if (value == 3)
+ {
+ sourceOffset++;
+ if (sourceOffset == encodedPayload.Length || encodedPayload[sourceOffset] > 3)
+ {
+ throw new InvalidImageContentException("The HEVC NAL unit contains an invalid emulation-prevention byte.");
+ }
+
+ // Removal depends on the preceding two decoded bytes, so this deliberately remains a single
+ // scalar pass rather than introducing a second SIMD behavior model for a non-hot syntax path.
+ value = encodedPayload[sourceOffset];
+ consecutiveZeroBytes = 0;
+ }
+ }
+
+ destination[destinationOffset++] = value;
+ consecutiveZeroBytes = value == 0 ? consecutiveZeroBytes + 1 : 0;
+ }
+
+ return destinationOffset;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnitHeader.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnitHeader.cs
new file mode 100644
index 000000000..897e88d08
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Hevc/HevcNalUnitHeader.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
+
+///
+/// Contains the type, layer, and temporal identifier encoded by an HEVC NAL-unit header.
+///
+internal readonly struct HevcNalUnitHeader
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The six-bit NAL-unit type.
+ /// The six-bit layer identifier.
+ /// The zero-based temporal identifier.
+ private HevcNalUnitHeader(byte nalUnitType, byte layerId, byte temporalId)
+ {
+ this.NalUnitType = nalUnitType;
+ this.LayerId = layerId;
+ this.TemporalId = temporalId;
+ }
+
+ ///
+ /// Gets the six-bit NAL-unit type.
+ ///
+ public byte NalUnitType { get; }
+
+ ///
+ /// Gets the six-bit layer identifier.
+ ///
+ public byte LayerId { get; }
+
+ ///
+ /// Gets the zero-based temporal identifier.
+ ///
+ public byte TemporalId { get; }
+
+ ///
+ /// Reads and validates an HEVC NAL-unit header.
+ ///
+ /// The complete NAL unit beginning with its two-byte header.
+ /// The decoded header.
+ ///
+ /// The header is truncated, its forbidden bit is set, or its temporal identifier is reserved.
+ ///
+ public static HevcNalUnitHeader Parse(ReadOnlySpan data)
+ {
+ if (data.Length < 2)
+ {
+ throw new InvalidImageContentException("The HEVC NAL-unit header is truncated.");
+ }
+
+ // Use the same bounded MSB-first reader as the RBSP parsers so header truncation and field ordering have
+ // one behavior model instead of a second set of shifts and masks.
+ HevcBitReader reader = new(data[..2]);
+ bool forbiddenZeroBit = reader.ReadFlag();
+ byte nalUnitType = (byte)reader.ReadBits(6);
+ byte layerId = (byte)reader.ReadBits(6);
+ byte temporalIdPlusOne = (byte)reader.ReadBits(3);
+ if (forbiddenZeroBit || temporalIdPlusOne == 0)
+ {
+ throw new InvalidImageContentException("The HEVC NAL-unit header is invalid.");
+ }
+
+ return new HevcNalUnitHeader(nalUnitType, layerId, (byte)(temporalIdPlusOne - 1));
+ }
+}