Browse Source

Document HEIF AV1 tiling pipeline

pull/2633/head
James Jackson-South 1 week ago
parent
commit
a954e97fea
  1. 64
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs
  2. 36
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs
  3. 36
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs
  4. 27
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs
  5. 18
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCommon.cs
  6. 12
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs
  7. 18
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs
  8. 26
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs
  9. 11
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs
  10. 182
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs
  11. 27
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs
  12. 9
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs
  13. 52
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs
  14. 42
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs
  15. 12
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs
  16. 6
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs
  17. 92
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs
  18. 3
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs
  19. 59
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs
  20. 57
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs
  21. 47
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs
  22. 86
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs
  23. 77
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs
  24. 24
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs
  25. 10
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs
  26. 9
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs
  27. 15
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs
  28. 6
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs
  29. 73
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs
  30. 40
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs
  31. 413
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
  32. 313
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
  33. 9
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs
  34. 21
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs
  35. 9
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs

64
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs

@ -5,10 +5,22 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores block-size, prediction-mode, transform, and palette decisions shared by AV1 block processing.
/// </summary>
internal class Av1BlockModeInfo internal class Av1BlockModeInfo
{ {
/// <summary>
/// Stores the palette size for luma and for the shared chroma mode.
/// </summary>
private int[] paletteSize; private int[] paletteSize;
/// <summary>
/// Initializes a new instance of the <see cref="Av1BlockModeInfo"/> class.
/// </summary>
/// <param name="numPlanes">The number of color planes in the decoded frame.</param>
/// <param name="blockSize">The decoded block size.</param>
/// <param name="positionInSuperblock">The block origin relative to its superblock in 4x4 mode-information units.</param>
public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point positionInSuperblock) public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point positionInSuperblock)
{ {
this.BlockSize = blockSize; this.BlockSize = blockSize;
@ -20,6 +32,9 @@ internal class Av1BlockModeInfo
this.TransformUnitsCount = new int[numPlanes - 1]; this.TransformUnitsCount = new int[numPlanes - 1];
} }
/// <summary>
/// Gets the decoded block size.
/// </summary>
public Av1BlockSize BlockSize { get; } public Av1BlockSize BlockSize { get; }
/// <summary> /// <summary>
@ -27,12 +42,24 @@ internal class Av1BlockModeInfo
/// </summary> /// </summary>
public Av1PredictionMode YMode { get; set; } public Av1PredictionMode YMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether residual coefficients are omitted for the block.
/// </summary>
public bool Skip { get; set; } public bool Skip { get; set; }
/// <summary>
/// Gets or sets the partition type that produced the block.
/// </summary>
public Av1PartitionType PartitionType { get; set; } public Av1PartitionType PartitionType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether compound skip mode is selected.
/// </summary>
public bool SkipMode { get; set; } public bool SkipMode { get; set; }
/// <summary>
/// Gets or sets the segmentation identifier assigned to the block.
/// </summary>
public int SegmentId { get; set; } public int SegmentId { get; set; }
/// <summary> /// <summary>
@ -40,31 +67,64 @@ internal class Av1BlockModeInfo
/// </summary> /// </summary>
public Av1PredictionMode UvMode { get; set; } public Av1PredictionMode UvMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether intra block copy is selected.
/// </summary>
public bool UseUltraBlockCopy { get; set; } public bool UseUltraBlockCopy { get; set; }
/// <summary>
/// Gets or sets the packed chroma-from-luma alpha magnitude indices.
/// </summary>
public int ChromaFromLumaAlphaIndex { get; set; } public int ChromaFromLumaAlphaIndex { get; set; }
/// <summary>
/// Gets or sets the joint chroma-from-luma alpha sign value.
/// </summary>
public int ChromaFromLumaAlphaSign { get; set; } public int ChromaFromLumaAlphaSign { get; set; }
/// <summary>
/// Gets or sets the directional prediction angle adjustments for the chroma planes.
/// </summary>
public int[] AngleDelta { get; set; } public int[] AngleDelta { get; set; }
/// <summary> /// <summary>
/// Gets the position relative to the Superblock, counted in mode info (4x4 pixels). /// Gets the position relative to the superblock in 4x4 mode-information units.
/// </summary> /// </summary>
public Point PositionInSuperblock { get; } public Point PositionInSuperblock { get; }
/// <summary>
/// Gets or sets the filter-intra syntax for the block.
/// </summary>
public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; }
/// <summary> /// <summary>
/// Gets the index of the first <see cref="Av1TransformInfo"/> of this Mode Info in the <see cref="Av1FrameInfo"/>. /// Gets the plane-relative index of the first <see cref="Av1TransformInfo"/> for this block.
/// </summary> /// </summary>
public int[] FirstTransformLocation { get; } public int[] FirstTransformLocation { get; }
/// <summary>
/// Gets or sets the number of transform units for luma and for each chroma plane.
/// </summary>
public int[] TransformUnitsCount { get; internal set; } public int[] TransformUnitsCount { get; internal set; }
/// <summary>
/// Gets the palette size for the specified color plane.
/// </summary>
/// <param name="plane">The color plane.</param>
/// <returns>The palette size for the plane.</returns>
public int GetPaletteSize(Av1Plane plane) => this.paletteSize[Math.Min(1, (int)plane)]; public int GetPaletteSize(Av1Plane plane) => this.paletteSize[Math.Min(1, (int)plane)];
/// <summary>
/// Gets the palette size for the specified plane class.
/// </summary>
/// <param name="planeType">The luma or chroma plane class.</param>
/// <returns>The palette size for the plane class.</returns>
public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType]; public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType];
/// <summary>
/// Sets the luma and shared chroma palette sizes.
/// </summary>
/// <param name="ySize">The luma palette size.</param>
/// <param name="uvSize">The palette size shared by the chroma planes.</param>
public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize]; public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize];
} }

36
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs

@ -3,12 +3,38 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Identifies the luma or chroma component class used by AV1 entropy contexts.
/// </summary>
internal enum Av1ComponentType internal enum Av1ComponentType
{ {
Luminance = 0, // luma /// <summary>
Chroma = 1, // chroma (Cb+Cr) /// The luma component.
ChromaCb = 2, // chroma Cb /// </summary>
ChromaCr = 3, // chroma Cr Luminance = 0,
All = 4, // Y+Cb+Cr
/// <summary>
/// Both chroma components.
/// </summary>
Chroma = 1,
/// <summary>
/// The blue-difference chroma component.
/// </summary>
ChromaCb = 2,
/// <summary>
/// The red-difference chroma component.
/// </summary>
ChromaCr = 3,
/// <summary>
/// The luma and both chroma components.
/// </summary>
All = 4,
/// <summary>
/// No component.
/// </summary>
None = 15 None = 15
} }

36
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs

@ -5,27 +5,63 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores encoder-selected prediction, transform, skip, and palette state for one block.
/// </summary>
internal class Av1EncoderBlockModeInfo internal class Av1EncoderBlockModeInfo
{ {
/// <summary>
/// Gets the encoded block size.
/// </summary>
public Av1BlockSize BlockSize { get; } public Av1BlockSize BlockSize { get; }
/// <summary>
/// Gets the selected luma prediction mode.
/// </summary>
public Av1PredictionMode PredictionMode { get; } public Av1PredictionMode PredictionMode { get; }
/// <summary>
/// Gets the partition type that produced the block.
/// </summary>
public Av1PartitionType PartitionType { get; } public Av1PartitionType PartitionType { get; }
/// <summary>
/// Gets the selected chroma prediction mode.
/// </summary>
public Av1PredictionMode UvPredictionMode { get; } public Av1PredictionMode UvPredictionMode { get; }
/// <summary>
/// Gets a value indicating whether residual coefficients are omitted for the block.
/// </summary>
public bool Skip { get; } = true; public bool Skip { get; } = true;
/// <summary>
/// Gets a value indicating whether compound skip mode is selected.
/// </summary>
public bool SkipMode { get; } = true; public bool SkipMode { get; } = true;
/// <summary>
/// Gets a value indicating whether intra block copy is selected.
/// </summary>
public bool UseIntraBlockCopy { get; } = true; public bool UseIntraBlockCopy { get; } = true;
/// <summary>
/// Gets the segmentation identifier assigned to the block.
/// </summary>
public int SegmentId { get; } public int SegmentId { get; }
/// <summary>
/// Gets or sets the transform-tree depth selected for the block.
/// </summary>
public int TransformDepth { get; internal set; } public int TransformDepth { get; internal set; }
/// <summary>
/// Gets or sets the luma prediction mode written for the block.
/// </summary>
public Av1PredictionMode Mode { get; internal set; } public Av1PredictionMode Mode { get; internal set; }
/// <summary>
/// Gets or sets the chroma prediction mode written for the block.
/// </summary>
public Av1PredictionMode UvMode { get; internal set; } public Av1PredictionMode UvMode { get; internal set; }
} }

27
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs

@ -3,21 +3,48 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores encoder block geometry and its selected coding-mode information.
/// </summary>
internal class Av1EncoderBlockStruct internal class Av1EncoderBlockStruct
{ {
/// <summary>
/// Gets the transform-unit state in transform traversal order.
/// </summary>
public Av1TransformUnit[] TransformBlocks { get; } = new Av1TransformUnit[Av1Constants.MaxTransformUnitCount]; public Av1TransformUnit[] TransformBlocks { get; } = new Av1TransformUnit[Av1Constants.MaxTransformUnitCount];
/// <summary>
/// Gets or sets the macroblock edge and neighbor state used while writing the block.
/// </summary>
public required Av1MacroBlockD MacroBlock { get; set; } public required Av1MacroBlockD MacroBlock { get; set; }
/// <summary>
/// Gets or sets the index used to resolve the block geometry from mode-decision scan order.
/// </summary>
public int ModeDecisionScanIndex { get; set; } public int ModeDecisionScanIndex { get; set; }
/// <summary>
/// Gets or sets the quantizer index used for the block.
/// </summary>
public int QuantizationIndex { get; set; } public int QuantizationIndex { get; set; }
/// <summary>
/// Gets or sets the segmentation identifier assigned to the block.
/// </summary>
public int SegmentId { get; set; } public int SegmentId { get; set; }
/// <summary>
/// Gets or sets the filter-intra mode selected for the block.
/// </summary>
public Av1FilterIntraMode FilterIntraMode { get; set; } public Av1FilterIntraMode FilterIntraMode { get; set; }
/// <summary>
/// Gets or sets the palette size for luma and for the shared chroma mode.
/// </summary>
public required int[] PaletteSize { get; internal set; } public required int[] PaletteSize { get; internal set; }
/// <summary>
/// Gets or sets the encoder prediction-unit state for the block.
/// </summary>
public required Av1EncoderPredictionUnit[] PredictionUnits { get; internal set; } public required Av1EncoderPredictionUnit[] PredictionUnits { get; internal set; }
} }

18
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCommon.cs

@ -5,15 +5,33 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Holds sequence, frame, and macroblock state shared across AV1 encoder stages.
/// </summary>
internal class Av1EncoderCommon internal class Av1EncoderCommon
{ {
/// <summary>
/// Gets or sets the frame height in 4x4 mode-information units.
/// </summary>
public int ModeInfoRowCount { get; internal set; } public int ModeInfoRowCount { get; internal set; }
/// <summary>
/// Gets or sets the frame width in 4x4 mode-information units.
/// </summary>
public int ModeInfoColumnCount { get; internal set; } public int ModeInfoColumnCount { get; internal set; }
/// <summary>
/// Gets or sets the row stride of frame mode information in 4x4 units.
/// </summary>
public int ModeInfoStride { get; internal set; } public int ModeInfoStride { get; internal set; }
/// <summary>
/// Gets or sets the coded frame dimensions.
/// </summary>
public required ObuFrameSize FrameSize { get; internal set; } public required ObuFrameSize FrameSize { get; internal set; }
/// <summary>
/// Gets or sets the tile layout for the current frame.
/// </summary>
public required ObuTileGroupHeader TilesInfo { get; internal set; } public required ObuTileGroupHeader TilesInfo { get; internal set; }
} }

12
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs

@ -3,11 +3,23 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores encoder-selected intra prediction modes and directional-angle adjustments for one block.
/// </summary>
internal class Av1EncoderPredictionUnit internal class Av1EncoderPredictionUnit
{ {
/// <summary>
/// Gets or sets the directional angle adjustment for each prediction plane.
/// </summary>
public required byte[] AngleDelta { get; set; } public required byte[] AngleDelta { get; set; }
/// <summary>
/// Gets or sets the chroma-from-luma alpha magnitude index.
/// </summary>
public int ChromaFromLumaIndex { get; internal set; } public int ChromaFromLumaIndex { get; internal set; }
/// <summary>
/// Gets or sets the packed chroma-from-luma alpha signs for the U and V planes.
/// </summary>
public int ChromaFromLumaSigns { get; internal set; } public int ChromaFromLumaSigns { get; internal set; }
} }

18
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs

@ -3,16 +3,34 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Provides tile-writer operations that maintain encoder entropy-neighbor state.
/// </summary>
internal partial class Av1TileWriter internal partial class Av1TileWriter
{ {
/// <summary>
/// Tracks the mode and coefficient positions while entropy-coding one AV1 superblock.
/// </summary>
internal class Av1EntropyCodingContext internal class Av1EntropyCodingContext
{ {
/// <summary>
/// Gets or sets the macroblock mode information currently being encoded.
/// </summary>
public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; } public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; }
/// <summary>
/// Gets or sets the pixel origin of the current superblock.
/// </summary>
public Point SuperblockOrigin { get; internal set; } public Point SuperblockOrigin { get; internal set; }
/// <summary>
/// Gets or sets the number of luma coefficient positions consumed in the current superblock.
/// </summary>
public int CodedAreaSuperblock { get; internal set; } public int CodedAreaSuperblock { get; internal set; }
/// <summary>
/// Gets or sets the number of chroma coefficient positions consumed in the current superblock.
/// </summary>
public int CodedAreaSuperblockUv { get; internal set; } public int CodedAreaSuperblockUv { get; internal set; }
} }
} }

26
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs

@ -3,12 +3,38 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Identifies the filter-intra predictor kernel selected for an AV1 block.
/// </summary>
internal enum Av1FilterIntraMode internal enum Av1FilterIntraMode
{ {
/// <summary>
/// The filter-intra DC predictor.
/// </summary>
DC, DC,
/// <summary>
/// The filter-intra vertical predictor.
/// </summary>
Vertical, Vertical,
/// <summary>
/// The filter-intra horizontal predictor.
/// </summary>
Horizontal, Horizontal,
/// <summary>
/// The filter-intra 157-degree directional predictor.
/// </summary>
Directional157, Directional157,
/// <summary>
/// The filter-intra Paeth predictor.
/// </summary>
Paeth, Paeth,
/// <summary>
/// The number of filter-intra modes.
/// </summary>
AllFilterIntraModes, AllFilterIntraModes,
} }

11
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs

@ -5,11 +5,22 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Provides validity checks for AV1 filter-intra modes.
/// </summary>
internal static class Av1FilterIntraModeExtensions internal static class Av1FilterIntraModeExtensions
{ {
/// <summary>
/// Maps filter-intra syntax values to the directional mode used by the predictor.
/// </summary>
private static readonly Av1PredictionMode[] IntraDirection = private static readonly Av1PredictionMode[] IntraDirection =
[Av1PredictionMode.DC, Av1PredictionMode.Vertical, Av1PredictionMode.Horizontal, Av1PredictionMode.Directional157Degrees, Av1PredictionMode.DC]; [Av1PredictionMode.DC, Av1PredictionMode.Vertical, Av1PredictionMode.Horizontal, Av1PredictionMode.Directional157Degrees, Av1PredictionMode.DC];
/// <summary>
/// Gets the intra-prediction direction associated with the specified filter-intra mode.
/// </summary>
/// <param name="mode">The filter-intra mode.</param>
/// <returns>The corresponding intra-prediction direction.</returns>
public static Av1PredictionMode ToIntraDirection(this Av1FilterIntraMode mode) public static Av1PredictionMode ToIntraDirection(this Av1FilterIntraMode mode)
=> IntraDirection[(int)mode]; => IntraDirection[(int)mode];
} }

182
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs

