diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 6d604778e..555551e6a 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -119,6 +119,7 @@ This snapshot pins or classifies the available references and failures; it does | `HevcImageItemBitstream`, `HevcSliceSegmentHeader`, and `HevcSliceType` | HEVC sections 7.3.1.2 NAL-unit syntax, 7.3.6 slice-segment headers, and 7.4.7 slice-segment-header semantics; HEVC image-item length delimiting | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` function `parseSliceHeader`, `source/Lib/TLibDecoder/NALread.cpp`, and slice byte-alignment and entry-point handling at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_nal.c`, `ihevcd_parse_slice_header.c`, and `ihevcd_parse_headers.c` at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Split only the selected `hvc1` image item's one-through-four-byte length-delimited NAL units, reject in-band parameter-set replacement and end-of-sequence state, and require exactly one base-layer IDR picture. Retain the intra slice, separate-plane, SAO, quantization, deblocking, cross-slice filtering, tile/wavefront entry-point, extension, byte-alignment, and entropy-payload fields required by reconstruction. Keep emulation-prevention positions only to translate slice substream byte boundaries; add no Annex B scanner, access-unit collection, non-IDR reference lifetime, playback, timing, track, or sample model. | | `HevcCabacContext` and `HevcCabacDecoder` | HEVC sections 9.3.3.2 through 9.3.3.4 context initialization, arithmetic decoding, bypass decoding, and termination | HM `source/Lib/TLibCommon/ContextModel.cpp`, `source/Lib/TLibCommon/TComCABACTables.cpp`, and `source/Lib/TLibDecoder/TDecBinCoderCABAC.cpp` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `common/ihevc_cabac_tables.c`, `common/ihevc_cabac_tables.h`, `decoder/ihevcd_cabac.c`, and `decoder/ihevcd_cabac.h` at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Implement the bit-exact scalar probability-state transitions, range normalization, bounded byte refills, bypass modes, termination, and alignment validation for one slice tile or wavefront substream. CABAC decisions are serial and adaptive, so this primitive is the scalar oracle rather than a SIMD target; later prediction, transform, filtering, and color-conversion stages must reuse ImageSharp's compatible SIMD kernels. The decoder owns no container boxes, access units, reference pictures, tracks, timing, playback, or general-purpose bitstream surface. | | `HevcCabacContexts` | HEVC section 9.3.2.2 initialization of context variables for an intra-coded slice and Range Extensions context additions | HM `source/Lib/TLibCommon/ContextTables.h` and `source/Lib/TLibDecoder/TDecSbac.cpp` function `resetEntropy` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `common/ihevc_cabac_tables.c` and `decoder/ihevcd_cabac.c` at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Initialize only the context groups reachable from the independently coded intra-picture syntax, including transform skip, chroma quantization adjustment, and cross-component prediction. Keep one contiguous mutable context set per entropy substream so tile and wavefront synchronization can copy the normative adaptive state without retaining reference-picture or playback state. Omit skip, merge, motion, reference-index, and other inter-picture-only groups. | +| `HevcCabacSyntaxReader` | HEVC sections 9.3.3 through 9.3.4 CABAC binarization and context selection for intra coding units, transform trees, residual coefficients, sample-adaptive offset, and Range Extensions tools | HM `source/Lib/TLibDecoder/TDecSbac.cpp`, `source/Lib/TLibCommon/TComChromaFormat.h`, and `source/Lib/TLibCommon/TComTrQuant.cpp` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_cabac.c`, `decoder/ihevcd_parse_slice.c`, and `decoder/ihevcd_parse_residual.c` at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Decode only syntax values reachable while reconstructing the selected independently coded still picture: intra partition and mode selectors, transform splitting and coded-block flags, quantization adjustments, transform skip, coefficient significance and levels, cross-component prediction, SAO, bypass alignment, and termination. Keep neighbor-dependent context selection in the owning coding-tree/reconstruction layer and expose no inter prediction, reference-picture, access-unit, track, timing, or playback syntax. | | `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. | @@ -159,7 +160,7 @@ This assessment is based on the current source after the upstream ImageSharp mer ### HEVC decoder and encoder - `Heif4CharCode` recognizes `hvc1` image items, `HevcCodecConfiguration` validates and associates the bounded `hvcC` property, and Identify reports its HEVC precision and monochrome shape, but `HeifCompressionFactory` has no HEVC item decoder. -- The HEVC path now validates NAL headers and RBSP escaping, parses and links `hvcC` VPS/SPS/PPS structures through Range Extensions, and parses the selected image item's length-delimited base-layer IDR slice headers and entropy payload boundaries. Its scalar CABAC primitive implements the exact adaptive state transitions, arithmetic range normalization, bypass modes, bounded refills, termination, and stop-bit validation from the pinned HM reference. The one-allocation context owner initializes every intra-picture and Range Extensions context that still-image reconstruction can select while omitting inter-picture-only groups; coding-tree syntax is not yet wired. The path is deliberately not registered as an item decoder until the complete CABAC syntax, coding-tree reconstruction, still-image intra prediction, inverse quantization/transform, deblocking, sample-adaptive offset, color conversion, and high-bit-depth pixel output are implemented. +- The HEVC path now validates NAL headers and RBSP escaping, parses and links `hvcC` VPS/SPS/PPS structures through Range Extensions, and parses the selected image item's length-delimited base-layer IDR slice headers and entropy payload boundaries. Its scalar CABAC primitive implements the exact adaptive state transitions, arithmetic range normalization, bypass modes, bounded refills, termination, and stop-bit validation from the pinned HM reference. The one-allocation context owner initializes every intra-picture and Range Extensions context that still-image reconstruction can select while omitting inter-picture-only groups. The codec-local syntax reader decodes the intra coding-unit, transform, coefficient, SAO, and Range Extensions binarizations, but coding-tree traversal and neighbor-derived context selection are not yet wired. The path is deliberately not registered as an item decoder until coding-tree reconstruction, still-image intra prediction, inverse quantization/transform, deblocking, sample-adaptive offset, color conversion, and high-bit-depth pixel output are implemented. - There is no HEVC encoder. The current HEIC-branded encoder writes a legacy JPEG payload and therefore cannot provide HEIC output. - Existing HEVC tests prove container identification only; they do not decode or compare HEIC pixels. diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcCabacSyntaxReader.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcCabacSyntaxReader.cs new file mode 100644 index 000000000..d108dda3f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcCabacSyntaxReader.cs @@ -0,0 +1,471 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Decodes the CABAC syntax values used to reconstruct one independently coded HEVC still picture. +/// +internal ref struct HevcCabacSyntaxReader +{ + /// The truncated-unary cutoff for a coding-unit luma quantization delta. + private const int DeltaQuantizationCutoff = 5; + + /// The prefix length at which coefficient levels switch from Rice to exponential-Golomb coding. + private const int CoefficientRemainingReduction = 3; + + /// The binary arithmetic decoder for the current entropy substream. + private HevcCabacDecoder decoder; + + /// The adaptive intra-picture probability contexts for the current entropy substream. + private readonly HevcCabacContexts contexts; + + /// + /// Initializes a new instance of the struct. + /// + /// The bytes of one bounded slice tile or wavefront entropy substream. + /// The slice luma quantization parameter. + /// The entropy substream is truncated. + public HevcCabacSyntaxReader(ReadOnlySpan data, int quantizationParameter) + { + this.decoder = new HevcCabacDecoder(data); + this.contexts = new HevcCabacContexts(quantizationParameter); + } + + /// Gets the number of entropy-substream bytes loaded by the arithmetic decoder. + public readonly int BytesConsumed => this.decoder.BytesConsumed; + + /// Decodes the coding-unit transquant-bypass flag. + /// The decoded flag value. + public bool ReadTransquantBypass() + { + Span selectedContexts = this.contexts.TransquantBypass; + return this.decoder.ReadDecision(ref selectedContexts[0]); + } + + /// Decodes a coding-unit split flag. + /// The context derived from the available neighboring coding-unit depths. + /// The decoded flag value. + public bool ReadSplit(int contextIndex) + { + DebugGuard.MustBeBetweenOrEqualTo(contextIndex, 0, 2, nameof(contextIndex)); + Span selectedContexts = this.contexts.Split; + return this.decoder.ReadDecision(ref selectedContexts[contextIndex]); + } + + /// Decodes whether a minimum-size intra coding unit uses four square prediction partitions. + /// + /// A value indicating whether the coding unit is at the minimum coding-block size. + /// + /// + /// for four square prediction partitions; for one square partition. + /// + public bool ReadIntraNxNPartition(bool isMinimumCodingBlockSize) + { + if (!isMinimumCodingBlockSize) + { + return false; + } + + Span selectedContexts = this.contexts.PartitionSize; + return !this.decoder.ReadDecision(ref selectedContexts[0]); + } + + /// Decodes whether a luma intra mode is selected from the three most-probable modes. + /// The decoded flag value. + public bool ReadPreviousIntraLumaPredictionFlag() + { + Span selectedContexts = this.contexts.IntraPrediction; + return this.decoder.ReadDecision(ref selectedContexts[0]); + } + + /// Decodes the zero-based selector for one of the three most-probable luma intra modes. + /// The selector in the inclusive range zero through two. + public int ReadMostProbableIntraLumaPredictionIndex() + { + if (!this.decoder.ReadBypass()) + { + return 0; + } + + return this.decoder.ReadBypass() ? 2 : 1; + } + + /// Decodes the five-bit selector for a luma intra mode outside the most-probable set. + /// The decoded selector in the inclusive range zero through thirty-one. + public int ReadRemainingIntraLumaPredictionMode() => (int)this.decoder.ReadBypassBits(5); + + /// Decodes the chroma intra prediction selector. + /// + /// Negative one when chroma derives its mode from luma; otherwise, the decoded selector in the inclusive range + /// zero through three. + /// + public int ReadChromaPredictionModeIndex() + { + Span selectedContexts = this.contexts.ChromaPrediction; + if (!this.decoder.ReadDecision(ref selectedContexts[0])) + { + return -1; + } + + return (int)this.decoder.ReadBypassBits(2); + } + + /// Decodes a transform-tree subdivision flag. + /// The base-two logarithm of the current transform-block size. + /// The decoded flag value. + public bool ReadTransformSubdivision(int log2TransformBlockSize) + { + DebugGuard.MustBeBetweenOrEqualTo(log2TransformBlockSize, 3, 5, nameof(log2TransformBlockSize)); + Span selectedContexts = this.contexts.TransformSubdivision; + return this.decoder.ReadDecision(ref selectedContexts[5 - log2TransformBlockSize]); + } + + /// Decodes a transform-tree coded-block flag. + /// A value indicating whether the flag describes a chroma transform block. + /// The transform-depth-derived context index. + /// The decoded flag value. + public bool ReadTransformCodedBlockFlag(bool isChroma, int contextIndex) + { + DebugGuard.MustBeBetweenOrEqualTo(contextIndex, 0, 4, nameof(contextIndex)); + Span selectedContexts = this.contexts.TransformCodedBlockFlag; + int channelOffset = isChroma ? 5 : 0; + return this.decoder.ReadDecision(ref selectedContexts[channelOffset + contextIndex]); + } + + /// Decodes whether a transform block bypasses the inverse transform. + /// A value indicating whether the transform block belongs to a chroma channel. + /// The decoded flag value. + public bool ReadTransformSkip(bool isChroma) + { + Span selectedContexts = this.contexts.TransformSkip; + return this.decoder.ReadDecision(ref selectedContexts[isChroma ? 1 : 0]); + } + + /// Decodes the signed coding-unit luma quantization-parameter delta. + /// The signed delta value. + /// The coded magnitude exceeds a 32-bit signed value. + public int ReadDeltaQuantizationParameter() + { + Span selectedContexts = this.contexts.DeltaQuantization; + ulong magnitude = this.ReadTruncatedUnary(selectedContexts, 0, 1, DeltaQuantizationCutoff); + if (magnitude == DeltaQuantizationCutoff) + { + magnitude += this.ReadBypassExponentialGolomb(0); + } + + if (magnitude > int.MaxValue) + { + throw new InvalidImageContentException("The HEVC coding-unit quantization delta is too large."); + } + + if (magnitude == 0) + { + return 0; + } + + int signedMagnitude = (int)magnitude; + return this.decoder.ReadBypass() ? -signedMagnitude : signedMagnitude; + } + + /// Decodes the coding-unit chroma quantization-adjustment selector. + /// The number of chroma offset pairs declared by the picture parameters. + /// Zero when no adjustment applies; otherwise, the one-based offset-list selector. + public int ReadChromaQuantizationAdjustment(int listLength) + { + Span flagContexts = this.contexts.ChromaQuantizationAdjustmentFlag; + if (!this.decoder.ReadDecision(ref flagContexts[0])) + { + return 0; + } + + if (listLength == 1) + { + return 1; + } + + Span indexContexts = this.contexts.ChromaQuantizationAdjustmentIndex; + return (int)this.ReadTruncatedUnary(indexContexts, 0, 0, listLength - 1) + 1; + } + + /// Decodes the cross-component residual-prediction scale for one chroma plane. + /// Zero for Cb or one for Cr. + /// Zero when prediction is disabled; otherwise, a signed power of two from one through eight. + public int ReadCrossComponentPredictionScale(int chromaPlaneIndex) + { + DebugGuard.MustBeBetweenOrEqualTo(chromaPlaneIndex, 0, 1, nameof(chromaPlaneIndex)); + Span selectedContexts = this.contexts.CrossComponentPrediction; + int contextOffset = chromaPlaneIndex * 5; + if (!this.decoder.ReadDecision(ref selectedContexts[contextOffset])) + { + return 0; + } + + int magnitudeLog2 = 0; + if (this.decoder.ReadDecision(ref selectedContexts[contextOffset + 1])) + { + Span magnitudeContexts = selectedContexts.Slice(contextOffset + 2, 2); + magnitudeLog2 = (int)this.ReadTruncatedUnary(magnitudeContexts, 0, 1, 2) + 1; + } + + int magnitude = 1 << magnitudeLog2; + return this.decoder.ReadDecision(ref selectedContexts[contextOffset + 4]) ? -magnitude : magnitude; + } + + /// Decodes a sample-adaptive-offset merge flag. + /// The decoded flag value. + public bool ReadSampleAdaptiveOffsetMerge() + { + Span selectedContexts = this.contexts.SampleAdaptiveOffsetMerge; + return this.decoder.ReadDecision(ref selectedContexts[0]); + } + + /// Decodes the sample-adaptive-offset mode selector. + /// Zero for off, one for band offset, or two for edge offset. + public int ReadSampleAdaptiveOffsetType() + { + Span selectedContexts = this.contexts.SampleAdaptiveOffsetType; + if (!this.decoder.ReadDecision(ref selectedContexts[0])) + { + return 0; + } + + return this.decoder.ReadBypass() ? 2 : 1; + } + + /// Decodes a truncated-unary absolute sample-adaptive-offset value. + /// The inclusive maximum offset magnitude. + /// The decoded offset magnitude. + public int ReadSampleAdaptiveOffsetAbsolute(int maximumValue) + { + if (maximumValue == 0 || !this.decoder.ReadBypass()) + { + return 0; + } + + int value = 1; + while (value < maximumValue && this.decoder.ReadBypass()) + { + value++; + } + + return value; + } + + /// Decodes the five-bit sample-adaptive band-offset starting position. + /// The decoded band position. + public int ReadSampleAdaptiveOffsetBandPosition() => (int)this.decoder.ReadBypassBits(5); + + /// Decodes the two-bit sample-adaptive edge-offset class. + /// The decoded edge class. + public int ReadSampleAdaptiveOffsetEdgeClass() => (int)this.decoder.ReadBypassBits(2); + + /// Decodes a sample-adaptive band-offset sign. + /// for a negative offset; otherwise, . + public bool ReadSampleAdaptiveOffsetSign() => this.decoder.ReadBypass(); + + /// Decodes a horizontal last-significant-coefficient prefix flag. + /// A value indicating whether the coefficient belongs to a chroma channel. + /// The block-size and prefix-derived context index within the channel. + /// The decoded flag value. + public bool ReadLastSignificantX(bool isChroma, int contextIndex) + { + Span selectedContexts = this.contexts.LastSignificantX; + return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 15 : 0) + contextIndex]); + } + + /// Decodes a vertical last-significant-coefficient prefix flag. + /// A value indicating whether the coefficient belongs to a chroma channel. + /// The block-size and prefix-derived context index within the channel. + /// The decoded flag value. + public bool ReadLastSignificantY(bool isChroma, int contextIndex) + { + Span selectedContexts = this.contexts.LastSignificantY; + return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 15 : 0) + contextIndex]); + } + + /// Decodes a significant-coefficient-group flag. + /// A value indicating whether the coefficient group belongs to a chroma channel. + /// The neighboring-group-derived context index. + /// The decoded flag value. + public bool ReadSignificantCoefficientGroup(bool isChroma, int contextIndex) + { + Span selectedContexts = this.contexts.SignificantCoefficientGroup; + return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 2 : 0) + contextIndex]); + } + + /// Decodes a significant-coefficient flag. + /// A value indicating whether the coefficient belongs to a chroma channel. + /// The scan-position-derived context index within the channel. + /// The decoded flag value. + public bool ReadSignificantCoefficient(bool isChroma, int contextIndex) + { + Span selectedContexts = this.contexts.SignificantCoefficient; + return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 28 : 0) + contextIndex]); + } + + /// Decodes whether a significant coefficient has an absolute level greater than one. + /// A value indicating whether the coefficient belongs to a chroma channel. + /// The coefficient-group and preceding-level-derived context index. + /// The decoded flag value. + public bool ReadCoefficientGreaterThanOne(bool isChroma, int contextIndex) + { + Span selectedContexts = this.contexts.GreaterThanOne; + return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 16 : 0) + contextIndex]); + } + + /// Decodes whether the first eligible coefficient has an absolute level greater than two. + /// A value indicating whether the coefficient belongs to a chroma channel. + /// The coefficient-group-derived context index within the channel. + /// The decoded flag value. + public bool ReadCoefficientGreaterThanTwo(bool isChroma, int contextIndex) + { + Span selectedContexts = this.contexts.GreaterThanTwo; + return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 4 : 0) + contextIndex]); + } + + /// Decodes an absolute coefficient-level remainder. + /// The current Golomb-Rice parameter. + /// + /// A value indicating whether extended-precision processing limits the prefix length. + /// + /// The channel's maximum transform dynamic range. + /// The decoded nonnegative coefficient-level remainder. + /// The coded remainder exceeds a 32-bit unsigned value. + public uint ReadCoefficientRemaining( + int riceParameter, + bool useLimitedPrefixLength, + int maximumLog2TransformDynamicRange) + { + int longestPrefix = useLimitedPrefixLength + ? 32 - maximumLog2TransformDynamicRange + : int.MaxValue; + + // Extended-precision streams cap the unary prefix at the transform dynamic range. Reaching that cap + // implies the end of the prefix even when the final bypass bin is one, so no terminating zero is required. + int prefix = 0; + while (prefix < longestPrefix && this.decoder.ReadBypass()) + { + prefix++; + } + + if (prefix < CoefficientRemainingReduction) + { + uint suffix = this.decoder.ReadBypassBits(riceParameter); + ulong value = ((ulong)prefix << riceParameter) + suffix; + if (value > uint.MaxValue) + { + throw new InvalidImageContentException("The HEVC coefficient level is too large."); + } + + return (uint)value; + } + + int prefixLength = prefix - CoefficientRemainingReduction; + int suffixLength; + if (useLimitedPrefixLength) + { + int maximumPrefixLength = 32 + - (CoefficientRemainingReduction + maximumLog2TransformDynamicRange); + + suffixLength = prefixLength == maximumPrefixLength + ? maximumLog2TransformDynamicRange - riceParameter + : prefixLength; + } + else + { + suffixLength = prefixLength; + } + + int codedSuffixLength = suffixLength + riceParameter; + if (prefixLength >= 32 || codedSuffixLength > 32) + { + throw new InvalidImageContentException("The HEVC coefficient level is too large."); + } + + // Prefixes beyond the first three represent an exponential-Golomb basis; the Rice parameter scales both + // that basis and the suffix while the bounded arithmetic reader supplies the remaining low bits. + uint codeWord = this.decoder.ReadBypassBits(codedSuffixLength); + ulong baseValue = (((1UL << prefixLength) - 1) + CoefficientRemainingReduction) << riceParameter; + ulong result = baseValue + codeWord; + if (result > uint.MaxValue) + { + throw new InvalidImageContentException("The HEVC coefficient level is too large."); + } + + return (uint)result; + } + + /// Decodes a most-significant-bit-first sequence of equal-probability flags. + /// The number of flags to decode. + /// The decoded unsigned value. + public uint ReadBypassBits(int bitCount) => this.decoder.ReadBypassBits(bitCount); + + /// Selects the byte-aligned range used by aligned bypass syntax. + public void AlignBypass() => this.decoder.AlignBypass(); + + /// Decodes the flag that terminates a coding-tree block or entropy substream. + /// The decoded termination flag. + public bool ReadTerminate() => this.decoder.ReadTerminate(); + + /// Validates the stop bit and zero padding after a terminating entropy-coded value. + /// The entropy substream has invalid termination alignment. + public readonly void ValidateTerminationAlignment() => this.decoder.ValidateTerminationAlignment(); + + /// Decodes a context-adaptive truncated-unary value. + /// The context set selected for the syntax element. + /// The context used by the first binary decision. + /// The context used by each subsequent decision. + /// The inclusive maximum decoded value. + /// The decoded truncated-unary value. + private uint ReadTruncatedUnary( + Span selectedContexts, + int firstContextIndex, + int continuationContextIndex, + int maximumValue) + { + if (maximumValue == 0 + || !this.decoder.ReadDecision(ref selectedContexts[firstContextIndex])) + { + return 0; + } + + uint value = 1; + while (value < maximumValue + && this.decoder.ReadDecision(ref selectedContexts[continuationContextIndex])) + { + value++; + } + + return value; + } + + /// Decodes an equal-probability exponential-Golomb value. + /// The initial suffix width. + /// The decoded unsigned value. + /// The coded value exceeds a 32-bit unsigned value. + private uint ReadBypassExponentialGolomb(int order) + { + ulong value = 0; + int suffixWidth = order; + while (this.decoder.ReadBypass()) + { + if (suffixWidth >= 32) + { + throw new InvalidImageContentException("The HEVC exponential-Golomb value is too large."); + } + + value += 1UL << suffixWidth; + suffixWidth++; + } + + // Each leading one adds the basis for the current order and widens the final suffix by one bit. + value += this.decoder.ReadBypassBits(suffixWidth); + if (value > uint.MaxValue) + { + throw new InvalidImageContentException("The HEVC exponential-Golomb value is too large."); + } + + return (uint)value; + } +}