diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index a3e0bfc7f5..57abf1cfe4 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -122,6 +122,7 @@ This snapshot pins or classifies the available references and failures; it does | `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. | | `HevcPlane` and `HevcPictureBuffer` | HEVC sections 6.2 and 6.3 source and decoded picture sample-array dimensions for monochrome, 4:2:0, 4:2:2, 4:4:4, and separate-color-plane coding | HM `source/Lib/TLibCommon/TComPicYuv.cpp` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` decoded-picture buffer layout paths at `c83a76b084498d55f252f48b2e3786804cdf24b7`; ImageSharp `MemoryAllocator` and `Buffer2D` | Allocate one allocator-owned native `ushort` plane set for the selected still picture so the same reconstruction path preserves every supported 8-through-16-bit sample without byte-backed reinterpretation. Derive chroma dimensions by ceiling division for each sampling layout and treat separate color planes as full-resolution independently coded arrays. The buffer contains one picture only and introduces no decoded-picture buffer, reference lifetime, frame queue, or playback state. | | `HevcCodingTreeState` | HEVC sections 6.4 coding-tree block and coding-block availability plus section 9.3 split-flag context derivation | HM `source/Lib/TLibCommon/TComDataCU.cpp` function `getCtxSplitFlag` and `source/Lib/TLibDecoder/TDecCu.cpp` coding-tree traversal at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_parse_slice.c` coding-tree paths at `c83a76b084498d55f252f48b2e3786804cdf24b7`; ImageSharp `MemoryAllocator` and `Buffer2D` | Store only leaf depth, effective luma QP, transquant-bypass, and PCM state at minimum-coding-block resolution for the selected picture. Derive split contexts from caller-approved left and above availability so slice and tile boundaries remain owned by traversal rather than hidden in a general block graph. Clip edge writes to the coded still-picture state and add no prediction units, motion fields, references, decoded-picture queue, or sequence lifetime. | +| `HevcIntraPredictionState` | HEVC sections 8.4.2 and 9.3 intra luma/chroma prediction-mode derivation and binarization | HM `source/Lib/TLibCommon/TComDataCU.cpp` functions `getIntraDirPredictor` and `getAllowedChromaDir`, plus `source/Lib/TLibDecoder/TDecSbac.cpp` functions `parseIntraDirLumaAng` and `parseIntraDirChroma`, at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` intra-mode parsing and neighbor derivation paths at `c83a76b084498d55f252f48b2e3786804cdf24b7`; ImageSharp `MemoryAllocator` and `Buffer2D` | Preserve the normative two-pass luma flag/suffix order, spatial most-probable-mode derivation, omitted-mode reinsertion, four-way minimum-CU partition order, explicit chroma candidate substitution, and derived-chroma mode at 4x4 luma resolution. Accept slice/tile availability from traversal, fill only the selected still-picture map, and add no inter prediction unit, motion, reference, or sequence state. | | `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/src/ImageSharp/Formats/Heif/Hevc/HevcIntraPredictionState.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcIntraPredictionState.cs new file mode 100644 index 0000000000..a8939716cb --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcIntraPredictionState.cs @@ -0,0 +1,283 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Stores and decodes the luma and chroma intra prediction modes for one HEVC still picture. +/// +internal sealed class HevcIntraPredictionState : IDisposable +{ + /// The base-two logarithm of the minimum luma prediction-block size. + private const int MinPredictionBlockLog2 = 2; + + /// The planar intra prediction mode. + private const byte PlanarMode = 0; + + /// The DC intra prediction mode. + private const byte DcMode = 1; + + /// The horizontal intra prediction mode. + private const byte HorizontalMode = 10; + + /// The vertical intra prediction mode. + private const byte VerticalMode = 26; + + /// The replacement chroma mode used when an explicit chroma candidate equals the luma mode. + private const byte ChromaReplacementMode = 34; + + /// The chroma mode that derives its direction from the colocated luma prediction block. + private const byte DerivedChromaMode = 36; + + /// The luma intra mode at minimum-prediction-block resolution. + private readonly Buffer2D lumaModes; + + /// The coded chroma intra mode at minimum-prediction-block resolution in luma coordinates. + private readonly Buffer2D chromaModes; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration providing the image memory allocator. + /// The coded luma picture dimensions. + public HevcIntraPredictionState(Configuration configuration, HevcSequenceParameterSet sequenceParameterSet) + { + this.WidthInMinPredictionBlocks = DivideCeilingByPowerOfTwo( + sequenceParameterSet.Width, + MinPredictionBlockLog2); + + this.HeightInMinPredictionBlocks = DivideCeilingByPowerOfTwo( + sequenceParameterSet.Height, + MinPredictionBlockLog2); + + this.lumaModes = configuration.MemoryAllocator.Allocate2D( + this.WidthInMinPredictionBlocks, + this.HeightInMinPredictionBlocks); + + this.chromaModes = configuration.MemoryAllocator.Allocate2D( + this.WidthInMinPredictionBlocks, + this.HeightInMinPredictionBlocks); + } + + /// Gets the map width in minimum luma prediction blocks. + public int WidthInMinPredictionBlocks { get; } + + /// Gets the map height in minimum luma prediction blocks. + public int HeightInMinPredictionBlocks { get; } + + /// Decodes and records the luma intra modes of one leaf coding unit. + /// The current entropy-substream syntax reader. + /// The coding-unit left coordinate in luma samples. + /// The coding-unit top coordinate in luma samples. + /// The base-two logarithm of the square coding-unit size. + /// A value indicating whether the coding unit has four square prediction blocks. + /// A value indicating whether the external left prediction block is available. + /// A value indicating whether the external above prediction block is available. + public void DecodeLumaModes( + ref HevcCabacSyntaxReader reader, + int x, + int y, + int log2Size, + bool usesNxNPartitions, + bool leftAvailable, + bool aboveAvailable) + { + int predictionBlockLog2 = usesNxNPartitions ? log2Size - 1 : log2Size; + int predictionBlockSize = 1 << predictionBlockLog2; + int predictionBlockCount = usesNxNPartitions ? 4 : 1; + Span mostProbableFlags = stackalloc byte[4]; + + // HEVC codes every prev_intra_luma_pred_flag before any associated mode suffix. Preserve that two-pass + // ordering because decoding one complete mode at a time would consume a different CABAC bit sequence. + for (int index = 0; index < predictionBlockCount; index++) + { + mostProbableFlags[index] = reader.ReadPreviousIntraLumaPredictionFlag() ? (byte)1 : (byte)0; + } + + Span mostProbableModes = stackalloc byte[3]; + for (int index = 0; index < predictionBlockCount; index++) + { + int offsetX = (index & 1) * predictionBlockSize; + int offsetY = (index >> 1) * predictionBlockSize; + int predictionX = x + offsetX; + int predictionY = y + offsetY; + bool predictionLeftAvailable = offsetX != 0 || leftAvailable; + bool predictionAboveAvailable = offsetY != 0 || aboveAvailable; + + this.GetMostProbableLumaModes( + predictionX, + predictionY, + predictionLeftAvailable, + predictionAboveAvailable, + mostProbableModes); + + int mode; + if (mostProbableFlags[index] != 0) + { + mode = mostProbableModes[reader.ReadMostProbableIntraLumaPredictionIndex()]; + } + else + { + SortThree(mostProbableModes); + mode = reader.ReadRemainingIntraLumaPredictionMode(); + for (int candidate = 0; candidate < mostProbableModes.Length; candidate++) + { + // The remaining-mode code omits the three probable values, so each candidate at or below the + // provisional result advances the decoded mode over that omitted slot. + mode += mode >= mostProbableModes[candidate] ? 1 : 0; + } + } + + this.SetMode(this.lumaModes, predictionX, predictionY, predictionBlockLog2, (byte)mode); + } + } + + /// Decodes and records the chroma intra mode of one leaf coding unit. + /// The current entropy-substream syntax reader. + /// The coding-unit left coordinate in luma samples. + /// The coding-unit top coordinate in luma samples. + /// The base-two logarithm of the square coding-unit size. + public void DecodeChromaMode(ref HevcCabacSyntaxReader reader, int x, int y, int log2Size) + { + int selector = reader.ReadChromaPredictionModeIndex(); + byte mode; + if (selector < 0) + { + mode = DerivedChromaMode; + } + else + { + ReadOnlySpan candidates = [PlanarMode, VerticalMode, HorizontalMode, DcMode]; + mode = candidates[selector]; + if (mode == this.GetLumaMode(x, y)) + { + mode = ChromaReplacementMode; + } + } + + this.SetMode(this.chromaModes, x, y, log2Size, mode); + } + + /// Gets the luma intra mode at a luma sample coordinate. + /// The luma sample X coordinate. + /// The luma sample Y coordinate. + /// The luma intra mode in the inclusive range zero through thirty-four. + public byte GetLumaMode(int x, int y) + => this.lumaModes.DangerousGetRowSpan(y >> MinPredictionBlockLog2)[x >> MinPredictionBlockLog2]; + + /// Gets the coded chroma intra mode at a luma sample coordinate. + /// The luma sample X coordinate. + /// The luma sample Y coordinate. + /// An explicit chroma direction or the derived-mode value. + public byte GetChromaMode(int x, int y) + => this.chromaModes.DangerousGetRowSpan(y >> MinPredictionBlockLog2)[x >> MinPredictionBlockLog2]; + + /// Gets the effective chroma intra mode at a luma sample coordinate. + /// The luma sample X coordinate. + /// The luma sample Y coordinate. + /// The explicit chroma mode, or the colocated luma mode when chroma uses derived mode. + public byte GetEffectiveChromaMode(int x, int y) + { + byte mode = this.GetChromaMode(x, y); + return mode == DerivedChromaMode ? this.GetLumaMode(x, y) : mode; + } + + /// Releases the owned intra-mode maps. + public void Dispose() + { + this.lumaModes.Dispose(); + this.chromaModes.Dispose(); + } + + /// Derives the three most-probable luma intra modes from available spatial neighbors. + /// The prediction-block left coordinate in luma samples. + /// The prediction-block top coordinate in luma samples. + /// A value indicating whether the left prediction block is available. + /// A value indicating whether the above prediction block is available. + /// The three-element destination span. + private void GetMostProbableLumaModes( + int x, + int y, + bool leftAvailable, + bool aboveAvailable, + Span modes) + { + byte leftMode = leftAvailable ? this.GetLumaMode(x - 1, y) : DcMode; + byte aboveMode = aboveAvailable ? this.GetLumaMode(x, y - 1) : DcMode; + if (leftMode == aboveMode) + { + if (leftMode > DcMode) + { + modes[0] = leftMode; + modes[1] = (byte)(((leftMode + 29) % 32) + 2); + modes[2] = (byte)(((leftMode - 1) % 32) + 2); + } + else + { + modes[0] = PlanarMode; + modes[1] = DcMode; + modes[2] = VerticalMode; + } + + return; + } + + modes[0] = leftMode; + modes[1] = aboveMode; + if (leftMode != PlanarMode && aboveMode != PlanarMode) + { + modes[2] = PlanarMode; + } + else + { + modes[2] = leftMode + aboveMode < 2 ? VerticalMode : DcMode; + } + } + + /// Records one prediction mode over a square luma-coordinate region. + /// The luma or chroma mode map. + /// The region left coordinate in luma samples. + /// The region top coordinate in luma samples. + /// The base-two logarithm of the square region size. + /// The prediction mode. + private void SetMode(Buffer2D map, int x, int y, int log2Size, byte mode) + { + int unitX = x >> MinPredictionBlockLog2; + int unitY = y >> MinPredictionBlockLog2; + int unitCount = 1 << (log2Size - MinPredictionBlockLog2); + int endX = Math.Min(unitX + unitCount, this.WidthInMinPredictionBlocks); + int endY = Math.Min(unitY + unitCount, this.HeightInMinPredictionBlocks); + for (int row = unitY; row < endY; row++) + { + map.DangerousGetRowSpan(row)[unitX..endX].Fill(mode); + } + } + + /// Sorts three intra-mode values into ascending order. + /// The three-element mode span. + private static void SortThree(Span values) + { + if (values[0] > values[1]) + { + (values[0], values[1]) = (values[1], values[0]); + } + + if (values[0] > values[2]) + { + (values[0], values[2]) = (values[2], values[0]); + } + + if (values[1] > values[2]) + { + (values[1], values[2]) = (values[2], values[1]); + } + } + + /// Divides a nonnegative sample count by a power of two with upward rounding. + /// The sample count. + /// The base-two divisor logarithm. + /// The upward-rounded quotient. + private static int DivideCeilingByPowerOfTwo(int value, int shift) => (value + (1 << shift) - 1) >> shift; +}