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;
/// <summary>
/// Stores block-size, prediction-mode, transform, and palette decisions shared by AV1 block processing.
/// </summary>
internal class Av1BlockModeInfo
{
/// <summary>
/// Stores the palette size for luma and for the shared chroma mode.
/// </summary>
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)
{
this.BlockSize = blockSize;
@ -20,6 +32,9 @@ internal class Av1BlockModeInfo
this.TransformUnitsCount = new int[numPlanes - 1];
}
/// <summary>
/// Gets the decoded block size.
/// </summary>
public Av1BlockSize BlockSize { get; }
/// <summary>
@ -27,12 +42,24 @@ internal class Av1BlockModeInfo
/// </summary>
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; }
/// <summary>
/// Gets or sets the partition type that produced the block.
/// </summary>
public Av1PartitionType PartitionType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether compound skip mode is selected.
/// </summary>
public bool SkipMode { get; set; }
/// <summary>
/// Gets or sets the segmentation identifier assigned to the block.
/// </summary>
public int SegmentId { get; set; }
/// <summary>
@ -40,31 +67,64 @@ internal class Av1BlockModeInfo
/// </summary>
public Av1PredictionMode UvMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether intra block copy is selected.
/// </summary>
public bool UseUltraBlockCopy { get; set; }
/// <summary>
/// Gets or sets the packed chroma-from-luma alpha magnitude indices.
/// </summary>
public int ChromaFromLumaAlphaIndex { get; set; }
/// <summary>
/// Gets or sets the joint chroma-from-luma alpha sign value.
/// </summary>
public int ChromaFromLumaAlphaSign { get; set; }
/// <summary>
/// Gets or sets the directional prediction angle adjustments for the chroma planes.
/// </summary>
public int[] AngleDelta { get; set; }
/// <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>
public Point PositionInSuperblock { get; }
/// <summary>
/// Gets or sets the filter-intra syntax for the block.
/// </summary>
public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; }
/// <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>
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; }
/// <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)];
/// <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];
/// <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];
}

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

@ -3,12 +3,38 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Identifies the luma or chroma component class used by AV1 entropy contexts.
/// </summary>
internal enum Av1ComponentType
{
Luminance = 0, // luma
Chroma = 1, // chroma (Cb+Cr)
ChromaCb = 2, // chroma Cb
ChromaCr = 3, // chroma Cr
All = 4, // Y+Cb+Cr
/// <summary>
/// The luma component.
/// </summary>
Luminance = 0,
/// <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
}

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;
/// <summary>
/// Stores encoder-selected prediction, transform, skip, and palette state for one block.
/// </summary>
internal class Av1EncoderBlockModeInfo
{
/// <summary>
/// Gets the encoded block size.
/// </summary>
public Av1BlockSize BlockSize { get; }
/// <summary>
/// Gets the selected luma prediction mode.
/// </summary>
public Av1PredictionMode PredictionMode { get; }
/// <summary>
/// Gets the partition type that produced the block.
/// </summary>
public Av1PartitionType PartitionType { get; }
/// <summary>
/// Gets the selected chroma prediction mode.
/// </summary>
public Av1PredictionMode UvPredictionMode { get; }
/// <summary>
/// Gets a value indicating whether residual coefficients are omitted for the block.
/// </summary>
public bool Skip { get; } = true;
/// <summary>
/// Gets a value indicating whether compound skip mode is selected.
/// </summary>
public bool SkipMode { get; } = true;
/// <summary>
/// Gets a value indicating whether intra block copy is selected.
/// </summary>
public bool UseIntraBlockCopy { get; } = true;
/// <summary>
/// Gets the segmentation identifier assigned to the block.
/// </summary>
public int SegmentId { get; }
/// <summary>
/// Gets or sets the transform-tree depth selected for the block.
/// </summary>
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; }
/// <summary>
/// Gets or sets the chroma prediction mode written for the block.
/// </summary>
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;
/// <summary>
/// Stores encoder block geometry and its selected coding-mode information.
/// </summary>
internal class Av1EncoderBlockStruct
{
/// <summary>
/// Gets the transform-unit state in transform traversal order.
/// </summary>
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; }
/// <summary>
/// Gets or sets the index used to resolve the block geometry from mode-decision scan order.
/// </summary>
public int ModeDecisionScanIndex { get; set; }
/// <summary>
/// Gets or sets the quantizer index used for the block.
/// </summary>
public int QuantizationIndex { get; set; }
/// <summary>
/// Gets or sets the segmentation identifier assigned to the block.
/// </summary>
public int SegmentId { get; set; }
/// <summary>
/// Gets or sets the filter-intra mode selected for the block.
/// </summary>
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; }
/// <summary>
/// Gets or sets the encoder prediction-unit state for the block.
/// </summary>
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;
/// <summary>
/// Holds sequence, frame, and macroblock state shared across AV1 encoder stages.
/// </summary>
internal class Av1EncoderCommon
{
/// <summary>
/// Gets or sets the frame height in 4x4 mode-information units.
/// </summary>
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; }
/// <summary>
/// Gets or sets the row stride of frame mode information in 4x4 units.
/// </summary>
public int ModeInfoStride { get; internal set; }
/// <summary>
/// Gets or sets the coded frame dimensions.
/// </summary>
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; }
}

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

@ -3,11 +3,23 @@
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
{
/// <summary>
/// Gets or sets the directional angle adjustment for each prediction plane.
/// </summary>
public required byte[] AngleDelta { get; set; }
/// <summary>
/// Gets or sets the chroma-from-luma alpha magnitude index.
/// </summary>
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; }
}

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

@ -3,16 +3,34 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Provides tile-writer operations that maintain encoder entropy-neighbor state.
/// </summary>
internal partial class Av1TileWriter
{
/// <summary>
/// Tracks the mode and coefficient positions while entropy-coding one AV1 superblock.
/// </summary>
internal class Av1EntropyCodingContext
{
/// <summary>
/// Gets or sets the macroblock mode information currently being encoded.
/// </summary>
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; }
/// <summary>
/// Gets or sets the number of luma coefficient positions consumed in the current superblock.
/// </summary>
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; }
}
}

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

