From 020181d22ed0897335cf69ba13edd00e7fce1f6a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 25 Aug 2026 06:24:50 +1000 Subject: [PATCH] Initialize HEVC still image CABAC contexts --- HEIF_IMPLEMENTATION_PLAN.md | 3 +- .../Formats/Heif/Hevc/HevcCabacContexts.cs | 225 ++++++++++++++++++ 2 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcCabacContexts.cs diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 496471f6d..6d604778e 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -118,6 +118,7 @@ This snapshot pins or classifies the available references and failures; it does | `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. | +| `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. | | `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. | @@ -158,7 +159,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; 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. +- 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. - 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/HevcCabacContexts.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcCabacContexts.cs new file mode 100644 index 000000000..6d9d40a11 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcCabacContexts.cs @@ -0,0 +1,225 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Owns the adaptive CABAC probability contexts used to decode one intra-coded HEVC entropy substream. +/// +internal sealed class HevcCabacContexts +{ + /// The first transquant-bypass context. + private const int TransquantBypassOffset = 0; + + /// The first coding-unit split context. + private const int SplitOffset = 1; + + /// The intra partition-size context. + private const int PartitionSizeOffset = 4; + + /// The luma intra-prediction context. + private const int IntraPredictionOffset = 5; + + /// The first chroma intra-prediction context. + private const int ChromaPredictionOffset = 6; + + /// The first luma quantization-delta context. + private const int DeltaQuantizationOffset = 8; + + /// The chroma quantization-adjustment flag context. + private const int ChromaQuantizationAdjustmentFlagOffset = 11; + + /// The chroma quantization-adjustment index context. + private const int ChromaQuantizationAdjustmentIndexOffset = 12; + + /// The first transform-tree coded-block-flag context. + private const int TransformCodedBlockFlagOffset = 13; + + /// The first horizontal last-significant-coefficient context. + private const int LastSignificantXOffset = 23; + + /// The first vertical last-significant-coefficient context. + private const int LastSignificantYOffset = 53; + + /// The first significant-coefficient-group context. + private const int SignificantCoefficientGroupOffset = 83; + + /// The first significant-coefficient context. + private const int SignificantCoefficientOffset = 87; + + /// The first greater-than-one coefficient-level context. + private const int GreaterThanOneOffset = 131; + + /// The first greater-than-two coefficient-level context. + private const int GreaterThanTwoOffset = 155; + + /// The sample-adaptive-offset merge context. + private const int SampleAdaptiveOffsetMergeOffset = 161; + + /// The sample-adaptive-offset type context. + private const int SampleAdaptiveOffsetTypeOffset = 162; + + /// The first transform-tree subdivision context. + private const int TransformSubdivisionOffset = 163; + + /// The first transform-skip context. + private const int TransformSkipOffset = 166; + + /// The first cross-component prediction context. + private const int CrossComponentPredictionOffset = 168; + + /// The number of contexts used by the independently coded intra-picture syntax. + private const int ContextCount = 178; + + /// + /// The HEVC intra-slice initialization values in the same order as the owned context ranges. + /// + private static readonly byte[] IntraInitializationValues = + [ + + // cu_transquant_bypass_flag + 154, + + // split_cu_flag + 139, 141, 157, + + // part_mode and prev_intra_luma_pred_flag + 184, + 184, + + // intra_chroma_pred_mode + 63, 139, + + // cu_qp_delta_abs, cu_chroma_qp_offset_flag, and cu_chroma_qp_offset_idx + 154, 154, 154, + 154, + 154, + + // cbf_luma followed by the chroma coded-block flags + 111, 141, 154, 154, 154, + 94, 138, 182, 154, 154, + + // last_sig_coeff_x_prefix: luma followed by chroma + 110, 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, 79, + 108, 123, 63, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, + + // last_sig_coeff_y_prefix: luma followed by chroma + 110, 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, 79, + 108, 123, 63, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, + + // coded_sub_block_flag: luma followed by chroma + 91, 171, 134, 141, + + // sig_coeff_flag: luma followed by chroma + 111, 111, 125, 110, 110, 94, 124, 108, 124, 107, 125, 141, 179, 153, + 125, 107, 125, 141, 179, 153, 125, 107, 125, 141, 179, 153, 125, 141, + 140, 139, 182, 182, 152, 136, 152, 136, 153, 136, 139, 111, 136, 139, 111, 111, + + // coeff_abs_level_greater1_flag: luma followed by chroma + 140, 92, 137, 138, 140, 152, 138, 139, 153, 74, 149, 92, 139, 107, 122, 152, + 140, 179, 166, 182, 140, 227, 122, 197, + + // coeff_abs_level_greater2_flag: luma followed by chroma + 138, 153, 136, 167, 152, 152, + + // sao_merge_flag and sao_type_idx + 153, + 200, + + // split_transform_flag + 153, 138, 138, + + // transform_skip_flag: luma followed by chroma + 139, 139, + + // cross_comp_pred: five sign/magnitude contexts for Cb followed by five for Cr + 154, 154, 154, 154, 154, 154, 154, 154, 154, 154 + ]; + + /// + /// The contiguous adaptive context storage owned by the entropy substream. + /// + private readonly HevcCabacContext[] contexts; + + /// + /// Initializes a new instance of the class for an intra-coded slice. + /// + /// The slice luma quantization parameter. + public HevcCabacContexts(int quantizationParameter) + { + this.contexts = new HevcCabacContext[ContextCount]; + for (int index = 0; index < this.contexts.Length; index++) + { + this.contexts[index] = new HevcCabacContext(quantizationParameter, IntraInitializationValues[index]); + } + } + + /// Gets the coding-unit transquant-bypass context. + public Span TransquantBypass => this.contexts.AsSpan(TransquantBypassOffset, 1); + + /// Gets the coding-unit split contexts, ordered by neighboring split depth. + public Span Split => this.contexts.AsSpan(SplitOffset, 3); + + /// Gets the intra partition-size context. + public Span PartitionSize => this.contexts.AsSpan(PartitionSizeOffset, 1); + + /// Gets the luma intra-prediction context. + public Span IntraPrediction => this.contexts.AsSpan(IntraPredictionOffset, 1); + + /// Gets the chroma intra-prediction contexts. + public Span ChromaPrediction => this.contexts.AsSpan(ChromaPredictionOffset, 2); + + /// Gets the luma quantization-delta contexts. + public Span DeltaQuantization => this.contexts.AsSpan(DeltaQuantizationOffset, 3); + + /// Gets the chroma quantization-adjustment flag context. + public Span ChromaQuantizationAdjustmentFlag => + this.contexts.AsSpan(ChromaQuantizationAdjustmentFlagOffset, 1); + + /// Gets the chroma quantization-adjustment index context. + public Span ChromaQuantizationAdjustmentIndex => + this.contexts.AsSpan(ChromaQuantizationAdjustmentIndexOffset, 1); + + /// Gets the transform-tree coded-block-flag contexts, with luma preceding chroma. + public Span TransformCodedBlockFlag => + this.contexts.AsSpan(TransformCodedBlockFlagOffset, 10); + + /// Gets the horizontal last-significant-coefficient contexts, with luma preceding chroma. + public Span LastSignificantX => this.contexts.AsSpan(LastSignificantXOffset, 30); + + /// Gets the vertical last-significant-coefficient contexts, with luma preceding chroma. + public Span LastSignificantY => this.contexts.AsSpan(LastSignificantYOffset, 30); + + /// Gets the significant-coefficient-group contexts, with luma preceding chroma. + public Span SignificantCoefficientGroup => + this.contexts.AsSpan(SignificantCoefficientGroupOffset, 4); + + /// Gets the significant-coefficient contexts, with luma preceding chroma. + public Span SignificantCoefficient => + this.contexts.AsSpan(SignificantCoefficientOffset, 44); + + /// Gets the greater-than-one coefficient-level contexts, with luma preceding chroma. + public Span GreaterThanOne => this.contexts.AsSpan(GreaterThanOneOffset, 24); + + /// Gets the greater-than-two coefficient-level contexts, with luma preceding chroma. + public Span GreaterThanTwo => this.contexts.AsSpan(GreaterThanTwoOffset, 6); + + /// Gets the sample-adaptive-offset merge context. + public Span SampleAdaptiveOffsetMerge => + this.contexts.AsSpan(SampleAdaptiveOffsetMergeOffset, 1); + + /// Gets the sample-adaptive-offset type context. + public Span SampleAdaptiveOffsetType => + this.contexts.AsSpan(SampleAdaptiveOffsetTypeOffset, 1); + + /// Gets the transform-tree subdivision contexts. + public Span TransformSubdivision => + this.contexts.AsSpan(TransformSubdivisionOffset, 3); + + /// Gets the transform-skip contexts, with luma preceding chroma. + public Span TransformSkip => this.contexts.AsSpan(TransformSkipOffset, 2); + + /// Gets the cross-component prediction contexts, with Cb preceding Cr. + public Span CrossComponentPrediction => + this.contexts.AsSpan(CrossComponentPredictionOffset, 10); +}