@ -6,35 +6,113 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary> /// <summary>
/// Collection of all information for a single frame. /// Owns the mode, transform, coefficient, quantizer, and filter state decoded for one AV1 frame.
/// </summary> /// </summary>
internal partial class Av1FrameInfo internal partial class Av1FrameInfo
{ {
// Number of Coefficients in a single ModeInfo 4x4 block of pixels (1 length + 4 x 4). /// <summary>
/// The coefficient slots reserved for one 4x4 mode-information unit: one end index followed by 16 coefficients.
/// </summary>
public const int CoefficientCountPerModeInfo = 1 + 16; public const int CoefficientCountPerModeInfo = 1 + 16;
/// <summary>
/// Stores raster-ordered luma coefficients for every frame superblock.
/// </summary>
private readonly int[] coefficientsY = []; private readonly int[] coefficientsY = [];
/// <summary>
/// Stores raster-ordered blue-difference chroma coefficients for every frame superblock.
/// </summary>
private readonly int[] coefficientsU = []; private readonly int[] coefficientsU = [];
/// <summary>
/// Stores raster-ordered red-difference chroma coefficients for every frame superblock.
/// </summary>
private readonly int[] coefficientsV = []; private readonly int[] coefficientsV = [];
/// <summary>
/// The width and height of a superblock in 4x4 mode-information units.
/// </summary>
private readonly int modeInfoSizePerSuperblock; private readonly int modeInfoSizePerSuperblock;
/// <summary>
/// The number of 4x4 mode-information positions in one square superblock.
/// </summary>
private readonly int modeInfoCountPerSuperblock; private readonly int modeInfoCountPerSuperblock;
/// <summary>
/// The number of columns in the frame superblock grid.
/// </summary>
private readonly int superblockColumnCount; private readonly int superblockColumnCount;
/// <summary>
/// The number of rows in the frame superblock grid.
/// </summary>
private readonly int superblockRowCount; private readonly int superblockRowCount;
/// <summary>
/// The base-2 reduction from luma coefficient capacity to per-chroma-plane capacity.
/// </summary>
private readonly int subsamplingFactor; private readonly int subsamplingFactor;
/// <summary>
/// Stores one addressing view for each frame superblock.
/// </summary>
private readonly Av1SuperblockInfo[] superblockInfos; private readonly Av1SuperblockInfo[] superblockInfos;
/// <summary>
/// Stores decoded block mode information in bitstream traversal order.
/// </summary>
private readonly Av1BlockModeInfo[] modeInfos; private readonly Av1BlockModeInfo[] modeInfos;
/// <summary>
/// Maps every frame-relative 4x4 position to its covering entry in <see cref="modeInfos"/>.
/// </summary>
private readonly Av1FrameModeInfoMap modeInfoMap; private readonly Av1FrameModeInfoMap modeInfoMap;
/// <summary>
/// Stores luma transform information grouped by superblock.
/// </summary>
private readonly Av1TransformInfo[] transformInfosY; private readonly Av1TransformInfo[] transformInfosY;
/// <summary>
/// Stores both chroma planes' transform information grouped by superblock.
/// </summary>
private readonly Av1TransformInfo[] transformInfosUv; private readonly Av1TransformInfo[] transformInfosUv;
/// <summary>
/// Stores the quantizer-index delta for each frame superblock.
/// </summary>
private readonly int[] deltaQ; private readonly int[] deltaQ;
/// <summary>
/// The base-2 number of constrained directional enhancement filter entries allocated per superblock.
/// </summary>
private readonly int cdefStrengthFactorLog2; private readonly int cdefStrengthFactorLog2;
/// <summary>
/// Stores constrained directional enhancement filter strengths grouped by superblock.
/// </summary>
private readonly int[] cdefStrength; private readonly int[] cdefStrength;
/// <summary>
/// The base-2 number of loop-filter delta values stored per superblock.
/// </summary>
private readonly int deltaLoopFactorLog2 = 2; private readonly int deltaLoopFactorLog2 = 2;
/// <summary>
/// Stores the four loop-filter delta values for each superblock.
/// </summary>
private readonly int[] deltaLoopFilter; private readonly int[] deltaLoopFilter;
/// <summary>
/// Initializes a new instance of the <see cref="Av1FrameInfo"/> class.
/// </summary>
/// <param name="sequenceHeader">The sequence header defining maximum dimensions, superblock size, and color sampling.</param>
public Av1FrameInfo(ObuSequenceHeader sequenceHeader) public Av1FrameInfo(ObuSequenceHeader sequenceHeader)
{ {
// init_main_frame_ctxt // Size frame-owned storage from the sequence maximums because later frame headers may select
// any coded dimensions up to these bounds without rebuilding the decoder's indexing model.
int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2; int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2;
int superblockAlignedWidth = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, superblockSizeLog2); int superblockAlignedWidth = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, superblockSizeLog2);
int superblockAlignedHeight = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameHeight, superblockSizeLog2); int superblockAlignedHeight = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameHeight, superblockSizeLog2);
@ -45,14 +123,15 @@ internal partial class Av1FrameInfo
this.modeInfoCountPerSuperblock = this.modeInfoSizePerSuperblock * this.modeInfoSizePerSuperblock; this.modeInfoCountPerSuperblock = this.modeInfoSizePerSuperblock * this.modeInfoSizePerSuperblock;
int numPlanes = sequenceHeader.ColorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; int numPlanes = sequenceHeader.ColorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes;
// Allocate the arrays. // A decoded block can cover multiple 4x4 positions, so modeInfos stores each block once while
// modeInfoMap makes every covered position resolve to that single traversal-order entry.
this.superblockInfos = new Av1SuperblockInfo[superblockCount]; this.superblockInfos = new Av1SuperblockInfo[superblockCount];
this.modeInfos = new Av1BlockModeInfo[superblockCount * this.modeInfoCountPerSuperblock]; this.modeInfos = new Av1BlockModeInfo[superblockCount * this.modeInfoCountPerSuperblock];
this.modeInfoMap = new Av1FrameModeInfoMap(new Size(this.modeInfoSizePerSuperblock * this.superblockColumnCount, this.modeInfoSizePerSuperblock * this.superblockRowCount)); this.modeInfoMap = new Av1FrameModeInfoMap(new Size(this.modeInfoSizePerSuperblock * this.superblockColumnCount, this.modeInfoSizePerSuperblock * this.superblockRowCount));
this.transformInfosY = new Av1TransformInfo[superblockCount * this.modeInfoCountPerSuperblock]; this.transformInfosY = new Av1TransformInfo[superblockCount * this.modeInfoCountPerSuperblock];
this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.modeInfoCountPerSuperblock]; this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.modeInfoCountPerSuperblock];
// Initialize the arrays. // Superblock views retain only their grid position and address all storage through this owner.
int i = 0; int i = 0;
for (int y = 0; y < this.superblockRowCount; y++) for (int y = 0; y < this.superblockRowCount; y++)
{ {
@ -67,7 +146,7 @@ internal partial class Av1FrameInfo
bool subX = sequenceHeader.ColorConfig.SubSamplingX; bool subX = sequenceHeader.ColorConfig.SubSamplingX;
bool subY = sequenceHeader.ColorConfig.SubSamplingY; bool subY = sequenceHeader.ColorConfig.SubSamplingY;
// Factor: 444 => 0, 422 => 1, 420 => 2. // Chroma capacity scales by two for each sampled axis: 4:4:4 => 0, 4:2:2 => 1, 4:2:0 => 2.
this.subsamplingFactor = (subX && subY) ? 2 : (subX && !subY) ? 1 : (!subX && !subY) ? 0 : -1; this.subsamplingFactor = (subX && subY) ? 2 : (subX && !subY) ? 1 : (!subX && !subY) ? 0 : -1;
Guard.IsFalse(this.subsamplingFactor == -1, nameof(this.subsamplingFactor), "Invalid combination of subsampling."); Guard.IsFalse(this.subsamplingFactor == -1, nameof(this.subsamplingFactor), "Invalid combination of subsampling.");
int lumaCoefficientCountPerSuperblock = this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo; int lumaCoefficientCountPerSuperblock = this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo;
@ -77,7 +156,7 @@ internal partial class Av1FrameInfo
this.coefficientsV = new int[superblockCount * chromaCoefficientCountPerSuperblock]; this.coefficientsV = new int[superblockCount * chromaCoefficientCountPerSuperblock];
this.deltaQ = new int[superblockCount]; this.deltaQ = new int[superblockCount];
// Superblock size: 128x128 has sizelog2 = 7, 64x64 = 6. Factor should be 128x128 => 4 and 64x64 => 1. // A 128x128 superblock contains four 64x64 CDEF filter blocks; a 64x64 superblock contains one.
this.cdefStrengthFactorLog2 = (superblockSizeLog2 - 6) << 2; this.cdefStrengthFactorLog2 = (superblockSizeLog2 - 6) << 2;
this.cdefStrength = new int[superblockCount << this.cdefStrengthFactorLog2]; this.cdefStrength = new int[superblockCount << this.cdefStrengthFactorLog2];
Array.Fill(this.cdefStrength, -1); Array.Fill(this.cdefStrength, -1);
@ -85,15 +164,20 @@ internal partial class Av1FrameInfo
} }
/// <summary> /// <summary>
/// Gets the number of mode info blocks in a single superblock. /// Gets the total mode-information capacity allocated for the frame.
/// </summary> /// </summary>
public int ModeInfoCount => this.modeInfos.Length; public int ModeInfoCount => this.modeInfos.Length;
/// <summary> /// <summary>
/// Gets the Width or height of a single superblock, counted in mode info blocks. /// Gets the width or height of one square superblock in 4x4 mode-information units.
/// </summary> /// </summary>
public int SuperblockModeInfoSize => this.modeInfoSizePerSuperblock; public int SuperblockModeInfoSize => this.modeInfoSizePerSuperblock;
/// <summary>
/// Gets the superblock view at the specified frame-grid position.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The superblock view.</returns>
public Av1SuperblockInfo GetSuperblock(Point index) public Av1SuperblockInfo GetSuperblock(Point index)
{ {
Span<Av1SuperblockInfo> span = this.superblockInfos; Span<Av1SuperblockInfo> span = this.superblockInfos;
@ -101,8 +185,19 @@ internal partial class Av1FrameInfo
return span[i]; return span[i];
} }
/// <summary>
/// Gets the mode information covering the origin of a specified superblock.
/// </summary>
/// <param name="superblockIndex">The position in the frame superblock grid.</param>
/// <returns>The mode information covering the superblock origin.</returns>
public Av1BlockModeInfo GetModeInfo(Point superblockIndex) => this.GetModeInfo(superblockIndex, Point.Empty); public Av1BlockModeInfo GetModeInfo(Point superblockIndex) => this.GetModeInfo(superblockIndex, Point.Empty);
/// <summary>
/// Gets the mode information covering a position relative to a specified superblock.
/// </summary>
/// <param name="superblockIndex">The position in the frame superblock grid.</param>
/// <param name="modeInfoIndex">The position within the superblock in 4x4 mode-information units.</param>
/// <returns>The mode information covering the position.</returns>
public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex) public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex)
{ {
Point location = this.GetModeInfoPosition(superblockIndex, modeInfoIndex); Point location = this.GetModeInfoPosition(superblockIndex, modeInfoIndex);
@ -113,11 +208,16 @@ internal partial class Av1FrameInfo
/// <summary> /// <summary>
/// Gets the mode information record covering the specified frame-relative mode information position. /// Gets the mode information record covering the specified frame-relative mode information position.
/// </summary> /// </summary>
/// <param name="modeInfoPosition">The frame-relative position in 4x4 mode-information units.</param>
/// <returns>The mode information covering the position.</returns>
public Av1BlockModeInfo GetModeInfoAt(Point modeInfoPosition) => this.modeInfos[this.modeInfoMap[modeInfoPosition]]; public Av1BlockModeInfo GetModeInfoAt(Point modeInfoPosition) => this.modeInfos[this.modeInfoMap[modeInfoPosition]];
/// <summary> /// <summary>
/// Gets the mode information records parsed for the specified superblock in bitstream order. /// Gets the mode information records parsed for the specified superblock in bitstream order.
/// </summary> /// </summary>
/// <param name="superblockIndex">The position in the frame superblock grid.</param>
/// <param name="count">The number of parsed records to return.</param>
/// <returns>The parsed mode-information records.</returns>
public Span<Av1BlockModeInfo> GetModeInfos(Point superblockIndex, int count) public Span<Av1BlockModeInfo> GetModeInfos(Point superblockIndex, int count)
{ {
Point location = this.GetModeInfoPosition(superblockIndex, Point.Empty); Point location = this.GetModeInfoPosition(superblockIndex, Point.Empty);
@ -125,6 +225,12 @@ internal partial class Av1FrameInfo
return this.modeInfos.AsSpan(index, count); return this.modeInfos.AsSpan(index, count);
} }
/// <summary>
/// Gets the transform-information storage for one plane of a specified superblock.
/// </summary>
/// <param name="plane">The zero-based plane index.</param>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The luma storage for plane zero; otherwise, the shared chroma storage.</returns>
public Span<Av1TransformInfo> GetSuperblockTransform(int plane, Point index) public Span<Av1TransformInfo> GetSuperblockTransform(int plane, Point index)
{ {
if (plane == 0) if (plane == 0)
@ -135,6 +241,11 @@ internal partial class Av1FrameInfo
return this.GetSuperblockTransformUv(index); return this.GetSuperblockTransformUv(index);
} }
/// <summary>
/// Gets the luma transform-information storage for a specified superblock.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The superblock luma transform-information span.</returns>
public Span<Av1TransformInfo> GetSuperblockTransformY(Point index) public Span<Av1TransformInfo> GetSuperblockTransformY(Point index)
{ {
Span<Av1TransformInfo> span = this.transformInfosY; Span<Av1TransformInfo> span = this.transformInfosY;
@ -142,6 +253,11 @@ internal partial class Av1FrameInfo
return span.Slice(offset, this.modeInfoCountPerSuperblock); return span.Slice(offset, this.modeInfoCountPerSuperblock);
} }
/// <summary>
/// Gets the shared chroma transform-information storage for a specified superblock.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The superblock chroma transform-information span.</returns>
public Span<Av1TransformInfo> GetSuperblockTransformUv(Point index) public Span<Av1TransformInfo> GetSuperblockTransformUv(Point index)
{ {
Span<Av1TransformInfo> span = this.transformInfosUv; Span<Av1TransformInfo> span = this.transformInfosUv;
@ -149,6 +265,11 @@ internal partial class Av1FrameInfo
return span.Slice(offset, this.modeInfoCountPerSuperblock << 1); return span.Slice(offset, this.modeInfoCountPerSuperblock << 1);
} }
/// <summary>
/// Gets the luma coefficient storage for a specified superblock.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The superblock luma coefficient span.</returns>
public Span<int> GetCoefficientsY(Point index) public Span<int> GetCoefficientsY(Point index)
{ {
Span<int> span = this.coefficientsY; Span<int> span = this.coefficientsY;
@ -157,6 +278,11 @@ internal partial class Av1FrameInfo
return span.Slice(superblock * count, count); return span.Slice(superblock * count, count);
} }
/// <summary>
/// Gets the blue-difference chroma coefficient storage for a specified superblock.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The superblock blue-difference chroma coefficient span.</returns>
public Span<int> GetCoefficientsU(Point index) public Span<int> GetCoefficientsU(Point index)
{ {
Span<int> span = this.coefficientsU; Span<int> span = this.coefficientsU;
@ -165,6 +291,11 @@ internal partial class Av1FrameInfo
return span.Slice(superblock * count, count); return span.Slice(superblock * count, count);
} }
/// <summary>
/// Gets the red-difference chroma coefficient storage for a specified superblock.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The superblock red-difference chroma coefficient span.</returns>
public Span<int> GetCoefficientsV(Point index) public Span<int> GetCoefficientsV(Point index)
{ {
Span<int> span = this.coefficientsV; Span<int> span = this.coefficientsV;
@ -173,6 +304,11 @@ internal partial class Av1FrameInfo
return span.Slice(superblock * count, count); return span.Slice(superblock * count, count);
} }
/// <summary>
/// Gets a reference to the quantizer-index delta for a specified superblock.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>A reference to the superblock quantizer-index delta.</returns>
public ref int GetDeltaQuantizationIndex(Point index) public ref int GetDeltaQuantizationIndex(Point index)
{ {
Span<int> span = this.deltaQ; Span<int> span = this.deltaQ;
@ -180,6 +316,11 @@ internal partial class Av1FrameInfo
return ref span[i]; return ref span[i];
} }
/// <summary>
/// Gets the constrained directional enhancement filter strengths for a specified superblock.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The superblock filter-strength span.</returns>
public Span<int> GetCdefStrength(Point index) public Span<int> GetCdefStrength(Point index)
{ {
Span<int> span = this.cdefStrength; Span<int> span = this.cdefStrength;
@ -187,6 +328,10 @@ internal partial class Av1FrameInfo
return span.Slice(i, 1 << this.cdefStrengthFactorLog2); return span.Slice(i, 1 << this.cdefStrengthFactorLog2);
} }
/// <summary>
/// Resets every constrained directional enhancement filter strength for a superblock to its unassigned value.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
internal void ClearCdef(Point index) internal void ClearCdef(Point index)
{ {
Span<int> cdefs = this.GetCdefStrength(index); Span<int> cdefs = this.GetCdefStrength(index);
@ -196,6 +341,11 @@ internal partial class Av1FrameInfo
} }
} }
/// <summary>
/// Gets the four loop-filter delta values for a specified superblock.
/// </summary>
/// <param name="index">The position in the frame superblock grid.</param>
/// <returns>The superblock loop-filter delta span.</returns>
public Span<int> GetDeltaLoopFilter(Point index) public Span<int> GetDeltaLoopFilter(Point index)
{ {
Span<int> span = this.deltaLoopFilter; Span<int> span = this.deltaLoopFilter;
@ -203,14 +353,28 @@ internal partial class Av1FrameInfo
return span.Slice(i, 1 << this.deltaLoopFactorLog2); return span.Slice(i, 1 << this.deltaLoopFactorLog2);
} }
/// <summary>
/// Resets all frame loop-filter delta values to zero.
/// </summary>
public void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); public void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0);
/// <summary>
/// Stores decoded mode information and maps every 4x4 position covered by its block.
/// </summary>
/// <param name="modeInfo">The decoded block mode information.</param>
/// <param name="superblockInfo">The containing superblock.</param>
public void UpdateModeInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo) public void UpdateModeInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo)
{ {
this.modeInfos[this.modeInfoMap.NextIndex] = modeInfo; this.modeInfos[this.modeInfoMap.NextIndex] = modeInfo;
this.modeInfoMap.Update(this.GetModeInfoPosition(superblockInfo.Position, modeInfo.PositionInSuperblock), modeInfo.BlockSize); this.modeInfoMap.Update(this.GetModeInfoPosition(superblockInfo.Position, modeInfo.PositionInSuperblock), modeInfo.BlockSize);
} }
/// <summary>
/// Converts a superblock-relative mode-information position to frame-relative coordinates.
/// </summary>
/// <param name="superblockPosition">The position in the frame superblock grid.</param>
/// <param name="positionInSuperblock">The position within the superblock in 4x4 units.</param>
/// <returns>The frame-relative position in 4x4 mode-information units.</returns>
private Point GetModeInfoPosition(Point superblockPosition, Point positionInSuperblock) private Point GetModeInfoPosition(Point superblockPosition, Point positionInSuperblock)
{ {
int x = (superblockPosition.X * this.modeInfoSizePerSuperblock) + positionInSuperblock.X; int x = (superblockPosition.X * this.modeInfoSizePerSuperblock) + positionInSuperblock.X;

27
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs

@ -3,6 +3,9 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Provides frame-wide lookup and storage for decoded AV1 mode-information blocks.
/// </summary>
internal partial class Av1FrameInfo internal partial class Av1FrameInfo
{ {
/// <summary> /// <summary>
@ -13,9 +16,20 @@ internal partial class Av1FrameInfo
/// </remarks> /// </remarks>
public class Av1FrameModeInfoMap public class Av1FrameModeInfoMap
{ {
/// <summary>
/// Stores the mode-information index assigned to each aligned 4x4 frame location.
/// </summary>
private readonly ushort[] offsets; private readonly ushort[] offsets;
/// <summary>
/// The dimensions of <see cref="offsets"/> in 4x4 mode-information units.
/// </summary>
private readonly Size alignedModeInfoCount; private readonly Size alignedModeInfoCount;
/// <summary>
/// Initializes a new instance of the <see cref="Av1FrameModeInfoMap"/> class.
/// </summary>
/// <param name="modeInfoCount">The aligned frame dimensions in 4x4 mode-information units.</param>
public Av1FrameModeInfoMap(Size modeInfoCount) public Av1FrameModeInfoMap(Size modeInfoCount)
{ {
this.alignedModeInfoCount = modeInfoCount; this.alignedModeInfoCount = modeInfoCount;
@ -29,8 +43,9 @@ internal partial class Av1FrameInfo
public int NextIndex { get; private set; } public int NextIndex { get; private set; }
/// <summary> /// <summary>
/// Gets the mapped index for the given location. /// Gets the mode-information index mapped to the specified 4x4 location.
/// </summary> /// </summary>
/// <param name="location">The location in 4x4 mode-information units.</param>
public int this[Point location] public int this[Point location]
{ {
get get
@ -40,16 +55,22 @@ internal partial class Av1FrameInfo
} }
} }
/// <summary>
/// Maps every 4x4 location covered by a decoded block to the next mode-information index.
/// </summary>
/// <param name="modeInfoLocation">The block origin in 4x4 mode-information units.</param>
/// <param name="blockSize">The decoded block size.</param>
public void Update(Point modeInfoLocation, Av1BlockSize blockSize) public void Update(Point modeInfoLocation, Av1BlockSize blockSize)
{ {
// Equivalent in SVT-Av1: EbDecNbr.c svt_aom_update_block_nbrs
int bw4 = blockSize.Get4x4WideCount(); int bw4 = blockSize.Get4x4WideCount();
int bh4 = blockSize.Get4x4HighCount(); int bh4 = blockSize.Get4x4HighCount();
DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.Y, 0, nameof(modeInfoLocation)); DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.Y, 0, nameof(modeInfoLocation));
DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.Y + bh4, this.alignedModeInfoCount.Height, nameof(modeInfoLocation)); DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.Y + bh4, this.alignedModeInfoCount.Height, nameof(modeInfoLocation));
DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.X, 0, nameof(modeInfoLocation)); DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.X, 0, nameof(modeInfoLocation));
DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.X + bw4, this.alignedModeInfoCount.Width, nameof(modeInfoLocation)); DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.X + bw4, this.alignedModeInfoCount.Width, nameof(modeInfoLocation));
/* Update 4x4 nbr offset map */
// Every 4x4 cell covered by the block must resolve to the same mode information,
// because later blocks query their above and left neighbors at cell granularity.
for (int i = modeInfoLocation.Y; i < modeInfoLocation.Y + bh4; i++) for (int i = modeInfoLocation.Y; i < modeInfoLocation.Y + bh4; i++)
{ {
Array.Fill(this.offsets, (ushort)this.NextIndex, (i * this.alignedModeInfoCount.Width) + modeInfoLocation.X, bw4); Array.Fill(this.offsets, (ushort)this.NextIndex, (i * this.alignedModeInfoCount.Width) + modeInfoLocation.X, bw4);

9
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs

@ -3,9 +3,18 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores whether filter-intra prediction is active and which filter-intra mode is selected.
/// </summary>
internal class Av1IntraFilterModeInfo internal class Av1IntraFilterModeInfo
{ {
/// <summary>
/// Gets or sets a value indicating whether filter-intra prediction is enabled for the block.
/// </summary>
public bool UseFilterIntra { get; set; } public bool UseFilterIntra { get; set; }
/// <summary>
/// Gets or sets the filter-intra mode selected for the block.
/// </summary>
public Av1FilterIntraMode Mode { get; set; } public Av1FilterIntraMode Mode { get; set; }
} }

52
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs

@ -8,29 +8,61 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Owns the padded absolute-coefficient level plane used to derive AV1 coefficient entropy contexts.
/// </summary>
internal sealed class Av1LevelBuffer : IDisposable internal sealed class Av1LevelBuffer : IDisposable
{ {
/// <summary>
/// Owns the padded level storage until the buffer is disposed.
/// </summary>
private IMemoryOwner<byte>? memory; private IMemoryOwner<byte>? memory;
/// <summary>
/// Initializes a new instance of the <see cref="Av1LevelBuffer"/> class for the maximum AV1 transform size.
/// </summary>
/// <param name="configuration">The configuration providing the memory allocator.</param>
public Av1LevelBuffer(Configuration configuration) public Av1LevelBuffer(Configuration configuration)
: this(configuration, new Size(Av1Constants.MaxTransformSize, Av1Constants.MaxTransformSize)) : this(configuration, new Size(Av1Constants.MaxTransformSize, Av1Constants.MaxTransformSize))
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="Av1LevelBuffer"/> class for the specified coefficient dimensions.
/// </summary>
/// <param name="configuration">The configuration providing the memory allocator.</param>
/// <param name="size">The unpadded coefficient dimensions.</param>
public Av1LevelBuffer(Configuration configuration, Size size) public Av1LevelBuffer(Configuration configuration, Size size)
{ {
this.Size = size; this.Size = size;
// Coefficient-context derivation reads fixed neighboring offsets around the coded transform.
// Keeping those offsets inside one clean allocation avoids branches at transform boundaries.
int totalHeight = Av1Constants.TransformPadTop + size.Height + Av1Constants.TransformPadBottom; int totalHeight = Av1Constants.TransformPadTop + size.Height + Av1Constants.TransformPadBottom;
this.Stride = Av1Constants.TransformPadHorizontal + size.Width; this.Stride = Av1Constants.TransformPadHorizontal + size.Width;
this.memory = configuration.MemoryAllocator.Allocate<byte>(this.Stride * totalHeight, AllocationOptions.Clean); this.memory = configuration.MemoryAllocator.Allocate<byte>(this.Stride * totalHeight, AllocationOptions.Clean);
} }
/// <summary>
/// Gets the unpadded coefficient dimensions.
/// </summary>
public Size Size { get; } public Size Size { get; }
/// <summary>
/// Gets the padded row stride in bytes.
/// </summary>
public int Stride { get; } public int Stride { get; }
/// <summary>
/// Gets the coefficient level at the specified unpadded position.
/// </summary>
/// <param name="position">The coefficient position.</param>
public int this[Point position] => this.GetRow(position.Y)[position.X]; public int this[Point position] => this.GetRow(position.Y)[position.X];
/// <summary>
/// Initializes the unpadded level plane from raster-ordered coefficient magnitudes.
/// </summary>
/// <param name="coefficientBuffer">The coefficient levels to copy.</param>
public void Initialize(Span<int> coefficientBuffer) public void Initialize(Span<int> coefficientBuffer)
{ {
ObjectDisposedException.ThrowIf(this.memory == null, this); ObjectDisposedException.ThrowIf(this.memory == null, this);
@ -41,6 +73,7 @@ internal sealed class Av1LevelBuffer : IDisposable
ref int sourceRef = ref coefficientBuffer[y * this.Size.Width]; ref int sourceRef = ref coefficientBuffer[y * this.Size.Width];
for (int x = 0; x < this.Size.Width; x++) for (int x = 0; x < this.Size.Width; x++)
{ {
// Entropy contexts use a saturated byte-level summary rather than the full coefficient magnitude.
destRef = (byte)Av1Math.Clamp(sourceRef, 0, byte.MaxValue); destRef = (byte)Av1Math.Clamp(sourceRef, 0, byte.MaxValue);
destRef = ref Unsafe.Add(ref destRef, 1); destRef = ref Unsafe.Add(ref destRef, 1);
sourceRef = ref Unsafe.Add(ref sourceRef, 1); sourceRef = ref Unsafe.Add(ref sourceRef, 1);
@ -48,6 +81,11 @@ internal sealed class Av1LevelBuffer : IDisposable
} }
} }
/// <summary>
/// Converts a raster-order coefficient index to its two-dimensional position.
/// </summary>
/// <param name="index">The raster-order coefficient index.</param>
/// <returns>The corresponding coefficient position.</returns>
public Point GetPosition(int index) public Point GetPosition(int index)
{ {
int x = index % this.Size.Width; int x = index % this.Size.Width;
@ -55,9 +93,19 @@ internal sealed class Av1LevelBuffer : IDisposable
return new Point(x, y); return new Point(x, y);
} }
/// <summary>
/// Gets a padded coefficient row for the specified position.
/// </summary>
/// <param name="pos">A position whose vertical coordinate selects the row.</param>
/// <returns>The selected row, including its horizontal context padding.</returns>
public Span<byte> GetRow(Point pos) public Span<byte> GetRow(Point pos)
=> this.GetRow(pos.Y); => this.GetRow(pos.Y);
/// <summary>
/// Gets a padded coefficient row by its unpadded vertical coordinate.
/// </summary>
/// <param name="y">The row coordinate, which may address the top context padding.</param>
/// <returns>The selected row, including its horizontal context padding.</returns>
public Span<byte> GetRow(int y) public Span<byte> GetRow(int y)
{ {
ObjectDisposedException.ThrowIf(this.memory == null, this); ObjectDisposedException.ThrowIf(this.memory == null, this);
@ -66,12 +114,16 @@ internal sealed class Av1LevelBuffer : IDisposable
return this.memory.Memory.Span.Slice(row * this.Stride, this.Size.Width + Av1Constants.TransformPadHorizontal); return this.memory.Memory.Span.Slice(row * this.Stride, this.Size.Width + Av1Constants.TransformPadHorizontal);
} }
/// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
this.memory?.Dispose(); this.memory?.Dispose();
this.memory = null; this.memory = null;
} }
/// <summary>
/// Clears all coefficient levels and context padding.
/// </summary>
internal void Clear() internal void Clear()
{ {
ObjectDisposedException.ThrowIf(this.memory == null, this); ObjectDisposedException.ThrowIf(this.memory == null, this);

42
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs

@ -3,53 +3,87 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Holds decoder-side macroblock edges, plane state, and neighboring mode information.
/// </summary>
internal class Av1MacroBlockD internal class Av1MacroBlockD
{ {
/// <summary>
/// Stores the mode-information entries exposed through <see cref="ModeInfo"/>.
/// </summary>
private Av1ModeInfo[] modeInfo = []; private Av1ModeInfo[] modeInfo = [];
/// <summary>
/// Gets or sets the mode-information entries for the current block and its mapped neighbors.
/// </summary>
public required Span<Av1ModeInfo> ModeInfo public required Span<Av1ModeInfo> ModeInfo
{ {
get => this.modeInfo; get => this.modeInfo;
internal set internal set
{ {
// A span cannot be retained by the class, so preserve the selected map entries in owned storage.
this.modeInfo = new Av1ModeInfo[value.Length]; this.modeInfo = new Av1ModeInfo[value.Length];
value.CopyTo(this.modeInfo); value.CopyTo(this.modeInfo);
} }
} }
/// <summary>
/// Gets or sets the tile containing the current block.
/// </summary>
public required Av1TileInfo Tile { get; internal set; } public required Av1TileInfo Tile { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether an above block is available within the tile.
/// </summary>
public bool IsUpAvailable { get; internal set; } public bool IsUpAvailable { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether a left block is available within the tile.
/// </summary>
public bool IsLeftAvailable { get; internal set; } public bool IsLeftAvailable { get; internal set; }
/// <summary>
/// Gets or sets the above macroblock mode information, when available.
/// </summary>
public Av1MacroBlockModeInfo? AboveMacroBlock { get; internal set; } public Av1MacroBlockModeInfo? AboveMacroBlock { get; internal set; }
/// <summary>
/// Gets or sets the left macroblock mode information, when available.
/// </summary>
public Av1MacroBlockModeInfo? LeftMacroBlock { get; internal set; } public Av1MacroBlockModeInfo? LeftMacroBlock { get; internal set; }
/// <summary>
/// Gets or sets the row stride of the frame mode-information map.
/// </summary>
public int ModeInfoStride { get; internal set; } public int ModeInfoStride { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the number of macro blocks until the top edge. /// Gets or sets the signed distance from the block to the top frame edge in one-eighth-sample units.
/// </summary> /// </summary>
public int ToTopEdge { get; internal set; } public int ToTopEdge { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the number of macro blocks until the bottom edge. /// Gets or sets the signed distance from the block to the bottom frame edge in one-eighth-sample units.
/// </summary> /// </summary>
public int ToBottomEdge { get; internal set; } public int ToBottomEdge { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the number of macro blocks until the left edge. /// Gets or sets the signed distance from the block to the left frame edge in one-eighth-sample units.
/// </summary> /// </summary>
public int ToLeftEdge { get; internal set; } public int ToLeftEdge { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the number of macro blocks until the right edge. /// Gets or sets the signed distance from the block to the right frame edge in one-eighth-sample units.
/// </summary> /// </summary>
public int ToRightEdge { get; internal set; } public int ToRightEdge { get; internal set; }
/// <summary>
/// Gets or sets the block dimensions in samples for rectangular-partition context selection.
/// </summary>
public Size N8Size { get; internal set; } public Size N8Size { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether this block is the second half of a rectangular partition.
/// </summary>
public bool IsSecondRectangle { get; internal set; } public bool IsSecondRectangle { get; internal set; }
} }

12
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs

@ -3,11 +3,23 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores the encoder's selected modes and references for an AV1 macroblock.
/// </summary>
internal class Av1MacroBlockModeInfo internal class Av1MacroBlockModeInfo
{ {
/// <summary>
/// Gets or sets the prediction, transform, and segmentation decisions for the block.
/// </summary>
public required Av1EncoderBlockModeInfo Block { get; internal set; } public required Av1EncoderBlockModeInfo Block { get; internal set; }
/// <summary>
/// Gets or sets the luma palette decisions for the block.
/// </summary>
public required Av1PaletteLumaModeInfo Palette { get; internal set; } public required Av1PaletteLumaModeInfo Palette { get; internal set; }
/// <summary>
/// Gets or sets the constrained directional enhancement filter strength for the block.
/// </summary>
public int CdefStrength { get; internal set; } public int CdefStrength { get; internal set; }
} }

6
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs

@ -3,7 +3,13 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores the decoded prediction, segmentation, skip, and transform state for an AV1 mode-information block.
/// </summary>
internal class Av1ModeInfo internal class Av1ModeInfo
{ {
/// <summary>
/// Gets or sets the macroblock mode information associated with this map entry.
/// </summary>
public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; } public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; }
} }

92
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs

@ -6,15 +6,39 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores left, top, and top-left neighbor values at the granularity required by AV1 encoder contexts.
/// </summary>
/// <typeparam name="T">The context value type, including its invalid sentinel value.</typeparam>
internal class Av1NeighborArrayUnit<T> internal class Av1NeighborArrayUnit<T>
where T : struct, IMinMaxValue<T> where T : struct, IMinMaxValue<T>
{ {
/// <summary>
/// The sentinel used for neighbor positions that have not been populated.
/// </summary>
public static readonly T InvalidNeighborData = T.MaxValue; public static readonly T InvalidNeighborData = T.MaxValue;
/// <summary>
/// Stores context units exposed to blocks on the right.
/// </summary>
private readonly T[] left; private readonly T[] left;
/// <summary>
/// Stores context units exposed to blocks below.
/// </summary>
private readonly T[] top; private readonly T[] top;
/// <summary>
/// Stores context units indexed by the diagonal difference between horizontal and vertical positions.
/// </summary>
private readonly T[] topLeft; private readonly T[] topLeft;
/// <summary>
/// Initializes a new instance of the <see cref="Av1NeighborArrayUnit{T}"/> class.
/// </summary>
/// <param name="leftSize">The number of values in the left-neighbor storage.</param>
/// <param name="topSize">The number of values in the top-neighbor storage.</param>
/// <param name="topLeftSize">The number of values in the diagonal-neighbor storage.</param>
public Av1NeighborArrayUnit(int leftSize, int topSize, int topLeftSize) public Av1NeighborArrayUnit(int leftSize, int topSize, int topLeftSize)
{ {
this.left = new T[leftSize]; this.left = new T[leftSize];
@ -22,33 +46,87 @@ internal class Av1NeighborArrayUnit<T>
this.topLeft = new T[topLeftSize]; this.topLeft = new T[topLeftSize];
} }
/// <summary>
/// Selects which neighbor arrays receive an update.
/// </summary>
[Flags] [Flags]
public enum UnitMask public enum UnitMask
{ {
/// <summary>
/// Update the left-neighbor storage.
/// </summary>
Left = 1, Left = 1,
/// <summary>
/// Update the top-neighbor storage.
/// </summary>
Top = 2, Top = 2,
/// <summary>
/// Update the top-left diagonal storage.
/// </summary>
TopLeft = 4, TopLeft = 4,
} }
/// <summary>
/// Gets the left-neighbor storage.
/// </summary>
public Span<T> Left => this.left; public Span<T> Left => this.left;
/// <summary>
/// Gets the top-neighbor storage.
/// </summary>
public Span<T> Top => this.top; public Span<T> Top => this.top;
/// <summary>
/// Gets the top-left diagonal storage.
/// </summary>
public Span<T> TopLeft => this.topLeft; public Span<T> TopLeft => this.topLeft;
/// <summary>
/// Gets or sets the base-2 logarithm of the top and left context granularity in samples.
/// </summary>
public required int GranularityNormalLog2 { get; set; } public required int GranularityNormalLog2 { get; set; }
/// <summary>
/// Gets or sets the base-2 logarithm of the diagonal context granularity in samples.
/// </summary>
public required int GranularityTopLeftLog2 { get; set; } public required int GranularityTopLeftLog2 { get; set; }
/// <summary>
/// Gets the number of consecutive values stored for each neighbor-array unit.
/// </summary>
public int UnitSize { get; private set; } public int UnitSize { get; private set; }
/// <summary>
/// Gets the left-neighbor unit index for a sample position.
/// </summary>
/// <param name="loc">The sample position.</param>
/// <returns>The left-neighbor unit index.</returns>
public int GetLeftIndex(Point loc) => loc.Y >> this.GranularityNormalLog2; public int GetLeftIndex(Point loc) => loc.Y >> this.GranularityNormalLog2;
/// <summary>
/// Gets the top-neighbor unit index for a sample position.
/// </summary>
/// <param name="loc">The sample position.</param>
/// <returns>The top-neighbor unit index.</returns>
public int GetTopIndex(Point loc) => loc.X >> this.GranularityNormalLog2; public int GetTopIndex(Point loc) => loc.X >> this.GranularityNormalLog2;
/// <summary>
/// Gets the diagonal-neighbor unit index for a sample position.
/// </summary>
/// <param name="loc">The sample position.</param>
/// <returns>The top-left neighbor index derived from the position's diagonal.</returns>
public int GetTopLeftIndex(Point loc) public int GetTopLeftIndex(Point loc)
=> this.left.Length + (loc.X >> this.GranularityTopLeftLog2) - (loc.Y >> this.GranularityTopLeftLog2); => this.left.Length + (loc.X >> this.GranularityTopLeftLog2) - (loc.Y >> this.GranularityTopLeftLog2);
/// <summary>
/// Writes one context unit across the selected block edges.
/// </summary>
/// <param name="value">The values that make up one context unit.</param>
/// <param name="origin">The block origin in samples.</param>
/// <param name="blockSize">The block dimensions in samples.</param>
/// <param name="mask">The neighbor arrays to update.</param>
public void UnitModeWrite(ReadOnlySpan<T> value, Point origin, Size blockSize, UnitMask mask) public void UnitModeWrite(ReadOnlySpan<T> value, Point origin, Size blockSize, UnitMask mask)
{ {
int idx, j; int idx, j;
@ -83,7 +161,7 @@ internal class Av1NeighborArrayUnit<T>
for (idx = 0; idx < count; ++idx) for (idx = 0; idx < count; ++idx)
{ {
/* svt_memcpy less that 10 bytes*/ // Unit sizes are deliberately tiny, so direct ref copies avoid slicing for every neighbor position.
for (j = 0; j < na_unit_size; ++j) for (j = 0; j < na_unit_size; ++j)
{ {
dst_ptr = value[j]; dst_ptr = value[j];
@ -117,7 +195,7 @@ internal class Av1NeighborArrayUnit<T>
for (idx = 0; idx < count; ++idx) for (idx = 0; idx < count; ++idx)
{ {
/* svt_memcpy less that 10 bytes*/ // Unit sizes are deliberately tiny, so direct ref copies avoid slicing for every neighbor position.
for (j = 0; j < na_unit_size; ++j) for (j = 0; j < na_unit_size; ++j)
{ {
dst_ptr = value[j]; dst_ptr = value[j];
@ -156,7 +234,7 @@ internal class Av1NeighborArrayUnit<T>
for (idx = 0; idx < count; ++idx) for (idx = 0; idx < count; ++idx)
{ {
/* svt_memcpy less that 10 bytes*/ // Unit sizes are deliberately tiny, so direct ref copies avoid slicing for every neighbor position.
for (j = 0; j < na_unit_size; ++j) for (j = 0; j < na_unit_size; ++j)
{ {
dst_ptr = value[j]; dst_ptr = value[j];
@ -166,5 +244,13 @@ internal class Av1NeighborArrayUnit<T>
} }
} }
/// <summary>
/// Writes a DC-sign context across selected block edges.
/// </summary>
/// <param name="dcSignSpan">The encoded DC-sign context.</param>
/// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="blockSize">The block dimensions in samples.</param>
/// <param name="unitMask">The neighbor arrays to update.</param>
/// <exception cref="NotImplementedException">The byte-specific write path is not implemented.</exception>
internal void UnitModeWrite(Span<byte> dcSignSpan, Point blockOrigin, Size blockSize, Av1NeighborArrayUnit<Av1PartitionContext>.UnitMask unitMask) => throw new NotImplementedException(); internal void UnitModeWrite(Span<byte> dcSignSpan, Point blockOrigin, Size blockSize, Av1NeighborArrayUnit<Av1PartitionContext>.UnitMask unitMask) => throw new NotImplementedException();
} }

3
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs

@ -3,6 +3,9 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Reserves encoder-side state for AV1 luma palette mode decisions.
/// </summary>
internal class Av1PaletteLumaModeInfo internal class Av1PaletteLumaModeInfo
{ {
} }

59
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs

@ -6,21 +6,36 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores entropy, palette, partition, and transform contexts for 4-by-4 blocks above the current block.
/// </summary>
internal class Av1ParseAboveNeighbor4x4Context internal class Av1ParseAboveNeighbor4x4Context
{ {
/* Buffer holding the sign of the DC coefficients and the /// <summary>
cumulative sum of the coefficient levels of the above 4x4 /// Stores DC-sign and cumulative coefficient-level contexts for each plane above the current block.
blocks corresponding to the current super block row. */ /// </summary>
private readonly int[][] aboveContext = new int[Av1Constants.MaxPlanes][]; private readonly int[][] aboveContext = new int[Av1Constants.MaxPlanes][];
/* Buffer holding the seg_id_predicted of the previous 4x4 block row. */ /// <summary>
/// Stores segmentation-prediction contexts from the preceding 4x4 row.
/// </summary>
private readonly int[] aboveSegmentIdPredictionContext; private readonly int[] aboveSegmentIdPredictionContext;
/* Value of base colors for Y, U, and V */ /// <summary>
/// Stores palette base colors for each plane above the current block.
/// </summary>
private readonly int[][] abovePaletteColors = new int[Av1Constants.MaxPlanes][]; private readonly int[][] abovePaletteColors = new int[Av1Constants.MaxPlanes][];
/// <summary>
/// Stores compound-reference group contexts from the preceding 4x4 row.
/// </summary>
private readonly int[] aboveCompGroupIndex; private readonly int[] aboveCompGroupIndex;
/// <summary>
/// Initializes a new instance of the <see cref="Av1ParseAboveNeighbor4x4Context"/> class.
/// </summary>
/// <param name="planesCount">The number of color planes.</param>
/// <param name="modeInfoColumnCount">The frame width in 4x4 mode-information columns.</param>
public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount) public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount)
{ {
int wide64x64Count = Av1BlockSize.Block64x64.Get4x4WideCount(); int wide64x64Count = Av1BlockSize.Block64x64.Get4x4WideCount();
@ -46,8 +61,19 @@ internal class Av1ParseAboveNeighbor4x4Context
/// </summary> /// </summary>
public int[] AboveTransformWidth { get; } public int[] AboveTransformWidth { get; }
/// <summary>
/// Gets the coefficient context row for the specified plane.
/// </summary>
/// <param name="plane">The zero-based plane index.</param>
/// <returns>The coefficient contexts for the plane.</returns>
public int[] GetContext(int plane) => this.aboveContext[plane]; public int[] GetContext(int plane) => this.aboveContext[plane];
/// <summary>
/// Resets above-neighbor state for the active tile-column range.
/// </summary>
/// <param name="sequenceHeader">The sequence header describing the color planes.</param>
/// <param name="modeInfoColumnStart">The first mode-information column in the tile.</param>
/// <param name="modeInfoColumnEnd">The exclusive end mode-information column in the tile.</param>
public void Clear(ObuSequenceHeader sequenceHeader, int modeInfoColumnStart, int modeInfoColumnEnd) public void Clear(ObuSequenceHeader sequenceHeader, int modeInfoColumnStart, int modeInfoColumnEnd)
{ {
int planeCount = sequenceHeader.ColorConfig.PlaneCount; int planeCount = sequenceHeader.ColorConfig.PlaneCount;
@ -64,8 +90,16 @@ internal class Av1ParseAboveNeighbor4x4Context
Array.Fill(this.aboveCompGroupIndex, 0, 0, width); Array.Fill(this.aboveCompGroupIndex, 0, 0, width);
} }
/// <summary>
/// Updates the above partition context for every 4x4 column covered by a block.
/// </summary>
/// <param name="modeInfoLocation">The block origin in frame mode-information units.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <param name="subSize">The size produced by the decoded partition.</param>
/// <param name="blockSize">The parent block size.</param>
public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileInfo, Av1BlockSize subSize, Av1BlockSize blockSize) public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileInfo, Av1BlockSize subSize, Av1BlockSize blockSize)
{ {
// Above contexts are tile-local even though block positions are frame-relative.
int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart; int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart;
int bw = blockSize.Get4x4WideCount(); int bw = blockSize.Get4x4WideCount();
int value = Av1PartitionContext.GetAboveContext(subSize); int value = Av1PartitionContext.GetAboveContext(subSize);
@ -74,6 +108,14 @@ internal class Av1ParseAboveNeighbor4x4Context
Array.Fill(this.AbovePartitionWidth, value, startIndex, bw); Array.Fill(this.AbovePartitionWidth, value, startIndex, bw);
} }
/// <summary>
/// Updates the above transform-size context for every 4x4 column covered by a block.
/// </summary>
/// <param name="modeInfoLocation">The block origin in frame mode-information units.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <param name="transformSize">The selected transform size.</param>
/// <param name="blockSize">The decoded block size.</param>
/// <param name="skip">A value indicating whether the block omits residual coefficients.</param>
public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip)
{ {
int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart; int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart;
@ -81,6 +123,7 @@ internal class Av1ParseAboveNeighbor4x4Context
int n4w = blockSize.Get4x4WideCount(); int n4w = blockSize.Get4x4WideCount();
if (skip) if (skip)
{ {
// Skipped blocks expose the full block width as their effective transform extent.
transformWidth = n4w << Av1Constants.ModeInfoSizeLog2; transformWidth = n4w << Av1Constants.ModeInfoSizeLog2;
} }
@ -88,6 +131,12 @@ internal class Av1ParseAboveNeighbor4x4Context
Array.Fill(this.AboveTransformWidth, transformWidth, startIndex, n4w); Array.Fill(this.AboveTransformWidth, transformWidth, startIndex, n4w);
} }
/// <summary>
/// Clears a range of above coefficient contexts for one plane.
/// </summary>
/// <param name="plane">The zero-based plane index.</param>
/// <param name="offset">The first context index to clear.</param>
/// <param name="length">The number of context entries to clear.</param>
internal void ClearContext(int plane, int offset, int length) internal void ClearContext(int plane, int offset, int length)
=> Array.Fill(this.aboveContext[plane], 0, offset, length); => Array.Fill(this.aboveContext[plane], 0, offset, length);
} }

57
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs

@ -6,21 +6,36 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores entropy, palette, partition, and transform contexts for 4-by-4 blocks left of the current block.
/// </summary>
internal class Av1ParseLeftNeighbor4x4Context internal class Av1ParseLeftNeighbor4x4Context
{ {
/* Buffer holding the sign of the DC coefficients and the /// <summary>
cumulative sum of the coefficient levels of the left 4x4 /// Stores DC-sign and cumulative coefficient-level contexts for each plane left of the current block.
blocks corresponding to the current super block row. */ /// </summary>
private readonly int[][] leftContext = new int[Av1Constants.MaxPlanes][]; private readonly int[][] leftContext = new int[Av1Constants.MaxPlanes][];
/* Buffer holding the seg_id_predicted of the previous 4x4 block row. */ /// <summary>
/// Stores segmentation-prediction contexts for the current superblock row.
/// </summary>
private readonly int[] leftSegmentIdPredictionContext; private readonly int[] leftSegmentIdPredictionContext;
/* Value of base colors for Y, U, and V */ /// <summary>
/// Stores palette base colors for each plane left of the current block.
/// </summary>
private readonly int[][] leftPaletteColors = new int[Av1Constants.MaxPlanes][]; private readonly int[][] leftPaletteColors = new int[Av1Constants.MaxPlanes][];
/// <summary>
/// Stores compound-reference group contexts for the current superblock row.
/// </summary>
private readonly int[] leftCompGroupIndex; private readonly int[] leftCompGroupIndex;
/// <summary>
/// Initializes a new instance of the <see cref="Av1ParseLeftNeighbor4x4Context"/> class.
/// </summary>
/// <param name="planesCount">The number of color planes.</param>
/// <param name="superblockModeInfoSize">The superblock height in 4x4 mode-information rows.</param>
public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSize) public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSize)
{ {
this.LeftTransformHeight = new int[superblockModeInfoSize]; this.LeftTransformHeight = new int[superblockModeInfoSize];
@ -47,6 +62,10 @@ internal class Av1ParseLeftNeighbor4x4Context
/// </summary> /// </summary>
public int[] LeftTransformHeight { get; } public int[] LeftTransformHeight { get; }
/// <summary>
/// Resets all left-neighbor state for a new superblock row.
/// </summary>
/// <param name="sequenceHeader">The sequence header describing the superblock size and color planes.</param>
public void Clear(ObuSequenceHeader sequenceHeader) public void Clear(ObuSequenceHeader sequenceHeader)
{ {
int blockCount = sequenceHeader.SuperblockModeInfoSize; int blockCount = sequenceHeader.SuperblockModeInfoSize;
@ -64,8 +83,16 @@ internal class Av1ParseLeftNeighbor4x4Context
Array.Fill(this.leftCompGroupIndex, 0, 0, blockCount); Array.Fill(this.leftCompGroupIndex, 0, 0, blockCount);
} }
/// <summary>
/// Updates the left partition context for every 4x4 row covered by a block.
/// </summary>
/// <param name="modeInfoLocation">The block origin in frame mode-information units.</param>
/// <param name="superblockInfo">The active superblock location.</param>
/// <param name="subSize">The size produced by the decoded partition.</param>
/// <param name="blockSize">The parent block size.</param>
public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize) public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize)
{ {
// The left context is reused for each superblock row, so address it relative to the superblock origin.
int startIndex = (modeInfoLocation.Y - superblockInfo.ModeInfoPosition.Y) & Av1PartitionContext.Mask; int startIndex = (modeInfoLocation.Y - superblockInfo.ModeInfoPosition.Y) & Av1PartitionContext.Mask;
int bh = blockSize.Get4x4HighCount(); int bh = blockSize.Get4x4HighCount();
int value = Av1PartitionContext.GetLeftContext(subSize); int value = Av1PartitionContext.GetLeftContext(subSize);
@ -73,6 +100,14 @@ internal class Av1ParseLeftNeighbor4x4Context
Array.Fill(this.LeftPartitionHeight, value, startIndex, bh); Array.Fill(this.LeftPartitionHeight, value, startIndex, bh);
} }
/// <summary>
/// Updates the left transform-size context for every 4x4 row covered by a block.
/// </summary>
/// <param name="modeInfoLocation">The block origin in frame mode-information units.</param>
/// <param name="superblockInfo">The active superblock location.</param>
/// <param name="transformSize">The selected transform size.</param>
/// <param name="blockSize">The decoded block size.</param>
/// <param name="skip">A value indicating whether the block omits residual coefficients.</param>
public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip)
{ {
int startIndex = modeInfoLocation.Y - superblockInfo.ModeInfoPosition.Y; int startIndex = modeInfoLocation.Y - superblockInfo.ModeInfoPosition.Y;
@ -80,6 +115,7 @@ internal class Av1ParseLeftNeighbor4x4Context
int n4h = blockSize.Get4x4HighCount(); int n4h = blockSize.Get4x4HighCount();
if (skip) if (skip)
{ {
// Skipped blocks expose the full block height as their effective transform extent.
transformHeight = n4h << Av1Constants.ModeInfoSizeLog2; transformHeight = n4h << Av1Constants.ModeInfoSizeLog2;
} }
@ -87,8 +123,19 @@ internal class Av1ParseLeftNeighbor4x4Context
Array.Fill(this.LeftTransformHeight, transformHeight, startIndex, n4h); Array.Fill(this.LeftTransformHeight, transformHeight, startIndex, n4h);
} }
/// <summary>
/// Clears a range of left coefficient contexts for one plane.
/// </summary>
/// <param name="plane">The zero-based plane index.</param>
/// <param name="offset">The first context index to clear.</param>
/// <param name="length">The number of context entries to clear.</param>
internal void ClearContext(int plane, int offset, int length) internal void ClearContext(int plane, int offset, int length)
=> Array.Fill(this.leftContext[plane], 0, offset, length); => Array.Fill(this.leftContext[plane], 0, offset, length);
/// <summary>
/// Gets the coefficient context column for the specified plane.
/// </summary>
/// <param name="plane">The zero-based plane index.</param>
/// <returns>The coefficient contexts for the plane.</returns>
internal int[] GetContext(int plane) => this.leftContext[plane]; internal int[] GetContext(int plane) => this.leftContext[plane];
} }

47
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs

@ -5,35 +5,74 @@ using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
// Generates 5 bit field in which each bit set to 1 represents /// <summary>
// a BlockSize partition 11111 means we split 128x128, 64x64, 32x32, 16x16 /// Stores the above and left five-bit AV1 partition contexts for a mode-information position.
// and 8x8. 10000 means we just split the 128x128 to 64x64 /// </summary>
/// <remarks>
/// Each set bit records a split at one block-size level. For example, <c>11111</c> records splits from
/// 128 by 128 through 8 by 8, while <c>10000</c> records only the 128 by 128 split.
/// </remarks>
internal struct Av1PartitionContext : IMinMaxValue<Av1PartitionContext> internal struct Av1PartitionContext : IMinMaxValue<Av1PartitionContext>
{ {
/// <summary>
/// Maps each block size to the five-bit context stored for an above neighbor.
/// </summary>
private static readonly int[] AboveLookup = private static readonly int[] AboveLookup =
[31, 31, 30, 30, 30, 28, 28, 28, 24, 24, 24, 16, 16, 16, 0, 0, 31, 28, 30, 24, 28, 16]; [31, 31, 30, 30, 30, 28, 28, 28, 24, 24, 24, 16, 16, 16, 0, 0, 31, 28, 30, 24, 28, 16];
/// <summary>
/// Maps each block size to the five-bit context stored for a left neighbor.
/// </summary>
private static readonly int[] LeftLookup = private static readonly int[] LeftLookup =
[31, 30, 31, 30, 28, 30, 28, 24, 28, 24, 16, 24, 16, 0, 16, 0, 28, 31, 24, 30, 16, 28]; [31, 30, 31, 30, 28, 30, 28, 24, 28, 24, 16, 24, 16, 0, 16, 0, 28, 31, 24, 30, 16, 28];
// Mask to extract ModeInfo offset within max ModeInfoBlock /// <summary>
/// The mask used to convert a frame mode-information row to its position within a 128-sample superblock.
/// </summary>
public const int Mask = (1 << (7 - 2)) - 1; public const int Mask = (1 << (7 - 2)) - 1;
/// <summary>
/// Initializes a new instance of the <see cref="Av1PartitionContext"/> struct.
/// </summary>
/// <param name="above">The context stored for blocks below this block.</param>
/// <param name="left">The context stored for blocks to the right of this block.</param>
public Av1PartitionContext(byte above, byte left) public Av1PartitionContext(byte above, byte left)
{ {
this.Above = above; this.Above = above;
this.Left = left; this.Left = left;
} }
/// <summary>
/// Gets the maximum representable partition context.
/// </summary>
public static Av1PartitionContext MaxValue => throw new NotImplementedException(); public static Av1PartitionContext MaxValue => throw new NotImplementedException();
/// <summary>
/// Gets the minimum representable partition context.
/// </summary>
public static Av1PartitionContext MinValue => throw new NotImplementedException(); public static Av1PartitionContext MinValue => throw new NotImplementedException();
/// <summary>
/// Gets or sets the five-bit context derived from the left neighbor.
/// </summary>
public byte Left { get; internal set; } public byte Left { get; internal set; }
/// <summary>
/// Gets or sets the five-bit context derived from the above neighbor.
/// </summary>
public byte Above { get; internal set; } public byte Above { get; internal set; }
/// <summary>
/// Gets the above-neighbor partition context for the specified block size.
/// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The five-bit above-neighbor context.</returns>
public static int GetAboveContext(Av1BlockSize blockSize) => AboveLookup[(int)blockSize]; public static int GetAboveContext(Av1BlockSize blockSize) => AboveLookup[(int)blockSize];
/// <summary>
/// Gets the left-neighbor partition context for the specified block size.
/// </summary>
/// <param name="blockSize">The block size.</param>
/// <returns>The five-bit left-neighbor context.</returns>
public static int GetLeftContext(Av1BlockSize blockSize) => LeftLookup[(int)blockSize]; public static int GetLeftContext(Av1BlockSize blockSize) => LeftLookup[(int)blockSize];
} }

86
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs

@ -6,8 +6,18 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Describes a decoded AV1 partition's block geometry, neighbors, and frame-boundary availability.
/// </summary>
internal class Av1PartitionInfo internal class Av1PartitionInfo
{ {
/// <summary>
/// Initializes a new instance of the <see cref="Av1PartitionInfo"/> class.
/// </summary>
/// <param name="modeInfo">The decoded mode information for the partition block.</param>
/// <param name="superblockInfo">The containing superblock.</param>
/// <param name="isChroma">A value indicating whether the partition has chroma samples.</param>
/// <param name="partitionType">The partition type that produced the block.</param>
public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType) public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType)
{ {
this.ModeInfo = modeInfo; this.ModeInfo = modeInfo;
@ -20,6 +30,9 @@ internal class Av1PartitionInfo
this.HeightInPixels = new int[3]; this.HeightInPixels = new int[3];
} }
/// <summary>
/// Gets the decoded block mode information.
/// </summary>
public Av1BlockModeInfo ModeInfo { get; } public Av1BlockModeInfo ModeInfo { get; }
/// <summary> /// <summary>
@ -27,8 +40,14 @@ internal class Av1PartitionInfo
/// </summary> /// </summary>
public Av1SuperblockInfo SuperblockInfo { get; } public Av1SuperblockInfo SuperblockInfo { get; }
/// <summary>
/// Gets a value indicating whether the partition has chroma samples at its current luma position.
/// </summary>
public bool IsChroma { get; } public bool IsChroma { get; }
/// <summary>
/// Gets the partition type that produced the block.
/// </summary>
public Av1PartitionType Type { get; } public Av1PartitionType Type { get; }
/// <summary> /// <summary>
@ -61,32 +80,77 @@ internal class Av1PartitionInfo
/// </summary> /// </summary>
public int RowIndex { get; set; } public int RowIndex { get; set; }
/// <summary>
/// Gets or sets the mode information covering the immediately above luma neighbor.
/// </summary>
public Av1BlockModeInfo? AboveModeInfo { get; set; } public Av1BlockModeInfo? AboveModeInfo { get; set; }
/// <summary>
/// Gets or sets the mode information covering the immediately left luma neighbor.
/// </summary>
public Av1BlockModeInfo? LeftModeInfo { get; set; } public Av1BlockModeInfo? LeftModeInfo { get; set; }
/// <summary>
/// Gets or sets the mode information covering the above chroma neighbor.
/// </summary>
public Av1BlockModeInfo? AboveModeInfoForChroma { get; set; } public Av1BlockModeInfo? AboveModeInfoForChroma { get; set; }
/// <summary>
/// Gets or sets the mode information covering the left chroma neighbor.
/// </summary>
public Av1BlockModeInfo? LeftModeInfoForChroma { get; set; } public Av1BlockModeInfo? LeftModeInfoForChroma { get; set; }
/// <summary>
/// Gets or sets the constrained directional enhancement filter strengths associated with the block.
/// </summary>
public int[] CdefStrength { get; set; } public int[] CdefStrength { get; set; }
/// <summary>
/// Gets or sets the reference-frame identifiers selected for the block.
/// </summary>
public int[] ReferenceFrame { get; set; } public int[] ReferenceFrame { get; set; }
/// <summary>
/// Gets the signed distance from the block to the left frame edge in one-eighth-sample units.
/// </summary>
public int ModeBlockToLeftEdge { get; private set; } public int ModeBlockToLeftEdge { get; private set; }
/// <summary>
/// Gets the signed distance from the block to the right frame edge in one-eighth-sample units.
/// </summary>
public int ModeBlockToRightEdge { get; private set; } public int ModeBlockToRightEdge { get; private set; }
/// <summary>
/// Gets the signed distance from the block to the top frame edge in one-eighth-sample units.
/// </summary>
public int ModeBlockToTopEdge { get; private set; } public int ModeBlockToTopEdge { get; private set; }
/// <summary>
/// Gets the signed distance from the block to the bottom frame edge in one-eighth-sample units.
/// </summary>
public int ModeBlockToBottomEdge { get; private set; } public int ModeBlockToBottomEdge { get; private set; }
/// <summary>
/// Gets the block width in samples for each color plane.
/// </summary>
public int[] WidthInPixels { get; private set; } public int[] WidthInPixels { get; private set; }
/// <summary>
/// Gets the block height in samples for each color plane.
/// </summary>
public int[] HeightInPixels { get; private set; } public int[] HeightInPixels { get; private set; }
/// <summary>
/// Gets or sets the neighboring luma samples used by chroma-from-luma prediction.
/// </summary>
public Av1ChromaFromLumaContext? ChromaFromLumaContext { get; internal set; } public Av1ChromaFromLumaContext? ChromaFromLumaContext { get; internal set; }
/// <summary>
/// Computes tile-neighbor availability, frame-edge distances, and per-plane block dimensions.
/// </summary>
/// <param name="sequenceHeader">The sequence header describing color subsampling.</param>
/// <param name="frameHeader">The frame header describing coded dimensions.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
public void ComputeBoundaryOffsets(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1TileInfo tileInfo) public void ComputeBoundaryOffsets(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1TileInfo tileInfo)
{ {
Av1BlockSize blockSize = this.ModeInfo.BlockSize; Av1BlockSize blockSize = this.ModeInfo.BlockSize;
@ -105,21 +169,23 @@ internal class Av1PartitionInfo
this.ModeBlockToTopEdge = -this.RowIndex << shift; this.ModeBlockToTopEdge = -this.RowIndex << shift;
this.ModeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift; this.ModeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift;
// Block Size width & height in pixels. // The bitstream expresses block size on the luma grid. Chroma dimensions are derived by
// For Luma bock // subsampling that grid while retaining at least one 4x4 chroma unit for narrow blocks.
const int modeInfoSize = 1 << Av1Constants.ModeInfoSizeLog2; const int modeInfoSize = 1 << Av1Constants.ModeInfoSizeLog2;
this.WidthInPixels[0] = bw4 * modeInfoSize; this.WidthInPixels[0] = bw4 * modeInfoSize;
this.HeightInPixels[0] = bh4 * modeInfoSize; this.HeightInPixels[0] = bh4 * modeInfoSize;
// For U plane chroma bock
this.WidthInPixels[1] = Math.Max(1, bw4 >> subX) * modeInfoSize; this.WidthInPixels[1] = Math.Max(1, bw4 >> subX) * modeInfoSize;
this.HeightInPixels[1] = Math.Max(1, bh4 >> subY) * modeInfoSize; this.HeightInPixels[1] = Math.Max(1, bh4 >> subY) * modeInfoSize;
// For V plane chroma bock
this.WidthInPixels[2] = Math.Max(1, bw4 >> subX) * modeInfoSize; this.WidthInPixels[2] = Math.Max(1, bw4 >> subX) * modeInfoSize;
this.HeightInPixels[2] = Math.Max(1, bh4 >> subY) * modeInfoSize; this.HeightInPixels[2] = Math.Max(1, bh4 >> subY) * modeInfoSize;
} }
/// <summary>
/// Resolves the decoded luma and chroma mode information for available above and left neighbors.
/// </summary>
/// <param name="colorConfig">The color-plane subsampling configuration.</param>
public void PopulateModeInfoNeighbors(ObuColorConfig colorConfig) public void PopulateModeInfoNeighbors(ObuColorConfig colorConfig)
{ {
if (this.AvailableAbove) if (this.AvailableAbove)
@ -154,6 +220,12 @@ internal class Av1PartitionInfo
} }
} }
/// <summary>
/// Gets the block width clipped to the right frame edge.
/// </summary>
/// <param name="blockSize">The luma block size.</param>
/// <param name="subX">A value indicating whether the target plane is horizontally subsampled.</param>
/// <returns>The clipped width in 4x4 units of the target plane.</returns>
public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX) public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX)
{ {
int maxBlockWide = blockSize.GetWidth(); int maxBlockWide = blockSize.GetWidth();
@ -166,6 +238,12 @@ internal class Av1PartitionInfo
return maxBlockWide >> 2; return maxBlockWide >> 2;
} }
/// <summary>
/// Gets the block height clipped to the bottom frame edge.
/// </summary>
/// <param name="blockSize">The luma block size.</param>
/// <param name="subY">A value indicating whether the target plane is vertically subsampled.</param>
/// <returns>The clipped height in 4x4 units of the target plane.</returns>
public int GetMaxBlockHigh(Av1BlockSize blockSize, bool subY) public int GetMaxBlockHigh(Av1BlockSize blockSize, bool subY)
{ {
int maxBlockHigh = blockSize.GetHeight(); int maxBlockHigh = blockSize.GetHeight();

77
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs

@ -5,70 +5,131 @@ using System;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Holds the coding decisions, buffers, and sequence context for one AV1 picture pass.
/// </summary>
internal class Av1PictureControlSet internal class Av1PictureControlSet
{ {
/// <summary>
/// Gets or sets the partition neighbor contexts for each tile.
/// </summary>
public required Av1NeighborArrayUnit<Av1PartitionContext>[] PartitionContexts { get; internal set; } public required Av1NeighborArrayUnit<Av1PartitionContext>[] PartitionContexts { get; internal set; }
/// <summary>
/// Gets or sets the luma DC-sign and coefficient-level neighbor contexts for each tile.
/// </summary>
public required Av1NeighborArrayUnit<byte>[] LuminanceDcSignLevelCoefficientNeighbors { get; internal set; } public required Av1NeighborArrayUnit<byte>[] LuminanceDcSignLevelCoefficientNeighbors { get; internal set; }
/// <summary>
/// Gets or sets the red-difference chroma DC-sign and coefficient-level neighbor contexts for each tile.
/// </summary>
public required Av1NeighborArrayUnit<byte>[] CrDcSignLevelCoefficientNeighbors { get; internal set; } public required Av1NeighborArrayUnit<byte>[] CrDcSignLevelCoefficientNeighbors { get; internal set; }
/// <summary>
/// Gets or sets the blue-difference chroma DC-sign and coefficient-level neighbor contexts for each tile.
/// </summary>
public required Av1NeighborArrayUnit<byte>[] CbDcSignLevelCoefficientNeighbors { get; internal set; } public required Av1NeighborArrayUnit<byte>[] CbDcSignLevelCoefficientNeighbors { get; internal set; }
/// <summary>
/// Gets or sets the transform-function neighbor contexts for each tile.
/// </summary>
public required Av1NeighborArrayUnit<byte>[] TransformFunctionContexts { get; internal set; } public required Av1NeighborArrayUnit<byte>[] TransformFunctionContexts { get; internal set; }
/// <summary>
/// Gets or sets the sequence-wide encoder state.
/// </summary>
public required Av1SequenceControlSet Sequence { get; internal set; } public required Av1SequenceControlSet Sequence { get; internal set; }
/// <summary>
/// Gets or sets the parent picture state shared across coding passes.
/// </summary>
public required Av1PictureParentControlSet Parent { get; internal set; } public required Av1PictureParentControlSet Parent { get; internal set; }
/// <summary>
/// Gets or sets the frame segmentation identifiers used for spatial prediction.
/// </summary>
public required byte[] SegmentationNeighborMap { get; internal set; } public required byte[] SegmentationNeighborMap { get; internal set; }
/// <summary>
/// Gets the frame grid that maps each 4x4 position to its mode-information span.
/// </summary>
public Av1ModeInfo[][] ModeInfoGrid { get; } = []; public Av1ModeInfo[][] ModeInfoGrid { get; } = [];
/// <summary>
/// Gets or sets the contiguous mode-information storage addressed by <see cref="ModeInfoGrid"/>.
/// </summary>
public required Av1ModeInfo[] Mip { get; internal set; } public required Av1ModeInfo[] Mip { get; internal set; }
/// <summary>
/// Gets or sets the row stride of <see cref="ModeInfoGrid"/> in 4x4 mode-information units.
/// </summary>
public int ModeInfoStride { get; internal set; } public int ModeInfoStride { get; internal set; }
// true if 4x4 blocks are disallowed for all frames, and NSQ is disabled (since granularity is /// <summary>
// needed for 8x8 NSQ blocks). Used to compute the offset for mip. /// Gets or sets a value indicating whether the mode-information backing store uses 8x8 rather than 4x4 granularity.
/// </summary>
public bool Disallow4x4AllFrames { get; internal set; } public bool Disallow4x4AllFrames { get; internal set; }
/// <summary>
/// Gets or sets the constrained directional enhancement filter presets for each filter block.
/// </summary>
public required int[][] CdefPreset { get; internal set; } public required int[][] CdefPreset { get; internal set; }
/// <summary>
/// Gets the mode-information span mapped to a frame position.
/// </summary>
/// <param name="position">The frame position in 4x4 mode-information units.</param>
/// <returns>The mode-information span beginning at the position.</returns>
public Span<Av1ModeInfo> GetFromModeInfoGrid(Point position) public Span<Av1ModeInfo> GetFromModeInfoGrid(Point position)
=> this.ModeInfoGrid[(position.Y * this.ModeInfoStride) + position.X]; => this.ModeInfoGrid[(position.Y * this.ModeInfoStride) + position.X];
/// <summary>
/// Maps a frame position to the supplied mode-information span.
/// </summary>
/// <param name="position">The frame position in 4x4 mode-information units.</param>
/// <param name="span">The mode-information entries to map.</param>
public void SetModeInfoGridRow(Point position, Span<Av1ModeInfo> span) public void SetModeInfoGridRow(Point position, Span<Av1ModeInfo> span)
=> this.SetModeInfoGridRow((position.Y * this.ModeInfoStride) + position.X, span); => this.SetModeInfoGridRow((position.Y * this.ModeInfoStride) + position.X, span);
/// <summary>
/// Maps a linear grid offset to the supplied mode-information span.
/// </summary>
/// <param name="offset">The linear grid offset.</param>
/// <param name="span">The mode-information entries to map.</param>
public void SetModeInfoGridRow(int offset, Span<Av1ModeInfo> span) public void SetModeInfoGridRow(int offset, Span<Av1ModeInfo> span)
{ {
// Grid entries own their arrays because the source span can refer to temporary traversal state.
this.ModeInfoGrid[offset] = new Av1ModeInfo[span.Length]; this.ModeInfoGrid[offset] = new Av1ModeInfo[span.Length];
span.CopyTo(this.ModeInfoGrid[offset]); span.CopyTo(this.ModeInfoGrid[offset]);
} }
/// <summary> /// <summary>
/// SVT: get_mbmi /// Gets the macroblock mode information at a block origin and refreshes its grid mapping.
/// </summary> /// </summary>
/// <param name="blockOrigin">The block origin in 4x4 mode-information units.</param>
/// <returns>The macroblock mode information at the origin.</returns>
internal Av1MacroBlockModeInfo GetMacroBlockModeInfo(Point blockOrigin) internal Av1MacroBlockModeInfo GetMacroBlockModeInfo(Point blockOrigin)
{ {
int modeInfoStride = this.ModeInfoStride; int modeInfoStride = this.ModeInfoStride;
int offset = (blockOrigin.Y * modeInfoStride) + blockOrigin.X; int offset = (blockOrigin.Y * modeInfoStride) + blockOrigin.X;
// Reset the mi_grid (needs to be done here in case it was changed for NSQ blocks during MD - svt_aom_init_xd()) // Rectangular mode-decision blocks can replace grid entries. Restore the entry from the
// mip offset may be different from grid offset when 4x4 blocks are disallowed // contiguous backing store, whose index is halved when 4x4 blocks are globally disabled.
int disallow4x4 = this.Disallow4x4AllFrames ? 1 : 0; int disallow4x4 = this.Disallow4x4AllFrames ? 1 : 0;
int mipOffset = ((blockOrigin.Y >> disallow4x4) * (modeInfoStride >> disallow4x4)) + (blockOrigin.X >> disallow4x4); int mipOffset = ((blockOrigin.Y >> disallow4x4) * (modeInfoStride >> disallow4x4)) + (blockOrigin.X >> disallow4x4);
this.SetModeInfoGridRow(offset, ((Span<Av1ModeInfo>)this.Mip)[mipOffset..]); this.SetModeInfoGridRow(offset, ((Span<Av1ModeInfo>)this.Mip)[mipOffset..]);
// use idx 0 as that's the first MacroBlockModeInfo in the block. // The first mapped entry owns the macroblock state for the entire block.
Av1ModeInfo modeInfo = this.ModeInfoGrid[offset][0]; Av1ModeInfo modeInfo = this.ModeInfoGrid[offset][0];
return modeInfo.MacroBlockModeInfo; return modeInfo.MacroBlockModeInfo;
} }
/// <summary> /// <summary>
/// SVT: svt_av1_update_segmentation_map /// Writes a segment identifier to every segmentation-map entry covered by a block.
/// </summary> /// </summary>
/// <param name="blockSize">The block size.</param>
/// <param name="origin">The block origin in samples.</param>
/// <param name="segmentId">The segment identifier.</param>
internal void UpdateSegmentation(Av1BlockSize blockSize, Point origin, int segmentId) internal void UpdateSegmentation(Av1BlockSize blockSize, Point origin, int segmentId)
{ {
Av1EncoderCommon cm = this.Parent.Common; Av1EncoderCommon cm = this.Parent.Common;
@ -80,6 +141,8 @@ internal class Av1PictureControlSet
int bh = blockSize.GetHeight(); int bh = blockSize.GetHeight();
int xmis = Math.Min(cm.ModeInfoColumnCount - mi_col, bw); int xmis = Math.Min(cm.ModeInfoColumnCount - mi_col, bw);
int ymis = Math.Min(cm.ModeInfoRowCount - mi_row, bh); int ymis = Math.Min(cm.ModeInfoRowCount - mi_row, bh);
// Clip edge blocks to the coded mode-information grid before filling complete rows.
for (int y = 0; y < ymis; ++y) for (int y = 0; y < ymis; ++y)
{ {
int offset = mi_offset + (y * cm.ModeInfoColumnCount); int offset = mi_offset + (y * cm.ModeInfoColumnCount);

24
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs

@ -5,19 +5,43 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Holds encoder state that is shared by all coding passes for one AV1 picture.
/// </summary>
internal class Av1PictureParentControlSet internal class Av1PictureParentControlSet
{ {
/// <summary>
/// Gets or sets frame dimensions and tile state shared by encoder stages.
/// </summary>
public required Av1EncoderCommon Common { get; internal set; } public required Av1EncoderCommon Common { get; internal set; }
/// <summary>
/// Gets or sets the frame header being encoded.
/// </summary>
public required ObuFrameHeader FrameHeader { get; internal set; } public required ObuFrameHeader FrameHeader { get; internal set; }
/// <summary>
/// Gets or sets the preceding quantizer index for each tile context.
/// </summary>
public required int[] PreviousQIndex { get; internal set; } public required int[] PreviousQIndex { get; internal set; }
/// <summary>
/// Gets or sets the encoder palette-search level.
/// </summary>
public int PaletteLevel { get; internal set; } public int PaletteLevel { get; internal set; }
/// <summary>
/// Gets or sets the frame width aligned for superblock traversal.
/// </summary>
public int AlignedWidth { get; internal set; } public int AlignedWidth { get; internal set; }
/// <summary>
/// Gets or sets the frame height aligned for superblock traversal.
/// </summary>
public int AlignedHeight { get; internal set; } public int AlignedHeight { get; internal set; }
/// <summary>
/// Gets or sets the geometry state for each superblock in the picture.
/// </summary>
public required Av1SuperblockGeometry[] SuperblockGeometry { get; internal set; } public required Av1SuperblockGeometry[] SuperblockGeometry { get; internal set; }
} }

10
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs

@ -3,8 +3,18 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Identifies whether AV1 block processing targets the luma plane or either chroma plane.
/// </summary>
internal enum Av1PlaneType : int internal enum Av1PlaneType : int
{ {
/// <summary>
/// The luma plane.
/// </summary>
Y, Y,
/// <summary>
/// Either chroma plane.
/// </summary>
Uv Uv
} }

9
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs

@ -5,9 +5,18 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Holds encoder configuration and sequence-wide state shared by AV1 pictures.
/// </summary>
internal class Av1SequenceControlSet internal class Av1SequenceControlSet
{ {
/// <summary>
/// Gets or sets the sequence header that governs encoded pictures.
/// </summary>
public required ObuSequenceHeader SequenceHeader { get; internal set; } public required ObuSequenceHeader SequenceHeader { get; internal set; }
/// <summary>
/// Gets or sets the maximum number of encoded blocks allocated for a picture.
/// </summary>
public int MaxBlockCount { get; internal set; } public int MaxBlockCount { get; internal set; }
} }

15
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs

@ -5,13 +5,28 @@ using static SixLabors.ImageSharp.Formats.Heif.Av1.Tiling.Av1TileWriter;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Holds encoder-side block decisions and transform data for one AV1 superblock.
/// </summary>
internal class Av1Superblock internal class Av1Superblock
{ {
/// <summary>
/// Gets or sets the final encoder decisions in partition traversal order.
/// </summary>
public required Av1EncoderBlockStruct[] FinalBlocks { get; set; } public required Av1EncoderBlockStruct[] FinalBlocks { get; set; }
/// <summary>
/// Gets or sets the tile containing the superblock.
/// </summary>
public required Av1TileInfo TileInfo { get; set; } public required Av1TileInfo TileInfo { get; set; }
/// <summary>
/// Gets or sets the selected partition type for each partition-tree node.
/// </summary>
public required Av1PartitionType[] CodingUnitPartitionTypes { get; internal set; } public required Av1PartitionType[] CodingUnitPartitionTypes { get; internal set; }
/// <summary>
/// Gets or sets the superblock index within the picture.
/// </summary>
public int Index { get; internal set; } public int Index { get; internal set; }
} }

6
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs

@ -3,7 +3,13 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Describes a superblock node's location, dimensions, and children in the encoder partition tree.
/// </summary>
internal class Av1SuperblockGeometry internal class Av1SuperblockGeometry
{ {
/// <summary>
/// Gets or sets a value indicating whether the superblock lies completely within the coded frame.
/// </summary>
public bool IsComplete { get; internal set; } public bool IsComplete { get; internal set; }
} }

73
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs

@ -3,10 +3,21 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores the partition tree and decoded mode information for one AV1 superblock.
/// </summary>
internal class Av1SuperblockInfo internal class Av1SuperblockInfo
{ {
/// <summary>
/// Provides the frame-owned arrays addressed by this superblock view.
/// </summary>
private readonly Av1FrameInfo frameInfo; private readonly Av1FrameInfo frameInfo;
/// <summary>
/// Initializes a new instance of the <see cref="Av1SuperblockInfo"/> class.
/// </summary>
/// <param name="frameInfo">The owning frame information.</param>
/// <param name="position">The superblock position in the frame superblock grid.</param>
public Av1SuperblockInfo(Av1FrameInfo frameInfo, Point position) public Av1SuperblockInfo(Av1FrameInfo frameInfo, Point position)
{ {
this.Position = position; this.Position = position;
@ -14,39 +25,82 @@ internal class Av1SuperblockInfo
} }
/// <summary> /// <summary>
/// Gets the position of this superblock inside the tile, counted in superblocks. /// Gets the position of this superblock in the frame superblock grid.
/// </summary> /// </summary>
public Point Position { get; } public Point Position { get; }
/// <summary> /// <summary>
/// Gets the position of this superblock inside the tile, counted in mode info blocks (of 4x4 pixels). /// Gets the frame-relative superblock origin in 4x4 mode-information units.
/// </summary> /// </summary>
public Point ModeInfoPosition => this.Position * this.frameInfo.SuperblockModeInfoSize; public Point ModeInfoPosition => this.Position * this.frameInfo.SuperblockModeInfoSize;
/// <summary>
/// Gets a reference to the superblock quantizer-index delta.
/// </summary>
public ref int SuperblockDeltaQ => ref this.frameInfo.GetDeltaQuantizationIndex(this.Position); public ref int SuperblockDeltaQ => ref this.frameInfo.GetDeltaQuantizationIndex(this.Position);
/// <summary>
/// Gets the mode information that covers the superblock origin.
/// </summary>
public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(new Point(0, 0)); public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(new Point(0, 0));
/// <summary>
/// Gets the luma coefficient storage reserved for this superblock.
/// </summary>
public Span<int> CoefficientsY => this.frameInfo.GetCoefficientsY(this.Position); public Span<int> CoefficientsY => this.frameInfo.GetCoefficientsY(this.Position);
/// <summary>
/// Gets the blue-difference chroma coefficient storage reserved for this superblock.
/// </summary>
public Span<int> CoefficientsU => this.frameInfo.GetCoefficientsU(this.Position); public Span<int> CoefficientsU => this.frameInfo.GetCoefficientsU(this.Position);
/// <summary>
/// Gets the red-difference chroma coefficient storage reserved for this superblock.
/// </summary>
public Span<int> CoefficientsV => this.frameInfo.GetCoefficientsV(this.Position); public Span<int> CoefficientsV => this.frameInfo.GetCoefficientsV(this.Position);
/// <summary>
/// Gets the constrained directional enhancement filter strengths for this superblock.
/// </summary>
public Span<int> CdefStrength => this.frameInfo.GetCdefStrength(this.Position); public Span<int> CdefStrength => this.frameInfo.GetCdefStrength(this.Position);
/// <summary>
/// Gets the loop-filter deltas for this superblock.
/// </summary>
public Span<int> SuperblockDeltaLoopFilter => this.frameInfo.GetDeltaLoopFilter(this.Position); public Span<int> SuperblockDeltaLoopFilter => this.frameInfo.GetDeltaLoopFilter(this.Position);
/// <summary>
/// Gets or sets the next luma transform-information index while parsing this superblock.
/// </summary>
public int TransformInfoIndexY { get; internal set; } public int TransformInfoIndexY { get; internal set; }
/// <summary>
/// Gets or sets the next shared chroma transform-information index while parsing this superblock.
/// </summary>
public int TransformInfoIndexUv { get; internal set; } public int TransformInfoIndexUv { get; internal set; }
/// <summary>
/// Gets or sets the number of mode-information records parsed for this superblock.
/// </summary>
public int BlockCount { get; internal set; } public int BlockCount { get; internal set; }
/// <summary>
/// Gets the luma transform-information storage reserved for this superblock.
/// </summary>
/// <returns>The superblock luma transform-information span.</returns>
public Span<Av1TransformInfo> GetTransformInfoY() => this.frameInfo.GetSuperblockTransformY(this.Position); public Span<Av1TransformInfo> GetTransformInfoY() => this.frameInfo.GetSuperblockTransformY(this.Position);
/// <summary>
/// Gets the shared chroma transform-information storage reserved for this superblock.
/// </summary>
/// <returns>The superblock chroma transform-information span.</returns>
public Span<Av1TransformInfo> GetTransformInfoUv() => this.frameInfo.GetSuperblockTransformUv(this.Position); public Span<Av1TransformInfo> GetTransformInfoUv() => this.frameInfo.GetSuperblockTransformUv(this.Position);
/// <summary>
/// Gets the transform-information storage for the specified color plane.
/// </summary>
/// <param name="plane">The zero-based color-plane index.</param>
/// <returns>The transform-information span for the plane.</returns>
public Span<Av1TransformInfo> GetTransformInfo(int plane) => this.frameInfo.GetSuperblockTransform(plane, this.Position); public Span<Av1TransformInfo> GetTransformInfo(int plane) => this.frameInfo.GetSuperblockTransform(plane, this.Position);
/// <summary> /// <summary>
@ -54,10 +108,25 @@ internal class Av1SuperblockInfo
/// </summary> /// </summary>
public Span<Av1BlockModeInfo> GetModeInfos() => this.frameInfo.GetModeInfos(this.Position, this.BlockCount); public Span<Av1BlockModeInfo> GetModeInfos() => this.frameInfo.GetModeInfos(this.Position, this.BlockCount);
/// <summary>
/// Gets the mode information covering a position relative to this superblock.
/// </summary>
/// <param name="index">The position in 4x4 mode-information units relative to the superblock.</param>
/// <returns>The mode information covering the position.</returns>
public Av1BlockModeInfo GetModeInfo(Point index) => this.frameInfo.GetModeInfo(this.Position, index); public Av1BlockModeInfo GetModeInfo(Point index) => this.frameInfo.GetModeInfo(this.Position, index);
/// <summary>
/// Gets the mode information covering a frame-relative position.
/// </summary>
/// <param name="index">The frame-relative position in 4x4 mode-information units.</param>
/// <returns>The mode information covering the position.</returns>
public Av1BlockModeInfo GetModeInfoAt(Point index) => this.frameInfo.GetModeInfoAt(index); public Av1BlockModeInfo GetModeInfoAt(Point index) => this.frameInfo.GetModeInfoAt(index);
/// <summary>
/// Gets the coefficient storage for the specified color plane.
/// </summary>
/// <param name="plane">The color plane.</param>
/// <returns>The coefficient span for the plane, or an empty span for an unsupported value.</returns>
public Span<int> GetCoefficients(Av1Plane plane) => plane switch public Span<int> GetCoefficients(Av1Plane plane) => plane switch
{ {
Av1Plane.Y => this.CoefficientsY, Av1Plane.Y => this.CoefficientsY,

40
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs

@ -5,14 +5,27 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Describes one AV1 tile's superblock and mode-information boundaries.
/// </summary>
internal class Av1TileInfo internal class Av1TileInfo
{ {
/// <summary>
/// Initializes a new instance of the <see cref="Av1TileInfo"/> class for the specified tile coordinates.
/// </summary>
/// <param name="row">The tile row index.</param>
/// <param name="column">The tile column index.</param>
/// <param name="frameHeader">The frame header that defines the tile layout.</param>
public Av1TileInfo(int row, int column, ObuFrameHeader frameHeader) public Av1TileInfo(int row, int column, ObuFrameHeader frameHeader)
{ {
this.SetTileRow(frameHeader.TilesInfo, frameHeader.ModeInfoRowCount, row); this.SetTileRow(frameHeader.TilesInfo, frameHeader.ModeInfoRowCount, row);
this.SetTileColumn(frameHeader.TilesInfo, frameHeader.ModeInfoColumnCount, column); this.SetTileColumn(frameHeader.TilesInfo, frameHeader.ModeInfoColumnCount, column);
} }
/// <summary>
/// Initializes a new instance of the <see cref="Av1TileInfo"/> class by copying another tile description.
/// </summary>
/// <param name="tileInfo">The tile description to copy.</param>
public Av1TileInfo(Av1TileInfo tileInfo) public Av1TileInfo(Av1TileInfo tileInfo)
{ {
this.ModeInfoColumnStart = tileInfo.ModeInfoColumnStart; this.ModeInfoColumnStart = tileInfo.ModeInfoColumnStart;
@ -22,16 +35,37 @@ internal class Av1TileInfo
this.TileIndex = tileInfo.TileIndex; this.TileIndex = tileInfo.TileIndex;
} }
/// <summary>
/// Gets the first mode-information row in the tile.
/// </summary>
public int ModeInfoRowStart { get; private set; } public int ModeInfoRowStart { get; private set; }
/// <summary>
/// Gets the exclusive end mode-information row in the tile.
/// </summary>
public int ModeInfoRowEnd { get; private set; } public int ModeInfoRowEnd { get; private set; }
/// <summary>
/// Gets the first mode-information column in the tile.
/// </summary>
public int ModeInfoColumnStart { get; private set; } public int ModeInfoColumnStart { get; private set; }
/// <summary>
/// Gets the exclusive end mode-information column in the tile.
/// </summary>
public int ModeInfoColumnEnd { get; private set; } public int ModeInfoColumnEnd { get; private set; }
/// <summary>
/// Gets the tile column and row indices.
/// </summary>
public Point TileIndex { get; private set; } public Point TileIndex { get; private set; }
/// <summary>
/// Selects the tile row and updates its mode-information boundaries.
/// </summary>
/// <param name="tileGroupHeader">The tile layout.</param>
/// <param name="modeInfoRowCount">The coded frame height in mode-information rows.</param>
/// <param name="row">The tile row index.</param>
public void SetTileRow(ObuTileGroupHeader tileGroupHeader, int modeInfoRowCount, int row) public void SetTileRow(ObuTileGroupHeader tileGroupHeader, int modeInfoRowCount, int row)
{ {
this.ModeInfoRowStart = tileGroupHeader.TileRowStartModeInfo[row]; this.ModeInfoRowStart = tileGroupHeader.TileRowStartModeInfo[row];
@ -41,6 +75,12 @@ internal class Av1TileInfo
this.TileIndex = loc; this.TileIndex = loc;
} }
/// <summary>
/// Selects the tile column and updates its mode-information boundaries.
/// </summary>
/// <param name="tileGroupHeader">The tile layout.</param>
/// <param name="modeInfoColumnCount">The coded frame width in mode-information columns.</param>
/// <param name="column">The tile column index.</param>
public void SetTileColumn(ObuTileGroupHeader tileGroupHeader, int modeInfoColumnCount, int column) public void SetTileColumn(ObuTileGroupHeader tileGroupHeader, int modeInfoColumnCount, int column)
{ {
this.ModeInfoColumnStart = tileGroupHeader.TileColumnStartModeInfo[column]; this.ModeInfoColumnStart = tileGroupHeader.TileColumnStartModeInfo[column];

413
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs

@ -11,37 +11,107 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Parses partition, mode, transform, and coefficient syntax for one AV1 tile.
/// </summary>
internal class Av1TileReader : IAv1TileReader internal class Av1TileReader : IAv1TileReader
{ {
/// <summary>
/// The default self-guided restoration projection coefficients for each color plane.
/// </summary>
private static readonly int[] SgrprojXqdMid = [-32, 31]; private static readonly int[] SgrprojXqdMid = [-32, 31];
/// <summary>
/// The default Wiener restoration taps retained between restoration units.
/// </summary>
private static readonly int[] WienerTapsMid = [3, -7, 15]; private static readonly int[] WienerTapsMid = [3, -7, 15];
/// <summary>
/// Maps packed coefficient sign classes to their signed contribution to the DC context.
/// </summary>
private static readonly int[] Signs = [0, -1, 1]; private static readonly int[] Signs = [0, -1, 1];
/// <summary>
/// Maps the summed neighboring DC signs to the AV1 DC-sign entropy context.
/// </summary>
private static readonly int[] DcSignContexts = [ private static readonly int[] DcSignContexts = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]; 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
/// <summary>
/// Maps the minimum and union of luma neighbor levels to a transform-block skip context.
/// </summary>
private static readonly int[][] SkipContexts = [ private static readonly int[][] SkipContexts = [
[1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]]; [1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]];
/// <summary>
/// Stores the preceding self-guided restoration coefficients for each color plane.
/// </summary>
private int[][] referenceSgrXqd = []; private int[][] referenceSgrXqd = [];
/// <summary>
/// Stores the preceding horizontal and vertical Wiener taps for each color plane.
/// </summary>
private int[][][] referenceLrWiener = []; private int[][][] referenceLrWiener = [];
/// <summary>
/// Tracks entropy, partition, transform, and palette state above the current block.
/// </summary>
private readonly Av1ParseAboveNeighbor4x4Context aboveNeighborContext; private readonly Av1ParseAboveNeighbor4x4Context aboveNeighborContext;
/// <summary>
/// Tracks entropy, partition, transform, and palette state left of the current block.
/// </summary>
private readonly Av1ParseLeftNeighbor4x4Context leftNeighborContext; private readonly Av1ParseLeftNeighbor4x4Context leftNeighborContext;
/// <summary>
/// The quantizer index carried between delta-quantized blocks in the current tile.
/// </summary>
private int currentQuantizerIndex; private int currentQuantizerIndex;
/// <summary>
/// Stores the segment identifier covering each 4x4 frame position.
/// </summary>
private readonly int[][] segmentIds = []; private readonly int[][] segmentIds = [];
/// <summary>
/// Stores per-plane transform counts for each forced 64x64 residual region.
/// </summary>
private readonly int[][] transformUnitCount; private readonly int[][] transformUnitCount;
/// <summary>
/// Tracks the first unassigned transform-information index for luma and shared chroma storage.
/// </summary>
private readonly int[] firstTransformOffset = new int[2]; private readonly int[] firstTransformOffset = new int[2];
/// <summary>
/// Tracks the next coefficient slot for each color plane within the current superblock.
/// </summary>
private readonly int[] coefficientIndex = []; private readonly int[] coefficientIndex = [];
/// <summary>
/// Provides allocator and decoder configuration to tile entropy decoding.
/// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// Reconstructs each parsed superblock when pixel decoding is requested; otherwise, tile parsing is metadata-only.
/// </summary>
private readonly IAv1FrameDecoder? frameDecoder; private readonly IAv1FrameDecoder? frameDecoder;
/// <summary>
/// Initializes a new instance of the <see cref="Av1TileReader"/> class for syntax parsing without reconstruction.
/// </summary>
/// <param name="configuration">The decoder configuration.</param>
/// <param name="sequenceHeader">The active AV1 sequence header.</param>
/// <param name="frameHeader">The frame header whose tiles will be parsed.</param>
public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
this.FrameHeader = frameHeader; this.FrameHeader = frameHeader;
this.configuration = configuration; this.configuration = configuration;
this.SequenceHeader = sequenceHeader; this.SequenceHeader = sequenceHeader;
// init_main_frame_ctxt // FrameInfo owns all traversal-order records and coefficient storage produced by the tile readers.
this.FrameInfo = new(this.SequenceHeader); this.FrameInfo = new(this.SequenceHeader);
this.segmentIds = new int[this.FrameHeader.ModeInfoRowCount][]; this.segmentIds = new int[this.FrameHeader.ModeInfoRowCount][];
for (int y = 0; y < this.FrameHeader.ModeInfoRowCount; y++) for (int y = 0; y < this.FrameHeader.ModeInfoRowCount; y++)
@ -49,8 +119,7 @@ internal class Av1TileReader : IAv1TileReader
this.segmentIds[y] = new int[this.FrameHeader.ModeInfoColumnCount]; this.segmentIds[y] = new int[this.FrameHeader.ModeInfoColumnCount];
} }
// reallocate_parse_context_memory // Above contexts span the aligned frame width, while left contexts are reused for each superblock row.
// Hard code number of threads to 1 for now.
int planesCount = sequenceHeader.ColorConfig.PlaneCount; int planesCount = sequenceHeader.ColorConfig.PlaneCount;
int superblockColumnCount = int superblockColumnCount =
Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperblockSizeLog2) >> sequenceHeader.SuperblockSizeLog2; Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperblockSizeLog2) >> sequenceHeader.SuperblockSizeLog2;
@ -65,19 +134,38 @@ internal class Av1TileReader : IAv1TileReader
this.coefficientIndex = new int[Av1Constants.MaxPlanes]; this.coefficientIndex = new int[Av1Constants.MaxPlanes];
} }
/// <summary>
/// Initializes a new instance of the <see cref="Av1TileReader"/> class that reconstructs parsed superblocks.
/// </summary>
/// <param name="configuration">The decoder configuration.</param>
/// <param name="sequenceHeader">The active AV1 sequence header.</param>
/// <param name="frameHeader">The frame header whose tiles will be parsed.</param>
/// <param name="frameDecoder">The frame decoder that reconstructs each parsed superblock.</param>
public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1FrameDecoder frameDecoder) public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1FrameDecoder frameDecoder)
: this(configuration, sequenceHeader, frameHeader) : this(configuration, sequenceHeader, frameHeader)
=> this.frameDecoder = frameDecoder; => this.frameDecoder = frameDecoder;
/// <summary>
/// Gets the frame header whose tile syntax is being parsed.
/// </summary>
public ObuFrameHeader FrameHeader { get; } public ObuFrameHeader FrameHeader { get; }
/// <summary>
/// Gets the sequence header governing the frame.
/// </summary>
public ObuSequenceHeader SequenceHeader { get; } public ObuSequenceHeader SequenceHeader { get; }
/// <summary>
/// Gets the frame-owned mode, transform, coefficient, quantizer, and filter state populated by tile parsing.
/// </summary>
public Av1FrameInfo FrameInfo { get; } public Av1FrameInfo FrameInfo { get; }
/// <summary> /// <summary>
/// SVT: parse_tile /// Parses one tile's partition, mode, transform, coefficient, and filter syntax in superblock order.
/// </summary> /// </summary>
/// <param name="tileData">The entropy-coded tile payload.</param>
/// <param name="tileNum">The zero-based tile index in row-major order.</param>
/// <remarks>Corresponds to <c>parse_tile</c> in SVT-AV1.</remarks>
public void ReadTile(Span<byte> tileData, int tileNum) public void ReadTile(Span<byte> tileData, int tileNum)
{ {
Av1SymbolDecoder reader = new(this.configuration, tileData, this.FrameHeader.QuantizationParameters.BaseQIndex); Av1SymbolDecoder reader = new(this.configuration, tileData, this.FrameHeader.QuantizationParameters.BaseQIndex);
@ -92,7 +180,7 @@ internal class Av1TileReader : IAv1TileReader
this.ClearLoopFilterDelta(); this.ClearLoopFilterDelta();
int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
// Default initialization of Wiener and SGR Filter. // Restoration coefficients are differentially coded, so each tile begins from the AV1 defaults.
this.referenceSgrXqd = new int[planesCount][]; this.referenceSgrXqd = new int[planesCount][];
this.referenceLrWiener = new int[planesCount][][]; this.referenceLrWiener = new int[planesCount][][];
for (int plane = 0; plane < planesCount; plane++) for (int plane = 0; plane < planesCount; plane++)
@ -129,15 +217,24 @@ internal class Av1TileReader : IAv1TileReader
this.ReadLoopRestoration(modeInfoPosition, superBlockSize); this.ReadLoopRestoration(modeInfoPosition, superBlockSize);
this.ParsePartition(ref reader, modeInfoPosition, superBlockSize, superblockInfo, tileInfo); this.ParsePartition(ref reader, modeInfoPosition, superBlockSize, superblockInfo, tileInfo);
// decoding of the superblock // Identify-only parsing omits a frame decoder but still populates the complete syntax model.
this.frameDecoder?.DecodeSuperblock(modeInfoPosition, superblockInfo, tileInfo); this.frameDecoder?.DecodeSuperblock(modeInfoPosition, superblockInfo, tileInfo);
} }
} }
} }
/// <summary>
/// Resets all frame loop-filter delta state before parsing a tile.
/// </summary>
private void ClearLoopFilterDelta() private void ClearLoopFilterDelta()
=> this.FrameInfo.ClearDeltaLoopFilter(); => this.FrameInfo.ClearDeltaLoopFilter();
/// <summary>
/// Reads loop-restoration unit syntax that begins at a superblock location.
/// </summary>
/// <param name="modeInfoLocation">The superblock origin in 4x4 mode-information units.</param>
/// <param name="superBlockSize">The superblock size.</param>
/// <exception cref="NotImplementedException">A color plane signals a loop-restoration filter.</exception>
private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlockSize) private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlockSize)
{ {
int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
@ -145,15 +242,20 @@ internal class Av1TileReader : IAv1TileReader
{ {
if (this.FrameHeader.LoopRestorationParameters.Items[plane].Type != ObuRestorationType.None) if (this.FrameHeader.LoopRestorationParameters.Items[plane].Type != ObuRestorationType.None)
{ {
// TODO: Implement.
throw new NotImplementedException("No loop restoration filter support."); throw new NotImplementedException("No loop restoration filter support.");
} }
} }
} }
/// <summary> /// <summary>
/// 5.11.4. Decode partition syntax. /// Decodes AV1 partition syntax and recursively visits each resulting coding block.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="modeInfoLocation">The parent block origin in 4x4 mode-information units.</param>
/// <param name="blockSize">The parent block size.</param>
/// <param name="superblockInfo">The containing superblock.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <remarks>Implements AV1 section 5.11.4.</remarks>
private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{ {
int columnIndex = modeInfoLocation.X; int columnIndex = modeInfoLocation.X;
@ -189,6 +291,9 @@ internal class Av1TileReader : IAv1TileReader
Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize); Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize);
Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize); Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize);
// Partition syntax is depth-first. The visit order here is also the order in which mode,
// transform, and coefficient records are appended to their frame-owned arrays.
switch (partitionType) switch (partitionType)
{ {
case Av1PartitionType.Split: case Av1PartitionType.Split:
@ -284,6 +389,15 @@ internal class Av1TileReader : IAv1TileReader
this.UpdatePartitionContext(new Point(columnIndex, rowIndex), tileInfo, superblockInfo, subSize, blockSize, partitionType); this.UpdatePartitionContext(new Point(columnIndex, rowIndex), tileInfo, superblockInfo, subSize, blockSize, partitionType);
} }
/// <summary>
/// Parses all syntax associated with one final coding block and stores its frame mode information.
/// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="modeInfoLocation">The block origin in 4x4 mode-information units.</param>
/// <param name="blockSize">The final block size.</param>
/// <param name="superblockInfo">The containing superblock.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <param name="partitionType">The partition type that produced the block.</param>
private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1PartitionType partitionType) private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1PartitionType partitionType)
{ {
int rowIndex = modeInfoLocation.Y; int rowIndex = modeInfoLocation.Y;
@ -328,13 +442,16 @@ internal class Av1TileReader : IAv1TileReader
this.Residual(ref reader, partitionInfo, superblockInfo, tileInfo, blockSize); this.Residual(ref reader, partitionInfo, superblockInfo, tileInfo, blockSize);
// Update the Frame buffer for this ModeInfo. // Store the record only after all syntax has populated it, then map every covered 4x4 position.
this.FrameInfo.UpdateModeInfo(blockModeInfo, superblockInfo); this.FrameInfo.UpdateModeInfo(blockModeInfo, superblockInfo);
} }
/// <summary> /// <summary>
/// SVT: reset_skip_context /// Clears coefficient neighbor contexts across every plane of a skipped block.
/// </summary> /// </summary>
/// <param name="partitionInfo">The skipped block and its frame position.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <remarks>Corresponds to <c>reset_skip_context</c> in SVT-AV1.</remarks>
private void ResetSkipContext(Av1PartitionInfo partitionInfo, Av1TileInfo tileInfo) private void ResetSkipContext(Av1PartitionInfo partitionInfo, Av1TileInfo tileInfo)
{ {
int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
@ -354,9 +471,14 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.34. Residual syntax. /// Parses every luma and chroma transform block and its coefficients for a coding block.
/// </summary> /// </summary>
/// <remarks>SVT: parse_residual</remarks> /// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <param name="superblockInfo">The containing superblock and coefficient storage.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <param name="blockSize">The coding block size.</param>
/// <remarks>Implements AV1 section 5.11.34 and corresponds to <c>parse_residual</c> in SVT-AV1.</remarks>
private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1BlockSize blockSize) private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1BlockSize blockSize)
{ {
int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false); int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false);
@ -378,6 +500,8 @@ internal class Av1TileReader : IAv1TileReader
transformInfoIndices[2] = transformInfoIndices[1] + chromaTransformUnitCount; transformInfoIndices[2] = transformInfoIndices[1] + chromaTransformUnitCount;
int forceSplitCount = 0; int forceSplitCount = 0;
// AV1 forces residual traversal into at most 64x64 regions even when the coding block is larger.
// transformUnitCount preserves the transform geometry generated for each such region and plane.
for (int row = 0; row < maxBlocksHigh; row += modeUnitBlocksHigh) for (int row = 0; row < maxBlocksHigh; row += modeUnitBlocksHigh)
{ {
for (int column = 0; column < maxBlocksWide; column += modeUnitBlocksWide) for (int column = 0; column < maxBlocksWide; column += modeUnitBlocksWide)
@ -397,7 +521,8 @@ internal class Av1TileReader : IAv1TileReader
Span<Av1TransformInfo> transformInfoSpan = (plane == 0) ? superblockInfo.GetTransformInfoY() : superblockInfo.GetTransformInfoUv(); Span<Av1TransformInfo> transformInfoSpan = (plane == 0) ? superblockInfo.GetTransformInfoY() : superblockInfo.GetTransformInfoUv();
if (isLosslessBlock) if (isLosslessBlock)
{ {
// TODO: Implement. // Lossless coding fixes transforms at 4x4, so count each clipped 4x4 unit
// directly after applying the plane's chroma subsampling.
int unitHeight = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksHigh + row, maxBlocksHigh), 0); int unitHeight = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksHigh + row, maxBlocksHigh), 0);
int unitWidth = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksWide + column, maxBlocksWide), 0); int unitWidth = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksWide + column, maxBlocksWide), 0);
DebugGuard.IsTrue(transformInfoSpan[transformInfoIndices[plane]].Size == Av1TransformSize.Size4x4, "Lossless frame shall have transform units of size 4x4."); DebugGuard.IsTrue(transformInfoSpan[transformInfoIndices[plane]].Size == Av1TransformSize.Size4x4, "Lossless frame shall have transform units of size 4x4.");
@ -445,6 +570,8 @@ internal class Av1TileReader : IAv1TileReader
if (endOfBlock != 0) if (endOfBlock != 0)
{ {
// Coefficients are stored as an end index followed by scan-order values, so the
// next transform begins after both the prefix and its decoded coefficient range.
this.coefficientIndex[plane] += endOfBlock + 1; this.coefficientIndex[plane] += endOfBlock + 1;
transformInfo.CodeBlockFlag = true; transformInfo.CodeBlockFlag = true;
} }
@ -462,6 +589,13 @@ internal class Av1TileReader : IAv1TileReader
} }
} }
/// <summary>
/// Determines whether a luma coding block owns chroma mode and residual syntax at its frame position.
/// </summary>
/// <param name="sequenceHeader">The sequence header describing chroma subsampling.</param>
/// <param name="modeInfoLocation">The block origin in 4x4 luma mode-information units.</param>
/// <param name="blockSize">The luma block size.</param>
/// <returns><see langword="true"/> when the block is a chroma reference position; otherwise, <see langword="false"/>.</returns>
public static bool HasChroma(ObuSequenceHeader sequenceHeader, Point modeInfoLocation, Av1BlockSize blockSize) public static bool HasChroma(ObuSequenceHeader sequenceHeader, Point modeInfoLocation, Av1BlockSize blockSize)
{ {
int blockWide = blockSize.Get4x4WideCount(); int blockWide = blockSize.Get4x4WideCount();
@ -474,10 +608,23 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.35. Transform block syntax. /// Derives a transform block's entropy context and decodes its coefficient syntax.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The containing coding block.</param>
/// <param name="coefficientBuffer">The destination beginning at this transform's coefficient slot.</param>
/// <param name="transformInfo">The transform geometry and syntax state to populate.</param>
/// <param name="plane">The zero-based color-plane index.</param>
/// <param name="blockColumn">The transform's horizontal offset within the coding block in 4x4 units.</param>
/// <param name="blockRow">The transform's vertical offset within the coding block in 4x4 units.</param>
/// <param name="startX">The frame-relative transform column in 4x4 units of the target plane.</param>
/// <param name="startY">The frame-relative transform row in 4x4 units of the target plane.</param>
/// <param name="transformSize">The transform size.</param>
/// <param name="subX">A value indicating whether the target plane is horizontally subsampled.</param>
/// <param name="subY">A value indicating whether the target plane is vertically subsampled.</param>
/// <returns>The decoded end-of-block coefficient position, or zero for an all-zero transform.</returns>
/// <remarks> /// <remarks>
/// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification. /// Implements AV1 section 5.11.35 using the traversal shape of the corresponding SVT-AV1 implementation.
/// </remarks> /// </remarks>
private int ParseTransformBlock( private int ParseTransformBlock(
ref Av1SymbolDecoder reader, ref Av1SymbolDecoder reader,
@ -517,10 +664,22 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.39. Coefficients syntax. /// Decodes transform coefficients and updates the coefficient neighbor contexts for one color plane.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The containing coding block.</param>
/// <param name="blockRow">The frame-relative transform row in 4x4 units of the target plane.</param>
/// <param name="blockColumn">The frame-relative transform column in 4x4 units of the target plane.</param>
/// <param name="aboveOffset">The horizontal transform offset within the coding block in 4x4 units.</param>
/// <param name="leftOffset">The vertical transform offset within the coding block in 4x4 units.</param>
/// <param name="plane">The zero-based color-plane index.</param>
/// <param name="transformBlockContext">The coefficient skip and DC-sign entropy contexts.</param>
/// <param name="transformSize">The transform size.</param>
/// <param name="transformInfo">The transform syntax state to populate.</param>
/// <param name="coefficientBuffer">The destination beginning at this transform's coefficient slot.</param>
/// <returns>The decoded end-of-block coefficient position, or zero for an all-zero transform.</returns>
/// <remarks> /// <remarks>
/// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification. /// Implements AV1 section 5.11.39 using the traversal shape of the corresponding SVT-AV1 implementation.
/// </remarks> /// </remarks>
private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int plane, Av1TransformBlockContext transformBlockContext, Av1TransformSize transformSize, Av1TransformInfo transformInfo, Span<int> coefficientBuffer) private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int plane, Av1TransformBlockContext transformBlockContext, Av1TransformSize transformSize, Av1TransformInfo transformInfo, Span<int> coefficientBuffer)
{ {
@ -539,6 +698,17 @@ internal class Av1TileReader : IAv1TileReader
return reader.ReadCoefficients(partitionInfo.ModeInfo, blockPosition, this.aboveNeighborContext.GetContext(plane), this.leftNeighborContext.GetContext(plane), aboveOffset, leftOffset, plane, blocksWide, blocksHigh, transformBlockContext, transformSize, isLossless, this.FrameHeader.UseReducedTransformSet, transformInfo, partitionInfo.ModeBlockToRightEdge, partitionInfo.ModeBlockToBottomEdge, coefficientBuffer); return reader.ReadCoefficients(partitionInfo.ModeInfo, blockPosition, this.aboveNeighborContext.GetContext(plane), this.leftNeighborContext.GetContext(plane), aboveOffset, leftOffset, plane, blocksWide, blocksHigh, transformBlockContext, transformSize, isLossless, this.FrameHeader.UseReducedTransformSet, transformInfo, partitionInfo.ModeBlockToRightEdge, partitionInfo.ModeBlockToBottomEdge, coefficientBuffer);
} }
/// <summary>
/// Derives coefficient skip and DC-sign contexts from the transform block's above and left neighbors.
/// </summary>
/// <param name="transformSize">The transform size.</param>
/// <param name="plane">The zero-based color-plane index.</param>
/// <param name="planeBlockSize">The containing block size on the target plane.</param>
/// <param name="transformBlockUnitHighCount">The transform height clipped to the frame in 4x4 units.</param>
/// <param name="transformBlockUnitWideCount">The transform width clipped to the frame in 4x4 units.</param>
/// <param name="startY">The frame-relative transform row in 4x4 units of the target plane.</param>
/// <param name="startX">The frame-relative transform column in 4x4 units of the target plane.</param>
/// <returns>The derived transform-block entropy contexts.</returns>
private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize transformSize, int plane, Av1BlockSize planeBlockSize, int transformBlockUnitHighCount, int transformBlockUnitWideCount, int startY, int startX) private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize transformSize, int plane, Av1BlockSize planeBlockSize, int transformBlockUnitHighCount, int transformBlockUnitWideCount, int startY, int startX)
{ {
Av1TransformBlockContext transformBlockContext = new(); Av1TransformBlockContext transformBlockContext = new();
@ -548,6 +718,8 @@ internal class Av1TileReader : IAv1TileReader
int k = 0; int k = 0;
int mask = (1 << Av1Constants.CoefficientContextBitCount) - 1; int mask = (1 << Av1Constants.CoefficientContextBitCount) - 1;
// The high bits of each neighbor value encode its DC sign class. Summing both edges maps
// negative, balanced, and positive neighborhoods to the AV1 DC-sign context.
do do
{ {
uint sign = (uint)aboveContext[k] >> Av1Constants.CoefficientContextBitCount; uint sign = (uint)aboveContext[k] >> Av1Constants.CoefficientContextBitCount;
@ -575,6 +747,7 @@ internal class Av1TileReader : IAv1TileReader
} }
else else
{ {
// Luma skip contexts preserve both the weakest neighboring level and whether either edge is stronger.
int top = 0; int top = 0;
int left = 0; int left = 0;
@ -602,6 +775,8 @@ internal class Av1TileReader : IAv1TileReader
} }
else else
{ {
// Chroma needs only the presence of nonzero levels on each edge, plus an offset that
// distinguishes a transform smaller than its containing plane block.
int contextBase = GetEntropyContext(transformSize, aboveContext, leftContext); int contextBase = GetEntropyContext(transformSize, aboveContext, leftContext);
int contextOffset = planeBlockSize.GetPelsLog2Count() > transformSize.ToBlockSize().GetPelsLog2Count() ? 10 : 7; int contextOffset = planeBlockSize.GetPelsLog2Count() > transformSize.ToBlockSize().GetPelsLog2Count() ? 10 : 7;
transformBlockContext.SkipContext = contextBase + contextOffset; transformBlockContext.SkipContext = contextBase + contextOffset;
@ -610,11 +785,20 @@ internal class Av1TileReader : IAv1TileReader
return transformBlockContext; return transformBlockContext;
} }
/// <summary>
/// Determines whether the above and left edges contain nonzero chroma coefficient contexts.
/// </summary>
/// <param name="transformSize">The transform size that selects how many edge entries to inspect.</param>
/// <param name="above">The above coefficient contexts.</param>
/// <param name="left">The left coefficient contexts.</param>
/// <returns>The sum of the nonzero-above and nonzero-left flags.</returns>
private static int GetEntropyContext(Av1TransformSize transformSize, int[] above, int[] left) private static int GetEntropyContext(Av1TransformSize transformSize, int[] above, int[] left)
{ {
bool aboveEntropyContext = false; bool aboveEntropyContext = false;
bool leftEntropyContext = false; bool leftEntropyContext = false;
// The reference implementation tests packed 16, 32, 64, or 128-bit edge groups. Enumerating
// each transform shape keeps those exact edge widths without unaligned native memory reads.
switch (transformSize) switch (transformSize)
{ {
case Av1TransformSize.Size4x4: case Av1TransformSize.Size4x4:
@ -742,8 +926,15 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.15. TX size syntax. /// Selects the transform size for a coding block from lossless, explicit-selection, or maximum-size rules.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <param name="superblockInfo">The containing superblock.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <param name="allowSelect">A value indicating whether transform-size selection syntax is allowed at this node.</param>
/// <returns>The selected transform size.</returns>
/// <remarks>Implements AV1 section 5.11.15.</remarks>
private Av1TransformSize ReadTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, bool allowSelect) private Av1TransformSize ReadTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, bool allowSelect)
{ {
Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo; Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo;
@ -760,6 +951,14 @@ internal class Av1TileReader : IAv1TileReader
return modeInfo.BlockSize.GetMaximumTransformSize(); return modeInfo.BlockSize.GetMaximumTransformSize();
} }
/// <summary>
/// Reads a transform size using the available above and left transform-size contexts.
/// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <param name="superblockInfo">The containing superblock.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <returns>The decoded transform size.</returns>
private Av1TransformSize ReadSelectedTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) private Av1TransformSize ReadSelectedTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{ {
int context = 0; int context = 0;
@ -792,22 +991,34 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// Section 5.11.16. Block TX size syntax. /// Reads a coding block's transform size, updates neighbor contexts, and creates its transform geometry records.
/// </summary> /// </summary>
/// <remarks>SVT: read_block_tx_size</remarks> /// <param name="reader">The tile symbol decoder.</param>
/// <param name="modeInfoLocation">The block origin in 4x4 mode-information units.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <param name="superblockInfo">The containing superblock.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <remarks>Implements AV1 section 5.11.16 and corresponds to <c>read_block_tx_size</c> in SVT-AV1.</remarks>
private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{ {
Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize; Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize;
int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Width = blockSize.Get4x4WideCount();
int block4x4Height = blockSize.Get4x4HighCount(); int block4x4Height = blockSize.Get4x4HighCount();
// First condition in spec is for INTER frames, implemented only the INTRA condition. // HEIF still-image decoding follows the independently decodable intra-frame transform-size branch.
Av1TransformSize transformSize = this.ReadTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo, true); Av1TransformSize transformSize = this.ReadTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo, true);
this.aboveNeighborContext.UpdateTransformation(modeInfoLocation, tileInfo, transformSize, blockSize, false); this.aboveNeighborContext.UpdateTransformation(modeInfoLocation, tileInfo, transformSize, blockSize, false);
this.leftNeighborContext.UpdateTransformation(modeInfoLocation, superblockInfo, transformSize, blockSize, false); this.leftNeighborContext.UpdateTransformation(modeInfoLocation, superblockInfo, transformSize, blockSize, false);
this.UpdateTransformInfo(partitionInfo, superblockInfo, blockSize, transformSize); this.UpdateTransformInfo(partitionInfo, superblockInfo, blockSize, transformSize);
} }
/// <summary>
/// Populates luma and chroma transform-information records in residual traversal order.
/// </summary>
/// <param name="partitionInfo">The current coding block.</param>
/// <param name="superblockInfo">The containing superblock and transform storage.</param>
/// <param name="blockSize">The coding block size.</param>
/// <param name="transformSize">The selected luma transform size.</param>
private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1BlockSize blockSize, Av1TransformSize transformSize) private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1BlockSize blockSize, Av1TransformSize transformSize)
{ {
int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y];
@ -829,6 +1040,8 @@ internal class Av1TileReader : IAv1TileReader
bool isLossLess = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]; bool isLossLess = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId];
Av1TransformSize transformSizeUv = isLossLess ? Av1TransformSize.Size4x4 : blockSize.GetMaxUvTransformSize(subX, subY); Av1TransformSize transformSizeUv = isLossLess ? Av1TransformSize.Size4x4 : blockSize.GetMaxUvTransformSize(subX, subY);
// Residual syntax visits at most 64x64 luma regions. Record transform geometry in the same
// nested region/row/column order so coefficient parsing and reconstruction consume matching spans.
for (int idy = 0; idy < maxBlockHigh; idy += height) for (int idy = 0; idy < maxBlockHigh; idy += height)
{ {
for (int idx = 0; idx < maxBlockWide; idx += width, forceSplitCount++) for (int idx = 0; idx < maxBlockWide; idx += width, forceSplitCount++)
@ -836,7 +1049,7 @@ internal class Av1TileReader : IAv1TileReader
int lumaTransformUnitCount = 0; int lumaTransformUnitCount = 0;
int chromaTransformUnitCount = 0; int chromaTransformUnitCount = 0;
// Update Luminance Transform Info. // Luma transform offsets remain relative to the coding block in 4x4 luma units.
int stepColumn = transformSize.Get4x4WideCount(); int stepColumn = transformSize.Get4x4WideCount();
int stepRow = transformSize.Get4x4HighCount(); int stepRow = transformSize.Get4x4HighCount();
@ -861,7 +1074,7 @@ internal class Av1TileReader : IAv1TileReader
continue; continue;
} }
// Update Chroma Transform Info. // Chroma geometry is rounded to the subsampling grid before stepping its transform size.
stepColumn = transformSizeUv.Get4x4WideCount(); stepColumn = transformSizeUv.Get4x4WideCount();
stepRow = transformSizeUv.Get4x4HighCount(); stepRow = transformSizeUv.Get4x4HighCount();
@ -884,7 +1097,7 @@ internal class Av1TileReader : IAv1TileReader
} }
} }
// Cr Transform Info Update from Cb. // U and V share transform geometry, so append a second copy for V after the complete U sequence.
if (totalChromaTransformUnitCount != 0) if (totalChromaTransformUnitCount != 0)
{ {
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -911,26 +1124,31 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.49. Palette tokens syntax. /// Reads luma and chroma palette-map tokens when a block selects palette prediction.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <exception cref="NotImplementedException">The block selects a nonempty luma or chroma palette.</exception>
/// <remarks>Implements AV1 section 5.11.49.</remarks>
private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) != 0) if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) != 0)
{ {
// TODO: Implement.
throw new NotImplementedException(); throw new NotImplementedException();
} }
if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0) if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0)
{ {
// TODO: Implement.
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
/// <summary> /// <summary>
/// 5.11.6. Mode info syntax. /// Reads the prediction, segmentation, skip, quantizer, and filter mode information for a still-image block.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <remarks>Implements the intra-frame branch of AV1 section 5.11.6.</remarks>
private void ReadModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private void ReadModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
DebugGuard.IsTrue(this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported."); DebugGuard.IsTrue(this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported.");
@ -938,8 +1156,11 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.7. Intra frame mode info syntax. /// Reads all intra-frame mode syntax for a coding block in bitstream order.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block and its neighbors.</param>
/// <remarks>Implements AV1 section 5.11.7.</remarks>
private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
if (this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) if (this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip)
@ -947,7 +1168,6 @@ internal class Av1TileReader : IAv1TileReader
this.IntraSegmentId(ref reader, partitionInfo); this.IntraSegmentId(ref reader, partitionInfo);
} }
// this.skipMode = false;
partitionInfo.ModeInfo.Skip = this.ReadSkip(ref reader, partitionInfo); partitionInfo.ModeInfo.Skip = this.ReadSkip(ref reader, partitionInfo);
if (!this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) if (!this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip)
{ {
@ -962,8 +1182,9 @@ internal class Av1TileReader : IAv1TileReader
this.ReadDeltaLoopFilter(ref reader, partitionInfo); this.ReadDeltaLoopFilter(ref reader, partitionInfo);
} }
partitionInfo.ReferenceFrame[0] = 0; // IntraFrame; // Independently decodable still-image blocks reference only the current intra frame.
partitionInfo.ReferenceFrame[1] = -1; // None; partitionInfo.ReferenceFrame[0] = 0;
partitionInfo.ReferenceFrame[1] = -1;
partitionInfo.ModeInfo.SetPaletteSizes(0, 0); partitionInfo.ModeInfo.SetPaletteSizes(0, 0);
bool useIntraBlockCopy = false; bool useIntraBlockCopy = false;
if (this.AllowIntraBlockCopy()) if (this.AllowIntraBlockCopy())
@ -978,10 +1199,8 @@ internal class Av1TileReader : IAv1TileReader
} }
else else
{ {
// this.IsInter = false;
partitionInfo.ModeInfo.YMode = reader.ReadYMode(partitionInfo.AboveModeInfo, partitionInfo.LeftModeInfo); partitionInfo.ModeInfo.YMode = reader.ReadYMode(partitionInfo.AboveModeInfo, partitionInfo.LeftModeInfo);
// 5.11.42.Intra angle info luma syntax.
partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Y] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.YMode, partitionInfo.ModeInfo.BlockSize); partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Y] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.YMode, partitionInfo.ModeInfo.BlockSize);
if (partitionInfo.IsChroma && !this.SequenceHeader.ColorConfig.IsMonochrome) if (partitionInfo.IsChroma && !this.SequenceHeader.ColorConfig.IsMonochrome)
{ {
@ -991,7 +1210,6 @@ internal class Av1TileReader : IAv1TileReader
ReadChromaFromLumaAlphas(ref reader, partitionInfo.ModeInfo); ReadChromaFromLumaAlphas(ref reader, partitionInfo.ModeInfo);
} }
// 5.11.43.Intra angle info chroma syntax.
partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Uv] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.UvMode, partitionInfo.ModeInfo.BlockSize); partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Uv] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.UvMode, partitionInfo.ModeInfo.BlockSize);
} }
else else
@ -1011,27 +1229,41 @@ internal class Av1TileReader : IAv1TileReader
} }
} }
/// <summary>
/// Determines whether the frame header permits intra block copy for an intra still image.
/// </summary>
/// <returns><see langword="true"/> when the frame and sequence enable intra block copy; otherwise, <see langword="false"/>.</returns>
private bool AllowIntraBlockCopy() private bool AllowIntraBlockCopy()
=> (this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) && => (this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) &&
(this.SequenceHeader.ForceScreenContentTools > 0) && (this.SequenceHeader.ForceScreenContentTools > 0) &&
this.FrameHeader.AllowIntraBlockCopy; this.FrameHeader.AllowIntraBlockCopy;
/// <summary>
/// Determines whether chroma-from-luma prediction is available for a coding block.
/// </summary>
/// <param name="partitionInfo">The current coding block.</param>
/// <returns><see langword="true"/> when the lossless transform or block dimensions permit chroma-from-luma prediction; otherwise, <see langword="false"/>.</returns>
private bool IsChromaForLumaAllowed(Av1PartitionInfo partitionInfo) private bool IsChromaForLumaAllowed(Av1PartitionInfo partitionInfo)
{ {
if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]) if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId])
{ {
// In lossless, CfL is available when the partition size is equal to the // Lossless mode fixes transforms at 4x4, so CfL is available only when the subsampled
// transform size. // plane block is itself 4x4 and therefore has no smaller transform partition.
bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subX = this.SequenceHeader.ColorConfig.SubSamplingX;
bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; bool subY = this.SequenceHeader.ColorConfig.SubSamplingY;
Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
return planeBlockSize == Av1BlockSize.Block4x4; return planeBlockSize == Av1BlockSize.Block4x4;
} }
// Spec: CfL is available to luma partitions lesser than or equal to 32x32 // Outside lossless mode, AV1 limits CfL to luma blocks no larger than 32x32.
return partitionInfo.ModeInfo.BlockSize.GetWidth() <= 32 && partitionInfo.ModeInfo.BlockSize.GetHeight() <= 32; return partitionInfo.ModeInfo.BlockSize.GetWidth() <= 32 && partitionInfo.ModeInfo.BlockSize.GetHeight() <= 32;
} }
/// <summary>
/// Reads filter-intra selection for an eligible DC-predicted luma block.
/// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false; partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false;
@ -1050,16 +1282,21 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.46. Palette mode info syntax. /// Reads palette size and color syntax for an eligible screen-content block.
/// </summary> /// </summary>
private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => /// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
// TODO: Implement. /// <exception cref="NotImplementedException">Palette-mode syntax is not implemented.</exception>
throw new NotImplementedException(); /// <remarks>Implements AV1 section 5.11.46.</remarks>
private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
=> throw new NotImplementedException();
/// <summary> /// <summary>
/// 5.11.45. Read CFL alphas syntax. /// Reads the joint signs and nonzero alpha magnitudes for chroma-from-luma prediction.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="modeInfo">The block mode information to populate.</param>
/// <remarks>Implements AV1 section 5.11.45.</remarks>
private static void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1BlockModeInfo modeInfo) private static void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1BlockModeInfo modeInfo)
{ {
int jointSignPlus1 = reader.ReadChromFromLumaSign() + 1; int jointSignPlus1 = reader.ReadChromFromLumaSign() + 1;
@ -1079,8 +1316,13 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.42. and 5.11.43. /// Reads a directional intra-prediction angle adjustment when the block and mode permit one.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="mode">The selected luma or chroma prediction mode.</param>
/// <param name="blockSize">The block size.</param>
/// <returns>The signed angle adjustment.</returns>
/// <remarks>Implements AV1 sections 5.11.42 and 5.11.43.</remarks>
private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode mode, Av1BlockSize blockSize) private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode mode, Av1BlockSize blockSize)
{ {
int angleDelta = 0; int angleDelta = 0;
@ -1093,12 +1335,20 @@ internal class Av1TileReader : IAv1TileReader
return angleDelta; return angleDelta;
} }
/// <summary>
/// Determines whether a prediction mode belongs to the AV1 directional-mode range.
/// </summary>
/// <param name="mode">The prediction mode.</param>
/// <returns><see langword="true"/> for a directional mode; otherwise, <see langword="false"/>.</returns>
private static bool IsDirectionalMode(Av1PredictionMode mode) private static bool IsDirectionalMode(Av1PredictionMode mode)
=> mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
/// <summary> /// <summary>
/// 5.11.8. Intra segment ID syntax. /// Reads or inherits a segment identifier and writes it over every 4x4 position covered by the block.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <remarks>Implements AV1 section 5.11.8.</remarks>
private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
if (this.FrameHeader.SegmentationParameters.Enabled) if (this.FrameHeader.SegmentationParameters.Enabled)
@ -1111,6 +1361,8 @@ internal class Av1TileReader : IAv1TileReader
int modeInfoCountX = Math.Min(this.FrameHeader.ModeInfoColumnCount - partitionInfo.ColumnIndex, blockWidth4x4); int modeInfoCountX = Math.Min(this.FrameHeader.ModeInfoColumnCount - partitionInfo.ColumnIndex, blockWidth4x4);
int modeInfoCountY = Math.Min(this.FrameHeader.ModeInfoRowCount - partitionInfo.RowIndex, blockHeight4x4); int modeInfoCountY = Math.Min(this.FrameHeader.ModeInfoRowCount - partitionInfo.RowIndex, blockHeight4x4);
int segmentId = partitionInfo.ModeInfo.SegmentId; int segmentId = partitionInfo.ModeInfo.SegmentId;
// Later blocks predict from 4x4 positions, so replicate one block ID over its clipped frame coverage.
for (int y = 0; y < modeInfoCountY; y++) for (int y = 0; y < modeInfoCountY; y++)
{ {
int[] segmentRow = this.segmentIds[partitionInfo.RowIndex + y]; int[] segmentRow = this.segmentIds[partitionInfo.RowIndex + y];
@ -1122,8 +1374,11 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.9. Read segment ID syntax. /// Predicts and, when required, decodes the segment identifier for an intra block.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block and its available neighbors.</param>
/// <remarks>Implements AV1 section 5.11.9.</remarks>
private void ReadSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private void ReadSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
int predictor; int predictor;
@ -1166,7 +1421,9 @@ internal class Av1TileReader : IAv1TileReader
} }
else else
{ {
int ctx = prevUL < 0 ? 0 /* Edge cases */ // Any unavailable neighbor selects the edge context; otherwise, agreement among two
// or three neighbors increases the specificity of the segment-ID distribution.
int ctx = prevUL < 0 ? 0
: prevUL == prevU && prevUL == prevL ? 2 : prevUL == prevU && prevUL == prevL ? 2
: prevUL == prevU || prevUL == prevL || prevU == prevL ? 1 : 0; : prevUL == prevU || prevUL == prevL || prevU == prevL ? 1 : 0;
int lastActiveSegmentId = this.FrameHeader.SegmentationParameters.LastActiveSegmentId; int lastActiveSegmentId = this.FrameHeader.SegmentationParameters.LastActiveSegmentId;
@ -1175,9 +1432,11 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// 5.11.56. Read CDEF syntax. /// Reads the constrained directional enhancement filter strength for the block's 64x64 filter unit.
/// </summary> /// </summary>
/// <remarks>SVT: read_cdef</remarks> /// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <remarks>Implements AV1 section 5.11.56 and corresponds to <c>read_cdef</c> in SVT-AV1.</remarks>
private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
if (partitionInfo.ModeInfo.Skip || this.FrameHeader.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameHeader.AllowIntraBlockCopy) if (partitionInfo.ModeInfo.Skip || this.FrameHeader.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameHeader.AllowIntraBlockCopy)
@ -1194,7 +1453,8 @@ internal class Av1TileReader : IAv1TileReader
int cdfStrength = reader.ReadCdfStrength(this.FrameHeader.CdefParameters.BitCount); int cdfStrength = reader.ReadCdfStrength(this.FrameHeader.CdefParameters.BitCount);
partitionInfo.CdefStrength[index] = cdfStrength; partitionInfo.CdefStrength[index] = cdfStrength;
// Populate to nearby 64x64s if needed based on h4 & w4 // A block in a 128x128 superblock can cover multiple 64x64 CDEF units. Replicate the
// first decoded strength so subsequent blocks in every covered unit observe it as assigned.
if (this.SequenceHeader.SuperblockSize == Av1BlockSize.Block128x128) if (this.SequenceHeader.SuperblockSize == Av1BlockSize.Block128x128)
{ {
int w4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); int w4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount();
@ -1210,6 +1470,11 @@ internal class Av1TileReader : IAv1TileReader
} }
} }
/// <summary>
/// Reads and accumulates the loop-filter delta values carried by a coding block.
/// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block and superblock delta storage.</param>
private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
@ -1237,6 +1502,12 @@ internal class Av1TileReader : IAv1TileReader
} }
} }
/// <summary>
/// Reads or infers the residual-skip flag for a coding block.
/// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block and its available neighbors.</param>
/// <returns><see langword="true"/> when the block omits residual coefficients; otherwise, <see langword="false"/>.</returns>
private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
int segmentId = partitionInfo.ModeInfo.SegmentId; int segmentId = partitionInfo.ModeInfo.SegmentId;
@ -1254,8 +1525,11 @@ internal class Av1TileReader : IAv1TileReader
} }
/// <summary> /// <summary>
/// SVT: read_delta_qindex /// Reads and accumulates a superblock quantizer-index delta when the block carries one.
/// </summary> /// </summary>
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block and superblock quantizer storage.</param>
/// <remarks>Corresponds to <c>read_delta_qindex</c> in SVT-AV1.</remarks>
private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
{ {
Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
@ -1274,28 +1548,30 @@ internal class Av1TileReader : IAv1TileReader
} }
} }
/// <summary>
/// Determines whether a frame-relative mode-information position lies inside the active tile.
/// </summary>
/// <param name="rowIndex">The frame-relative mode-information row.</param>
/// <param name="columnIndex">The frame-relative mode-information column.</param>
/// <returns><see langword="true"/> when the position lies within the active tile; otherwise, <see langword="false"/>.</returns>
private bool IsInside(int rowIndex, int columnIndex) => private bool IsInside(int rowIndex, int columnIndex) =>
columnIndex >= this.FrameHeader.TilesInfo.TileColumnCount && columnIndex >= this.FrameHeader.TilesInfo.TileColumnCount &&
columnIndex < this.FrameHeader.TilesInfo.TileColumnCount && columnIndex < this.FrameHeader.TilesInfo.TileColumnCount &&
rowIndex >= this.FrameHeader.TilesInfo.TileRowCount && rowIndex >= this.FrameHeader.TilesInfo.TileRowCount &&
rowIndex < this.FrameHeader.TilesInfo.TileRowCount; rowIndex < this.FrameHeader.TilesInfo.TileRowCount;
/*
private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY)
{
int block4x4Width = blockMode.BlockSize.Get4x4WideCount();
int block4x4Height = blockMode.BlockSize.Get4x4HighCount();
bool xPos = (columnIndex & 0x1) > 0 || (block4x4Width & 0x1) > 0 || !subSamplingX;
bool yPos = (rowIndex & 0x1) > 0 || (block4x4Height & 0x1) > 0 || !subSamplingY;
return xPos && yPos;
}*/
/// <summary> /// <summary>
/// SVT: partition_plane_context /// Derives the partition entropy context from the current split bit of the above and left neighbors.
/// </summary> /// </summary>
/// <param name="location">The partition origin in 4x4 mode-information units.</param>
/// <param name="blockSize">The square parent block size.</param>
/// <param name="tileInfo">The active tile boundaries.</param>
/// <param name="superblockInfo">The containing superblock.</param>
/// <returns>The partition entropy context.</returns>
/// <remarks>Corresponds to <c>partition_plane_context</c> in SVT-AV1.</remarks>
private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileInfo, Av1SuperblockInfo superblockInfo) private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileInfo, Av1SuperblockInfo superblockInfo)
{ {
// Maximum partition point is 8x8. Offset the log value occordingly. // The five stored split bits begin at the 8x8 partition point, so normalize the block-size log to that bit index.
int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileInfo.ModeInfoColumnStart]; int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileInfo.ModeInfoColumnStart];
int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockInfo.ModeInfoPosition.Y) & Av1PartitionContext.Mask]; int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockInfo.ModeInfoPosition.Y) & Av1PartitionContext.Mask];
int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2();
@ -1306,6 +1582,15 @@ internal class Av1TileReader : IAv1TileReader
return ((left << 1) + above) + (blockSizeLog * Av1Constants.PartitionProbabilitySet); return ((left << 1) + above) + (blockSizeLog * Av1Constants.PartitionProbabilitySet);
} }
/// <summary>
/// Publishes the decoded partition sizes to the above and left neighbor contexts.
/// </summary>
/// <param name="modeInfoLocation">The parent block origin in 4x4 mode-information units.</param>
/// <param name="tileLoc">The active tile boundaries.</param>
/// <param name="superblockInfo">The containing superblock.</param>
/// <param name="subSize">The primary size produced by the partition.</param>
/// <param name="blockSize">The parent block size.</param>
/// <param name="partition">The decoded partition type.</param>
private void UpdatePartitionContext(Point modeInfoLocation, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition) private void UpdatePartitionContext(Point modeInfoLocation, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition)
{ {
if (blockSize >= Av1BlockSize.Block8x8) if (blockSize >= Av1BlockSize.Block8x8)

313
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs

@ -10,11 +10,18 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Writes the partition, mode, transform, and coefficient syntax for one AV1 tile.
/// </summary>
internal partial class Av1TileWriter internal partial class Av1TileWriter
{ {
// Generates 5 bit field in which each bit set to 1 represents /// <summary>
// a BlockSize partition 11111 means we split 128x128, 64x64, 32x32, 16x16 /// Maps each AV1 block size to the five-bit partition contexts written to its bottom and right edges.
// and 8x8. 10000 means we just split the 128x128 to 64x64 /// </summary>
/// <remarks>
/// Each set bit represents a split level from 128x128 through 8x8. For example, <c>11111</c>
/// records every split level, while <c>10000</c> records only the 128x128 split.
/// </remarks>
private static readonly Av1PartitionContext[] PartitionContextLookup = private static readonly Av1PartitionContext[] PartitionContextLookup =
[ [
new(31, 31), // 4X4 - {0b11111, 0b11111} new(31, 31), // 4X4 - {0b11111, 0b11111}
@ -41,11 +48,21 @@ internal partial class Av1TileWriter
new(16, 28), // 64X16 - {0b10000, 0b11100} new(16, 28), // 64X16 - {0b10000, 0b11100}
]; ];
/// <summary>
/// Maps neighboring intra prediction modes to the key-frame luma-mode entropy contexts.
/// </summary>
private static readonly byte[] IntraModeContextLookup = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; private static readonly byte[] IntraModeContextLookup = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0];
/// <summary> /// <summary>
/// SVT: svt_aom_write_sb /// Writes the partition tree and each final coding block for a superblock.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="ec_ctx">The entropy-coding position state for the superblock.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="superblock">The encoder decisions for the superblock.</param>
/// <param name="frameBuffer">The transformed coefficients for the frame.</param>
/// <param name="tileIndex">The zero-based tile index.</param>
/// <remarks>Corresponds to <c>svt_aom_write_sb</c> in SVT-AV1.</remarks>
public static void WriteSuperblock( public static void WriteSuperblock(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext ec_ctx, Av1EntropyCodingContext ec_ctx,
@ -57,7 +74,8 @@ internal partial class Av1TileWriter
Av1SequenceControlSet scs = pcs.Sequence; Av1SequenceControlSet scs = pcs.Sequence;
Av1NeighborArrayUnit<Av1PartitionContext> partitionContextNeighbors = pcs.PartitionContexts[tileIndex]; Av1NeighborArrayUnit<Av1PartitionContext> partitionContextNeighbors = pcs.PartitionContexts[tileIndex];
// CU Varaiables // The geometry scan includes both partition nodes and final coding blocks. These two indices
// advance independently because a split node consumes geometry without consuming FinalBlocks.
int blockIndex = 0; int blockIndex = 0;
uint finalBlockIndex = 0; uint finalBlockIndex = 0;
@ -67,7 +85,7 @@ internal partial class Av1TileWriter
bool check_blk_out_of_bound = !sb_geom.IsComplete; bool check_blk_out_of_bound = !sb_geom.IsComplete;
do do
{ {
bool code_blk_cond = true; // Code cu only if it is inside the picture bool code_blk_cond = true;
Av1EncoderBlockStruct blk_ptr = superblock.FinalBlocks[finalBlockIndex]; Av1EncoderBlockStruct blk_ptr = superblock.FinalBlocks[finalBlockIndex];
Av1BlockGeometry blk_geom = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blockIndex); Av1BlockGeometry blk_geom = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blockIndex);
@ -75,9 +93,10 @@ internal partial class Av1TileWriter
Point blockOrigin = blk_geom.Origin; Point blockOrigin = blk_geom.Origin;
Guard.IsTrue(bsize < Av1BlockSize.AllSizes, nameof(bsize), "Block size must be a valid value."); Guard.IsTrue(bsize < Av1BlockSize.AllSizes, nameof(bsize), "Block size must be a valid value.");
// assert(blk_geom->shape == PART_N);
if (check_blk_out_of_bound) if (check_blk_out_of_bound)
{ {
// Edge superblocks retain their complete geometry tree, but only nodes whose center or
// origin reaches the visible frame can contribute coding syntax.
code_blk_cond = (((blockOrigin.X + (blk_geom.BlockWidth / 2)) < pcs.Parent.AlignedWidth) || code_blk_cond = (((blockOrigin.X + (blk_geom.BlockWidth / 2)) < pcs.Parent.AlignedWidth) ||
((blockOrigin.Y + (blk_geom.BlockHeight / 2)) < pcs.Parent.AlignedHeight)) && ((blockOrigin.Y + (blk_geom.BlockHeight / 2)) < pcs.Parent.AlignedHeight)) &&
(blockOrigin.X < pcs.Parent.AlignedWidth && blockOrigin.Y < pcs.Parent.AlignedHeight); (blockOrigin.X < pcs.Parent.AlignedWidth && blockOrigin.Y < pcs.Parent.AlignedHeight);
@ -126,7 +145,7 @@ internal partial class Av1TileWriter
}*/ }*/
} }
// Code Split Flag // Blocks below 8x8 cannot be partition points in the AV1 syntax.
EncodePartition( EncodePartition(
pcs, pcs,
ref writer, ref writer,
@ -136,7 +155,6 @@ internal partial class Av1TileWriter
partitionContextNeighbors); partitionContextNeighbors);
} }
// assert(blk_geom.Shape == PART_N);
Guard.IsTrue(Av1Math.Implies(bsize == Av1BlockSize.Block4x4, superblock.CodingUnitPartitionTypes[blockIndex] == Av1PartitionType.None), nameof(bsize), string.Empty); Guard.IsTrue(Av1Math.Implies(bsize == Av1BlockSize.Block4x4, superblock.CodingUnitPartitionTypes[blockIndex] == Av1PartitionType.None), nameof(bsize), string.Empty);
switch (superblock.CodingUnitPartitionTypes[blockIndex]) switch (superblock.CodingUnitPartitionTypes[blockIndex])
{ {
@ -281,8 +299,15 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: encode_partition_av1 /// Writes a partition symbol using the above and left partition contexts available at a block origin.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="blockSize">The square parent block size.</param>
/// <param name="partitionType">The selected partition type.</param>
/// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="partition_context_na">The partition neighbor arrays for the tile.</param>
/// <remarks>Corresponds to <c>encode_partition_av1</c> in SVT-AV1.</remarks>
private static void EncodePartition( private static void EncodePartition(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
ref Av1SymbolEncoder writer, ref Av1SymbolEncoder writer,
@ -322,6 +347,7 @@ internal partial class Av1TileWriter
Guard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), nameof(blockSize), "Blocks need to be square."); Guard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), nameof(blockSize), "Blocks need to be square.");
Guard.IsTrue(blockSizeLog2 >= 0, nameof(blockSizeLog2), "bsl needs to be a positive integer."); Guard.IsTrue(blockSizeLog2 >= 0, nameof(blockSizeLog2), "bsl needs to be a positive integer.");
// Each square block-size level owns four contexts selected by the current split bit of its neighbors.
context_index = ((left * 2) + above) + (blockSizeLog2 * Av1Constants.PartitionProbabilitySet); context_index = ((left * 2) + above) + (blockSizeLog2 * Av1Constants.PartitionProbabilitySet);
if (!has_rows && !has_cols) if (!has_rows && !has_cols)
@ -347,8 +373,16 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: write_modes_b /// Writes the segmentation, prediction, transform, coefficient, and filter syntax for one final coding block.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="entropyCodingContext">The entropy-coding position state for the superblock.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="tb_ptr">The containing superblock.</param>
/// <param name="blk_ptr">The final encoder decisions for the block.</param>
/// <param name="tile_idx">The zero-based tile index.</param>
/// <param name="coeff_ptr">The transformed coefficients for the frame.</param>
/// <remarks>Corresponds to <c>write_modes_b</c> in SVT-AV1.</remarks>
private static void WriteModesBlock( private static void WriteModesBlock(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext, Av1EntropyCodingContext entropyCodingContext,
@ -402,8 +436,6 @@ internal partial class Av1TileWriter
blk_ptr.MacroBlock.LeftMacroBlock = null; blk_ptr.MacroBlock.LeftMacroBlock = null;
} }
// Not required, part of Av1SymbolEncoder.
// blk_ptr.MacroBlock.tile_ctx = frame_context;
SetModeInfoRowAndColumn( SetModeInfoRowAndColumn(
pcs, pcs,
blk_ptr.MacroBlock, blk_ptr.MacroBlock,
@ -414,10 +446,8 @@ internal partial class Av1TileWriter
pcs.Parent.Common.ModeInfoRowCount, pcs.Parent.Common.ModeInfoRowCount,
pcs.Parent.Common.ModeInfoColumnCount); pcs.Parent.Common.ModeInfoColumnCount);
// if (pcs.slice_type == I_SLICE) // This encoder path currently writes intra frames only, so every block follows the key-frame mode syntax.
// We implement only INTRA frames.
{ {
// const int32_t skip = write_skip(cm, xd, mbmi->segment_id, mi, w)
if (pcs.Parent.FrameHeader.SegmentationParameters.Enabled && pcs.Parent.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) if (pcs.Parent.FrameHeader.SegmentationParameters.Enabled && pcs.Parent.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip)
{ {
WriteSegmentId(pcs, ref writer, blockGeometry.BlockSize, blockOrigin, blk_ptr, skipWritingCoefficients); WriteSegmentId(pcs, ref writer, blockGeometry.BlockSize, blockOrigin, blk_ptr, skipWritingCoefficients);
@ -548,7 +578,6 @@ internal partial class Av1TileWriter
if (!skipWritingCoefficients) if (!skipWritingCoefficients)
{ {
// SVT: av1_encode_coeff_1d
EncodeCoefficients1d( EncodeCoefficients1d(
pcs, pcs,
entropyCodingContext, entropyCodingContext,
@ -565,7 +594,7 @@ internal partial class Av1TileWriter
} }
} }
// Update the neighbors // Neighbor state must be updated after all symbols for the block have used the preceding contexts.
UpdateNeighbors(pcs, entropyCodingContext, blockOrigin, blk_ptr, tile_idx, blockSize); UpdateNeighbors(pcs, entropyCodingContext, blockOrigin, blk_ptr, tile_idx, blockSize);
if (IsPaletteAllowed(pcs.Parent.PaletteLevel, blockGeometry.BlockSize)) if (IsPaletteAllowed(pcs.Parent.PaletteLevel, blockGeometry.BlockSize))
@ -579,6 +608,16 @@ internal partial class Av1TileWriter
} }
} }
/// <summary>
/// Writes the chroma intra mode, chroma-from-luma alpha values, and directional angle adjustment for a block.
/// </summary>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="macroBlockModeInfo">The selected block modes.</param>
/// <param name="blk_ptr">The encoder prediction-unit state.</param>
/// <param name="blockSize">The luma block size.</param>
/// <param name="lumaMode">The selected luma prediction mode.</param>
/// <param name="chromaMode">The selected chroma prediction mode.</param>
/// <param name="isChromaFromLumaAllowed">A value indicating whether chroma-from-luma mode is available.</param>
private static void EncodeIntraChromaMode( private static void EncodeIntraChromaMode(
ref Av1SymbolEncoder writer, ref Av1SymbolEncoder writer,
Av1MacroBlockModeInfo macroBlockModeInfo, Av1MacroBlockModeInfo macroBlockModeInfo,
@ -604,26 +643,24 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// Get the contexts (left and top) for writing the intra luma mode for key frames. /// Gets the above and left key-frame contexts used to write an intra luma mode.
/// Intended to be used for key frame only.
/// </summary> /// </summary>
/// <remarks>SVT: svt_aom_get_kf_y_mode_ctx</remarks> /// <param name="xd">The current macroblock and its mapped neighbors.</param>
/// <param name="above_ctx">The context derived from the above luma mode.</param>
/// <param name="left_ctx">The context derived from the left luma mode.</param>
/// <remarks>Corresponds to <c>svt_aom_get_kf_y_mode_ctx</c> in SVT-AV1.</remarks>
private static void GetYModeContext(Av1MacroBlockD xd, out byte above_ctx, out byte left_ctx) private static void GetYModeContext(Av1MacroBlockD xd, out byte above_ctx, out byte left_ctx)
{ {
Av1PredictionMode intraLumaLeftMode = Av1PredictionMode.DC; Av1PredictionMode intraLumaLeftMode = Av1PredictionMode.DC;
Av1PredictionMode intraLumaTopMode = Av1PredictionMode.DC; Av1PredictionMode intraLumaTopMode = Av1PredictionMode.DC;
if (xd.IsLeftAvailable) if (xd.IsLeftAvailable)
{ {
// When called for key frame, neighbouring mode should be intra // Key-frame neighbors are intra blocks, so their luma modes directly select the context class.
// assert(!is_inter_block(&xd->mi[-1]->mbmi.block_mi) || is_intrabc_block(&xd->mi[-1]->mbmi.block_mi));
intraLumaLeftMode = xd.ModeInfo[-1].MacroBlockModeInfo.Block.Mode; intraLumaLeftMode = xd.ModeInfo[-1].MacroBlockModeInfo.Block.Mode;
} }
if (xd.IsUpAvailable) if (xd.IsUpAvailable)
{ {
// When called for key frame, neighbouring mode should be intra
// assert(!is_inter_block(&xd->mi[-xd->mi_stride]->mbmi.block_mi) ||
// is_intrabc_block(&xd->mi[-xd->mi_stride]->mbmi.block_mi));
intraLumaTopMode = xd.ModeInfo[-xd.ModeInfoStride].MacroBlockModeInfo.Block.Mode; intraLumaTopMode = xd.ModeInfo[-xd.ModeInfoStride].MacroBlockModeInfo.Block.Mode;
} }
@ -632,8 +669,14 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: encode_intra_luma_mode_kf_av1 /// Writes the key-frame luma prediction mode and any directional angle adjustment.
/// </summary> /// </summary>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="macroBlockModeInfo">The selected block modes.</param>
/// <param name="blk_ptr">The encoder prediction-unit state.</param>
/// <param name="blockSize">The block size.</param>
/// <param name="lumaMode">The selected luma prediction mode.</param>
/// <remarks>Corresponds to <c>encode_intra_luma_mode_kf_av1</c> in SVT-AV1.</remarks>
private static void EncodeIntraLumaMode( private static void EncodeIntraLumaMode(
ref Av1SymbolEncoder writer, ref Av1SymbolEncoder writer,
Av1MacroBlockModeInfo macroBlockModeInfo, Av1MacroBlockModeInfo macroBlockModeInfo,
@ -650,6 +693,16 @@ internal partial class Av1TileWriter
} }
} }
/// <summary>
/// Writes luma and chroma palette-mode syntax for a block.
/// </summary>
/// <param name="scs">The sequence coding state.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="macroBlockModeInfo">The selected block modes.</param>
/// <param name="blk_ptr">The encoder block state.</param>
/// <param name="blockSize">The block size.</param>
/// <param name="point">The block position in mode-information units.</param>
/// <exception cref="NotImplementedException">Palette-mode encoding is not implemented.</exception>
private static void WritePaletteModeInfo( private static void WritePaletteModeInfo(
Av1SequenceControlSet scs, Av1SequenceControlSet scs,
ref Av1SymbolEncoder writer, ref Av1SymbolEncoder writer,
@ -687,8 +740,14 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: svt_aom_filter_intra_allowed /// Determines whether filter-intra syntax is available for a block mode.
/// </summary> /// </summary>
/// <param name="enableFilterIntra">A value indicating whether the sequence enables filter-intra prediction.</param>
/// <param name="blockSize">The block size.</param>
/// <param name="paletteSize">The selected luma palette size.</param>
/// <param name="mode">The selected luma prediction mode.</param>
/// <returns><see langword="true"/> when the block can use filter-intra prediction; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_filter_intra_allowed</c> in SVT-AV1.</remarks>
private static bool IsFilterIntraAllowed( private static bool IsFilterIntraAllowed(
bool enableFilterIntra, bool enableFilterIntra,
Av1BlockSize blockSize, Av1BlockSize blockSize,
@ -697,8 +756,12 @@ internal partial class Av1TileWriter
=> mode == Av1PredictionMode.DC && paletteSize == 0 && IsFilterIntraAllowedBlockSize(enableFilterIntra, blockSize); => mode == Av1PredictionMode.DC && paletteSize == 0 && IsFilterIntraAllowedBlockSize(enableFilterIntra, blockSize);
/// <summary> /// <summary>
/// SVT: svt_aom_filter_intra_allowed_bsize /// Determines whether filter-intra prediction is enabled for a block size.
/// </summary> /// </summary>
/// <param name="enableFilterIntra">A value indicating whether the sequence enables filter-intra prediction.</param>
/// <param name="blockSize">The block size.</param>
/// <returns><see langword="true"/> when filter-intra prediction supports the block dimensions; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_filter_intra_allowed_bsize</c> in SVT-AV1.</remarks>
private static bool IsFilterIntraAllowedBlockSize(bool enableFilterIntra, Av1BlockSize blockSize) private static bool IsFilterIntraAllowedBlockSize(bool enableFilterIntra, Av1BlockSize blockSize)
{ {
if (!enableFilterIntra) if (!enableFilterIntra)
@ -710,8 +773,13 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: write_intrabc_info /// Writes the intra-block-copy selection and displacement-vector syntax for a block.
/// </summary> /// </summary>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="macroBlockModeInfo">The selected block modes.</param>
/// <param name="block">The encoder block state.</param>
/// <exception cref="NotImplementedException">The displacement-vector syntax is not implemented when intra block copy is selected.</exception>
/// <remarks>Corresponds to <c>write_intrabc_info</c> in SVT-AV1.</remarks>
private static void WriteIntraBlockCopyInfo( private static void WriteIntraBlockCopyInfo(
ref Av1SymbolEncoder writer, ref Av1SymbolEncoder writer,
Av1MacroBlockModeInfo macroBlockModeInfo, Av1MacroBlockModeInfo macroBlockModeInfo,
@ -734,14 +802,24 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: svt_aom_allow_intrabc /// Determines whether the current frame permits intra block copy.
/// </summary> /// </summary>
/// <param name="frameHeader">The current frame header.</param>
/// <returns><see langword="true"/> when both screen-content tools and intra block copy are enabled; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_allow_intrabc</c> in SVT-AV1.</remarks>
private static bool IsIntraBlockCopyAllowed(ObuFrameHeader frameHeader) private static bool IsIntraBlockCopyAllowed(ObuFrameHeader frameHeader)
=> frameHeader.AllowScreenContentTools && frameHeader.AllowIntraBlockCopy; => frameHeader.AllowScreenContentTools && frameHeader.AllowIntraBlockCopy;
/// <summary> /// <summary>
/// SVT: ec_update_neighbors /// Updates partition and coefficient neighbor arrays after writing a block.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="entropyCodingContext">The entropy-coding position state for the superblock.</param>
/// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="blk_ptr">The encoder block state.</param>
/// <param name="tile_idx">The zero-based tile index.</param>
/// <param name="blockSize">The block size.</param>
/// <remarks>Corresponds to <c>ec_update_neighbors</c> in SVT-AV1.</remarks>
private static void UpdateNeighbors( private static void UpdateNeighbors(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext, Av1EntropyCodingContext entropyCodingContext,
@ -758,7 +836,7 @@ internal partial class Av1TileWriter
Av1MacroBlockModeInfo mbmi = pcs.GetMacroBlockModeInfo(blockOrigin); Av1MacroBlockModeInfo mbmi = pcs.GetMacroBlockModeInfo(blockOrigin);
bool skip_coeff = mbmi.Block.Skip; bool skip_coeff = mbmi.Block.Skip;
// Update the Leaf Depth Neighbor Array // Store the block-size split mask across the edges that future partition symbols can observe.
Av1PartitionContext partition = new( Av1PartitionContext partition = new(
PartitionContextLookup[(int)blockSize].Above, PartitionContextLookup[(int)blockSize].Above,
PartitionContextLookup[(int)blockSize].Left); PartitionContextLookup[(int)blockSize].Left);
@ -771,6 +849,8 @@ internal partial class Av1TileWriter
Av1NeighborArrayUnit<Av1PartitionContext>.UnitMask.Left | Av1NeighborArrayUnit<Av1PartitionContext>.UnitMask.Top); Av1NeighborArrayUnit<Av1PartitionContext>.UnitMask.Left | Av1NeighborArrayUnit<Av1PartitionContext>.UnitMask.Top);
if (skip_coeff) if (skip_coeff)
{ {
// A skipped block has an all-zero residual, so publish a zero sign/level context over its edges
// and advance coefficient positions without reading transform units.
byte dcSignLevelCoefficient = 0; byte dcSignLevelCoefficient = 0;
Span<byte> dcSignSpan = new(ref dcSignLevelCoefficient); Span<byte> dcSignSpan = new(ref dcSignLevelCoefficient);
@ -800,8 +880,12 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: svt_av1_allow_palette /// Determines whether the encoder palette level and block dimensions permit palette mode.
/// </summary> /// </summary>
/// <param name="allowPalette">The nonzero encoder palette level.</param>
/// <param name="blockSize">The block size.</param>
/// <returns><see langword="true"/> when palette mode is enabled for the block; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_av1_allow_palette</c> in SVT-AV1.</remarks>
private static bool IsPaletteAllowed(int allowPalette, Av1BlockSize blockSize) private static bool IsPaletteAllowed(int allowPalette, Av1BlockSize blockSize)
{ {
Guard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize)); Guard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize));
@ -812,8 +896,12 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: svt_aom_allow_palette /// Determines whether screen-content tools and block dimensions permit palette mode.
/// </summary> /// </summary>
/// <param name="allowScreenContentTools">A value indicating whether screen-content tools are enabled.</param>
/// <param name="blockSize">The block size.</param>
/// <returns><see langword="true"/> when palette mode is available for the block; otherwise, <see langword="false"/>.</returns>
/// <remarks>Corresponds to <c>svt_aom_allow_palette</c> in SVT-AV1.</remarks>
private static bool IsPaletteAllowed(bool allowScreenContentTools, Av1BlockSize blockSize) private static bool IsPaletteAllowed(bool allowScreenContentTools, Av1BlockSize blockSize)
=> allowScreenContentTools && => allowScreenContentTools &&
blockSize.GetWidth() <= 64 && blockSize.GetWidth() <= 64 &&
@ -821,8 +909,15 @@ internal partial class Av1TileWriter
blockSize >= Av1BlockSize.Block8x8; blockSize >= Av1BlockSize.Block8x8;
/// <summary> /// <summary>
/// SVT: write_cdef /// Writes the constrained directional enhancement filter strength at its first coded block in a filter unit.
/// </summary> /// </summary>
/// <param name="scs">The sequence coding state.</param>
/// <param name="pcs">The picture coding state.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="tileIndex">The zero-based tile index.</param>
/// <param name="skip">A value indicating whether the current block omits residual coefficients.</param>
/// <param name="modeInfoPosition">The block position in 4x4 mode-information units.</param>
/// <remarks>Corresponds to <c>write_cdef</c> in SVT-AV1.</remarks>
private static void WriteCdef( private static void WriteCdef(
Av1SequenceControlSet scs, Av1SequenceControlSet scs,
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
@ -836,7 +931,7 @@ internal partial class Av1TileWriter
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy) if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy)
{ {
// Initialize to indicate no CDEF for safety. // Lossless and intra-block-copy frames disable CDEF, so normalize the header to its single zero-strength form.
frameHeader.CdefParameters.BitCount = 0; frameHeader.CdefParameters.BitCount = 0;
frameHeader.CdefParameters.YStrength[0] = 0; frameHeader.CdefParameters.YStrength[0] = 0;
frameHeader.CdefParameters.UvStrength[0] = 0; frameHeader.CdefParameters.UvStrength[0] = 0;
@ -849,18 +944,17 @@ internal partial class Av1TileWriter
// cm->mi_grid_visible[(mi_row & m) * cm->mi_stride + (mi_col & m)]; // cm->mi_grid_visible[(mi_row & m) * cm->mi_stride + (mi_col & m)];
Av1ModeInfo mi = pcs.GetFromModeInfoGrid(modeInfoPosition)[0]; Av1ModeInfo mi = pcs.GetFromModeInfoGrid(modeInfoPosition)[0];
// Initialise when at top left part of the superblock // Each superblock begins with all contained 64x64 filter units unassigned.
if ((modeInfoPosition.Y & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0 && if ((modeInfoPosition.Y & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0 &&
(modeInfoPosition.X & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0) (modeInfoPosition.X & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0)
{ {
// Top left?
pcs.CdefPreset[tileIndex][0] = -1; pcs.CdefPreset[tileIndex][0] = -1;
pcs.CdefPreset[tileIndex][1] = -1; pcs.CdefPreset[tileIndex][1] = -1;
pcs.CdefPreset[tileIndex][2] = -1; pcs.CdefPreset[tileIndex][2] = -1;
pcs.CdefPreset[tileIndex][3] = -1; pcs.CdefPreset[tileIndex][3] = -1;
} }
// Emit CDEF param at first non-skip coding block // The strength is coded once, at the first non-skipped block in each 64x64 CDEF filter unit.
int mask = 1 << (6 - Av1Constants.ModeInfoSizeLog2); int mask = 1 << (6 - Av1Constants.ModeInfoSizeLog2);
int index = scs.SequenceHeader.Use128x128Superblock ? Math.Max(1, modeInfoPosition.X & mask) + (2 * Math.Max(1, modeInfoPosition.Y & mask)) : 0; int index = scs.SequenceHeader.Use128x128Superblock ? Math.Max(1, modeInfoPosition.X & mask) + (2 * Math.Max(1, modeInfoPosition.Y & mask)) : 0;
@ -872,8 +966,17 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: set_mi_row_col /// Populates a macroblock's frame edges, tile-neighbor availability, and rectangular-partition context.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="macroBlock">The macroblock state to populate.</param>
/// <param name="tile">The active tile boundaries.</param>
/// <param name="modeInfoPosition">The block position in 4x4 mode-information units.</param>
/// <param name="blockSize">The block size.</param>
/// <param name="modeInfoStride">The row stride of the mode-information grid.</param>
/// <param name="modeInfoRowCount">The coded frame height in mode-information rows.</param>
/// <param name="modeInfoColumnCount">The coded frame width in mode-information columns.</param>
/// <remarks>Corresponds to <c>set_mi_row_col</c> in SVT-AV1.</remarks>
private static void SetModeInfoRowAndColumn( private static void SetModeInfoRowAndColumn(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
@ -891,7 +994,7 @@ internal partial class Av1TileWriter
macroBlock.ModeInfoStride = modeInfoStride; macroBlock.ModeInfoStride = modeInfoStride;
// Are edges available for intra prediction? // Prediction cannot cross tile boundaries even when frame mode information exists there.
macroBlock.IsUpAvailable = modeInfoPosition.Y > tile.ModeInfoRowStart; macroBlock.IsUpAvailable = modeInfoPosition.Y > tile.ModeInfoRowStart;
macroBlock.IsLeftAvailable = modeInfoPosition.X > tile.ModeInfoColumnStart; macroBlock.IsLeftAvailable = modeInfoPosition.X > tile.ModeInfoColumnStart;
macroBlock.ModeInfo = pcs.GetFromModeInfoGrid(modeInfoPosition); macroBlock.ModeInfo = pcs.GetFromModeInfoGrid(modeInfoPosition);
@ -918,9 +1021,8 @@ internal partial class Av1TileWriter
macroBlock.IsSecondRectangle = false; macroBlock.IsSecondRectangle = false;
if (macroBlock.N8Size.Width < macroBlock.N8Size.Height) if (macroBlock.N8Size.Width < macroBlock.N8Size.Height)
{ {
// Only mark is_sec_rect as 1 for the last block. // Only the last sub-block of a rectangular partition selects the secondary transform context.
// For PARTITION_VERT_4, it would be (0, 0, 0, 1); // Vertical-four therefore maps to (0, 0, 0, 1), while two-way partitions map to (0, 1).
// For other partitions, it would be (0, 1).
if (((modeInfoPosition.X + macroBlock.N8Size.Width) & (macroBlock.N8Size.Height - 1)) == 0) if (((modeInfoPosition.X + macroBlock.N8Size.Width) & (macroBlock.N8Size.Height - 1)) == 0)
{ {
macroBlock.IsSecondRectangle = true; macroBlock.IsSecondRectangle = true;
@ -937,8 +1039,22 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: av1_encode_coeff_1d /// Writes luma and chroma transform coefficients for a block in plane order.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="ec_ctx">The entropy-coding position state for the superblock.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="mbmi">The selected macroblock modes.</param>
/// <param name="blk_ptr">The encoder block state.</param>
/// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="intraLumaDir">The luma prediction direction.</param>
/// <param name="planeBlockSize">The luma block size.</param>
/// <param name="coeff_ptr">The transformed coefficients for the frame.</param>
/// <param name="luma_dc_sign_level_coeff_na">The luma coefficient neighbor contexts.</param>
/// <param name="cr_dc_sign_level_coeff_na">The red-difference chroma coefficient neighbor contexts.</param>
/// <param name="cb_dc_sign_level_coeff_na">The blue-difference chroma coefficient neighbor contexts.</param>
/// <exception cref="NotImplementedException">The transform-depth path required by the block is not implemented.</exception>
/// <remarks>Corresponds to <c>av1_encode_coeff_1d</c> in SVT-AV1.</remarks>
private static void EncodeCoefficients1d( private static void EncodeCoefficients1d(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext ec_ctx, Av1EntropyCodingContext ec_ctx,
@ -987,8 +1103,19 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: av1_encode_tx_coef_y /// Writes each luma transform block and updates its DC-sign and coefficient-level neighbor contexts.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="entropyCodingContext">The entropy-coding position state for the superblock.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="mbmi">The selected macroblock modes.</param>
/// <param name="blk_ptr">The encoder block state.</param>
/// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="intraLumaDir">The luma prediction direction.</param>
/// <param name="plane_bsize">The luma block size.</param>
/// <param name="coeff_ptr">The transformed coefficients for the frame.</param>
/// <param name="luma_dc_sign_level_coeff_na">The luma coefficient neighbor contexts.</param>
/// <remarks>Corresponds to <c>av1_encode_tx_coef_y</c> in SVT-AV1.</remarks>
public static void EncodeTransformCoefficientsY( public static void EncodeTransformCoefficientsY(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext, Av1EntropyCodingContext entropyCodingContext,
@ -1001,7 +1128,7 @@ internal partial class Av1TileWriter
Av1FrameBuffer<int> coeff_ptr, Av1FrameBuffer<int> coeff_ptr,
Av1NeighborArrayUnit<byte> luma_dc_sign_level_coeff_na) Av1NeighborArrayUnit<byte> luma_dc_sign_level_coeff_na)
{ {
// Removed any code related to INTER frames. // This writer currently emits intra frames, so coefficient contexts use only intra prediction state.
Av1BlockGeometry blockGeometry = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blk_ptr.ModeDecisionScanIndex); Av1BlockGeometry blockGeometry = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blk_ptr.ModeDecisionScanIndex);
int tx_depth = mbmi.Block.TransformDepth; int tx_depth = mbmi.Block.TransformDepth;
int txb_count = blockGeometry.TransformBlockCount[mbmi.Block.TransformDepth]; int txb_count = blockGeometry.TransformBlockCount[mbmi.Block.TransformDepth];
@ -1031,7 +1158,7 @@ internal partial class Av1TileWriter
int eob = blk_ptr.TransformBlocks[txb_itr].NzCoefficientCount[0]; int eob = blk_ptr.TransformBlocks[txb_itr].NzCoefficientCount[0];
if (eob == 0) if (eob == 0)
{ {
// INTRA // AV1 requires the canonical DCT_DCT transform type when a transform block has no coefficients.
tx_type = blk_ptr.TransformBlocks[txb_itr].TransformType[(int)Av1PlaneType.Y] = Av1TransformType.DctDct; tx_type = blk_ptr.TransformBlocks[txb_itr].TransformType[(int)Av1PlaneType.Y] = Av1TransformType.DctDct;
Guard.IsTrue(tx_type == Av1TransformType.DctDct, nameof(tx_type), string.Empty); Guard.IsTrue(tx_type == Av1TransformType.DctDct, nameof(tx_type), string.Empty);
} }
@ -1047,7 +1174,8 @@ internal partial class Av1TileWriter
frameHeader.UseReducedTransformSet, frameHeader.UseReducedTransformSet,
blk_ptr.FilterIntraMode); blk_ptr.FilterIntraMode);
// Update the luma Dc Sign Level Coeff Neighbor Array // WriteCoefficients packs the DC sign and cumulative level into one integer; publish its bytes
// across the transform edges so the next blocks derive identical entropy contexts.
Span<int> culLevelSpan = new(ref cul_level_y); Span<int> culLevelSpan = new(ref cul_level_y);
ReadOnlySpan<byte> dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelSpan); ReadOnlySpan<byte> dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelSpan);
@ -1064,8 +1192,20 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: av1_encode_tx_coef_uv /// Writes both chroma transform blocks and updates their DC-sign and coefficient-level neighbor contexts.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="entropyCodingContext">The entropy-coding position state for the superblock.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="mbmi">The selected macroblock modes.</param>
/// <param name="blk_ptr">The encoder block state.</param>
/// <param name="blockOrigin">The luma block origin in samples.</param>
/// <param name="intraLumaDir">The luma prediction direction used by coefficient contexts.</param>
/// <param name="plane_bsize">The luma block size.</param>
/// <param name="coeff_ptr">The transformed coefficients for the frame.</param>
/// <param name="cr_dc_sign_level_coeff_na">The red-difference chroma coefficient neighbor contexts.</param>
/// <param name="cb_dc_sign_level_coeff_na">The blue-difference chroma coefficient neighbor contexts.</param>
/// <remarks>Corresponds to <c>av1_encode_tx_coef_uv</c> in SVT-AV1.</remarks>
private static void EncodeTransformCoefficientsUv( private static void EncodeTransformCoefficientsUv(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext, Av1EntropyCodingContext entropyCodingContext,
@ -1098,7 +1238,7 @@ internal partial class Av1TileWriter
if (blockGeometry.HasUv) if (blockGeometry.HasUv)
{ {
// cb // Both chroma planes share transform geometry but retain independent coefficient contexts.
Span<int> coeff_buffer = coeff_ptr.BufferCb!.DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv); Span<int> coeff_buffer = coeff_ptr.BufferCb!.DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv);
Av1TransformBlockContext blockContext = new(); Av1TransformBlockContext blockContext = new();
Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][tx_index]; Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][tx_index];
@ -1123,7 +1263,6 @@ internal partial class Av1TileWriter
frameHeader.UseReducedTransformSet, frameHeader.UseReducedTransformSet,
blk_ptr.FilterIntraMode); blk_ptr.FilterIntraMode);
// cr
coeff_buffer = coeff_ptr.BufferCr!.DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv); coeff_buffer = coeff_ptr.BufferCr!.DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv);
blockContext = new(); blockContext = new();
int endOfBlockCr = blk_ptr.TransformBlocks[tx_index].NzCoefficientCount[2]; int endOfBlockCr = blk_ptr.TransformBlocks[tx_index].NzCoefficientCount[2];
@ -1148,7 +1287,7 @@ internal partial class Av1TileWriter
frameHeader.UseReducedTransformSet, frameHeader.UseReducedTransformSet,
blk_ptr.FilterIntraMode); blk_ptr.FilterIntraMode);
// Update the cb Dc Sign Level Coeff Neighbor Array // Publish each plane's packed sign/level summary across its transform edges.
Span<int> culLevelCbSpan = new(ref cul_level_cb); Span<int> culLevelCbSpan = new(ref cul_level_cb);
ReadOnlySpan<byte> dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelCbSpan); ReadOnlySpan<byte> dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelCbSpan);
cb_dc_sign_level_coeff_na.UnitModeWrite( cb_dc_sign_level_coeff_na.UnitModeWrite(
@ -1157,7 +1296,6 @@ internal partial class Av1TileWriter
new Size(transformWidth, transformHeight), new Size(transformWidth, transformHeight),
Av1NeighborArrayUnit<byte>.UnitMask.Top | Av1NeighborArrayUnit<byte>.UnitMask.Left); Av1NeighborArrayUnit<byte>.UnitMask.Top | Av1NeighborArrayUnit<byte>.UnitMask.Left);
// Update the cr DC Sign Level Coeff Neighbor Array
Span<int> culLevelCrSpan = new(ref cul_level_cr); Span<int> culLevelCrSpan = new(ref cul_level_cr);
dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelCrSpan); dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelCrSpan);
cr_dc_sign_level_coeff_na.UnitModeWrite( cr_dc_sign_level_coeff_na.UnitModeWrite(
@ -1171,11 +1309,24 @@ internal partial class Av1TileWriter
} }
} }
/// <summary>
/// Rounds a luma sample position down to the 8-sample alignment used before chroma subsampling.
/// </summary>
/// <param name="point">The luma sample position.</param>
/// <returns>The aligned luma position.</returns>
private static Point RoundUv(Point point) => (point >> 3) << 3; private static Point RoundUv(Point point) => (point >> 3) << 3;
/// <summary> /// <summary>
/// SVT: svt_aom_get_txb_ctx /// Derives coefficient skip and DC-sign contexts from the transform block's above and left neighbors.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="plane">The luma or chroma component class.</param>
/// <param name="dcSignLevelCoefficientNeighborArray">The packed DC-sign and coefficient-level neighbor contexts.</param>
/// <param name="blockOrigin">The transform-block origin in samples of the target plane.</param>
/// <param name="planeBlockSize">The containing block size on the target plane.</param>
/// <param name="transformSize">The transform size.</param>
/// <param name="blockContext">The context object to populate.</param>
/// <remarks>Corresponds to <c>svt_aom_get_txb_ctx</c> in SVT-AV1.</remarks>
private static void GetTransformBlockContexts( private static void GetTransformBlockContexts(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1ComponentType plane, Av1ComponentType plane,
@ -1207,6 +1358,8 @@ internal partial class Av1TileWriter
byte sign; byte sign;
// The high bits encode the DC sign class: zero, negative, or positive. Summing classes over
// both edges selects whether neighboring DC coefficients bias the current sign context.
if (dcSignLevelCoefficientNeighborArray.Top[dcSignLevelCoefficientTopNeighborIndex] != Av1NeighborArrayUnit<byte>.InvalidNeighborData) if (dcSignLevelCoefficientNeighborArray.Top[dcSignLevelCoefficientTopNeighborIndex] != Av1NeighborArrayUnit<byte>.InvalidNeighborData)
{ {
do do
@ -1253,6 +1406,7 @@ internal partial class Av1TileWriter
} }
else else
{ {
// Luma skip contexts depend on the minimum and union of the clipped edge levels.
byte[][] skip_contexts = [ byte[][] skip_contexts = [
[1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6] [1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]
]; ];
@ -1292,6 +1446,8 @@ internal partial class Av1TileWriter
} }
else else
{ {
// Chroma contexts use only the presence of nonzero levels on each edge, plus an offset
// that distinguishes a transform smaller than its containing plane block.
short ctx_base_left = 0; short ctx_base_left = 0;
short ctx_base_top = 0; short ctx_base_top = 0;
@ -1324,6 +1480,15 @@ internal partial class Av1TileWriter
} }
} }
/// <summary>
/// Writes or predicts a block segment identifier and updates the frame segmentation map.
/// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="blockSize">The block size.</param>
/// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="block">The encoder block state.</param>
/// <param name="skip">A value indicating whether residual coefficients are omitted.</param>
private static void WriteSegmentId(Av1PictureControlSet pcs, ref Av1SymbolEncoder writer, Av1BlockSize blockSize, Point blockOrigin, Av1EncoderBlockStruct block, bool skip) private static void WriteSegmentId(Av1PictureControlSet pcs, ref Av1SymbolEncoder writer, Av1BlockSize blockSize, Point blockOrigin, Av1EncoderBlockStruct block, bool skip)
{ {
ObuSegmentationParameters segmentation_params = pcs.Parent.FrameHeader.SegmentationParameters; ObuSegmentationParameters segmentation_params = pcs.Parent.FrameHeader.SegmentationParameters;
@ -1335,6 +1500,7 @@ internal partial class Av1TileWriter
int spatial_pred = GetSpatialSegmentationPrediction(pcs, block.MacroBlock, blockOrigin, out int cdf_num); int spatial_pred = GetSpatialSegmentationPrediction(pcs, block.MacroBlock, blockOrigin, out int cdf_num);
if (skip) if (skip)
{ {
// With segment-id-before-skip syntax, a skipped block inherits the spatial predictor without coding a residual ID.
pcs.UpdateSegmentation(blockSize, blockOrigin, spatial_pred); pcs.UpdateSegmentation(blockSize, blockOrigin, spatial_pred);
block.SegmentId = spatial_pred; block.SegmentId = spatial_pred;
return; return;
@ -1346,17 +1512,24 @@ internal partial class Av1TileWriter
} }
/// <summary> /// <summary>
/// SVT: svt_av1_get_spatial_seg_prediction /// Derives a segment identifier predictor and entropy context from the upper-left, above, and left neighbors.
/// </summary> /// </summary>
/// <param name="pcs">The picture coding state.</param>
/// <param name="xd">The current macroblock and its neighbor availability.</param>
/// <param name="blockOrigin">The block origin in samples.</param>
/// <param name="cdf_index">The entropy context selected by matching neighbor identifiers.</param>
/// <returns>The spatially predicted segment identifier.</returns>
/// <remarks>Corresponds to <c>svt_av1_get_spatial_seg_prediction</c> in SVT-AV1.</remarks>
private static int GetSpatialSegmentationPrediction( private static int GetSpatialSegmentationPrediction(
Av1PictureControlSet pcs, Av1PictureControlSet pcs,
Av1MacroBlockD xd, Av1MacroBlockD xd,
Point blockOrigin, Point blockOrigin,
out int cdf_index) out int cdf_index)
{ {
int prev_ul = -1; // top left segment_id const int unavailableSegmentId = -1;
int prev_l = -1; // left segment_id int prev_ul = unavailableSegmentId;
int prev_u = -1; // top segment_id int prev_l = unavailableSegmentId;
int prev_u = unavailableSegmentId;
int mi_col = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2; int mi_col = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2;
int mi_row = blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2; int mi_row = blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2;
@ -1380,8 +1553,8 @@ internal partial class Av1TileWriter
prev_l = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_row - 0, mi_col - 1)); prev_l = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_row - 0, mi_col - 1));
} }
// Pick CDF index based on number of matching/out-of-bounds segment IDs. // The entropy context records whether zero, two, or all three neighboring IDs agree.
// Edge case // Any unavailable neighbor falls back to the least-specific context.
if (prev_ul < 0 || prev_u < 0 || prev_l < 0) if (prev_ul < 0 || prev_u < 0 || prev_l < 0)
{ {
cdf_index = 0; cdf_index = 0;
@ -1399,15 +1572,13 @@ internal partial class Av1TileWriter
cdf_index = 0; cdf_index = 0;
} }
// If 2 or more are identical returns that as predictor, otherwise prev_l. // Select the majority value when possible; otherwise AV1 gives the left neighbor precedence.
// edge case if (prev_u == unavailableSegmentId)
if (prev_u == -1)
{ {
return prev_l == -1 ? 0 : prev_l; return prev_l == unavailableSegmentId ? 0 : prev_l;
} }
// edge case if (prev_l == unavailableSegmentId)
if (prev_l == -1)
{ {
return prev_u; return prev_u;
} }
@ -1415,6 +1586,12 @@ internal partial class Av1TileWriter
return (prev_ul == prev_u) ? prev_u : prev_l; return (prev_ul == prev_u) ? prev_u : prev_l;
} }
/// <summary>
/// Writes the block skip flag using the sum of available above and left skip states as its context.
/// </summary>
/// <param name="writer">The tile symbol encoder.</param>
/// <param name="block">The encoder block state.</param>
/// <param name="skip">The skip value to write.</param>
internal static void EncodeSkipCoefficients(ref Av1SymbolEncoder writer, Av1EncoderBlockStruct block, bool skip) internal static void EncodeSkipCoefficients(ref Av1SymbolEncoder writer, Av1EncoderBlockStruct block, bool skip)
{ {
Av1MacroBlockModeInfo? above_mi = block.MacroBlock.AboveMacroBlock; Av1MacroBlockModeInfo? above_mi = block.MacroBlock.AboveMacroBlock;

9
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs

@ -3,9 +3,18 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Carries the neighboring coefficient contexts used to entropy-code an AV1 transform block.
/// </summary>
internal class Av1TransformBlockContext internal class Av1TransformBlockContext
{ {
/// <summary>
/// Gets or sets the context used to decode the sign of the DC coefficient.
/// </summary>
public int DcSignContext { get; set; } public int DcSignContext { get; set; }
/// <summary>
/// Gets or sets the neighboring transform-block skip context.
/// </summary>
public int SkipContext { get; set; } public int SkipContext { get; set; }
} }

21
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs

@ -6,12 +6,12 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary> /// <summary>
/// Information of a single Transform Block. /// Describes the size, position, type, and residual state of one AV1 transform block.
/// </summary> /// </summary>
internal class Av1TransformInfo internal class Av1TransformInfo
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Av1TransformInfo"/> class. /// Initializes a new instance of the <see cref="Av1TransformInfo"/> class with a 4x4 transform at the origin.
/// </summary> /// </summary>
public Av1TransformInfo() public Av1TransformInfo()
: this(Av1TransformSize.Size4x4, 0, 0) : this(Av1TransformSize.Size4x4, 0, 0)
@ -21,6 +21,9 @@ internal class Av1TransformInfo
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Av1TransformInfo"/> class. /// Initializes a new instance of the <see cref="Av1TransformInfo"/> class.
/// </summary> /// </summary>
/// <param name="size">The transform size.</param>
/// <param name="offsetX">The horizontal offset in mode-information units.</param>
/// <param name="offsetY">The vertical offset in mode-information units.</param>
public Av1TransformInfo(Av1TransformSize size, int offsetX, int offsetY) public Av1TransformInfo(Av1TransformSize size, int offsetX, int offsetY)
{ {
this.Size = size; this.Size = size;
@ -40,35 +43,35 @@ internal class Av1TransformInfo
} }
/// <summary> /// <summary>
/// Gets or sets the transform size to be used for this Transform Block. /// Gets or sets the transform size used for this transform block.
/// </summary> /// </summary>
public Av1TransformSize Size { get; internal set; } public Av1TransformSize Size { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the transform type to be used for this Transform Block. /// Gets or sets the transform type used for this transform block.
/// </summary> /// </summary>
public Av1TransformType Type { get; internal set; } public Av1TransformType Type { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the X offset of this block in ModeInfo units. /// Gets or sets the horizontal offset of this block in mode-information units.
/// </summary> /// </summary>
public int OffsetX { get; internal set; } public int OffsetX { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the Y offset of this block in ModeInfo units. /// Gets or sets the vertical offset of this block in mode-information units.
/// </summary> /// </summary>
public int OffsetY { get; internal set; } public int OffsetY { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the Code block flag is set. /// Gets or sets a value indicating whether the transform block contains a coded residual.
/// <list type="table"> /// <list type="table">
/// <item> /// <item>
/// <term>false</term> /// <term>false</term>
/// <description>No residual for the block</description> /// <description>The block has no residual.</description>
/// </item> /// </item>
/// <item> /// <item>
/// <term>true</term> /// <term>true</term>
/// <description>Residual exists for the block</description> /// <description>The block has a residual.</description>
/// </item> /// </item>
/// </list> /// </list>
/// </summary> /// </summary>

9
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs

@ -5,9 +5,18 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores the transform syntax and coefficient range for one AV1 transform unit.
/// </summary>
internal class Av1TransformUnit internal class Av1TransformUnit
{ {
/// <summary>
/// Gets the nonzero-coefficient count for each color plane.
/// </summary>
public ushort[] NzCoefficientCount { get; } = new ushort[3]; public ushort[] NzCoefficientCount { get; } = new ushort[3];
/// <summary>
/// Gets the transform type selected for each color plane.
/// </summary>
public Av1TransformType[] TransformType { get; } = new Av1TransformType[Av1Constants.PlaneTypeCount]; public Av1TransformType[] TransformType { get; } = new Av1TransformType[Av1Constants.PlaneTypeCount];
} }

Loading…
Cancel
Save