@ -3,12 +3,38 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Identifies the filter-intra predictor kernel selected for an AV1 block.
/// </summary>
internal enum Av1FilterIntraMode
{
/// <summary>
/// The filter-intra DC predictor.
/// </summary>
DC,
/// <summary>
/// The filter-intra vertical predictor.
/// </summary>
Vertical,
/// <summary>
/// The filter-intra horizontal predictor.
/// </summary>
Horizontal,
/// <summary>
/// The filter-intra 157-degree directional predictor.
/// </summary>
Directional157,
/// <summary>
/// The filter-intra Paeth predictor.
/// </summary>
Paeth,
/// <summary>
/// The number of filter-intra modes.
/// </summary>
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;
/// <summary>
/// Provides validity checks for AV1 filter-intra modes.
/// </summary>
internal static class Av1FilterIntraModeExtensions
{
/// <summary>
/// Maps filter-intra syntax values to the directional mode used by the predictor.
/// </summary>
private static readonly Av1PredictionMode[] IntraDirection =
[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)
=> 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;
/// <summary>
/// Collection of all information for a single frame.
/// Owns the mode, transform, coefficient, quantizer, and filter state decoded for one AV1 frame.
/// </summary>
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;
/// <summary>
/// Stores raster-ordered luma coefficients for every frame superblock.
/// </summary>
private readonly int[] coefficientsY = [];
/// <summary>
/// Stores raster-ordered blue-difference chroma coefficients for every frame superblock.
/// </summary>
private readonly int[] coefficientsU = [];
/// <summary>
/// Stores raster-ordered red-difference chroma coefficients for every frame superblock.
/// </summary>
private readonly int[] coefficientsV = [];
/// <summary>
/// The width and height of a superblock in 4x4 mode-information units.
/// </summary>
private readonly int modeInfoSizePerSuperblock;
/// <summary>
/// The number of 4x4 mode-information positions in one square superblock.
/// </summary>
private readonly int modeInfoCountPerSuperblock;
/// <summary>
/// The number of columns in the frame superblock grid.
/// </summary>
private readonly int superblockColumnCount;
/// <summary>
/// The number of rows in the frame superblock grid.
/// </summary>
private readonly int superblockRowCount;
/// <summary>
/// The base-2 reduction from luma coefficient capacity to per-chroma-plane capacity.
/// </summary>
private readonly int subsamplingFactor;
/// <summary>
/// Stores one addressing view for each frame superblock.
/// </summary>
private readonly Av1SuperblockInfo[] superblockInfos;
/// <summary>
/// Stores decoded block mode information in bitstream traversal order.
/// </summary>
private readonly Av1BlockModeInfo[] modeInfos;
/// <summary>
/// Maps every frame-relative 4x4 position to its covering entry in <see cref="modeInfos"/>.
/// </summary>
private readonly Av1FrameModeInfoMap modeInfoMap;
/// <summary>
/// Stores luma transform information grouped by superblock.
/// </summary>
private readonly Av1TransformInfo[] transformInfosY;
/// <summary>
/// Stores both chroma planes' transform information grouped by superblock.
/// </summary>
private readonly Av1TransformInfo[] transformInfosUv;
/// <summary>
/// Stores the quantizer-index delta for each frame superblock.
/// </summary>
private readonly int[] deltaQ;
/// <summary>
/// The base-2 number of constrained directional enhancement filter entries allocated per superblock.
/// </summary>
private readonly int cdefStrengthFactorLog2;
/// <summary>
/// Stores constrained directional enhancement filter strengths grouped by superblock.
/// </summary>
private readonly int[] cdefStrength;
/// <summary>
/// The base-2 number of loop-filter delta values stored per superblock.
/// </summary>
private readonly int deltaLoopFactorLog2 = 2;
/// <summary>
/// Stores the four loop-filter delta values for each superblock.
/// </summary>
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)
{
// 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 superblockAlignedWidth = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, superblockSizeLog2);
int superblockAlignedHeight = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameHeight, superblockSizeLog2);
@ -45,14 +123,15 @@ internal partial class Av1FrameInfo
this.modeInfoCountPerSuperblock = this.modeInfoSizePerSuperblock * this.modeInfoSizePerSuperblock;
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.modeInfos = new Av1BlockModeInfo[superblockCount * this.modeInfoCountPerSuperblock];
this.modeInfoMap = new Av1FrameModeInfoMap(new Size(this.modeInfoSizePerSuperblock * this.superblockColumnCount, this.modeInfoSizePerSuperblock * this.superblockRowCount));
this.transformInfosY = new Av1TransformInfo[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;
for (int y = 0; y < this.superblockRowCount; y++)
{
@ -67,7 +146,7 @@ internal partial class Av1FrameInfo
bool subX = sequenceHeader.ColorConfig.SubSamplingX;
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;
Guard.IsFalse(this.subsamplingFactor == -1, nameof(this.subsamplingFactor), "Invalid combination of subsampling.");
int lumaCoefficientCountPerSuperblock = this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo;
@ -77,7 +156,7 @@ internal partial class Av1FrameInfo
this.coefficientsV = new int[superblockCount * chromaCoefficientCountPerSuperblock];
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.cdefStrength = new int[superblockCount << this.cdefStrengthFactorLog2];
Array.Fill(this.cdefStrength, -1);
@ -85,15 +164,20 @@ internal partial class Av1FrameInfo
}
/// <summary>
/// Gets the number of mode info blocks in a single superblock.
/// Gets the total mode-information capacity allocated for the frame.
/// </summary>
public int ModeInfoCount => this.modeInfos.Length;
/// <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>
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)
{
Span<Av1SuperblockInfo> span = this.superblockInfos;
@ -101,8 +185,19 @@ internal partial class Av1FrameInfo
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);
/// <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)
{
Point location = this.GetModeInfoPosition(superblockIndex, modeInfoIndex);
@ -113,11 +208,16 @@ internal partial class Av1FrameInfo
/// <summary>
/// Gets the mode information record covering the specified frame-relative mode information position.
/// </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]];
/// <summary>
/// Gets the mode information records parsed for the specified superblock in bitstream order.
/// </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)
{
Point location = this.GetModeInfoPosition(superblockIndex, Point.Empty);
@ -125,6 +225,12 @@ internal partial class Av1FrameInfo
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)
{
if (plane == 0)
@ -135,6 +241,11 @@ internal partial class Av1FrameInfo
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)
{
Span<Av1TransformInfo> span = this.transformInfosY;
@ -142,6 +253,11 @@ internal partial class Av1FrameInfo
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)
{
Span<Av1TransformInfo> span = this.transformInfosUv;
@ -149,6 +265,11 @@ internal partial class Av1FrameInfo
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)
{
Span<int> span = this.coefficientsY;
@ -157,6 +278,11 @@ internal partial class Av1FrameInfo
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)
{
Span<int> span = this.coefficientsU;
@ -165,6 +291,11 @@ internal partial class Av1FrameInfo
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)
{
Span<int> span = this.coefficientsV;
@ -173,6 +304,11 @@ internal partial class Av1FrameInfo
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)
{
Span<int> span = this.deltaQ;
@ -180,6 +316,11 @@ internal partial class Av1FrameInfo
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)
{
Span<int> span = this.cdefStrength;
@ -187,6 +328,10 @@ internal partial class Av1FrameInfo
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)
{
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)
{
Span<int> span = this.deltaLoopFilter;
@ -203,14 +353,28 @@ internal partial class Av1FrameInfo
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);
/// <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)
{
this.modeInfos[this.modeInfoMap.NextIndex] = modeInfo;
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)
{
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;
/// <summary>
/// Provides frame-wide lookup and storage for decoded AV1 mode-information blocks.
/// </summary>
internal partial class Av1FrameInfo
{
/// <summary>
@ -13,9 +16,20 @@ internal partial class Av1FrameInfo
/// </remarks>
public class Av1FrameModeInfoMap
{
/// <summary>
/// Stores the mode-information index assigned to each aligned 4x4 frame location.
/// </summary>
private readonly ushort[] offsets;
/// <summary>
/// The dimensions of <see cref="offsets"/> in 4x4 mode-information units.
/// </summary>
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)
{
this.alignedModeInfoCount = modeInfoCount;
@ -29,8 +43,9 @@ internal partial class Av1FrameInfo
public int NextIndex { get; private set; }
/// <summary>
/// Gets the mapped index for the given location.
/// Gets the mode-information index mapped to the specified 4x4 location.
/// </summary>
/// <param name="location">The location in 4x4 mode-information units.</param>
public int this[Point location]
{
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)
{
// Equivalent in SVT-Av1: EbDecNbr.c svt_aom_update_block_nbrs
int bw4 = blockSize.Get4x4WideCount();
int bh4 = blockSize.Get4x4HighCount();
DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.Y, 0, nameof(modeInfoLocation));
DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.Y + bh4, this.alignedModeInfoCount.Height, nameof(modeInfoLocation));
DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.X, 0, 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++)
{
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;
/// <summary>
/// Stores whether filter-intra prediction is active and which filter-intra mode is selected.
/// </summary>
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; }
/// <summary>
/// Gets or sets the filter-intra mode selected for the block.
/// </summary>
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;
/// <summary>
/// Owns the padded absolute-coefficient level plane used to derive AV1 coefficient entropy contexts.
/// </summary>
internal sealed class Av1LevelBuffer : IDisposable
{
/// <summary>
/// Owns the padded level storage until the buffer is disposed.
/// </summary>
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)
: 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)
{
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;
this.Stride = Av1Constants.TransformPadHorizontal + size.Width;
this.memory = configuration.MemoryAllocator.Allocate<byte>(this.Stride * totalHeight, AllocationOptions.Clean);
}
/// <summary>
/// Gets the unpadded coefficient dimensions.
/// </summary>
public Size Size { get; }
/// <summary>
/// Gets the padded row stride in bytes.
/// </summary>
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];
/// <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)
{
ObjectDisposedException.ThrowIf(this.memory == null, this);
@ -41,6 +73,7 @@ internal sealed class Av1LevelBuffer : IDisposable
ref int sourceRef = ref coefficientBuffer[y * this.Size.Width];
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 = ref Unsafe.Add(ref destRef, 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)
{
int x = index % this.Size.Width;
@ -55,9 +93,19 @@ internal sealed class Av1LevelBuffer : IDisposable
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)
=> 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)
{
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);
}
/// <inheritdoc/>
public void Dispose()
{
this.memory?.Dispose();
this.memory = null;
}
/// <summary>
/// Clears all coefficient levels and context padding.
/// </summary>
internal void Clear()
{
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;
/// <summary>
/// Holds decoder-side macroblock edges, plane state, and neighboring mode information.
/// </summary>
internal class Av1MacroBlockD
{
/// <summary>
/// Stores the mode-information entries exposed through <see cref="ModeInfo"/>.
/// </summary>
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
{
get => this.modeInfo;
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];
value.CopyTo(this.modeInfo);
}
}
/// <summary>
/// Gets or sets the tile containing the current block.
/// </summary>
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; }
/// <summary>
/// Gets or sets a value indicating whether a left block is available within the tile.
/// </summary>
public bool IsLeftAvailable { get; internal set; }
/// <summary>
/// Gets or sets the above macroblock mode information, when available.
/// </summary>
public Av1MacroBlockModeInfo? AboveMacroBlock { get; internal set; }
/// <summary>
/// Gets or sets the left macroblock mode information, when available.
/// </summary>
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; }
/// <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>
public int ToTopEdge { get; internal set; }
/// <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>
public int ToBottomEdge { get; internal set; }
/// <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>
public int ToLeftEdge { get; internal set; }
/// <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>
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; }
/// <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; }
}

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

