Browse Source

Decode HEVC intra prediction modes

pull/2633/head
James Jackson-South 2 months ago
parent
commit
402fe77aa3
  1. 1
      HEIF_IMPLEMENTATION_PLAN.md
  2. 283
      src/ImageSharp/Formats/Heif/Hevc/HevcIntraPredictionState.cs

1
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. | | `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<T>` | 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. | | `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<T>` | 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<T>` | 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. | | `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<T>` | 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<T>` | 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. | | `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. | | `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. | | `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. |

283
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;
/// <summary>
/// Stores and decodes the luma and chroma intra prediction modes for one HEVC still picture.
/// </summary>
internal sealed class HevcIntraPredictionState : IDisposable
{
/// <summary>The base-two logarithm of the minimum luma prediction-block size.</summary>
private const int MinPredictionBlockLog2 = 2;
/// <summary>The planar intra prediction mode.</summary>
private const byte PlanarMode = 0;
/// <summary>The DC intra prediction mode.</summary>
private const byte DcMode = 1;
/// <summary>The horizontal intra prediction mode.</summary>
private const byte HorizontalMode = 10;
/// <summary>The vertical intra prediction mode.</summary>
private const byte VerticalMode = 26;
/// <summary>The replacement chroma mode used when an explicit chroma candidate equals the luma mode.</summary>
private const byte ChromaReplacementMode = 34;
/// <summary>The chroma mode that derives its direction from the colocated luma prediction block.</summary>
private const byte DerivedChromaMode = 36;
/// <summary>The luma intra mode at minimum-prediction-block resolution.</summary>
private readonly Buffer2D<byte> lumaModes;
/// <summary>The coded chroma intra mode at minimum-prediction-block resolution in luma coordinates.</summary>
private readonly Buffer2D<byte> chromaModes;
/// <summary>
/// Initializes a new instance of the <see cref="HevcIntraPredictionState"/> class.
/// </summary>
/// <param name="configuration">The configuration providing the image memory allocator.</param>
/// <param name="sequenceParameterSet">The coded luma picture dimensions.</param>
public HevcIntraPredictionState(Configuration configuration, HevcSequenceParameterSet sequenceParameterSet)
{
this.WidthInMinPredictionBlocks = DivideCeilingByPowerOfTwo(
sequenceParameterSet.Width,
MinPredictionBlockLog2);
this.HeightInMinPredictionBlocks = DivideCeilingByPowerOfTwo(
sequenceParameterSet.Height,
MinPredictionBlockLog2);
this.lumaModes = configuration.MemoryAllocator.Allocate2D<byte>(
this.WidthInMinPredictionBlocks,
this.HeightInMinPredictionBlocks);
this.chromaModes = configuration.MemoryAllocator.Allocate2D<byte>(
this.WidthInMinPredictionBlocks,
this.HeightInMinPredictionBlocks);
}
/// <summary>Gets the map width in minimum luma prediction blocks.</summary>
public int WidthInMinPredictionBlocks { get; }
/// <summary>Gets the map height in minimum luma prediction blocks.</summary>
public int HeightInMinPredictionBlocks { get; }
/// <summary>Decodes and records the luma intra modes of one leaf coding unit.</summary>
/// <param name="reader">The current entropy-substream syntax reader.</param>
/// <param name="x">The coding-unit left coordinate in luma samples.</param>
/// <param name="y">The coding-unit top coordinate in luma samples.</param>
/// <param name="log2Size">The base-two logarithm of the square coding-unit size.</param>
/// <param name="usesNxNPartitions">A value indicating whether the coding unit has four square prediction blocks.</param>
/// <param name="leftAvailable">A value indicating whether the external left prediction block is available.</param>
/// <param name="aboveAvailable">A value indicating whether the external above prediction block is available.</param>
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<byte> 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<byte> 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);
}
}
/// <summary>Decodes and records the chroma intra mode of one leaf coding unit.</summary>
/// <param name="reader">The current entropy-substream syntax reader.</param>
/// <param name="x">The coding-unit left coordinate in luma samples.</param>
/// <param name="y">The coding-unit top coordinate in luma samples.</param>
/// <param name="log2Size">The base-two logarithm of the square coding-unit size.</param>
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<byte> candidates = [PlanarMode, VerticalMode, HorizontalMode, DcMode];
mode = candidates[selector];
if (mode == this.GetLumaMode(x, y))
{
mode = ChromaReplacementMode;
}
}
this.SetMode(this.chromaModes, x, y, log2Size, mode);
}
/// <summary>Gets the luma intra mode at a luma sample coordinate.</summary>
/// <param name="x">The luma sample X coordinate.</param>
/// <param name="y">The luma sample Y coordinate.</param>
/// <returns>The luma intra mode in the inclusive range zero through thirty-four.</returns>
public byte GetLumaMode(int x, int y)
=> this.lumaModes.DangerousGetRowSpan(y >> MinPredictionBlockLog2)[x >> MinPredictionBlockLog2];
/// <summary>Gets the coded chroma intra mode at a luma sample coordinate.</summary>
/// <param name="x">The luma sample X coordinate.</param>
/// <param name="y">The luma sample Y coordinate.</param>
/// <returns>An explicit chroma direction or the derived-mode value.</returns>
public byte GetChromaMode(int x, int y)
=> this.chromaModes.DangerousGetRowSpan(y >> MinPredictionBlockLog2)[x >> MinPredictionBlockLog2];
/// <summary>Gets the effective chroma intra mode at a luma sample coordinate.</summary>
/// <param name="x">The luma sample X coordinate.</param>
/// <param name="y">The luma sample Y coordinate.</param>
/// <returns>The explicit chroma mode, or the colocated luma mode when chroma uses derived mode.</returns>
public byte GetEffectiveChromaMode(int x, int y)
{
byte mode = this.GetChromaMode(x, y);
return mode == DerivedChromaMode ? this.GetLumaMode(x, y) : mode;
}
/// <summary>Releases the owned intra-mode maps.</summary>
public void Dispose()
{
this.lumaModes.Dispose();
this.chromaModes.Dispose();
}
/// <summary>Derives the three most-probable luma intra modes from available spatial neighbors.</summary>
/// <param name="x">The prediction-block left coordinate in luma samples.</param>
/// <param name="y">The prediction-block top coordinate in luma samples.</param>
/// <param name="leftAvailable">A value indicating whether the left prediction block is available.</param>
/// <param name="aboveAvailable">A value indicating whether the above prediction block is available.</param>
/// <param name="modes">The three-element destination span.</param>
private void GetMostProbableLumaModes(
int x,
int y,
bool leftAvailable,
bool aboveAvailable,
Span<byte> 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;
}
}
/// <summary>Records one prediction mode over a square luma-coordinate region.</summary>
/// <param name="map">The luma or chroma mode map.</param>
/// <param name="x">The region left coordinate in luma samples.</param>
/// <param name="y">The region top coordinate in luma samples.</param>
/// <param name="log2Size">The base-two logarithm of the square region size.</param>
/// <param name="mode">The prediction mode.</param>
private void SetMode(Buffer2D<byte> 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);
}
}
/// <summary>Sorts three intra-mode values into ascending order.</summary>
/// <param name="values">The three-element mode span.</param>
private static void SortThree(Span<byte> 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]);
}
}
/// <summary>Divides a nonnegative sample count by a power of two with upward rounding.</summary>
/// <param name="value">The sample count.</param>
/// <param name="shift">The base-two divisor logarithm.</param>
/// <returns>The upward-rounded quotient.</returns>
private static int DivideCeilingByPowerOfTwo(int value, int shift) => (value + (1 << shift) - 1) >> shift;
}
Loading…
Cancel
Save