From 9bc9459aa607ce6612c0bf1ecaa0d40653737693 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 25 Aug 2026 06:20:12 +1000 Subject: [PATCH] Implement HEVC CABAC binary decoding --- HEIF_IMPLEMENTATION_PLAN.md | 3 +- .../Formats/Heif/Hevc/HevcCabacContext.cs | 86 +++++ .../Formats/Heif/Hevc/HevcCabacDecoder.cs | 329 ++++++++++++++++++ 3 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcCabacContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcCabacDecoder.cs diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 2e0d67f90..496471f6d 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -117,6 +117,7 @@ This snapshot pins or classifies the available references and failures; it does | `HevcSequenceParameterSet`, `HevcVideoUsabilityInformation`, `HevcScalingList`, `HevcShortTermReferencePictureSet`, and `HevcParameterSetSyntax` | HEVC sections 7.3.2.2 sequence parameter sets, 7.3.4 scaling-list data, 7.3.7 short-term reference-picture sets, E.2.1 VUI syntax, and Range Extensions SPS syntax | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` functions `parseSPS`, `parseScalingList`, `xDecodeScalingList`, `parseShortTermRefPicSet`, `parseVUI`, and `parseHrdParameters`, plus `source/Lib/TLibCommon/TComRom.cpp` default matrices and diagonal scans, at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_parse_headers.c` SPS, VUI, scaling-list, and reference-set paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Retain coded/display dimensions, conformance crop, monochrome/4:2:0/4:2:2/4:4:4 and separate-plane layout, 8-through-16-bit precision, coding/transform/PCM geometry, effective scaling matrices, compression-tool flags, bounded reference-set declarations, Range Extensions tools, and still-image VUI aspect/color/range/chroma-location/display-window fields. Consume timing, HRD, ordering, and bitstream-restriction syntax without scheduling state. Reject interlaced fields and multilayer extensions because they do not represent the one progressive independently coded image-item contract; add no DPB pictures, playback state, tracks, or sample tables. | | `HevcPictureParameterSet` and `HevcCodecConfiguration.PictureParameterSets` | HEVC sections 7.3.2.3 picture parameter sets, 7.3.4 scaling-list data, and Range Extensions PPS syntax | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` function `parsePPS`, `source/Lib/TLibDecoder/TDecConformance.cpp` PPS range checks, and `source/Lib/TLibCommon/TComPicSym.cpp` tile derivation at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_parse_headers.c` PPS and tile paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Link each PPS to a configuration-local SPS and retain the CABAC, quantization, transform-skip, scaling-list, tile/wavefront, deblocking, SAO, slice-header, and Range Extensions controls required to reconstruct the independently coded picture. Derive the complete CTB tile grid and validate every SPS-dependent depth, size, precision, and offset bound during parsing. Reject multilayer extensions and add no access-unit, reference-picture lifetime, playback, track, timing, or sample-table model. | | `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. | | `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. | @@ -157,7 +158,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. It is deliberately not registered as an item decoder until CABAC, 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; context-set initialization and coding-tree syntax are 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. - 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/HevcCabacContext.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcCabacContext.cs new file mode 100644 index 000000000..4e7f42501 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcCabacContext.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Maintains the adaptive probability state for one HEVC context-coded binary syntax element. +/// +internal struct HevcCabacContext +{ + /// + /// Maps each packed context state to the state that follows its most-probable symbol. + /// + private static readonly byte[] MostProbableStateTransitions = + [ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 124, 125, 126, 127 + ]; + + /// + /// Maps each packed context state to the state that follows its least-probable symbol. + /// + private static readonly byte[] LeastProbableStateTransitions = + [ + 1, 0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 18, 19, 22, 23, 22, 23, 24, 25, + 26, 27, 26, 27, 30, 31, 30, 31, 32, 33, 32, 33, 36, 37, 36, 37, + 38, 39, 38, 39, 42, 43, 42, 43, 44, 45, 44, 45, 46, 47, 48, 49, + 48, 49, 50, 51, 52, 53, 52, 53, 54, 55, 54, 55, 56, 57, 58, 59, + 58, 59, 60, 61, 60, 61, 60, 61, 62, 63, 64, 65, 64, 65, 66, 67, + 66, 67, 66, 67, 68, 69, 68, 69, 70, 71, 70, 71, 70, 71, 72, 73, + 72, 73, 72, 73, 74, 75, 74, 75, 74, 75, 76, 77, 76, 77, 126, 127 + ]; + + /// + /// The packed probability-state index and most-probable-symbol value. + /// + private byte state; + + /// + /// Initializes a new instance of the struct. + /// + /// The luma quantization parameter that selects the initial probability. + /// The syntax-element initialization value. + public HevcCabacContext(int quantizationParameter, byte initializationValue) + { + int clippedQuantizationParameter = Math.Clamp(quantizationParameter, 0, 51); + int slope = ((initializationValue >> 4) * 5) - 45; + int offset = ((initializationValue & 15) << 3) - 16; + int initializationState = Math.Clamp( + ((slope * clippedQuantizationParameter) >> 4) + offset, + 1, + 126); + + bool mostProbableSymbol = initializationState >= 64; + this.state = (byte)( + ((mostProbableSymbol ? initializationState - 64 : 63 - initializationState) << 1) + + (mostProbableSymbol ? 1 : 0)); + } + + /// + /// Gets the probability-state index used to select the least-probable-symbol range. + /// + public readonly int StateIndex => this.state >> 1; + + /// + /// Gets a value indicating whether one is the current most-probable symbol. + /// + public readonly bool MostProbableSymbol => (this.state & 1) != 0; + + /// + /// Advances the context after decoding its most-probable symbol. + /// + public void UpdateMostProbableSymbol() => this.state = MostProbableStateTransitions[this.state]; + + /// + /// Advances the context after decoding its least-probable symbol. + /// + public void UpdateLeastProbableSymbol() => this.state = LeastProbableStateTransitions[this.state]; +} diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcCabacDecoder.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcCabacDecoder.cs new file mode 100644 index 000000000..2fa8d9992 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcCabacDecoder.cs @@ -0,0 +1,329 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Decodes context-adaptive and bypass-coded binary values from one bounded HEVC entropy substream. +/// +internal ref struct HevcCabacDecoder +{ + /// + /// The least-probable-symbol subrange for each probability state and current range class. + /// + private static readonly byte[] LeastProbableSymbolRanges = + [ + 128, 176, 208, 240, 128, 167, 197, 227, 128, 158, 187, 216, 123, 150, 178, 205, + 116, 142, 169, 195, 111, 135, 160, 185, 105, 128, 152, 175, 100, 122, 144, 166, + 95, 116, 137, 158, 90, 110, 130, 150, 85, 104, 123, 142, 81, 99, 117, 135, + 77, 94, 111, 128, 73, 89, 105, 122, 69, 85, 100, 116, 66, 80, 95, 110, + 62, 76, 90, 104, 59, 72, 86, 99, 56, 69, 81, 94, 53, 65, 77, 89, + 51, 62, 73, 85, 48, 59, 69, 80, 46, 56, 66, 76, 43, 53, 63, 72, + 41, 50, 59, 69, 39, 48, 56, 65, 37, 45, 54, 62, 35, 43, 51, 59, + 33, 41, 48, 56, 32, 39, 46, 53, 30, 37, 43, 50, 29, 35, 41, 48, + 27, 33, 39, 45, 26, 31, 37, 43, 24, 30, 35, 41, 23, 28, 33, 39, + 22, 27, 32, 37, 21, 26, 30, 35, 20, 24, 29, 33, 19, 23, 27, 31, + 18, 22, 26, 30, 17, 21, 25, 28, 16, 20, 23, 27, 15, 19, 22, 25, + 14, 18, 21, 24, 14, 17, 20, 23, 13, 16, 19, 22, 12, 15, 18, 21, + 12, 14, 17, 20, 11, 14, 16, 19, 11, 13, 15, 18, 10, 12, 15, 17, + 10, 12, 14, 16, 9, 11, 13, 15, 9, 11, 12, 14, 8, 10, 12, 14, + 8, 9, 11, 13, 7, 9, 11, 12, 7, 9, 10, 12, 7, 8, 10, 11, + 6, 8, 9, 11, 6, 7, 9, 10, 6, 7, 8, 9, 2, 2, 2, 2 + ]; + + /// + /// The normalization shift for each quantized least-probable-symbol range. + /// + private static readonly byte[] LeastProbableSymbolNormalizationShifts = + [ + 6, 5, 4, 4, + 3, 3, 3, 3, + 2, 2, 2, 2, + 2, 2, 2, 2, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1 + ]; + + /// + /// The complete bounded entropy-substream bytes. + /// + private readonly ReadOnlySpan data; + + /// + /// The zero-based offset of the next byte that can refill the arithmetic value register. + /// + private int byteOffset; + + /// + /// The current arithmetic interval width. + /// + private uint range; + + /// + /// The current arithmetic code value, scaled by seven fractional bits. + /// + private uint value; + + /// + /// The number of normalization shifts remaining before the value register requires another byte. + /// + private int bitsNeeded; + + /// + /// Initializes a new instance of the struct. + /// + /// The bytes of one independently bounded HEVC entropy substream. + /// The entropy substream is shorter than its initial value register. + public HevcCabacDecoder(ReadOnlySpan data) + { + if (data.Length < 2) + { + throw new InvalidImageContentException("The HEVC CABAC substream is truncated."); + } + + this.data = data; + this.byteOffset = 2; + this.range = 510; + this.value = ((uint)data[0] << 8) | data[1]; + this.bitsNeeded = -8; + } + + /// + /// Gets the number of whole entropy-substream bytes loaded into the arithmetic decoder. + /// + public readonly int BytesConsumed => this.byteOffset; + + /// + /// Decodes one context-adaptive binary value and advances its probability state. + /// + /// The adaptive probability context selected for the syntax element. + /// The decoded binary value. + /// The entropy substream ends while normalizing the decoded value. + public bool ReadDecision(ref HevcCabacContext context) + { + int rangeClass = ((int)this.range >> 6) - 4; + uint leastProbableSymbolRange = LeastProbableSymbolRanges[(context.StateIndex * 4) + rangeClass]; + this.range -= leastProbableSymbolRange; + uint scaledRange = this.range << 7; + + if (this.value < scaledRange) + { + bool symbol = context.MostProbableSymbol; + context.UpdateMostProbableSymbol(); + + if (scaledRange < (256U << 7)) + { + // Renormalization shifts both registers together so their comparison continues to describe the + // same arithmetic interval; a byte is loaded only when the buffered fractional bits are exhausted. + this.range = scaledRange >> 6; + this.value <<= 1; + if (++this.bitsNeeded == 0) + { + this.bitsNeeded = -8; + this.value += this.ReadByte(); + } + } + + return symbol; + } + + bool leastProbableSymbol = !context.MostProbableSymbol; + int normalizationShift = LeastProbableSymbolNormalizationShifts[(int)(leastProbableSymbolRange >> 3)]; + this.value = (this.value - scaledRange) << normalizationShift; + this.range = leastProbableSymbolRange << normalizationShift; + context.UpdateLeastProbableSymbol(); + this.bitsNeeded += normalizationShift; + if (this.bitsNeeded >= 0) + { + this.value += (uint)this.ReadByte() << this.bitsNeeded; + this.bitsNeeded -= 8; + } + + return leastProbableSymbol; + } + + /// + /// Decodes one equal-probability binary value without changing an adaptive context. + /// + /// The decoded binary value. + /// The entropy substream ends while loading the decoded value. + public bool ReadBypass() + { + if (this.range == 256) + { + return this.ReadAlignedBypassBits(1) != 0; + } + + this.value <<= 1; + if (++this.bitsNeeded >= 0) + { + this.bitsNeeded = -8; + this.value += this.ReadByte(); + } + + uint scaledRange = this.range << 7; + if (this.value < scaledRange) + { + return false; + } + + this.value -= scaledRange; + return true; + } + + /// + /// Decodes a most-significant-bit-first sequence of equal-probability binary values. + /// + /// The number of values to decode. + /// The decoded unsigned value. + /// The entropy substream ends while loading the decoded value. + public uint ReadBypassBits(int bitCount) + { + DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); + if (this.range == 256) + { + return this.ReadAlignedBypassBits(bitCount); + } + + uint bins = 0; + int remaining = bitCount; + while (remaining > 8) + { + this.value = (this.value << 8) + ((uint)this.ReadByte() << (8 + this.bitsNeeded)); + uint scaledRange = this.range << 15; + for (int bitIndex = 0; bitIndex < 8; bitIndex++) + { + bins <<= 1; + scaledRange >>= 1; + if (this.value >= scaledRange) + { + bins++; + this.value -= scaledRange; + } + } + + remaining -= 8; + } + + this.bitsNeeded += remaining; + this.value <<= remaining; + if (this.bitsNeeded >= 0) + { + this.value += (uint)this.ReadByte() << this.bitsNeeded; + this.bitsNeeded -= 8; + } + + uint finalScaledRange = this.range << (remaining + 7); + for (int bitIndex = 0; bitIndex < remaining; bitIndex++) + { + bins <<= 1; + finalScaledRange >>= 1; + if (this.value >= finalScaledRange) + { + bins++; + this.value -= finalScaledRange; + } + } + + return bins; + } + + /// + /// Selects the byte-aligned equal-probability range used by aligned bypass syntax. + /// + public void AlignBypass() => this.range = 256; + + /// + /// Decodes the binary value that terminates a coding-tree block or entropy substream. + /// + /// when the current entropy substream terminates; otherwise, . + /// The entropy substream ends while normalizing a non-terminating value. + public bool ReadTerminate() + { + this.range -= 2; + uint scaledRange = this.range << 7; + if (this.value >= scaledRange) + { + return true; + } + + if (scaledRange < (256U << 7)) + { + this.range = scaledRange >> 6; + this.value <<= 1; + if (++this.bitsNeeded == 0) + { + this.bitsNeeded = -8; + this.value += this.ReadByte(); + } + } + + return false; + } + + /// + /// Validates the stop bit and zero padding following a terminating entropy-coded value. + /// + /// The entropy substream has an invalid stop or alignment bit. + public readonly void ValidateTerminationAlignment() + { + int alignmentShift = 8 + this.bitsNeeded; + + // CABAC refills whole bytes ahead of consumption. The stop bit therefore remains in the most recently + // loaded byte, and bitsNeeded identifies its exact position without rewinding the arithmetic decoder. + int alignmentPattern = (this.data[this.byteOffset - 1] << alignmentShift) & 0xFF; + if (alignmentPattern != 0x80) + { + throw new InvalidImageContentException("The HEVC CABAC substream has invalid termination alignment."); + } + } + + /// + /// Decodes equal-probability values while the arithmetic range is byte aligned. + /// + /// The number of values to decode. + /// The decoded unsigned value. + /// The entropy substream ends while loading the decoded value. + private uint ReadAlignedBypassBits(int bitCount) + { + uint bins = 0; + int remaining = bitCount; + while (remaining > 0) + { + int binsToRead = Math.Min(remaining, 8); + uint binMask = (1U << binsToRead) - 1; + + // With a range of 256 the high value bit is known to be zero, so the following bits can be copied + // directly while preserving the same register refill schedule as individual bypass decisions. + uint newBins = (this.value >> (15 - binsToRead)) & binMask; + bins = (bins << binsToRead) | newBins; + this.value = (this.value << binsToRead) & 0x7FFF; + remaining -= binsToRead; + this.bitsNeeded += binsToRead; + if (this.bitsNeeded >= 0) + { + this.value |= (uint)this.ReadByte() << this.bitsNeeded; + this.bitsNeeded -= 8; + } + } + + return bins; + } + + /// + /// Loads the next byte into the arithmetic decoder. + /// + /// The next entropy-substream byte. + /// No byte remains in the bounded entropy substream. + private byte ReadByte() + { + if ((uint)this.byteOffset >= (uint)this.data.Length) + { + throw new InvalidImageContentException("The HEVC CABAC substream is truncated."); + } + + return this.data[this.byteOffset++]; + } +}