@ -3,11 +3,23 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores the encoder's selected modes and references for an AV1 macroblock.
/// </summary>
internal class Av1MacroBlockModeInfo
{
/// <summary>
/// Gets or sets the prediction, transform, and segmentation decisions for the block.
/// </summary>
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; }
/// <summary>
/// Gets or sets the constrained directional enhancement filter strength for the block.
/// </summary>
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;
/// <summary>
/// Stores the decoded prediction, segmentation, skip, and transform state for an AV1 mode-information block.
/// </summary>
internal class Av1ModeInfo
{
/// <summary>
/// Gets or sets the macroblock mode information associated with this map entry.
/// </summary>
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;
/// <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>
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;
/// <summary>
/// Stores context units exposed to blocks on the right.
/// </summary>
private readonly T[] left;
/// <summary>
/// Stores context units exposed to blocks below.
/// </summary>
private readonly T[] top;
/// <summary>
/// Stores context units indexed by the diagonal difference between horizontal and vertical positions.
/// </summary>
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)
{
this.left = new T[leftSize];
@ -22,33 +46,87 @@ internal class Av1NeighborArrayUnit<T>
this.topLeft = new T[topLeftSize];
}
/// <summary>
/// Selects which neighbor arrays receive an update.
/// </summary>
[Flags]
public enum UnitMask
{
/// <summary>
/// Update the left-neighbor storage.
/// </summary>
Left = 1,
/// <summary>
/// Update the top-neighbor storage.
/// </summary>
Top = 2,
/// <summary>
/// Update the top-left diagonal storage.
/// </summary>
TopLeft = 4,
}
/// <summary>
/// Gets the left-neighbor storage.
/// </summary>
public Span<T> Left => this.left;
/// <summary>
/// Gets the top-neighbor storage.
/// </summary>
public Span<T> Top => this.top;
/// <summary>
/// Gets the top-left diagonal storage.
/// </summary>
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; }
/// <summary>
/// Gets or sets the base-2 logarithm of the diagonal context granularity in samples.
/// </summary>
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; }
/// <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;
/// <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;
/// <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)
=> 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)
{
int idx, j;
@ -83,7 +161,7 @@ internal class Av1NeighborArrayUnit<T>
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)
{
dst_ptr = value[j];
@ -117,7 +195,7 @@ internal class Av1NeighborArrayUnit<T>
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)
{
dst_ptr = value[j];
@ -156,7 +234,7 @@ internal class Av1NeighborArrayUnit<T>
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)
{
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();
}

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

