diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index 3f0473150..7bf346dc6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/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; +/// +/// Stores block-size, prediction-mode, transform, and palette decisions shared by AV1 block processing. +/// internal class Av1BlockModeInfo { + /// + /// Stores the palette size for luma and for the shared chroma mode. + /// private int[] paletteSize; + /// + /// Initializes a new instance of the class. + /// + /// The number of color planes in the decoded frame. + /// The decoded block size. + /// The block origin relative to its superblock in 4x4 mode-information units. public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point positionInSuperblock) { this.BlockSize = blockSize; @@ -20,6 +32,9 @@ internal class Av1BlockModeInfo this.TransformUnitsCount = new int[numPlanes - 1]; } + /// + /// Gets the decoded block size. + /// public Av1BlockSize BlockSize { get; } /// @@ -27,12 +42,24 @@ internal class Av1BlockModeInfo /// public Av1PredictionMode YMode { get; set; } + /// + /// Gets or sets a value indicating whether residual coefficients are omitted for the block. + /// public bool Skip { get; set; } + /// + /// Gets or sets the partition type that produced the block. + /// public Av1PartitionType PartitionType { get; set; } + /// + /// Gets or sets a value indicating whether compound skip mode is selected. + /// public bool SkipMode { get; set; } + /// + /// Gets or sets the segmentation identifier assigned to the block. + /// public int SegmentId { get; set; } /// @@ -40,31 +67,64 @@ internal class Av1BlockModeInfo /// public Av1PredictionMode UvMode { get; set; } + /// + /// Gets or sets a value indicating whether intra block copy is selected. + /// public bool UseUltraBlockCopy { get; set; } + /// + /// Gets or sets the packed chroma-from-luma alpha magnitude indices. + /// public int ChromaFromLumaAlphaIndex { get; set; } + /// + /// Gets or sets the joint chroma-from-luma alpha sign value. + /// public int ChromaFromLumaAlphaSign { get; set; } + /// + /// Gets or sets the directional prediction angle adjustments for the chroma planes. + /// public int[] AngleDelta { get; set; } /// - /// 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. /// public Point PositionInSuperblock { get; } + /// + /// Gets or sets the filter-intra syntax for the block. + /// public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } /// - /// Gets the index of the first of this Mode Info in the . + /// Gets the plane-relative index of the first for this block. /// public int[] FirstTransformLocation { get; } + /// + /// Gets or sets the number of transform units for luma and for each chroma plane. + /// public int[] TransformUnitsCount { get; internal set; } + /// + /// Gets the palette size for the specified color plane. + /// + /// The color plane. + /// The palette size for the plane. public int GetPaletteSize(Av1Plane plane) => this.paletteSize[Math.Min(1, (int)plane)]; + /// + /// Gets the palette size for the specified plane class. + /// + /// The luma or chroma plane class. + /// The palette size for the plane class. public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType]; + /// + /// Sets the luma and shared chroma palette sizes. + /// + /// The luma palette size. + /// The palette size shared by the chroma planes. public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs index b8546e72b..2490f254c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs @@ -3,12 +3,38 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Identifies the luma or chroma component class used by AV1 entropy contexts. +/// internal enum Av1ComponentType { - Luminance = 0, // luma - Chroma = 1, // chroma (Cb+Cr) - ChromaCb = 2, // chroma Cb - ChromaCr = 3, // chroma Cr - All = 4, // Y+Cb+Cr + /// + /// The luma component. + /// + Luminance = 0, + + /// + /// Both chroma components. + /// + Chroma = 1, + + /// + /// The blue-difference chroma component. + /// + ChromaCb = 2, + + /// + /// The red-difference chroma component. + /// + ChromaCr = 3, + + /// + /// The luma and both chroma components. + /// + All = 4, + + /// + /// No component. + /// None = 15 } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs index 482b5c26b..949e5f23c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs +++ b/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; +/// +/// Stores encoder-selected prediction, transform, skip, and palette state for one block. +/// internal class Av1EncoderBlockModeInfo { + /// + /// Gets the encoded block size. + /// public Av1BlockSize BlockSize { get; } + /// + /// Gets the selected luma prediction mode. + /// public Av1PredictionMode PredictionMode { get; } + /// + /// Gets the partition type that produced the block. + /// public Av1PartitionType PartitionType { get; } + /// + /// Gets the selected chroma prediction mode. + /// public Av1PredictionMode UvPredictionMode { get; } + /// + /// Gets a value indicating whether residual coefficients are omitted for the block. + /// public bool Skip { get; } = true; + /// + /// Gets a value indicating whether compound skip mode is selected. + /// public bool SkipMode { get; } = true; + /// + /// Gets a value indicating whether intra block copy is selected. + /// public bool UseIntraBlockCopy { get; } = true; + /// + /// Gets the segmentation identifier assigned to the block. + /// public int SegmentId { get; } + /// + /// Gets or sets the transform-tree depth selected for the block. + /// public int TransformDepth { get; internal set; } + /// + /// Gets or sets the luma prediction mode written for the block. + /// public Av1PredictionMode Mode { get; internal set; } + /// + /// Gets or sets the chroma prediction mode written for the block. + /// public Av1PredictionMode UvMode { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs index cd988c96e..a0e730c75 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs @@ -3,21 +3,48 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Stores encoder block geometry and its selected coding-mode information. +/// internal class Av1EncoderBlockStruct { + /// + /// Gets the transform-unit state in transform traversal order. + /// public Av1TransformUnit[] TransformBlocks { get; } = new Av1TransformUnit[Av1Constants.MaxTransformUnitCount]; + /// + /// Gets or sets the macroblock edge and neighbor state used while writing the block. + /// public required Av1MacroBlockD MacroBlock { get; set; } + /// + /// Gets or sets the index used to resolve the block geometry from mode-decision scan order. + /// public int ModeDecisionScanIndex { get; set; } + /// + /// Gets or sets the quantizer index used for the block. + /// public int QuantizationIndex { get; set; } + /// + /// Gets or sets the segmentation identifier assigned to the block. + /// public int SegmentId { get; set; } + /// + /// Gets or sets the filter-intra mode selected for the block. + /// public Av1FilterIntraMode FilterIntraMode { get; set; } + /// + /// Gets or sets the palette size for luma and for the shared chroma mode. + /// public required int[] PaletteSize { get; internal set; } + /// + /// Gets or sets the encoder prediction-unit state for the block. + /// public required Av1EncoderPredictionUnit[] PredictionUnits { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCommon.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCommon.cs index 6efa09128..90eb8b152 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCommon.cs +++ b/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; +/// +/// Holds sequence, frame, and macroblock state shared across AV1 encoder stages. +/// internal class Av1EncoderCommon { + /// + /// Gets or sets the frame height in 4x4 mode-information units. + /// public int ModeInfoRowCount { get; internal set; } + /// + /// Gets or sets the frame width in 4x4 mode-information units. + /// public int ModeInfoColumnCount { get; internal set; } + /// + /// Gets or sets the row stride of frame mode information in 4x4 units. + /// public int ModeInfoStride { get; internal set; } + /// + /// Gets or sets the coded frame dimensions. + /// public required ObuFrameSize FrameSize { get; internal set; } + /// + /// Gets or sets the tile layout for the current frame. + /// public required ObuTileGroupHeader TilesInfo { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs index 5b46389ef..a57247f2c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs @@ -3,11 +3,23 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Stores encoder-selected intra prediction modes and directional-angle adjustments for one block. +/// internal class Av1EncoderPredictionUnit { + /// + /// Gets or sets the directional angle adjustment for each prediction plane. + /// public required byte[] AngleDelta { get; set; } + /// + /// Gets or sets the chroma-from-luma alpha magnitude index. + /// public int ChromaFromLumaIndex { get; internal set; } + /// + /// Gets or sets the packed chroma-from-luma alpha signs for the U and V planes. + /// public int ChromaFromLumaSigns { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs index ead8f4462..a0873b1db 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs @@ -3,16 +3,34 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Provides tile-writer operations that maintain encoder entropy-neighbor state. +/// internal partial class Av1TileWriter { + /// + /// Tracks the mode and coefficient positions while entropy-coding one AV1 superblock. + /// internal class Av1EntropyCodingContext { + /// + /// Gets or sets the macroblock mode information currently being encoded. + /// public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; } + /// + /// Gets or sets the pixel origin of the current superblock. + /// public Point SuperblockOrigin { get; internal set; } + /// + /// Gets or sets the number of luma coefficient positions consumed in the current superblock. + /// public int CodedAreaSuperblock { get; internal set; } + /// + /// Gets or sets the number of chroma coefficient positions consumed in the current superblock. + /// public int CodedAreaSuperblockUv { get; internal set; } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs index f536982e0..17c08e5ac 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs @@ -3,12 +3,38 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Identifies the filter-intra predictor kernel selected for an AV1 block. +/// internal enum Av1FilterIntraMode { + /// + /// The filter-intra DC predictor. + /// DC, + + /// + /// The filter-intra vertical predictor. + /// Vertical, + + /// + /// The filter-intra horizontal predictor. + /// Horizontal, + + /// + /// The filter-intra 157-degree directional predictor. + /// Directional157, + + /// + /// The filter-intra Paeth predictor. + /// Paeth, + + /// + /// The number of filter-intra modes. + /// AllFilterIntraModes, } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs index 0a2e2e832..c27af0242 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs +++ b/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; +/// +/// Provides validity checks for AV1 filter-intra modes. +/// internal static class Av1FilterIntraModeExtensions { + /// + /// Maps filter-intra syntax values to the directional mode used by the predictor. + /// private static readonly Av1PredictionMode[] IntraDirection = [Av1PredictionMode.DC, Av1PredictionMode.Vertical, Av1PredictionMode.Horizontal, Av1PredictionMode.Directional157Degrees, Av1PredictionMode.DC]; + /// + /// Gets the intra-prediction direction associated with the specified filter-intra mode. + /// + /// The filter-intra mode. + /// The corresponding intra-prediction direction. public static Av1PredictionMode ToIntraDirection(this Av1FilterIntraMode mode) => IntraDirection[(int)mode]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs index ee2bfb2f7..f8ff7b691 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs +++ b/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; /// -/// Collection of all information for a single frame. +/// Owns the mode, transform, coefficient, quantizer, and filter state decoded for one AV1 frame. /// internal partial class Av1FrameInfo { - // Number of Coefficients in a single ModeInfo 4x4 block of pixels (1 length + 4 x 4). + /// + /// The coefficient slots reserved for one 4x4 mode-information unit: one end index followed by 16 coefficients. + /// public const int CoefficientCountPerModeInfo = 1 + 16; + /// + /// Stores raster-ordered luma coefficients for every frame superblock. + /// private readonly int[] coefficientsY = []; + + /// + /// Stores raster-ordered blue-difference chroma coefficients for every frame superblock. + /// private readonly int[] coefficientsU = []; + + /// + /// Stores raster-ordered red-difference chroma coefficients for every frame superblock. + /// private readonly int[] coefficientsV = []; + + /// + /// The width and height of a superblock in 4x4 mode-information units. + /// private readonly int modeInfoSizePerSuperblock; + + /// + /// The number of 4x4 mode-information positions in one square superblock. + /// private readonly int modeInfoCountPerSuperblock; + + /// + /// The number of columns in the frame superblock grid. + /// private readonly int superblockColumnCount; + + /// + /// The number of rows in the frame superblock grid. + /// private readonly int superblockRowCount; + + /// + /// The base-2 reduction from luma coefficient capacity to per-chroma-plane capacity. + /// private readonly int subsamplingFactor; + + /// + /// Stores one addressing view for each frame superblock. + /// private readonly Av1SuperblockInfo[] superblockInfos; + + /// + /// Stores decoded block mode information in bitstream traversal order. + /// private readonly Av1BlockModeInfo[] modeInfos; + + /// + /// Maps every frame-relative 4x4 position to its covering entry in . + /// private readonly Av1FrameModeInfoMap modeInfoMap; + + /// + /// Stores luma transform information grouped by superblock. + /// private readonly Av1TransformInfo[] transformInfosY; + + /// + /// Stores both chroma planes' transform information grouped by superblock. + /// private readonly Av1TransformInfo[] transformInfosUv; + + /// + /// Stores the quantizer-index delta for each frame superblock. + /// private readonly int[] deltaQ; + + /// + /// The base-2 number of constrained directional enhancement filter entries allocated per superblock. + /// private readonly int cdefStrengthFactorLog2; + + /// + /// Stores constrained directional enhancement filter strengths grouped by superblock. + /// private readonly int[] cdefStrength; + + /// + /// The base-2 number of loop-filter delta values stored per superblock. + /// private readonly int deltaLoopFactorLog2 = 2; + + /// + /// Stores the four loop-filter delta values for each superblock. + /// private readonly int[] deltaLoopFilter; + /// + /// Initializes a new instance of the class. + /// + /// The sequence header defining maximum dimensions, superblock size, and color sampling. 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 } /// - /// Gets the number of mode info blocks in a single superblock. + /// Gets the total mode-information capacity allocated for the frame. /// public int ModeInfoCount => this.modeInfos.Length; /// - /// 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. /// public int SuperblockModeInfoSize => this.modeInfoSizePerSuperblock; + /// + /// Gets the superblock view at the specified frame-grid position. + /// + /// The position in the frame superblock grid. + /// The superblock view. public Av1SuperblockInfo GetSuperblock(Point index) { Span span = this.superblockInfos; @@ -101,8 +185,19 @@ internal partial class Av1FrameInfo return span[i]; } + /// + /// Gets the mode information covering the origin of a specified superblock. + /// + /// The position in the frame superblock grid. + /// The mode information covering the superblock origin. public Av1BlockModeInfo GetModeInfo(Point superblockIndex) => this.GetModeInfo(superblockIndex, Point.Empty); + /// + /// Gets the mode information covering a position relative to a specified superblock. + /// + /// The position in the frame superblock grid. + /// The position within the superblock in 4x4 mode-information units. + /// The mode information covering the position. public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex) { Point location = this.GetModeInfoPosition(superblockIndex, modeInfoIndex); @@ -113,11 +208,16 @@ internal partial class Av1FrameInfo /// /// Gets the mode information record covering the specified frame-relative mode information position. /// + /// The frame-relative position in 4x4 mode-information units. + /// The mode information covering the position. public Av1BlockModeInfo GetModeInfoAt(Point modeInfoPosition) => this.modeInfos[this.modeInfoMap[modeInfoPosition]]; /// /// Gets the mode information records parsed for the specified superblock in bitstream order. /// + /// The position in the frame superblock grid. + /// The number of parsed records to return. + /// The parsed mode-information records. public Span 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); } + /// + /// Gets the transform-information storage for one plane of a specified superblock. + /// + /// The zero-based plane index. + /// The position in the frame superblock grid. + /// The luma storage for plane zero; otherwise, the shared chroma storage. public Span GetSuperblockTransform(int plane, Point index) { if (plane == 0) @@ -135,6 +241,11 @@ internal partial class Av1FrameInfo return this.GetSuperblockTransformUv(index); } + /// + /// Gets the luma transform-information storage for a specified superblock. + /// + /// The position in the frame superblock grid. + /// The superblock luma transform-information span. public Span GetSuperblockTransformY(Point index) { Span span = this.transformInfosY; @@ -142,6 +253,11 @@ internal partial class Av1FrameInfo return span.Slice(offset, this.modeInfoCountPerSuperblock); } + /// + /// Gets the shared chroma transform-information storage for a specified superblock. + /// + /// The position in the frame superblock grid. + /// The superblock chroma transform-information span. public Span GetSuperblockTransformUv(Point index) { Span span = this.transformInfosUv; @@ -149,6 +265,11 @@ internal partial class Av1FrameInfo return span.Slice(offset, this.modeInfoCountPerSuperblock << 1); } + /// + /// Gets the luma coefficient storage for a specified superblock. + /// + /// The position in the frame superblock grid. + /// The superblock luma coefficient span. public Span GetCoefficientsY(Point index) { Span span = this.coefficientsY; @@ -157,6 +278,11 @@ internal partial class Av1FrameInfo return span.Slice(superblock * count, count); } + /// + /// Gets the blue-difference chroma coefficient storage for a specified superblock. + /// + /// The position in the frame superblock grid. + /// The superblock blue-difference chroma coefficient span. public Span GetCoefficientsU(Point index) { Span span = this.coefficientsU; @@ -165,6 +291,11 @@ internal partial class Av1FrameInfo return span.Slice(superblock * count, count); } + /// + /// Gets the red-difference chroma coefficient storage for a specified superblock. + /// + /// The position in the frame superblock grid. + /// The superblock red-difference chroma coefficient span. public Span GetCoefficientsV(Point index) { Span span = this.coefficientsV; @@ -173,6 +304,11 @@ internal partial class Av1FrameInfo return span.Slice(superblock * count, count); } + /// + /// Gets a reference to the quantizer-index delta for a specified superblock. + /// + /// The position in the frame superblock grid. + /// A reference to the superblock quantizer-index delta. public ref int GetDeltaQuantizationIndex(Point index) { Span span = this.deltaQ; @@ -180,6 +316,11 @@ internal partial class Av1FrameInfo return ref span[i]; } + /// + /// Gets the constrained directional enhancement filter strengths for a specified superblock. + /// + /// The position in the frame superblock grid. + /// The superblock filter-strength span. public Span GetCdefStrength(Point index) { Span span = this.cdefStrength; @@ -187,6 +328,10 @@ internal partial class Av1FrameInfo return span.Slice(i, 1 << this.cdefStrengthFactorLog2); } + /// + /// Resets every constrained directional enhancement filter strength for a superblock to its unassigned value. + /// + /// The position in the frame superblock grid. internal void ClearCdef(Point index) { Span cdefs = this.GetCdefStrength(index); @@ -196,6 +341,11 @@ internal partial class Av1FrameInfo } } + /// + /// Gets the four loop-filter delta values for a specified superblock. + /// + /// The position in the frame superblock grid. + /// The superblock loop-filter delta span. public Span GetDeltaLoopFilter(Point index) { Span span = this.deltaLoopFilter; @@ -203,14 +353,28 @@ internal partial class Av1FrameInfo return span.Slice(i, 1 << this.deltaLoopFactorLog2); } + /// + /// Resets all frame loop-filter delta values to zero. + /// public void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); + /// + /// Stores decoded mode information and maps every 4x4 position covered by its block. + /// + /// The decoded block mode information. + /// The containing superblock. public void UpdateModeInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo) { this.modeInfos[this.modeInfoMap.NextIndex] = modeInfo; this.modeInfoMap.Update(this.GetModeInfoPosition(superblockInfo.Position, modeInfo.PositionInSuperblock), modeInfo.BlockSize); } + /// + /// Converts a superblock-relative mode-information position to frame-relative coordinates. + /// + /// The position in the frame superblock grid. + /// The position within the superblock in 4x4 units. + /// The frame-relative position in 4x4 mode-information units. private Point GetModeInfoPosition(Point superblockPosition, Point positionInSuperblock) { int x = (superblockPosition.X * this.modeInfoSizePerSuperblock) + positionInSuperblock.X; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs index c2448e5a0..97427b20f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs @@ -3,6 +3,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Provides frame-wide lookup and storage for decoded AV1 mode-information blocks. +/// internal partial class Av1FrameInfo { /// @@ -13,9 +16,20 @@ internal partial class Av1FrameInfo /// public class Av1FrameModeInfoMap { + /// + /// Stores the mode-information index assigned to each aligned 4x4 frame location. + /// private readonly ushort[] offsets; + + /// + /// The dimensions of in 4x4 mode-information units. + /// private readonly Size alignedModeInfoCount; + /// + /// Initializes a new instance of the class. + /// + /// The aligned frame dimensions in 4x4 mode-information units. public Av1FrameModeInfoMap(Size modeInfoCount) { this.alignedModeInfoCount = modeInfoCount; @@ -29,8 +43,9 @@ internal partial class Av1FrameInfo public int NextIndex { get; private set; } /// - /// Gets the mapped index for the given location. + /// Gets the mode-information index mapped to the specified 4x4 location. /// + /// The location in 4x4 mode-information units. public int this[Point location] { get @@ -40,16 +55,22 @@ internal partial class Av1FrameInfo } } + /// + /// Maps every 4x4 location covered by a decoded block to the next mode-information index. + /// + /// The block origin in 4x4 mode-information units. + /// The decoded block size. 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); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs index 649a069b0..52db23dd4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs @@ -3,9 +3,18 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Stores whether filter-intra prediction is active and which filter-intra mode is selected. +/// internal class Av1IntraFilterModeInfo { + /// + /// Gets or sets a value indicating whether filter-intra prediction is enabled for the block. + /// public bool UseFilterIntra { get; set; } + /// + /// Gets or sets the filter-intra mode selected for the block. + /// public Av1FilterIntraMode Mode { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs index a21116b6f..84bbe9cfb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs @@ -8,29 +8,61 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Owns the padded absolute-coefficient level plane used to derive AV1 coefficient entropy contexts. +/// internal sealed class Av1LevelBuffer : IDisposable { + /// + /// Owns the padded level storage until the buffer is disposed. + /// private IMemoryOwner? memory; + /// + /// Initializes a new instance of the class for the maximum AV1 transform size. + /// + /// The configuration providing the memory allocator. public Av1LevelBuffer(Configuration configuration) : this(configuration, new Size(Av1Constants.MaxTransformSize, Av1Constants.MaxTransformSize)) { } + /// + /// Initializes a new instance of the class for the specified coefficient dimensions. + /// + /// The configuration providing the memory allocator. + /// The unpadded coefficient dimensions. 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(this.Stride * totalHeight, AllocationOptions.Clean); } + /// + /// Gets the unpadded coefficient dimensions. + /// public Size Size { get; } + /// + /// Gets the padded row stride in bytes. + /// public int Stride { get; } + /// + /// Gets the coefficient level at the specified unpadded position. + /// + /// The coefficient position. public int this[Point position] => this.GetRow(position.Y)[position.X]; + /// + /// Initializes the unpadded level plane from raster-ordered coefficient magnitudes. + /// + /// The coefficient levels to copy. public void Initialize(Span 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 } } + /// + /// Converts a raster-order coefficient index to its two-dimensional position. + /// + /// The raster-order coefficient index. + /// The corresponding coefficient position. 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); } + /// + /// Gets a padded coefficient row for the specified position. + /// + /// A position whose vertical coordinate selects the row. + /// The selected row, including its horizontal context padding. public Span GetRow(Point pos) => this.GetRow(pos.Y); + /// + /// Gets a padded coefficient row by its unpadded vertical coordinate. + /// + /// The row coordinate, which may address the top context padding. + /// The selected row, including its horizontal context padding. public Span 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); } + /// public void Dispose() { this.memory?.Dispose(); this.memory = null; } + /// + /// Clears all coefficient levels and context padding. + /// internal void Clear() { ObjectDisposedException.ThrowIf(this.memory == null, this); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs index bb69f9ee8..e7edd0622 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs @@ -3,53 +3,87 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Holds decoder-side macroblock edges, plane state, and neighboring mode information. +/// internal class Av1MacroBlockD { + /// + /// Stores the mode-information entries exposed through . + /// private Av1ModeInfo[] modeInfo = []; + /// + /// Gets or sets the mode-information entries for the current block and its mapped neighbors. + /// public required Span 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); } } + /// + /// Gets or sets the tile containing the current block. + /// public required Av1TileInfo Tile { get; internal set; } + /// + /// Gets or sets a value indicating whether an above block is available within the tile. + /// public bool IsUpAvailable { get; internal set; } + /// + /// Gets or sets a value indicating whether a left block is available within the tile. + /// public bool IsLeftAvailable { get; internal set; } + /// + /// Gets or sets the above macroblock mode information, when available. + /// public Av1MacroBlockModeInfo? AboveMacroBlock { get; internal set; } + /// + /// Gets or sets the left macroblock mode information, when available. + /// public Av1MacroBlockModeInfo? LeftMacroBlock { get; internal set; } + /// + /// Gets or sets the row stride of the frame mode-information map. + /// public int ModeInfoStride { get; internal set; } /// - /// 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. /// public int ToTopEdge { get; internal set; } /// - /// 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. /// public int ToBottomEdge { get; internal set; } /// - /// 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. /// public int ToLeftEdge { get; internal set; } /// - /// 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. /// public int ToRightEdge { get; internal set; } + /// + /// Gets or sets the block dimensions in samples for rectangular-partition context selection. + /// public Size N8Size { get; internal set; } + /// + /// Gets or sets a value indicating whether this block is the second half of a rectangular partition. + /// public bool IsSecondRectangle { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs index 1d916a531..ba1f72fbe 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs @@ -3,11 +3,23 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Stores the encoder's selected modes and references for an AV1 macroblock. +/// internal class Av1MacroBlockModeInfo { + /// + /// Gets or sets the prediction, transform, and segmentation decisions for the block. + /// public required Av1EncoderBlockModeInfo Block { get; internal set; } + /// + /// Gets or sets the luma palette decisions for the block. + /// public required Av1PaletteLumaModeInfo Palette { get; internal set; } + /// + /// Gets or sets the constrained directional enhancement filter strength for the block. + /// public int CdefStrength { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs index ba6f3b084..cdd6732c4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs @@ -3,7 +3,13 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Stores the decoded prediction, segmentation, skip, and transform state for an AV1 mode-information block. +/// internal class Av1ModeInfo { + /// + /// Gets or sets the macroblock mode information associated with this map entry. + /// public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs index f4e2957a5..c49b4f115 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs @@ -6,15 +6,39 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Stores left, top, and top-left neighbor values at the granularity required by AV1 encoder contexts. +/// +/// The context value type, including its invalid sentinel value. internal class Av1NeighborArrayUnit where T : struct, IMinMaxValue { + /// + /// The sentinel used for neighbor positions that have not been populated. + /// public static readonly T InvalidNeighborData = T.MaxValue; + /// + /// Stores context units exposed to blocks on the right. + /// private readonly T[] left; + + /// + /// Stores context units exposed to blocks below. + /// private readonly T[] top; + + /// + /// Stores context units indexed by the diagonal difference between horizontal and vertical positions. + /// private readonly T[] topLeft; + /// + /// Initializes a new instance of the class. + /// + /// The number of values in the left-neighbor storage. + /// The number of values in the top-neighbor storage. + /// The number of values in the diagonal-neighbor storage. public Av1NeighborArrayUnit(int leftSize, int topSize, int topLeftSize) { this.left = new T[leftSize]; @@ -22,33 +46,87 @@ internal class Av1NeighborArrayUnit this.topLeft = new T[topLeftSize]; } + /// + /// Selects which neighbor arrays receive an update. + /// [Flags] public enum UnitMask { + /// + /// Update the left-neighbor storage. + /// Left = 1, + + /// + /// Update the top-neighbor storage. + /// Top = 2, + + /// + /// Update the top-left diagonal storage. + /// TopLeft = 4, } + /// + /// Gets the left-neighbor storage. + /// public Span Left => this.left; + /// + /// Gets the top-neighbor storage. + /// public Span Top => this.top; + /// + /// Gets the top-left diagonal storage. + /// public Span TopLeft => this.topLeft; + /// + /// Gets or sets the base-2 logarithm of the top and left context granularity in samples. + /// public required int GranularityNormalLog2 { get; set; } + /// + /// Gets or sets the base-2 logarithm of the diagonal context granularity in samples. + /// public required int GranularityTopLeftLog2 { get; set; } + /// + /// Gets the number of consecutive values stored for each neighbor-array unit. + /// public int UnitSize { get; private set; } + /// + /// Gets the left-neighbor unit index for a sample position. + /// + /// The sample position. + /// The left-neighbor unit index. public int GetLeftIndex(Point loc) => loc.Y >> this.GranularityNormalLog2; + /// + /// Gets the top-neighbor unit index for a sample position. + /// + /// The sample position. + /// The top-neighbor unit index. public int GetTopIndex(Point loc) => loc.X >> this.GranularityNormalLog2; + /// + /// Gets the diagonal-neighbor unit index for a sample position. + /// + /// The sample position. + /// The top-left neighbor index derived from the position's diagonal. public int GetTopLeftIndex(Point loc) => this.left.Length + (loc.X >> this.GranularityTopLeftLog2) - (loc.Y >> this.GranularityTopLeftLog2); + /// + /// Writes one context unit across the selected block edges. + /// + /// The values that make up one context unit. + /// The block origin in samples. + /// The block dimensions in samples. + /// The neighbor arrays to update. public void UnitModeWrite(ReadOnlySpan value, Point origin, Size blockSize, UnitMask mask) { int idx, j; @@ -83,7 +161,7 @@ internal class Av1NeighborArrayUnit 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 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 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 } } + /// + /// Writes a DC-sign context across selected block edges. + /// + /// The encoded DC-sign context. + /// The block origin in samples. + /// The block dimensions in samples. + /// The neighbor arrays to update. + /// The byte-specific write path is not implemented. internal void UnitModeWrite(Span dcSignSpan, Point blockOrigin, Size blockSize, Av1NeighborArrayUnit.UnitMask unitMask) => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs index aa1e1e94f..67e9e1b81 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs @@ -3,6 +3,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Reserves encoder-side state for AV1 luma palette mode decisions. +/// internal class Av1PaletteLumaModeInfo { } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index 0f6c4149c..91442267f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/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; +/// +/// Stores entropy, palette, partition, and transform contexts for 4-by-4 blocks above the current block. +/// 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. */ + /// + /// Stores DC-sign and cumulative coefficient-level contexts for each plane above the current block. + /// private readonly int[][] aboveContext = new int[Av1Constants.MaxPlanes][]; - /* Buffer holding the seg_id_predicted of the previous 4x4 block row. */ + /// + /// Stores segmentation-prediction contexts from the preceding 4x4 row. + /// private readonly int[] aboveSegmentIdPredictionContext; - /* Value of base colors for Y, U, and V */ + /// + /// Stores palette base colors for each plane above the current block. + /// private readonly int[][] abovePaletteColors = new int[Av1Constants.MaxPlanes][]; + /// + /// Stores compound-reference group contexts from the preceding 4x4 row. + /// private readonly int[] aboveCompGroupIndex; + /// + /// Initializes a new instance of the class. + /// + /// The number of color planes. + /// The frame width in 4x4 mode-information columns. public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount) { int wide64x64Count = Av1BlockSize.Block64x64.Get4x4WideCount(); @@ -46,8 +61,19 @@ internal class Av1ParseAboveNeighbor4x4Context /// public int[] AboveTransformWidth { get; } + /// + /// Gets the coefficient context row for the specified plane. + /// + /// The zero-based plane index. + /// The coefficient contexts for the plane. public int[] GetContext(int plane) => this.aboveContext[plane]; + /// + /// Resets above-neighbor state for the active tile-column range. + /// + /// The sequence header describing the color planes. + /// The first mode-information column in the tile. + /// The exclusive end mode-information column in the tile. 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); } + /// + /// Updates the above partition context for every 4x4 column covered by a block. + /// + /// The block origin in frame mode-information units. + /// The active tile boundaries. + /// The size produced by the decoded partition. + /// The parent block size. 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); } + /// + /// Updates the above transform-size context for every 4x4 column covered by a block. + /// + /// The block origin in frame mode-information units. + /// The active tile boundaries. + /// The selected transform size. + /// The decoded block size. + /// A value indicating whether the block omits residual coefficients. 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); } + /// + /// Clears a range of above coefficient contexts for one plane. + /// + /// The zero-based plane index. + /// The first context index to clear. + /// The number of context entries to clear. internal void ClearContext(int plane, int offset, int length) => Array.Fill(this.aboveContext[plane], 0, offset, length); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index 70f846d85..7159036ab 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/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; +/// +/// Stores entropy, palette, partition, and transform contexts for 4-by-4 blocks left of the current block. +/// 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. */ + /// + /// Stores DC-sign and cumulative coefficient-level contexts for each plane left of the current block. + /// private readonly int[][] leftContext = new int[Av1Constants.MaxPlanes][]; - /* Buffer holding the seg_id_predicted of the previous 4x4 block row. */ + /// + /// Stores segmentation-prediction contexts for the current superblock row. + /// private readonly int[] leftSegmentIdPredictionContext; - /* Value of base colors for Y, U, and V */ + /// + /// Stores palette base colors for each plane left of the current block. + /// private readonly int[][] leftPaletteColors = new int[Av1Constants.MaxPlanes][]; + /// + /// Stores compound-reference group contexts for the current superblock row. + /// private readonly int[] leftCompGroupIndex; + /// + /// Initializes a new instance of the class. + /// + /// The number of color planes. + /// The superblock height in 4x4 mode-information rows. public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSize) { this.LeftTransformHeight = new int[superblockModeInfoSize]; @@ -47,6 +62,10 @@ internal class Av1ParseLeftNeighbor4x4Context /// public int[] LeftTransformHeight { get; } + /// + /// Resets all left-neighbor state for a new superblock row. + /// + /// The sequence header describing the superblock size and color planes. public void Clear(ObuSequenceHeader sequenceHeader) { int blockCount = sequenceHeader.SuperblockModeInfoSize; @@ -64,8 +83,16 @@ internal class Av1ParseLeftNeighbor4x4Context Array.Fill(this.leftCompGroupIndex, 0, 0, blockCount); } + /// + /// Updates the left partition context for every 4x4 row covered by a block. + /// + /// The block origin in frame mode-information units. + /// The active superblock location. + /// The size produced by the decoded partition. + /// The parent block size. 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); } + /// + /// Updates the left transform-size context for every 4x4 row covered by a block. + /// + /// The block origin in frame mode-information units. + /// The active superblock location. + /// The selected transform size. + /// The decoded block size. + /// A value indicating whether the block omits residual coefficients. 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); } + /// + /// Clears a range of left coefficient contexts for one plane. + /// + /// The zero-based plane index. + /// The first context index to clear. + /// The number of context entries to clear. internal void ClearContext(int plane, int offset, int length) => Array.Fill(this.leftContext[plane], 0, offset, length); + /// + /// Gets the coefficient context column for the specified plane. + /// + /// The zero-based plane index. + /// The coefficient contexts for the plane. internal int[] GetContext(int plane) => this.leftContext[plane]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs index 73b3d6757..e2fff6914 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs +++ b/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 +/// +/// Stores the above and left five-bit AV1 partition contexts for a mode-information position. +/// +/// +/// Each set bit records a split at one block-size level. For example, 11111 records splits from +/// 128 by 128 through 8 by 8, while 10000 records only the 128 by 128 split. +/// internal struct Av1PartitionContext : IMinMaxValue { + /// + /// Maps each block size to the five-bit context stored for an above neighbor. + /// 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]; + /// + /// Maps each block size to the five-bit context stored for a left neighbor. + /// 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 + /// + /// The mask used to convert a frame mode-information row to its position within a 128-sample superblock. + /// public const int Mask = (1 << (7 - 2)) - 1; + /// + /// Initializes a new instance of the struct. + /// + /// The context stored for blocks below this block. + /// The context stored for blocks to the right of this block. public Av1PartitionContext(byte above, byte left) { this.Above = above; this.Left = left; } + /// + /// Gets the maximum representable partition context. + /// public static Av1PartitionContext MaxValue => throw new NotImplementedException(); + /// + /// Gets the minimum representable partition context. + /// public static Av1PartitionContext MinValue => throw new NotImplementedException(); + /// + /// Gets or sets the five-bit context derived from the left neighbor. + /// public byte Left { get; internal set; } + /// + /// Gets or sets the five-bit context derived from the above neighbor. + /// public byte Above { get; internal set; } + /// + /// Gets the above-neighbor partition context for the specified block size. + /// + /// The block size. + /// The five-bit above-neighbor context. public static int GetAboveContext(Av1BlockSize blockSize) => AboveLookup[(int)blockSize]; + /// + /// Gets the left-neighbor partition context for the specified block size. + /// + /// The block size. + /// The five-bit left-neighbor context. public static int GetLeftContext(Av1BlockSize blockSize) => LeftLookup[(int)blockSize]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index 5f6fadbd4..bb6862e3c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/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; +/// +/// Describes a decoded AV1 partition's block geometry, neighbors, and frame-boundary availability. +/// internal class Av1PartitionInfo { + /// + /// Initializes a new instance of the class. + /// + /// The decoded mode information for the partition block. + /// The containing superblock. + /// A value indicating whether the partition has chroma samples. + /// The partition type that produced the block. 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]; } + /// + /// Gets the decoded block mode information. + /// public Av1BlockModeInfo ModeInfo { get; } /// @@ -27,8 +40,14 @@ internal class Av1PartitionInfo /// public Av1SuperblockInfo SuperblockInfo { get; } + /// + /// Gets a value indicating whether the partition has chroma samples at its current luma position. + /// public bool IsChroma { get; } + /// + /// Gets the partition type that produced the block. + /// public Av1PartitionType Type { get; } /// @@ -61,32 +80,77 @@ internal class Av1PartitionInfo /// public int RowIndex { get; set; } + /// + /// Gets or sets the mode information covering the immediately above luma neighbor. + /// public Av1BlockModeInfo? AboveModeInfo { get; set; } + /// + /// Gets or sets the mode information covering the immediately left luma neighbor. + /// public Av1BlockModeInfo? LeftModeInfo { get; set; } + /// + /// Gets or sets the mode information covering the above chroma neighbor. + /// public Av1BlockModeInfo? AboveModeInfoForChroma { get; set; } + /// + /// Gets or sets the mode information covering the left chroma neighbor. + /// public Av1BlockModeInfo? LeftModeInfoForChroma { get; set; } + /// + /// Gets or sets the constrained directional enhancement filter strengths associated with the block. + /// public int[] CdefStrength { get; set; } + /// + /// Gets or sets the reference-frame identifiers selected for the block. + /// public int[] ReferenceFrame { get; set; } + /// + /// Gets the signed distance from the block to the left frame edge in one-eighth-sample units. + /// public int ModeBlockToLeftEdge { get; private set; } + /// + /// Gets the signed distance from the block to the right frame edge in one-eighth-sample units. + /// public int ModeBlockToRightEdge { get; private set; } + /// + /// Gets the signed distance from the block to the top frame edge in one-eighth-sample units. + /// public int ModeBlockToTopEdge { get; private set; } + /// + /// Gets the signed distance from the block to the bottom frame edge in one-eighth-sample units. + /// public int ModeBlockToBottomEdge { get; private set; } + /// + /// Gets the block width in samples for each color plane. + /// public int[] WidthInPixels { get; private set; } + /// + /// Gets the block height in samples for each color plane. + /// public int[] HeightInPixels { get; private set; } + /// + /// Gets or sets the neighboring luma samples used by chroma-from-luma prediction. + /// public Av1ChromaFromLumaContext? ChromaFromLumaContext { get; internal set; } + /// + /// Computes tile-neighbor availability, frame-edge distances, and per-plane block dimensions. + /// + /// The sequence header describing color subsampling. + /// The frame header describing coded dimensions. + /// The active tile boundaries. 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; } + /// + /// Resolves the decoded luma and chroma mode information for available above and left neighbors. + /// + /// The color-plane subsampling configuration. public void PopulateModeInfoNeighbors(ObuColorConfig colorConfig) { if (this.AvailableAbove) @@ -154,6 +220,12 @@ internal class Av1PartitionInfo } } + /// + /// Gets the block width clipped to the right frame edge. + /// + /// The luma block size. + /// A value indicating whether the target plane is horizontally subsampled. + /// The clipped width in 4x4 units of the target plane. public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX) { int maxBlockWide = blockSize.GetWidth(); @@ -166,6 +238,12 @@ internal class Av1PartitionInfo return maxBlockWide >> 2; } + /// + /// Gets the block height clipped to the bottom frame edge. + /// + /// The luma block size. + /// A value indicating whether the target plane is vertically subsampled. + /// The clipped height in 4x4 units of the target plane. public int GetMaxBlockHigh(Av1BlockSize blockSize, bool subY) { int maxBlockHigh = blockSize.GetHeight(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs index 7e6978eda..e347ab95a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs @@ -5,70 +5,131 @@ using System; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Holds the coding decisions, buffers, and sequence context for one AV1 picture pass. +/// internal class Av1PictureControlSet { + /// + /// Gets or sets the partition neighbor contexts for each tile. + /// public required Av1NeighborArrayUnit[] PartitionContexts { get; internal set; } + /// + /// Gets or sets the luma DC-sign and coefficient-level neighbor contexts for each tile. + /// public required Av1NeighborArrayUnit[] LuminanceDcSignLevelCoefficientNeighbors { get; internal set; } + /// + /// Gets or sets the red-difference chroma DC-sign and coefficient-level neighbor contexts for each tile. + /// public required Av1NeighborArrayUnit[] CrDcSignLevelCoefficientNeighbors { get; internal set; } + /// + /// Gets or sets the blue-difference chroma DC-sign and coefficient-level neighbor contexts for each tile. + /// public required Av1NeighborArrayUnit[] CbDcSignLevelCoefficientNeighbors { get; internal set; } + /// + /// Gets or sets the transform-function neighbor contexts for each tile. + /// public required Av1NeighborArrayUnit[] TransformFunctionContexts { get; internal set; } + /// + /// Gets or sets the sequence-wide encoder state. + /// public required Av1SequenceControlSet Sequence { get; internal set; } + /// + /// Gets or sets the parent picture state shared across coding passes. + /// public required Av1PictureParentControlSet Parent { get; internal set; } + /// + /// Gets or sets the frame segmentation identifiers used for spatial prediction. + /// public required byte[] SegmentationNeighborMap { get; internal set; } + /// + /// Gets the frame grid that maps each 4x4 position to its mode-information span. + /// public Av1ModeInfo[][] ModeInfoGrid { get; } = []; + /// + /// Gets or sets the contiguous mode-information storage addressed by . + /// public required Av1ModeInfo[] Mip { get; internal set; } + /// + /// Gets or sets the row stride of in 4x4 mode-information units. + /// 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. + /// + /// Gets or sets a value indicating whether the mode-information backing store uses 8x8 rather than 4x4 granularity. + /// public bool Disallow4x4AllFrames { get; internal set; } + /// + /// Gets or sets the constrained directional enhancement filter presets for each filter block. + /// public required int[][] CdefPreset { get; internal set; } + /// + /// Gets the mode-information span mapped to a frame position. + /// + /// The frame position in 4x4 mode-information units. + /// The mode-information span beginning at the position. public Span GetFromModeInfoGrid(Point position) => this.ModeInfoGrid[(position.Y * this.ModeInfoStride) + position.X]; + /// + /// Maps a frame position to the supplied mode-information span. + /// + /// The frame position in 4x4 mode-information units. + /// The mode-information entries to map. public void SetModeInfoGridRow(Point position, Span span) => this.SetModeInfoGridRow((position.Y * this.ModeInfoStride) + position.X, span); + /// + /// Maps a linear grid offset to the supplied mode-information span. + /// + /// The linear grid offset. + /// The mode-information entries to map. public void SetModeInfoGridRow(int offset, Span 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]); } /// - /// SVT: get_mbmi + /// Gets the macroblock mode information at a block origin and refreshes its grid mapping. /// + /// The block origin in 4x4 mode-information units. + /// The macroblock mode information at the origin. 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)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; } /// - /// SVT: svt_av1_update_segmentation_map + /// Writes a segment identifier to every segmentation-map entry covered by a block. /// + /// The block size. + /// The block origin in samples. + /// The segment identifier. 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); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs index 2317f97f7..d3922de77 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs +++ b/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; +/// +/// Holds encoder state that is shared by all coding passes for one AV1 picture. +/// internal class Av1PictureParentControlSet { + /// + /// Gets or sets frame dimensions and tile state shared by encoder stages. + /// public required Av1EncoderCommon Common { get; internal set; } + /// + /// Gets or sets the frame header being encoded. + /// public required ObuFrameHeader FrameHeader { get; internal set; } + /// + /// Gets or sets the preceding quantizer index for each tile context. + /// public required int[] PreviousQIndex { get; internal set; } + /// + /// Gets or sets the encoder palette-search level. + /// public int PaletteLevel { get; internal set; } + /// + /// Gets or sets the frame width aligned for superblock traversal. + /// public int AlignedWidth { get; internal set; } + /// + /// Gets or sets the frame height aligned for superblock traversal. + /// public int AlignedHeight { get; internal set; } + /// + /// Gets or sets the geometry state for each superblock in the picture. + /// public required Av1SuperblockGeometry[] SuperblockGeometry { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs index 3c790f509..f6b70e542 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs @@ -3,8 +3,18 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Identifies whether AV1 block processing targets the luma plane or either chroma plane. +/// internal enum Av1PlaneType : int { + /// + /// The luma plane. + /// Y, + + /// + /// Either chroma plane. + /// Uv } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs index e19b7ba7b..156badf07 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs +++ b/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; +/// +/// Holds encoder configuration and sequence-wide state shared by AV1 pictures. +/// internal class Av1SequenceControlSet { + /// + /// Gets or sets the sequence header that governs encoded pictures. + /// public required ObuSequenceHeader SequenceHeader { get; internal set; } + /// + /// Gets or sets the maximum number of encoded blocks allocated for a picture. + /// public int MaxBlockCount { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs index a4d146595..780de9dd0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs +++ b/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; +/// +/// Holds encoder-side block decisions and transform data for one AV1 superblock. +/// internal class Av1Superblock { + /// + /// Gets or sets the final encoder decisions in partition traversal order. + /// public required Av1EncoderBlockStruct[] FinalBlocks { get; set; } + /// + /// Gets or sets the tile containing the superblock. + /// public required Av1TileInfo TileInfo { get; set; } + /// + /// Gets or sets the selected partition type for each partition-tree node. + /// public required Av1PartitionType[] CodingUnitPartitionTypes { get; internal set; } + /// + /// Gets or sets the superblock index within the picture. + /// public int Index { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs index 6422b88cd..13e7f7df4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs @@ -3,7 +3,13 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Describes a superblock node's location, dimensions, and children in the encoder partition tree. +/// internal class Av1SuperblockGeometry { + /// + /// Gets or sets a value indicating whether the superblock lies completely within the coded frame. + /// public bool IsComplete { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index 99a84cb3d..c381c2236 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -3,10 +3,21 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Stores the partition tree and decoded mode information for one AV1 superblock. +/// internal class Av1SuperblockInfo { + /// + /// Provides the frame-owned arrays addressed by this superblock view. + /// private readonly Av1FrameInfo frameInfo; + /// + /// Initializes a new instance of the class. + /// + /// The owning frame information. + /// The superblock position in the frame superblock grid. public Av1SuperblockInfo(Av1FrameInfo frameInfo, Point position) { this.Position = position; @@ -14,39 +25,82 @@ internal class Av1SuperblockInfo } /// - /// Gets the position of this superblock inside the tile, counted in superblocks. + /// Gets the position of this superblock in the frame superblock grid. /// public Point Position { get; } /// - /// 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. /// public Point ModeInfoPosition => this.Position * this.frameInfo.SuperblockModeInfoSize; + /// + /// Gets a reference to the superblock quantizer-index delta. + /// public ref int SuperblockDeltaQ => ref this.frameInfo.GetDeltaQuantizationIndex(this.Position); + /// + /// Gets the mode information that covers the superblock origin. + /// public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(new Point(0, 0)); + /// + /// Gets the luma coefficient storage reserved for this superblock. + /// public Span CoefficientsY => this.frameInfo.GetCoefficientsY(this.Position); + /// + /// Gets the blue-difference chroma coefficient storage reserved for this superblock. + /// public Span CoefficientsU => this.frameInfo.GetCoefficientsU(this.Position); + /// + /// Gets the red-difference chroma coefficient storage reserved for this superblock. + /// public Span CoefficientsV => this.frameInfo.GetCoefficientsV(this.Position); + /// + /// Gets the constrained directional enhancement filter strengths for this superblock. + /// public Span CdefStrength => this.frameInfo.GetCdefStrength(this.Position); + /// + /// Gets the loop-filter deltas for this superblock. + /// public Span SuperblockDeltaLoopFilter => this.frameInfo.GetDeltaLoopFilter(this.Position); + /// + /// Gets or sets the next luma transform-information index while parsing this superblock. + /// public int TransformInfoIndexY { get; internal set; } + /// + /// Gets or sets the next shared chroma transform-information index while parsing this superblock. + /// public int TransformInfoIndexUv { get; internal set; } + /// + /// Gets or sets the number of mode-information records parsed for this superblock. + /// public int BlockCount { get; internal set; } + /// + /// Gets the luma transform-information storage reserved for this superblock. + /// + /// The superblock luma transform-information span. public Span GetTransformInfoY() => this.frameInfo.GetSuperblockTransformY(this.Position); + /// + /// Gets the shared chroma transform-information storage reserved for this superblock. + /// + /// The superblock chroma transform-information span. public Span GetTransformInfoUv() => this.frameInfo.GetSuperblockTransformUv(this.Position); + /// + /// Gets the transform-information storage for the specified color plane. + /// + /// The zero-based color-plane index. + /// The transform-information span for the plane. public Span GetTransformInfo(int plane) => this.frameInfo.GetSuperblockTransform(plane, this.Position); /// @@ -54,10 +108,25 @@ internal class Av1SuperblockInfo /// public Span GetModeInfos() => this.frameInfo.GetModeInfos(this.Position, this.BlockCount); + /// + /// Gets the mode information covering a position relative to this superblock. + /// + /// The position in 4x4 mode-information units relative to the superblock. + /// The mode information covering the position. public Av1BlockModeInfo GetModeInfo(Point index) => this.frameInfo.GetModeInfo(this.Position, index); + /// + /// Gets the mode information covering a frame-relative position. + /// + /// The frame-relative position in 4x4 mode-information units. + /// The mode information covering the position. public Av1BlockModeInfo GetModeInfoAt(Point index) => this.frameInfo.GetModeInfoAt(index); + /// + /// Gets the coefficient storage for the specified color plane. + /// + /// The color plane. + /// The coefficient span for the plane, or an empty span for an unsupported value. public Span GetCoefficients(Av1Plane plane) => plane switch { Av1Plane.Y => this.CoefficientsY, diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs index 52a6d0d70..b5cb18d5b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs +++ b/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; +/// +/// Describes one AV1 tile's superblock and mode-information boundaries. +/// internal class Av1TileInfo { + /// + /// Initializes a new instance of the class for the specified tile coordinates. + /// + /// The tile row index. + /// The tile column index. + /// The frame header that defines the tile layout. public Av1TileInfo(int row, int column, ObuFrameHeader frameHeader) { this.SetTileRow(frameHeader.TilesInfo, frameHeader.ModeInfoRowCount, row); this.SetTileColumn(frameHeader.TilesInfo, frameHeader.ModeInfoColumnCount, column); } + /// + /// Initializes a new instance of the class by copying another tile description. + /// + /// The tile description to copy. public Av1TileInfo(Av1TileInfo tileInfo) { this.ModeInfoColumnStart = tileInfo.ModeInfoColumnStart; @@ -22,16 +35,37 @@ internal class Av1TileInfo this.TileIndex = tileInfo.TileIndex; } + /// + /// Gets the first mode-information row in the tile. + /// public int ModeInfoRowStart { get; private set; } + /// + /// Gets the exclusive end mode-information row in the tile. + /// public int ModeInfoRowEnd { get; private set; } + /// + /// Gets the first mode-information column in the tile. + /// public int ModeInfoColumnStart { get; private set; } + /// + /// Gets the exclusive end mode-information column in the tile. + /// public int ModeInfoColumnEnd { get; private set; } + /// + /// Gets the tile column and row indices. + /// public Point TileIndex { get; private set; } + /// + /// Selects the tile row and updates its mode-information boundaries. + /// + /// The tile layout. + /// The coded frame height in mode-information rows. + /// The tile row index. public void SetTileRow(ObuTileGroupHeader tileGroupHeader, int modeInfoRowCount, int row) { this.ModeInfoRowStart = tileGroupHeader.TileRowStartModeInfo[row]; @@ -41,6 +75,12 @@ internal class Av1TileInfo this.TileIndex = loc; } + /// + /// Selects the tile column and updates its mode-information boundaries. + /// + /// The tile layout. + /// The coded frame width in mode-information columns. + /// The tile column index. public void SetTileColumn(ObuTileGroupHeader tileGroupHeader, int modeInfoColumnCount, int column) { this.ModeInfoColumnStart = tileGroupHeader.TileColumnStartModeInfo[column]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index cd34a8777..ac7c89309 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/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; +/// +/// Parses partition, mode, transform, and coefficient syntax for one AV1 tile. +/// internal class Av1TileReader : IAv1TileReader { + /// + /// The default self-guided restoration projection coefficients for each color plane. + /// private static readonly int[] SgrprojXqdMid = [-32, 31]; + + /// + /// The default Wiener restoration taps retained between restoration units. + /// private static readonly int[] WienerTapsMid = [3, -7, 15]; + + /// + /// Maps packed coefficient sign classes to their signed contribution to the DC context. + /// private static readonly int[] Signs = [0, -1, 1]; + + /// + /// Maps the summed neighboring DC signs to the AV1 DC-sign entropy context. + /// 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]; + /// + /// Maps the minimum and union of luma neighbor levels to a transform-block skip context. + /// 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]]; + /// + /// Stores the preceding self-guided restoration coefficients for each color plane. + /// private int[][] referenceSgrXqd = []; + + /// + /// Stores the preceding horizontal and vertical Wiener taps for each color plane. + /// private int[][][] referenceLrWiener = []; + + /// + /// Tracks entropy, partition, transform, and palette state above the current block. + /// private readonly Av1ParseAboveNeighbor4x4Context aboveNeighborContext; + + /// + /// Tracks entropy, partition, transform, and palette state left of the current block. + /// private readonly Av1ParseLeftNeighbor4x4Context leftNeighborContext; + + /// + /// The quantizer index carried between delta-quantized blocks in the current tile. + /// private int currentQuantizerIndex; + + /// + /// Stores the segment identifier covering each 4x4 frame position. + /// private readonly int[][] segmentIds = []; + + /// + /// Stores per-plane transform counts for each forced 64x64 residual region. + /// private readonly int[][] transformUnitCount; + + /// + /// Tracks the first unassigned transform-information index for luma and shared chroma storage. + /// private readonly int[] firstTransformOffset = new int[2]; + + /// + /// Tracks the next coefficient slot for each color plane within the current superblock. + /// private readonly int[] coefficientIndex = []; + + /// + /// Provides allocator and decoder configuration to tile entropy decoding. + /// private readonly Configuration configuration; + + /// + /// Reconstructs each parsed superblock when pixel decoding is requested; otherwise, tile parsing is metadata-only. + /// private readonly IAv1FrameDecoder? frameDecoder; + /// + /// Initializes a new instance of the class for syntax parsing without reconstruction. + /// + /// The decoder configuration. + /// The active AV1 sequence header. + /// The frame header whose tiles will be parsed. 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]; } + /// + /// Initializes a new instance of the class that reconstructs parsed superblocks. + /// + /// The decoder configuration. + /// The active AV1 sequence header. + /// The frame header whose tiles will be parsed. + /// The frame decoder that reconstructs each parsed superblock. public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1FrameDecoder frameDecoder) : this(configuration, sequenceHeader, frameHeader) => this.frameDecoder = frameDecoder; + /// + /// Gets the frame header whose tile syntax is being parsed. + /// public ObuFrameHeader FrameHeader { get; } + /// + /// Gets the sequence header governing the frame. + /// public ObuSequenceHeader SequenceHeader { get; } + /// + /// Gets the frame-owned mode, transform, coefficient, quantizer, and filter state populated by tile parsing. + /// public Av1FrameInfo FrameInfo { get; } /// - /// SVT: parse_tile + /// Parses one tile's partition, mode, transform, coefficient, and filter syntax in superblock order. /// + /// The entropy-coded tile payload. + /// The zero-based tile index in row-major order. + /// Corresponds to parse_tile in SVT-AV1. public void ReadTile(Span 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); } } } + /// + /// Resets all frame loop-filter delta state before parsing a tile. + /// private void ClearLoopFilterDelta() => this.FrameInfo.ClearDeltaLoopFilter(); + /// + /// Reads loop-restoration unit syntax that begins at a superblock location. + /// + /// The superblock origin in 4x4 mode-information units. + /// The superblock size. + /// A color plane signals a loop-restoration filter. 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."); } } } /// - /// 5.11.4. Decode partition syntax. + /// Decodes AV1 partition syntax and recursively visits each resulting coding block. /// + /// The tile symbol decoder. + /// The parent block origin in 4x4 mode-information units. + /// The parent block size. + /// The containing superblock. + /// The active tile boundaries. + /// Implements AV1 section 5.11.4. 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); } + /// + /// Parses all syntax associated with one final coding block and stores its frame mode information. + /// + /// The tile symbol decoder. + /// The block origin in 4x4 mode-information units. + /// The final block size. + /// The containing superblock. + /// The active tile boundaries. + /// The partition type that produced the block. 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); } /// - /// SVT: reset_skip_context + /// Clears coefficient neighbor contexts across every plane of a skipped block. /// + /// The skipped block and its frame position. + /// The active tile boundaries. + /// Corresponds to reset_skip_context in SVT-AV1. private void ResetSkipContext(Av1PartitionInfo partitionInfo, Av1TileInfo tileInfo) { int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; @@ -354,9 +471,14 @@ internal class Av1TileReader : IAv1TileReader } /// - /// 5.11.34. Residual syntax. + /// Parses every luma and chroma transform block and its coefficients for a coding block. /// - /// SVT: parse_residual + /// The tile symbol decoder. + /// The current coding block. + /// The containing superblock and coefficient storage. + /// The active tile boundaries. + /// The coding block size. + /// Implements AV1 section 5.11.34 and corresponds to parse_residual in SVT-AV1. 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 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 } } + /// + /// Determines whether a luma coding block owns chroma mode and residual syntax at its frame position. + /// + /// The sequence header describing chroma subsampling. + /// The block origin in 4x4 luma mode-information units. + /// The luma block size. + /// when the block is a chroma reference position; otherwise, . public static bool HasChroma(ObuSequenceHeader sequenceHeader, Point modeInfoLocation, Av1BlockSize blockSize) { int blockWide = blockSize.Get4x4WideCount(); @@ -474,10 +608,23 @@ internal class Av1TileReader : IAv1TileReader } /// - /// 5.11.35. Transform block syntax. + /// Derives a transform block's entropy context and decodes its coefficient syntax. /// + /// The tile symbol decoder. + /// The containing coding block. + /// The destination beginning at this transform's coefficient slot. + /// The transform geometry and syntax state to populate. + /// The zero-based color-plane index. + /// The transform's horizontal offset within the coding block in 4x4 units. + /// The transform's vertical offset within the coding block in 4x4 units. + /// The frame-relative transform column in 4x4 units of the target plane. + /// The frame-relative transform row in 4x4 units of the target plane. + /// The transform size. + /// A value indicating whether the target plane is horizontally subsampled. + /// A value indicating whether the target plane is vertically subsampled. + /// The decoded end-of-block coefficient position, or zero for an all-zero transform. /// - /// 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. /// private int ParseTransformBlock( ref Av1SymbolDecoder reader, @@ -517,10 +664,22 @@ internal class Av1TileReader : IAv1TileReader } /// - /// 5.11.39. Coefficients syntax. + /// Decodes transform coefficients and updates the coefficient neighbor contexts for one color plane. /// + /// The tile symbol decoder. + /// The containing coding block. + /// The frame-relative transform row in 4x4 units of the target plane. + /// The frame-relative transform column in 4x4 units of the target plane. + /// The horizontal transform offset within the coding block in 4x4 units. + /// The vertical transform offset within the coding block in 4x4 units. + /// The zero-based color-plane index. + /// The coefficient skip and DC-sign entropy contexts. + /// The transform size. + /// The transform syntax state to populate. + /// The destination beginning at this transform's coefficient slot. + /// The decoded end-of-block coefficient position, or zero for an all-zero transform. /// - /// 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. /// 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 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); } + /// + /// Derives coefficient skip and DC-sign contexts from the transform block's above and left neighbors. + /// + /// The transform size. + /// The zero-based color-plane index. + /// The containing block size on the target plane. + /// The transform height clipped to the frame in 4x4 units. + /// The transform width clipped to the frame in 4x4 units. + /// The frame-relative transform row in 4x4 units of the target plane. + /// The frame-relative transform column in 4x4 units of the target plane. + /// The derived transform-block entropy contexts. 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; } + /// + /// Determines whether the above and left edges contain nonzero chroma coefficient contexts. + /// + /// The transform size that selects how many edge entries to inspect. + /// The above coefficient contexts. + /// The left coefficient contexts. + /// The sum of the nonzero-above and nonzero-left flags. 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 } /// - /// 5.11.15. TX size syntax. + /// Selects the transform size for a coding block from lossless, explicit-selection, or maximum-size rules. /// + /// The tile symbol decoder. + /// The current coding block. + /// The containing superblock. + /// The active tile boundaries. + /// A value indicating whether transform-size selection syntax is allowed at this node. + /// The selected transform size. + /// Implements AV1 section 5.11.15. 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(); } + /// + /// Reads a transform size using the available above and left transform-size contexts. + /// + /// The tile symbol decoder. + /// The current coding block. + /// The containing superblock. + /// The active tile boundaries. + /// The decoded transform size. private Av1TransformSize ReadSelectedTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { int context = 0; @@ -792,22 +991,34 @@ internal class Av1TileReader : IAv1TileReader } /// - /// Section 5.11.16. Block TX size syntax. + /// Reads a coding block's transform size, updates neighbor contexts, and creates its transform geometry records. /// - /// SVT: read_block_tx_size + /// The tile symbol decoder. + /// The block origin in 4x4 mode-information units. + /// The current coding block. + /// The containing superblock. + /// The active tile boundaries. + /// Implements AV1 section 5.11.16 and corresponds to read_block_tx_size in SVT-AV1. 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); } + /// + /// Populates luma and chroma transform-information records in residual traversal order. + /// + /// The current coding block. + /// The containing superblock and transform storage. + /// The coding block size. + /// The selected luma transform size. 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 } /// - /// 5.11.49. Palette tokens syntax. + /// Reads luma and chroma palette-map tokens when a block selects palette prediction. /// + /// The tile symbol decoder. + /// The current coding block. + /// The block selects a nonempty luma or chroma palette. + /// Implements AV1 section 5.11.49. 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(); } } /// - /// 5.11.6. Mode info syntax. + /// Reads the prediction, segmentation, skip, quantizer, and filter mode information for a still-image block. /// + /// The tile symbol decoder. + /// The current coding block. + /// Implements the intra-frame branch of AV1 section 5.11.6. 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 } /// - /// 5.11.7. Intra frame mode info syntax. + /// Reads all intra-frame mode syntax for a coding block in bitstream order. /// + /// The tile symbol decoder. + /// The current coding block and its neighbors. + /// Implements AV1 section 5.11.7. 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 } } + /// + /// Determines whether the frame header permits intra block copy for an intra still image. + /// + /// when the frame and sequence enable intra block copy; otherwise, . private bool AllowIntraBlockCopy() => (this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) && (this.SequenceHeader.ForceScreenContentTools > 0) && this.FrameHeader.AllowIntraBlockCopy; + /// + /// Determines whether chroma-from-luma prediction is available for a coding block. + /// + /// The current coding block. + /// when the lossless transform or block dimensions permit chroma-from-luma prediction; otherwise, . 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; } + /// + /// Reads filter-intra selection for an eligible DC-predicted luma block. + /// + /// The tile symbol decoder. + /// The current coding block. private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false; @@ -1050,16 +1282,21 @@ internal class Av1TileReader : IAv1TileReader } /// - /// 5.11.46. Palette mode info syntax. + /// Reads palette size and color syntax for an eligible screen-content block. /// - private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => - - // TODO: Implement. - throw new NotImplementedException(); + /// The tile symbol decoder. + /// The current coding block. + /// Palette-mode syntax is not implemented. + /// Implements AV1 section 5.11.46. + private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) + => throw new NotImplementedException(); /// - /// 5.11.45. Read CFL alphas syntax. + /// Reads the joint signs and nonzero alpha magnitudes for chroma-from-luma prediction. /// + /// The tile symbol decoder. + /// The block mode information to populate. + /// Implements AV1 section 5.11.45. private static void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1BlockModeInfo modeInfo) { int jointSignPlus1 = reader.ReadChromFromLumaSign() + 1; @@ -1079,8 +1316,13 @@ internal class Av1TileReader : IAv1TileReader } /// - /// 5.11.42. and 5.11.43. + /// Reads a directional intra-prediction angle adjustment when the block and mode permit one. /// + /// The tile symbol decoder. + /// The selected luma or chroma prediction mode. + /// The block size. + /// The signed angle adjustment. + /// Implements AV1 sections 5.11.42 and 5.11.43. private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode mode, Av1BlockSize blockSize) { int angleDelta = 0; @@ -1093,12 +1335,20 @@ internal class Av1TileReader : IAv1TileReader return angleDelta; } + /// + /// Determines whether a prediction mode belongs to the AV1 directional-mode range. + /// + /// The prediction mode. + /// for a directional mode; otherwise, . private static bool IsDirectionalMode(Av1PredictionMode mode) => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; /// - /// 5.11.8. Intra segment ID syntax. + /// Reads or inherits a segment identifier and writes it over every 4x4 position covered by the block. /// + /// The tile symbol decoder. + /// The current coding block. + /// Implements AV1 section 5.11.8. 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 } /// - /// 5.11.9. Read segment ID syntax. + /// Predicts and, when required, decodes the segment identifier for an intra block. /// + /// The tile symbol decoder. + /// The current coding block and its available neighbors. + /// Implements AV1 section 5.11.9. 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 } /// - /// 5.11.56. Read CDEF syntax. + /// Reads the constrained directional enhancement filter strength for the block's 64x64 filter unit. /// - /// SVT: read_cdef + /// The tile symbol decoder. + /// The current coding block. + /// Implements AV1 section 5.11.56 and corresponds to read_cdef in SVT-AV1. 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 } } + /// + /// Reads and accumulates the loop-filter delta values carried by a coding block. + /// + /// The tile symbol decoder. + /// The current coding block and superblock delta storage. 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 } } + /// + /// Reads or infers the residual-skip flag for a coding block. + /// + /// The tile symbol decoder. + /// The current coding block and its available neighbors. + /// when the block omits residual coefficients; otherwise, . private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { int segmentId = partitionInfo.ModeInfo.SegmentId; @@ -1254,8 +1525,11 @@ internal class Av1TileReader : IAv1TileReader } /// - /// SVT: read_delta_qindex + /// Reads and accumulates a superblock quantizer-index delta when the block carries one. /// + /// The tile symbol decoder. + /// The current coding block and superblock quantizer storage. + /// Corresponds to read_delta_qindex in SVT-AV1. 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 } } + /// + /// Determines whether a frame-relative mode-information position lies inside the active tile. + /// + /// The frame-relative mode-information row. + /// The frame-relative mode-information column. + /// when the position lies within the active tile; otherwise, . 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; - }*/ - /// - /// SVT: partition_plane_context + /// Derives the partition entropy context from the current split bit of the above and left neighbors. /// + /// The partition origin in 4x4 mode-information units. + /// The square parent block size. + /// The active tile boundaries. + /// The containing superblock. + /// The partition entropy context. + /// Corresponds to partition_plane_context in SVT-AV1. 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); } + /// + /// Publishes the decoded partition sizes to the above and left neighbor contexts. + /// + /// The parent block origin in 4x4 mode-information units. + /// The active tile boundaries. + /// The containing superblock. + /// The primary size produced by the partition. + /// The parent block size. + /// The decoded partition type. private void UpdatePartitionContext(Point modeInfoLocation, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition) { if (blockSize >= Av1BlockSize.Block8x8) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs index 2ee25e9bd..eb67fabdd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs +++ b/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; +/// +/// Writes the partition, mode, transform, and coefficient syntax for one AV1 tile. +/// 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 + /// + /// Maps each AV1 block size to the five-bit partition contexts written to its bottom and right edges. + /// + /// + /// Each set bit represents a split level from 128x128 through 8x8. For example, 11111 + /// records every split level, while 10000 records only the 128x128 split. + /// 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} ]; + /// + /// Maps neighboring intra prediction modes to the key-frame luma-mode entropy contexts. + /// private static readonly byte[] IntraModeContextLookup = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; /// - /// SVT: svt_aom_write_sb + /// Writes the partition tree and each final coding block for a superblock. /// + /// The picture coding state. + /// The entropy-coding position state for the superblock. + /// The tile symbol encoder. + /// The encoder decisions for the superblock. + /// The transformed coefficients for the frame. + /// The zero-based tile index. + /// Corresponds to svt_aom_write_sb in SVT-AV1. public static void WriteSuperblock( Av1PictureControlSet pcs, Av1EntropyCodingContext ec_ctx, @@ -57,7 +74,8 @@ internal partial class Av1TileWriter Av1SequenceControlSet scs = pcs.Sequence; Av1NeighborArrayUnit 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 } /// - /// SVT: encode_partition_av1 + /// Writes a partition symbol using the above and left partition contexts available at a block origin. /// + /// The picture coding state. + /// The tile symbol encoder. + /// The square parent block size. + /// The selected partition type. + /// The block origin in samples. + /// The partition neighbor arrays for the tile. + /// Corresponds to encode_partition_av1 in SVT-AV1. 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 } /// - /// SVT: write_modes_b + /// Writes the segmentation, prediction, transform, coefficient, and filter syntax for one final coding block. /// + /// The picture coding state. + /// The entropy-coding position state for the superblock. + /// The tile symbol encoder. + /// The containing superblock. + /// The final encoder decisions for the block. + /// The zero-based tile index. + /// The transformed coefficients for the frame. + /// Corresponds to write_modes_b in SVT-AV1. 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 } } + /// + /// Writes the chroma intra mode, chroma-from-luma alpha values, and directional angle adjustment for a block. + /// + /// The tile symbol encoder. + /// The selected block modes. + /// The encoder prediction-unit state. + /// The luma block size. + /// The selected luma prediction mode. + /// The selected chroma prediction mode. + /// A value indicating whether chroma-from-luma mode is available. private static void EncodeIntraChromaMode( ref Av1SymbolEncoder writer, Av1MacroBlockModeInfo macroBlockModeInfo, @@ -604,26 +643,24 @@ internal partial class Av1TileWriter } /// - /// 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. /// - /// SVT: svt_aom_get_kf_y_mode_ctx + /// The current macroblock and its mapped neighbors. + /// The context derived from the above luma mode. + /// The context derived from the left luma mode. + /// Corresponds to svt_aom_get_kf_y_mode_ctx in SVT-AV1. 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 } /// - /// SVT: encode_intra_luma_mode_kf_av1 + /// Writes the key-frame luma prediction mode and any directional angle adjustment. /// + /// The tile symbol encoder. + /// The selected block modes. + /// The encoder prediction-unit state. + /// The block size. + /// The selected luma prediction mode. + /// Corresponds to encode_intra_luma_mode_kf_av1 in SVT-AV1. private static void EncodeIntraLumaMode( ref Av1SymbolEncoder writer, Av1MacroBlockModeInfo macroBlockModeInfo, @@ -650,6 +693,16 @@ internal partial class Av1TileWriter } } + /// + /// Writes luma and chroma palette-mode syntax for a block. + /// + /// The sequence coding state. + /// The tile symbol encoder. + /// The selected block modes. + /// The encoder block state. + /// The block size. + /// The block position in mode-information units. + /// Palette-mode encoding is not implemented. private static void WritePaletteModeInfo( Av1SequenceControlSet scs, ref Av1SymbolEncoder writer, @@ -687,8 +740,14 @@ internal partial class Av1TileWriter } /// - /// SVT: svt_aom_filter_intra_allowed + /// Determines whether filter-intra syntax is available for a block mode. /// + /// A value indicating whether the sequence enables filter-intra prediction. + /// The block size. + /// The selected luma palette size. + /// The selected luma prediction mode. + /// when the block can use filter-intra prediction; otherwise, . + /// Corresponds to svt_aom_filter_intra_allowed in SVT-AV1. private static bool IsFilterIntraAllowed( bool enableFilterIntra, Av1BlockSize blockSize, @@ -697,8 +756,12 @@ internal partial class Av1TileWriter => mode == Av1PredictionMode.DC && paletteSize == 0 && IsFilterIntraAllowedBlockSize(enableFilterIntra, blockSize); /// - /// SVT: svt_aom_filter_intra_allowed_bsize + /// Determines whether filter-intra prediction is enabled for a block size. /// + /// A value indicating whether the sequence enables filter-intra prediction. + /// The block size. + /// when filter-intra prediction supports the block dimensions; otherwise, . + /// Corresponds to svt_aom_filter_intra_allowed_bsize in SVT-AV1. private static bool IsFilterIntraAllowedBlockSize(bool enableFilterIntra, Av1BlockSize blockSize) { if (!enableFilterIntra) @@ -710,8 +773,13 @@ internal partial class Av1TileWriter } /// - /// SVT: write_intrabc_info + /// Writes the intra-block-copy selection and displacement-vector syntax for a block. /// + /// The tile symbol encoder. + /// The selected block modes. + /// The encoder block state. + /// The displacement-vector syntax is not implemented when intra block copy is selected. + /// Corresponds to write_intrabc_info in SVT-AV1. private static void WriteIntraBlockCopyInfo( ref Av1SymbolEncoder writer, Av1MacroBlockModeInfo macroBlockModeInfo, @@ -734,14 +802,24 @@ internal partial class Av1TileWriter } /// - /// SVT: svt_aom_allow_intrabc + /// Determines whether the current frame permits intra block copy. /// + /// The current frame header. + /// when both screen-content tools and intra block copy are enabled; otherwise, . + /// Corresponds to svt_aom_allow_intrabc in SVT-AV1. private static bool IsIntraBlockCopyAllowed(ObuFrameHeader frameHeader) => frameHeader.AllowScreenContentTools && frameHeader.AllowIntraBlockCopy; /// - /// SVT: ec_update_neighbors + /// Updates partition and coefficient neighbor arrays after writing a block. /// + /// The picture coding state. + /// The entropy-coding position state for the superblock. + /// The block origin in samples. + /// The encoder block state. + /// The zero-based tile index. + /// The block size. + /// Corresponds to ec_update_neighbors in SVT-AV1. 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.UnitMask.Left | Av1NeighborArrayUnit.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 dcSignSpan = new(ref dcSignLevelCoefficient); @@ -800,8 +880,12 @@ internal partial class Av1TileWriter } /// - /// SVT: svt_av1_allow_palette + /// Determines whether the encoder palette level and block dimensions permit palette mode. /// + /// The nonzero encoder palette level. + /// The block size. + /// when palette mode is enabled for the block; otherwise, . + /// Corresponds to svt_av1_allow_palette in SVT-AV1. 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 } /// - /// SVT: svt_aom_allow_palette + /// Determines whether screen-content tools and block dimensions permit palette mode. /// + /// A value indicating whether screen-content tools are enabled. + /// The block size. + /// when palette mode is available for the block; otherwise, . + /// Corresponds to svt_aom_allow_palette in SVT-AV1. private static bool IsPaletteAllowed(bool allowScreenContentTools, Av1BlockSize blockSize) => allowScreenContentTools && blockSize.GetWidth() <= 64 && @@ -821,8 +909,15 @@ internal partial class Av1TileWriter blockSize >= Av1BlockSize.Block8x8; /// - /// SVT: write_cdef + /// Writes the constrained directional enhancement filter strength at its first coded block in a filter unit. /// + /// The sequence coding state. + /// The picture coding state. + /// The tile symbol encoder. + /// The zero-based tile index. + /// A value indicating whether the current block omits residual coefficients. + /// The block position in 4x4 mode-information units. + /// Corresponds to write_cdef in SVT-AV1. 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 } /// - /// SVT: set_mi_row_col + /// Populates a macroblock's frame edges, tile-neighbor availability, and rectangular-partition context. /// + /// The picture coding state. + /// The macroblock state to populate. + /// The active tile boundaries. + /// The block position in 4x4 mode-information units. + /// The block size. + /// The row stride of the mode-information grid. + /// The coded frame height in mode-information rows. + /// The coded frame width in mode-information columns. + /// Corresponds to set_mi_row_col in SVT-AV1. 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 } /// - /// SVT: av1_encode_coeff_1d + /// Writes luma and chroma transform coefficients for a block in plane order. /// + /// The picture coding state. + /// The entropy-coding position state for the superblock. + /// The tile symbol encoder. + /// The selected macroblock modes. + /// The encoder block state. + /// The block origin in samples. + /// The luma prediction direction. + /// The luma block size. + /// The transformed coefficients for the frame. + /// The luma coefficient neighbor contexts. + /// The red-difference chroma coefficient neighbor contexts. + /// The blue-difference chroma coefficient neighbor contexts. + /// The transform-depth path required by the block is not implemented. + /// Corresponds to av1_encode_coeff_1d in SVT-AV1. private static void EncodeCoefficients1d( Av1PictureControlSet pcs, Av1EntropyCodingContext ec_ctx, @@ -987,8 +1103,19 @@ internal partial class Av1TileWriter } /// - /// SVT: av1_encode_tx_coef_y + /// Writes each luma transform block and updates its DC-sign and coefficient-level neighbor contexts. /// + /// The picture coding state. + /// The entropy-coding position state for the superblock. + /// The tile symbol encoder. + /// The selected macroblock modes. + /// The encoder block state. + /// The block origin in samples. + /// The luma prediction direction. + /// The luma block size. + /// The transformed coefficients for the frame. + /// The luma coefficient neighbor contexts. + /// Corresponds to av1_encode_tx_coef_y in SVT-AV1. public static void EncodeTransformCoefficientsY( Av1PictureControlSet pcs, Av1EntropyCodingContext entropyCodingContext, @@ -1001,7 +1128,7 @@ internal partial class Av1TileWriter Av1FrameBuffer coeff_ptr, Av1NeighborArrayUnit 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 culLevelSpan = new(ref cul_level_y); ReadOnlySpan dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelSpan); @@ -1064,8 +1192,20 @@ internal partial class Av1TileWriter } /// - /// SVT: av1_encode_tx_coef_uv + /// Writes both chroma transform blocks and updates their DC-sign and coefficient-level neighbor contexts. /// + /// The picture coding state. + /// The entropy-coding position state for the superblock. + /// The tile symbol encoder. + /// The selected macroblock modes. + /// The encoder block state. + /// The luma block origin in samples. + /// The luma prediction direction used by coefficient contexts. + /// The luma block size. + /// The transformed coefficients for the frame. + /// The red-difference chroma coefficient neighbor contexts. + /// The blue-difference chroma coefficient neighbor contexts. + /// Corresponds to av1_encode_tx_coef_uv in SVT-AV1. 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 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 culLevelCbSpan = new(ref cul_level_cb); ReadOnlySpan 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.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); - // Update the cr DC Sign Level Coeff Neighbor Array Span 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 } } + /// + /// Rounds a luma sample position down to the 8-sample alignment used before chroma subsampling. + /// + /// The luma sample position. + /// The aligned luma position. private static Point RoundUv(Point point) => (point >> 3) << 3; /// - /// SVT: svt_aom_get_txb_ctx + /// Derives coefficient skip and DC-sign contexts from the transform block's above and left neighbors. /// + /// The picture coding state. + /// The luma or chroma component class. + /// The packed DC-sign and coefficient-level neighbor contexts. + /// The transform-block origin in samples of the target plane. + /// The containing block size on the target plane. + /// The transform size. + /// The context object to populate. + /// Corresponds to svt_aom_get_txb_ctx in SVT-AV1. 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.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 } } + /// + /// Writes or predicts a block segment identifier and updates the frame segmentation map. + /// + /// The picture coding state. + /// The tile symbol encoder. + /// The block size. + /// The block origin in samples. + /// The encoder block state. + /// A value indicating whether residual coefficients are omitted. 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 } /// - /// SVT: svt_av1_get_spatial_seg_prediction + /// Derives a segment identifier predictor and entropy context from the upper-left, above, and left neighbors. /// + /// The picture coding state. + /// The current macroblock and its neighbor availability. + /// The block origin in samples. + /// The entropy context selected by matching neighbor identifiers. + /// The spatially predicted segment identifier. + /// Corresponds to svt_av1_get_spatial_seg_prediction in SVT-AV1. 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; } + /// + /// Writes the block skip flag using the sum of available above and left skip states as its context. + /// + /// The tile symbol encoder. + /// The encoder block state. + /// The skip value to write. internal static void EncodeSkipCoefficients(ref Av1SymbolEncoder writer, Av1EncoderBlockStruct block, bool skip) { Av1MacroBlockModeInfo? above_mi = block.MacroBlock.AboveMacroBlock; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs index 6256867e7..0f15b2231 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs @@ -3,9 +3,18 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +/// +/// Carries the neighboring coefficient contexts used to entropy-code an AV1 transform block. +/// internal class Av1TransformBlockContext { + /// + /// Gets or sets the context used to decode the sign of the DC coefficient. + /// public int DcSignContext { get; set; } + /// + /// Gets or sets the neighboring transform-block skip context. + /// public int SkipContext { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs index dab777ed3..8cff3a790 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs +++ b/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; /// -/// Information of a single Transform Block. +/// Describes the size, position, type, and residual state of one AV1 transform block. /// internal class Av1TransformInfo { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with a 4x4 transform at the origin. /// public Av1TransformInfo() : this(Av1TransformSize.Size4x4, 0, 0) @@ -21,6 +21,9 @@ internal class Av1TransformInfo /// /// Initializes a new instance of the class. /// + /// The transform size. + /// The horizontal offset in mode-information units. + /// The vertical offset in mode-information units. public Av1TransformInfo(Av1TransformSize size, int offsetX, int offsetY) { this.Size = size; @@ -40,35 +43,35 @@ internal class Av1TransformInfo } /// - /// Gets or sets the transform size to be used for this Transform Block. + /// Gets or sets the transform size used for this transform block. /// public Av1TransformSize Size { get; internal set; } /// - /// Gets or sets the transform type to be used for this Transform Block. + /// Gets or sets the transform type used for this transform block. /// public Av1TransformType Type { get; internal set; } /// - /// 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. /// public int OffsetX { get; internal set; } /// - /// 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. /// public int OffsetY { get; internal set; } /// - /// 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. /// /// /// false - /// No residual for the block + /// The block has no residual. /// /// /// true - /// Residual exists for the block + /// The block has a residual. /// /// /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs index 6670f99ed..2755a37a9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs +++ b/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; +/// +/// Stores the transform syntax and coefficient range for one AV1 transform unit. +/// internal class Av1TransformUnit { + /// + /// Gets the nonzero-coefficient count for each color plane. + /// public ushort[] NzCoefficientCount { get; } = new ushort[3]; + /// + /// Gets the transform type selected for each color plane. + /// public Av1TransformType[] TransformType { get; } = new Av1TransformType[Av1Constants.PlaneTypeCount]; }