@ -3,6 +3,9 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Reserves encoder-side state for AV1 luma palette mode decisions.
/// </summary>
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;
/// <summary>
/// Stores entropy, palette, partition, and transform contexts for 4-by-4 blocks above the current block.
/// </summary>
internal class Av1ParseAboveNeighbor4x4Context
{
/* Buffer holding the sign of the DC coefficients and the
cumulative sum of the coefficient levels of the above 4x4
blocks corresponding to the current super block row. */
/// <summary>
/// Stores DC-sign and cumulative coefficient-level contexts for each plane above the current block.
/// </summary>
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;
/* 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][];
/// <summary>
/// Stores compound-reference group contexts from the preceding 4x4 row.
/// </summary>
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)
{
int wide64x64Count = Av1BlockSize.Block64x64.Get4x4WideCount();
@ -46,8 +61,19 @@ internal class Av1ParseAboveNeighbor4x4Context
/// </summary>
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];
/// <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)
{
int planeCount = sequenceHeader.ColorConfig.PlaneCount;
@ -64,8 +90,16 @@ internal class Av1ParseAboveNeighbor4x4Context
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)
{
// Above contexts are tile-local even though block positions are frame-relative.
int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart;
int bw = blockSize.Get4x4WideCount();
int value = Av1PartitionContext.GetAboveContext(subSize);
@ -74,6 +108,14 @@ internal class Av1ParseAboveNeighbor4x4Context
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)
{
int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart;
@ -81,6 +123,7 @@ internal class Av1ParseAboveNeighbor4x4Context
int n4w = blockSize.Get4x4WideCount();
if (skip)
{
// Skipped blocks expose the full block width as their effective transform extent.
transformWidth = n4w << Av1Constants.ModeInfoSizeLog2;
}
@ -88,6 +131,12 @@ internal class Av1ParseAboveNeighbor4x4Context
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)
=> 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;
/// <summary>
/// Stores entropy, palette, partition, and transform contexts for 4-by-4 blocks left of the current block.
/// </summary>
internal class Av1ParseLeftNeighbor4x4Context
{
/* Buffer holding the sign of the DC coefficients and the
cumulative sum of the coefficient levels of the left 4x4
blocks corresponding to the current super block row. */
/// <summary>
/// Stores DC-sign and cumulative coefficient-level contexts for each plane left of the current block.
/// </summary>
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;
/* 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][];
/// <summary>
/// Stores compound-reference group contexts for the current superblock row.
/// </summary>
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)
{
this.LeftTransformHeight = new int[superblockModeInfoSize];
@ -47,6 +62,10 @@ internal class Av1ParseLeftNeighbor4x4Context
/// </summary>
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)
{
int blockCount = sequenceHeader.SuperblockModeInfoSize;
@ -64,8 +83,16 @@ internal class Av1ParseLeftNeighbor4x4Context
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)
{
// 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 bh = blockSize.Get4x4HighCount();
int value = Av1PartitionContext.GetLeftContext(subSize);
@ -73,6 +100,14 @@ internal class Av1ParseLeftNeighbor4x4Context
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)
{
int startIndex = modeInfoLocation.Y - superblockInfo.ModeInfoPosition.Y;
@ -80,6 +115,7 @@ internal class Av1ParseLeftNeighbor4x4Context
int n4h = blockSize.Get4x4HighCount();
if (skip)
{
// Skipped blocks expose the full block height as their effective transform extent.
transformHeight = n4h << Av1Constants.ModeInfoSizeLog2;
}
@ -87,8 +123,19 @@ internal class Av1ParseLeftNeighbor4x4Context
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)
=> 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];
}

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

@ -5,35 +5,74 @@ using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
// Generates 5 bit field in which each bit set to 1 represents
// a BlockSize partition 11111 means we split 128x128, 64x64, 32x32, 16x16
// and 8x8. 10000 means we just split the 128x128 to 64x64
/// <summary>
/// Stores the above and left five-bit AV1 partition contexts for a mode-information position.
/// </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>
{
/// <summary>
/// Maps each block size to the five-bit context stored for an above neighbor.
/// </summary>
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];
/// <summary>
/// Maps each block size to the five-bit context stored for a left neighbor.
/// </summary>
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];
// 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;
/// <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)
{
this.Above = above;
this.Left = left;
}
/// <summary>
/// Gets the maximum representable partition context.
/// </summary>
public static Av1PartitionContext MaxValue => throw new NotImplementedException();
/// <summary>
/// Gets the minimum representable partition context.
/// </summary>
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; }
/// <summary>
/// Gets or sets the five-bit context derived from the above neighbor.
/// </summary>
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];
/// <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];
}

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;
/// <summary>
/// Describes a decoded AV1 partition's block geometry, neighbors, and frame-boundary availability.
/// </summary>
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)
{
this.ModeInfo = modeInfo;
@ -20,6 +30,9 @@ internal class Av1PartitionInfo
this.HeightInPixels = new int[3];
}
/// <summary>
/// Gets the decoded block mode information.
/// </summary>
public Av1BlockModeInfo ModeInfo { get; }
/// <summary>
@ -27,8 +40,14 @@ internal class Av1PartitionInfo
/// </summary>
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; }
/// <summary>
/// Gets the partition type that produced the block.
/// </summary>
public Av1PartitionType Type { get; }
/// <summary>
@ -61,32 +80,77 @@ internal class Av1PartitionInfo
/// </summary>
public int RowIndex { get; set; }
/// <summary>
/// Gets or sets the mode information covering the immediately above luma neighbor.
/// </summary>
public Av1BlockModeInfo? AboveModeInfo { get; set; }
/// <summary>
/// Gets or sets the mode information covering the immediately left luma neighbor.
/// </summary>
public Av1BlockModeInfo? LeftModeInfo { get; set; }
/// <summary>
/// Gets or sets the mode information covering the above chroma neighbor.
/// </summary>
public Av1BlockModeInfo? AboveModeInfoForChroma { get; set; }
/// <summary>
/// Gets or sets the mode information covering the left chroma neighbor.
/// </summary>
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; }
/// <summary>
/// Gets or sets the reference-frame identifiers selected for the block.
/// </summary>
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; }
/// <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; }
/// <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; }
/// <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; }
/// <summary>
/// Gets the block width in samples for each color plane.
/// </summary>
public int[] WidthInPixels { get; private set; }
/// <summary>
/// Gets the block height in samples for each color plane.
/// </summary>
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; }
/// <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)
{
Av1BlockSize blockSize = this.ModeInfo.BlockSize;
@ -105,21 +169,23 @@ internal class Av1PartitionInfo
this.ModeBlockToTopEdge = -this.RowIndex << shift;
this.ModeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift;
// Block Size width & height in pixels.
// For Luma bock
// The bitstream expresses block size on the luma grid. Chroma dimensions are derived by
// subsampling that grid while retaining at least one 4x4 chroma unit for narrow blocks.
const int modeInfoSize = 1 << Av1Constants.ModeInfoSizeLog2;
this.WidthInPixels[0] = bw4 * modeInfoSize;
this.HeightInPixels[0] = bh4 * modeInfoSize;
// For U plane chroma bock
this.WidthInPixels[1] = Math.Max(1, bw4 >> subX) * 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.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)
{
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)
{
int maxBlockWide = blockSize.GetWidth();
@ -166,6 +238,12 @@ internal class Av1PartitionInfo
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)
{
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;
/// <summary>
/// Holds the coding decisions, buffers, and sequence context for one AV1 picture pass.
/// </summary>
internal class Av1PictureControlSet
{
/// <summary>
/// Gets or sets the partition neighbor contexts for each tile.
/// </summary>
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; }
/// <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; }
/// <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; }
/// <summary>
/// Gets or sets the transform-function neighbor contexts for each tile.
/// </summary>
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; }
/// <summary>
/// Gets or sets the parent picture state shared across coding passes.
/// </summary>
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; }
/// <summary>
/// Gets the frame grid that maps each 4x4 position to its mode-information span.
/// </summary>
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; }
/// <summary>
/// Gets or sets the row stride of <see cref="ModeInfoGrid"/> in 4x4 mode-information units.
/// </summary>
public int ModeInfoStride { get; internal set; }
// true if 4x4 blocks are disallowed for all frames, and NSQ is disabled (since granularity is
// needed for 8x8 NSQ blocks). Used to compute the offset for mip.
/// <summary>
/// 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; }
/// <summary>
/// Gets or sets the constrained directional enhancement filter presets for each filter block.
/// </summary>
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)
=> 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)
=> 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)
{
// Grid entries own their arrays because the source span can refer to temporary traversal state.
this.ModeInfoGrid[offset] = new Av1ModeInfo[span.Length];
span.CopyTo(this.ModeInfoGrid[offset]);
}
/// <summary>
/// SVT: get_mbmi
/// Gets the macroblock mode information at a block origin and refreshes its grid mapping.
/// </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)
{
int modeInfoStride = this.ModeInfoStride;
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())
// mip offset may be different from grid offset when 4x4 blocks are disallowed
// Rectangular mode-decision blocks can replace grid entries. Restore the entry from the
// contiguous backing store, whose index is halved when 4x4 blocks are globally disabled.
int disallow4x4 = this.Disallow4x4AllFrames ? 1 : 0;
int mipOffset = ((blockOrigin.Y >> disallow4x4) * (modeInfoStride >> disallow4x4)) + (blockOrigin.X >> disallow4x4);
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];
return modeInfo.MacroBlockModeInfo;
}
/// <summary>
/// SVT: svt_av1_update_segmentation_map
/// Writes a segment identifier to every segmentation-map entry covered by a block.
/// </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)
{
Av1EncoderCommon cm = this.Parent.Common;
@ -80,6 +141,8 @@ internal class Av1PictureControlSet
int bh = blockSize.GetHeight();
int xmis = Math.Min(cm.ModeInfoColumnCount - mi_col, bw);
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)
{
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;
/// <summary>
/// Holds encoder state that is shared by all coding passes for one AV1 picture.
/// </summary>
internal class Av1PictureParentControlSet
{
/// <summary>
/// Gets or sets frame dimensions and tile state shared by encoder stages.
/// </summary>
public required Av1EncoderCommon Common { get; internal set; }
/// <summary>
/// Gets or sets the frame header being encoded.
/// </summary>
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; }
/// <summary>
/// Gets or sets the encoder palette-search level.
/// </summary>
public int PaletteLevel { get; internal set; }
/// <summary>
/// Gets or sets the frame width aligned for superblock traversal.
/// </summary>
public int AlignedWidth { get; internal set; }
/// <summary>
/// Gets or sets the frame height aligned for superblock traversal.
/// </summary>
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; }
}

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

@ -3,8 +3,18 @@
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
{
/// <summary>
/// The luma plane.
/// </summary>
Y,
/// <summary>
/// Either chroma plane.
/// </summary>
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;
/// <summary>
/// Holds encoder configuration and sequence-wide state shared by AV1 pictures.
/// </summary>
internal class Av1SequenceControlSet
{
/// <summary>
/// Gets or sets the sequence header that governs encoded pictures.
/// </summary>
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; }
}

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;
/// <summary>
/// Holds encoder-side block decisions and transform data for one AV1 superblock.
/// </summary>
internal class Av1Superblock
{
/// <summary>
/// Gets or sets the final encoder decisions in partition traversal order.
/// </summary>
public required Av1EncoderBlockStruct[] FinalBlocks { get; set; }
/// <summary>
/// Gets or sets the tile containing the superblock.
/// </summary>
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; }
/// <summary>
/// Gets or sets the superblock index within the picture.
/// </summary>
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;
/// <summary>
/// Describes a superblock node's location, dimensions, and children in the encoder partition tree.
/// </summary>
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; }
}

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

@ -3,10 +3,21 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary>
/// Stores the partition tree and decoded mode information for one AV1 superblock.
/// </summary>
internal class Av1SuperblockInfo
{
/// <summary>
/// Provides the frame-owned arrays addressed by this superblock view.
/// </summary>
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)
{
this.Position = position;
@ -14,39 +25,82 @@ internal class Av1SuperblockInfo
}
/// <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>
public Point Position { get; }
/// <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>
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);
/// <summary>
/// Gets the mode information that covers the superblock origin.
/// </summary>
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);
/// <summary>
/// Gets the blue-difference chroma coefficient storage reserved for this superblock.
/// </summary>
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);
/// <summary>
/// Gets the constrained directional enhancement filter strengths for this superblock.
/// </summary>
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);
/// <summary>
/// Gets or sets the next luma transform-information index while parsing this superblock.
/// </summary>
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; }
/// <summary>
/// Gets or sets the number of mode-information records parsed for this superblock.
/// </summary>
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);
/// <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);
/// <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);
/// <summary>
@ -54,10 +108,25 @@ internal class Av1SuperblockInfo
/// </summary>
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);
/// <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);
/// <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
{
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;
/// <summary>
/// Describes one AV1 tile's superblock and mode-information boundaries.
/// </summary>
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)
{
this.SetTileRow(frameHeader.TilesInfo, frameHeader.ModeInfoRowCount, row);
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)
{
this.ModeInfoColumnStart = tileInfo.ModeInfoColumnStart;
@ -22,16 +35,37 @@ internal class Av1TileInfo
this.TileIndex = tileInfo.TileIndex;
}
/// <summary>
/// Gets the first mode-information row in the tile.
/// </summary>
public int ModeInfoRowStart { get; private set; }
/// <summary>
/// Gets the exclusive end mode-information row in the tile.
/// </summary>
public int ModeInfoRowEnd { get; private set; }
/// <summary>
/// Gets the first mode-information column in the tile.
/// </summary>
public int ModeInfoColumnStart { get; private set; }
/// <summary>
/// Gets the exclusive end mode-information column in the tile.
/// </summary>
public int ModeInfoColumnEnd { get; private set; }
/// <summary>
/// Gets the tile column and row indices.
/// </summary>
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)
{
this.ModeInfoRowStart = tileGroupHeader.TileRowStartModeInfo[row];
@ -41,6 +75,12 @@ internal class Av1TileInfo
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)
{
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;
/// <summary>
/// Parses partition, mode, transform, and coefficient syntax for one AV1 tile.
/// </summary>
internal class Av1TileReader : IAv1TileReader
{
/// <summary>
/// The default self-guided restoration projection coefficients for each color plane.
/// </summary>
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];
/// <summary>
/// Maps packed coefficient sign classes to their signed contribution to the DC context.
/// </summary>
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 = [
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];
/// <summary>
/// Maps the minimum and union of luma neighbor levels to a transform-block skip context.
/// </summary>
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]];
/// <summary>
/// Stores the preceding self-guided restoration coefficients for each color plane.
/// </summary>
private int[][] referenceSgrXqd = [];
/// <summary>
/// Stores the preceding horizontal and vertical Wiener taps for each color plane.
/// </summary>
private int[][][] referenceLrWiener = [];
/// <summary>
/// Tracks entropy, partition, transform, and palette state above the current block.
/// </summary>
private readonly Av1ParseAboveNeighbor4x4Context aboveNeighborContext;
/// <summary>
/// Tracks entropy, partition, transform, and palette state left of the current block.
/// </summary>
private readonly Av1ParseLeftNeighbor4x4Context leftNeighborContext;
/// <summary>
/// The quantizer index carried between delta-quantized blocks in the current tile.
/// </summary>
private int currentQuantizerIndex;
/// <summary>
/// Stores the segment identifier covering each 4x4 frame position.
/// </summary>
private readonly int[][] segmentIds = [];
/// <summary>
/// Stores per-plane transform counts for each forced 64x64 residual region.
/// </summary>
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];
/// <summary>
/// Tracks the next coefficient slot for each color plane within the current superblock.
/// </summary>
private readonly int[] coefficientIndex = [];
/// <summary>
/// Provides allocator and decoder configuration to tile entropy decoding.
/// </summary>
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;
/// <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)
{
this.FrameHeader = frameHeader;
this.configuration = configuration;
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.segmentIds = new int[this.FrameHeader.ModeInfoRowCount][];
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];
}
// reallocate_parse_context_memory
// Hard code number of threads to 1 for now.
// Above contexts span the aligned frame width, while left contexts are reused for each superblock row.
int planesCount = sequenceHeader.ColorConfig.PlaneCount;
int superblockColumnCount =
Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperblockSizeLog2) >> sequenceHeader.SuperblockSizeLog2;
@ -65,19 +134,38 @@ internal class Av1TileReader : IAv1TileReader
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)
: this(configuration, sequenceHeader, frameHeader)
=> this.frameDecoder = frameDecoder;
/// <summary>
/// Gets the frame header whose tile syntax is being parsed.
/// </summary>
public ObuFrameHeader FrameHeader { get; }
/// <summary>
/// Gets the sequence header governing the frame.
/// </summary>
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; }
/// <summary>
/// SVT: parse_tile
/// Parses one tile's partition, mode, transform, coefficient, and filter syntax in superblock order.
/// </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)
{
Av1SymbolDecoder reader = new(this.configuration, tileData, this.FrameHeader.QuantizationParameters.BaseQIndex);
@ -92,7 +180,7 @@ internal class Av1TileReader : IAv1TileReader
this.ClearLoopFilterDelta();
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.referenceLrWiener = new int[planesCount][][];
for (int plane = 0; plane < planesCount; plane++)
@ -129,15 +217,24 @@ internal class Av1TileReader : IAv1TileReader
this.ReadLoopRestoration(modeInfoPosition, superBlockSize);
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);
}
}
}
/// <summary>
/// Resets all frame loop-filter delta state before parsing a tile.
/// </summary>
private void ClearLoopFilterDelta()
=> 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)
{
int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
@ -145,15 +242,20 @@ internal class Av1TileReader : IAv1TileReader
{
if (this.FrameHeader.LoopRestorationParameters.Items[plane].Type != ObuRestorationType.None)
{
// TODO: Implement.
throw new NotImplementedException("No loop restoration filter support.");
}
}
}
/// <summary>
/// 5.11.4. Decode partition syntax.
/// Decodes AV1 partition syntax and recursively visits each resulting coding block.
/// </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)
{
int columnIndex = modeInfoLocation.X;
@ -189,6 +291,9 @@ internal class Av1TileReader : IAv1TileReader
Av1BlockSize subSize = partitionType.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)
{
case Av1PartitionType.Split:
@ -284,6 +389,15 @@ internal class Av1TileReader : IAv1TileReader
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)
{
int rowIndex = modeInfoLocation.Y;
@ -328,13 +442,16 @@ internal class Av1TileReader : IAv1TileReader
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);
}
/// <summary>
/// SVT: reset_skip_context
/// Clears coefficient neighbor contexts across every plane of a skipped block.
/// </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)
{
int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
@ -354,9 +471,14 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.34. Residual syntax.
/// Parses every luma and chroma transform block and its coefficients for a coding block.
/// </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)
{
int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false);
@ -378,6 +500,8 @@ internal class Av1TileReader : IAv1TileReader
transformInfoIndices[2] = transformInfoIndices[1] + chromaTransformUnitCount;
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 column = 0; column < maxBlocksWide; column += modeUnitBlocksWide)
@ -397,7 +521,8 @@ internal class Av1TileReader : IAv1TileReader
Span<Av1TransformInfo> transformInfoSpan = (plane == 0) ? superblockInfo.GetTransformInfoY() : superblockInfo.GetTransformInfoUv();
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 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.");
@ -445,6 +570,8 @@ internal class Av1TileReader : IAv1TileReader
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;
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)
{
int blockWide = blockSize.Get4x4WideCount();
@ -474,10 +608,23 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.35. Transform block syntax.
/// Derives a transform block's entropy context and decodes its coefficient syntax.
/// </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>
/// 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>
private int ParseTransformBlock(
ref Av1SymbolDecoder reader,
@ -517,10 +664,22 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.39. Coefficients syntax.
/// Decodes transform coefficients and updates the coefficient neighbor contexts for one color plane.
/// </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>
/// 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>
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);
}
/// <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)
{
Av1TransformBlockContext transformBlockContext = new();
@ -548,6 +718,8 @@ internal class Av1TileReader : IAv1TileReader
int k = 0;
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
{
uint sign = (uint)aboveContext[k] >> Av1Constants.CoefficientContextBitCount;
@ -575,6 +747,7 @@ internal class Av1TileReader : IAv1TileReader
}
else
{
// Luma skip contexts preserve both the weakest neighboring level and whether either edge is stronger.
int top = 0;
int left = 0;
@ -602,6 +775,8 @@ internal class Av1TileReader : IAv1TileReader
}
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 contextOffset = planeBlockSize.GetPelsLog2Count() > transformSize.ToBlockSize().GetPelsLog2Count() ? 10 : 7;
transformBlockContext.SkipContext = contextBase + contextOffset;
@ -610,11 +785,20 @@ internal class Av1TileReader : IAv1TileReader
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)
{
bool aboveEntropyContext = 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)
{
case Av1TransformSize.Size4x4:
@ -742,8 +926,15 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.15. TX size syntax.
/// Selects the transform size for a coding block from lossless, explicit-selection, or maximum-size rules.
/// </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)
{
Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo;
@ -760,6 +951,14 @@ internal class Av1TileReader : IAv1TileReader
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)
{
int context = 0;
@ -792,22 +991,34 @@ internal class Av1TileReader : IAv1TileReader
}
/// <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>
/// <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)
{
Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize;
int block4x4Width = blockSize.Get4x4WideCount();
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);
this.aboveNeighborContext.UpdateTransformation(modeInfoLocation, tileInfo, transformSize, blockSize, false);
this.leftNeighborContext.UpdateTransformation(modeInfoLocation, superblockInfo, transformSize, blockSize, false);
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)
{
int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y];
@ -829,6 +1040,8 @@ internal class Av1TileReader : IAv1TileReader
bool isLossLess = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId];
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 idx = 0; idx < maxBlockWide; idx += width, forceSplitCount++)
@ -836,7 +1049,7 @@ internal class Av1TileReader : IAv1TileReader
int lumaTransformUnitCount = 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 stepRow = transformSize.Get4x4HighCount();
@ -861,7 +1074,7 @@ internal class Av1TileReader : IAv1TileReader
continue;
}
// Update Chroma Transform Info.
// Chroma geometry is rounded to the subsampling grid before stepping its transform size.
stepColumn = transformSizeUv.Get4x4WideCount();
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)
{
DebugGuard.IsTrue(
@ -911,26 +1124,31 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.49. Palette tokens syntax.
/// Reads luma and chroma palette-map tokens when a block selects palette prediction.
/// </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)
{
if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) != 0)
{
// TODO: Implement.
throw new NotImplementedException();
}
if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0)
{
// TODO: Implement.
throw new NotImplementedException();
}
}
/// <summary>
/// 5.11.6. Mode info syntax.
/// Reads the prediction, segmentation, skip, quantizer, and filter mode information for a still-image block.
/// </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)
{
DebugGuard.IsTrue(this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported.");
@ -938,8 +1156,11 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.7. Intra frame mode info syntax.
/// Reads all intra-frame mode syntax for a coding block in bitstream order.
/// </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)
{
if (this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip)
@ -947,7 +1168,6 @@ internal class Av1TileReader : IAv1TileReader
this.IntraSegmentId(ref reader, partitionInfo);
}
// this.skipMode = false;
partitionInfo.ModeInfo.Skip = this.ReadSkip(ref reader, partitionInfo);
if (!this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip)
{
@ -962,8 +1182,9 @@ internal class Av1TileReader : IAv1TileReader
this.ReadDeltaLoopFilter(ref reader, partitionInfo);
}
partitionInfo.ReferenceFrame[0] = 0; // IntraFrame;
partitionInfo.ReferenceFrame[1] = -1; // None;
// Independently decodable still-image blocks reference only the current intra frame.
partitionInfo.ReferenceFrame[0] = 0;
partitionInfo.ReferenceFrame[1] = -1;
partitionInfo.ModeInfo.SetPaletteSizes(0, 0);
bool useIntraBlockCopy = false;
if (this.AllowIntraBlockCopy())
@ -978,10 +1199,8 @@ internal class Av1TileReader : IAv1TileReader
}
else
{
// this.IsInter = false;
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);
if (partitionInfo.IsChroma && !this.SequenceHeader.ColorConfig.IsMonochrome)
{
@ -991,7 +1210,6 @@ internal class Av1TileReader : IAv1TileReader
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);
}
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()
=> (this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) &&
(this.SequenceHeader.ForceScreenContentTools > 0) &&
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)
{
if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId])
{
// In lossless, CfL is available when the partition size is equal to the
// transform size.
// Lossless mode fixes transforms at 4x4, so CfL is available only when the subsampled
// plane block is itself 4x4 and therefore has no smaller transform partition.
bool subX = this.SequenceHeader.ColorConfig.SubSamplingX;
bool subY = this.SequenceHeader.ColorConfig.SubSamplingY;
Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
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;
}
/// <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)
{
partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false;
@ -1050,16 +1282,21 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.46. Palette mode info syntax.
/// Reads palette size and color syntax for an eligible screen-content block.
/// </summary>
private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) =>
// TODO: Implement.
throw new NotImplementedException();
/// <param name="reader">The tile symbol decoder.</param>
/// <param name="partitionInfo">The current coding block.</param>
/// <exception cref="NotImplementedException">Palette-mode syntax is not implemented.</exception>
/// <remarks>Implements AV1 section 5.11.46.</remarks>
private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo)
=> throw new NotImplementedException();
/// <summary>
/// 5.11.45. Read CFL alphas syntax.
/// Reads the joint signs and nonzero alpha magnitudes for chroma-from-luma prediction.
/// </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)
{
int jointSignPlus1 = reader.ReadChromFromLumaSign() + 1;
@ -1079,8 +1316,13 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.42. and 5.11.43.
/// Reads a directional intra-prediction angle adjustment when the block and mode permit one.
/// </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)
{
int angleDelta = 0;
@ -1093,12 +1335,20 @@ internal class Av1TileReader : IAv1TileReader
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)
=> mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
/// <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>
/// <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)
{
if (this.FrameHeader.SegmentationParameters.Enabled)
@ -1111,6 +1361,8 @@ internal class Av1TileReader : IAv1TileReader
int modeInfoCountX = Math.Min(this.FrameHeader.ModeInfoColumnCount - partitionInfo.ColumnIndex, blockWidth4x4);
int modeInfoCountY = Math.Min(this.FrameHeader.ModeInfoRowCount - partitionInfo.RowIndex, blockHeight4x4);
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++)
{
int[] segmentRow = this.segmentIds[partitionInfo.RowIndex + y];
@ -1122,8 +1374,11 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.9. Read segment ID syntax.
/// Predicts and, when required, decodes the segment identifier for an intra block.
/// </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)
{
int predictor;
@ -1166,7 +1421,9 @@ internal class Av1TileReader : IAv1TileReader
}
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 || prevU == prevL ? 1 : 0;
int lastActiveSegmentId = this.FrameHeader.SegmentationParameters.LastActiveSegmentId;
@ -1175,9 +1432,11 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// 5.11.56. Read CDEF syntax.
/// Reads the constrained directional enhancement filter strength for the block's 64x64 filter unit.
/// </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)
{
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);
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)
{
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)
{
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)
{
int segmentId = partitionInfo.ModeInfo.SegmentId;
@ -1254,8 +1525,11 @@ internal class Av1TileReader : IAv1TileReader
}
/// <summary>
/// SVT: read_delta_qindex
/// Reads and accumulates a superblock quantizer-index delta when the block carries one.
/// </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)
{
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) =>
columnIndex >= this.FrameHeader.TilesInfo.TileColumnCount &&
columnIndex < this.FrameHeader.TilesInfo.TileColumnCount &&
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>
/// SVT: partition_plane_context
/// Derives the partition entropy context from the current split bit of the above and left neighbors.
/// </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)
{
// 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 leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockInfo.ModeInfoPosition.Y) & Av1PartitionContext.Mask];
int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2();
@ -1306,6 +1582,15 @@ internal class Av1TileReader : IAv1TileReader
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)
{
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;
/// <summary>
/// Writes the partition, mode, transform, and coefficient syntax for one AV1 tile.
/// </summary>
internal partial class Av1TileWriter
{
// Generates 5 bit field in which each bit set to 1 represents
// a BlockSize partition 11111 means we split 128x128, 64x64, 32x32, 16x16
// and 8x8. 10000 means we just split the 128x128 to 64x64
/// <summary>
/// Maps each AV1 block size to the five-bit partition contexts written to its bottom and right edges.
/// </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 =
[
new(31, 31), // 4X4 - {0b11111, 0b11111}
@ -41,11 +48,21 @@ internal partial class Av1TileWriter
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];
/// <summary>
/// SVT: svt_aom_write_sb
/// Writes the partition tree and each final coding block for a superblock.
/// </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(
Av1PictureControlSet pcs,
Av1EntropyCodingContext ec_ctx,
@ -57,7 +74,8 @@ internal partial class Av1TileWriter
Av1SequenceControlSet scs = pcs.Sequence;
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;
uint finalBlockIndex = 0;
@ -67,7 +85,7 @@ internal partial class Av1TileWriter
bool check_blk_out_of_bound = !sb_geom.IsComplete;
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];
Av1BlockGeometry blk_geom = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blockIndex);
@ -75,9 +93,10 @@ internal partial class Av1TileWriter
Point blockOrigin = blk_geom.Origin;
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)
{
// 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) ||
((blockOrigin.Y + (blk_geom.BlockHeight / 2)) < 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(
pcs,
ref writer,
@ -136,7 +155,6 @@ internal partial class Av1TileWriter
partitionContextNeighbors);
}
// assert(blk_geom.Shape == PART_N);
Guard.IsTrue(Av1Math.Implies(bsize == Av1BlockSize.Block4x4, superblock.CodingUnitPartitionTypes[blockIndex] == Av1PartitionType.None), nameof(bsize), string.Empty);
switch (superblock.CodingUnitPartitionTypes[blockIndex])
{
@ -281,8 +299,15 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: encode_partition_av1
/// Writes a partition symbol using the above and left partition contexts available at a block origin.
/// </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(
Av1PictureControlSet pcs,
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(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);
if (!has_rows && !has_cols)
@ -347,8 +373,16 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: write_modes_b
/// Writes the segmentation, prediction, transform, coefficient, and filter syntax for one final coding block.
/// </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(
Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext,
@ -402,8 +436,6 @@ internal partial class Av1TileWriter
blk_ptr.MacroBlock.LeftMacroBlock = null;
}
// Not required, part of Av1SymbolEncoder.
// blk_ptr.MacroBlock.tile_ctx = frame_context;
SetModeInfoRowAndColumn(
pcs,
blk_ptr.MacroBlock,
@ -414,10 +446,8 @@ internal partial class Av1TileWriter
pcs.Parent.Common.ModeInfoRowCount,
pcs.Parent.Common.ModeInfoColumnCount);
// if (pcs.slice_type == I_SLICE)
// We implement only INTRA frames.
// This encoder path currently writes intra frames only, so every block follows the key-frame mode syntax.
{
// const int32_t skip = write_skip(cm, xd, mbmi->segment_id, mi, w)
if (pcs.Parent.FrameHeader.SegmentationParameters.Enabled && pcs.Parent.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip)
{
WriteSegmentId(pcs, ref writer, blockGeometry.BlockSize, blockOrigin, blk_ptr, skipWritingCoefficients);
@ -548,7 +578,6 @@ internal partial class Av1TileWriter
if (!skipWritingCoefficients)
{
// SVT: av1_encode_coeff_1d
EncodeCoefficients1d(
pcs,
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);
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(
ref Av1SymbolEncoder writer,
Av1MacroBlockModeInfo macroBlockModeInfo,
@ -604,26 +643,24 @@ internal partial class Av1TileWriter
}
/// <summary>
/// Get the contexts (left and top) for writing the intra luma mode for key frames.
/// Intended to be used for key frame only.
/// Gets the above and left key-frame contexts used to write an intra luma mode.
/// </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)
{
Av1PredictionMode intraLumaLeftMode = Av1PredictionMode.DC;
Av1PredictionMode intraLumaTopMode = Av1PredictionMode.DC;
if (xd.IsLeftAvailable)
{
// When called for key frame, neighbouring mode should be intra
// assert(!is_inter_block(&xd->mi[-1]->mbmi.block_mi) || is_intrabc_block(&xd->mi[-1]->mbmi.block_mi));
// Key-frame neighbors are intra blocks, so their luma modes directly select the context class.
intraLumaLeftMode = xd.ModeInfo[-1].MacroBlockModeInfo.Block.Mode;
}
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;
}
@ -632,8 +669,14 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: encode_intra_luma_mode_kf_av1
/// Writes the key-frame luma prediction mode and any directional angle adjustment.
/// </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(
ref Av1SymbolEncoder writer,
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(
Av1SequenceControlSet scs,
ref Av1SymbolEncoder writer,
@ -687,8 +740,14 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: svt_aom_filter_intra_allowed
/// Determines whether filter-intra syntax is available for a block mode.
/// </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(
bool enableFilterIntra,
Av1BlockSize blockSize,
@ -697,8 +756,12 @@ internal partial class Av1TileWriter
=> mode == Av1PredictionMode.DC && paletteSize == 0 && IsFilterIntraAllowedBlockSize(enableFilterIntra, blockSize);
/// <summary>
/// SVT: svt_aom_filter_intra_allowed_bsize
/// Determines whether filter-intra prediction is enabled for a block size.
/// </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)
{
if (!enableFilterIntra)
@ -710,8 +773,13 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: write_intrabc_info
/// Writes the intra-block-copy selection and displacement-vector syntax for a block.
/// </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(
ref Av1SymbolEncoder writer,
Av1MacroBlockModeInfo macroBlockModeInfo,
@ -734,14 +802,24 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: svt_aom_allow_intrabc
/// Determines whether the current frame permits intra block copy.
/// </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)
=> frameHeader.AllowScreenContentTools && frameHeader.AllowIntraBlockCopy;
/// <summary>
/// SVT: ec_update_neighbors
/// Updates partition and coefficient neighbor arrays after writing a block.
/// </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(
Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext,
@ -758,7 +836,7 @@ internal partial class Av1TileWriter
Av1MacroBlockModeInfo mbmi = pcs.GetMacroBlockModeInfo(blockOrigin);
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(
PartitionContextLookup[(int)blockSize].Above,
PartitionContextLookup[(int)blockSize].Left);
@ -771,6 +849,8 @@ internal partial class Av1TileWriter
Av1NeighborArrayUnit<Av1PartitionContext>.UnitMask.Left | Av1NeighborArrayUnit<Av1PartitionContext>.UnitMask.Top);
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;
Span<byte> dcSignSpan = new(ref dcSignLevelCoefficient);
@ -800,8 +880,12 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: svt_av1_allow_palette
/// Determines whether the encoder palette level and block dimensions permit palette mode.
/// </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)
{
Guard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize));
@ -812,8 +896,12 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: svt_aom_allow_palette
/// Determines whether screen-content tools and block dimensions permit palette mode.
/// </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)
=> allowScreenContentTools &&
blockSize.GetWidth() <= 64 &&
@ -821,8 +909,15 @@ internal partial class Av1TileWriter
blockSize >= Av1BlockSize.Block8x8;
/// <summary>
/// SVT: write_cdef
/// Writes the constrained directional enhancement filter strength at its first coded block in a filter unit.
/// </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(
Av1SequenceControlSet scs,
Av1PictureControlSet pcs,
@ -836,7 +931,7 @@ internal partial class Av1TileWriter
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.YStrength[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)];
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 &&
(modeInfoPosition.X & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0)
{
// Top left?
pcs.CdefPreset[tileIndex][0] = -1;
pcs.CdefPreset[tileIndex][1] = -1;
pcs.CdefPreset[tileIndex][2] = -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 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>
/// SVT: set_mi_row_col
/// Populates a macroblock's frame edges, tile-neighbor availability, and rectangular-partition context.
/// </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(
Av1PictureControlSet pcs,
Av1MacroBlockD macroBlock,
@ -891,7 +994,7 @@ internal partial class Av1TileWriter
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.IsLeftAvailable = modeInfoPosition.X > tile.ModeInfoColumnStart;
macroBlock.ModeInfo = pcs.GetFromModeInfoGrid(modeInfoPosition);
@ -918,9 +1021,8 @@ internal partial class Av1TileWriter
macroBlock.IsSecondRectangle = false;
if (macroBlock.N8Size.Width < macroBlock.N8Size.Height)
{
// Only mark is_sec_rect as 1 for the last block.
// For PARTITION_VERT_4, it would be (0, 0, 0, 1);
// For other partitions, it would be (0, 1).
// Only the last sub-block of a rectangular partition selects the secondary transform context.
// Vertical-four therefore maps to (0, 0, 0, 1), while two-way partitions map to (0, 1).
if (((modeInfoPosition.X + macroBlock.N8Size.Width) & (macroBlock.N8Size.Height - 1)) == 0)
{
macroBlock.IsSecondRectangle = true;
@ -937,8 +1039,22 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: av1_encode_coeff_1d
/// Writes luma and chroma transform coefficients for a block in plane order.
/// </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(
Av1PictureControlSet pcs,
Av1EntropyCodingContext ec_ctx,
@ -987,8 +1103,19 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: av1_encode_tx_coef_y
/// Writes each luma transform block and updates its DC-sign and coefficient-level neighbor contexts.
/// </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(
Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext,
@ -1001,7 +1128,7 @@ internal partial class Av1TileWriter
Av1FrameBuffer<int> coeff_ptr,
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);
int tx_depth = 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];
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;
Guard.IsTrue(tx_type == Av1TransformType.DctDct, nameof(tx_type), string.Empty);
}
@ -1047,7 +1174,8 @@ internal partial class Av1TileWriter
frameHeader.UseReducedTransformSet,
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);
ReadOnlySpan<byte> dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelSpan);
@ -1064,8 +1192,20 @@ internal partial class Av1TileWriter
}
/// <summary>
/// SVT: av1_encode_tx_coef_uv
/// Writes both chroma transform blocks and updates their DC-sign and coefficient-level neighbor contexts.
/// </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(
Av1PictureControlSet pcs,
Av1EntropyCodingContext entropyCodingContext,
@ -1098,7 +1238,7 @@ internal partial class Av1TileWriter
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);
Av1TransformBlockContext blockContext = new();
Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][tx_index];
@ -1123,7 +1263,6 @@ internal partial class Av1TileWriter
frameHeader.UseReducedTransformSet,
blk_ptr.FilterIntraMode);
// cr
coeff_buffer = coeff_ptr.BufferCr!.DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv);
blockContext = new();
int endOfBlockCr = blk_ptr.TransformBlocks[tx_index].NzCoefficientCount[2];
@ -1148,7 +1287,7 @@ internal partial class Av1TileWriter
frameHeader.UseReducedTransformSet,
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);
ReadOnlySpan<byte> dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelCbSpan);
cb_dc_sign_level_coeff_na.UnitModeWrite(
@ -1157,7 +1296,6 @@ internal partial class Av1TileWriter
new Size(transformWidth, transformHeight),
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);
dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelCrSpan);
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;
/// <summary>
/// SVT: svt_aom_get_txb_ctx
/// Derives coefficient skip and DC-sign contexts from the transform block's above and left neighbors.
/// </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(
Av1PictureControlSet pcs,
Av1ComponentType plane,
@ -1207,6 +1358,8 @@ internal partial class Av1TileWriter
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)
{
do
@ -1253,6 +1406,7 @@ internal partial class Av1TileWriter
}
else
{
// Luma skip contexts depend on the minimum and union of the clipped edge levels.
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]
];
@ -1292,6 +1446,8 @@ internal partial class Av1TileWriter
}
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_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)
{
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);
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);
block.SegmentId = spatial_pred;
return;
@ -1346,17 +1512,24 @@ internal partial class Av1TileWriter
}
/// <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>
/// <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(
Av1PictureControlSet pcs,
Av1MacroBlockD xd,
Point blockOrigin,
out int cdf_index)
{
int prev_ul = -1; // top left segment_id
int prev_l = -1; // left segment_id
int prev_u = -1; // top segment_id
const int unavailableSegmentId = -1;
int prev_ul = unavailableSegmentId;
int prev_l = unavailableSegmentId;
int prev_u = unavailableSegmentId;
int mi_col = blockOrigin.X >> 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));
}
// Pick CDF index based on number of matching/out-of-bounds segment IDs.
// Edge case
// The entropy context records whether zero, two, or all three neighboring IDs agree.
// Any unavailable neighbor falls back to the least-specific context.
if (prev_ul < 0 || prev_u < 0 || prev_l < 0)
{
cdf_index = 0;
@ -1399,15 +1572,13 @@ internal partial class Av1TileWriter
cdf_index = 0;
}
// If 2 or more are identical returns that as predictor, otherwise prev_l.
// edge case
if (prev_u == -1)
// Select the majority value when possible; otherwise AV1 gives the left neighbor precedence.
if (prev_u == unavailableSegmentId)
{
return prev_l == -1 ? 0 : prev_l;
return prev_l == unavailableSegmentId ? 0 : prev_l;
}
// edge case
if (prev_l == -1)
if (prev_l == unavailableSegmentId)
{
return prev_u;
}
@ -1415,6 +1586,12 @@ internal partial class Av1TileWriter
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)
{
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;
/// <summary>
/// Carries the neighboring coefficient contexts used to entropy-code an AV1 transform block.
/// </summary>
internal class Av1TransformBlockContext
{
/// <summary>
/// Gets or sets the context used to decode the sign of the DC coefficient.
/// </summary>
public int DcSignContext { get; set; }
/// <summary>
/// Gets or sets the neighboring transform-block skip context.
/// </summary>
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;
/// <summary>
/// Information of a single Transform Block.
/// Describes the size, position, type, and residual state of one AV1 transform block.
/// </summary>
internal class Av1TransformInfo
{
/// <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>
public Av1TransformInfo()
: this(Av1TransformSize.Size4x4, 0, 0)
@ -21,6 +21,9 @@ internal class Av1TransformInfo
/// <summary>
/// Initializes a new instance of the <see cref="Av1TransformInfo"/> class.
/// </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)
{
this.Size = size;
@ -40,35 +43,35 @@ internal class Av1TransformInfo
}
/// <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>
public Av1TransformSize Size { get; internal set; }
/// <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>
public Av1TransformType Type { get; internal set; }
/// <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>
public int OffsetX { get; internal set; }
/// <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>
public int OffsetY { get; internal set; }
/// <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">
/// <item>
/// <term>false</term>
/// <description>No residual for the block</description>
/// <description>The block has no residual.</description>
/// </item>
/// <item>
/// <term>true</term>
/// <description>Residual exists for the block</description>
/// <description>The block has a residual.</description>
/// </item>
/// </list>
/// </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;
/// <summary>
/// Stores the transform syntax and coefficient range for one AV1 transform unit.
/// </summary>
internal class Av1TransformUnit
{
/// <summary>
/// Gets the nonzero-coefficient count for each color plane.
/// </summary>
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];
}

Loading…
Cancel
Save