From 8d13aa58081138ed5113af21a3c4a1056721fd62 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 9 Dec 2024 20:22:00 +0100 Subject: [PATCH] Preparations for Tile writing --- .../Av1/Entropy/Av1DefaultDistributions.cs | 10 + .../Av1/Entropy/Av1SymbolContextHelper.cs | 91 ++ .../Heif/Av1/Entropy/Av1SymbolDecoder.cs | 7 + .../Heif/Av1/Entropy/Av1SymbolEncoder.cs | 28 + .../Heif/Av1/Tiling/Av1BlockGeometry.cs | 71 +- .../Formats/Heif/Av1/Tiling/Av1BlockStruct.cs | 19 - .../Heif/Av1/Tiling/Av1EncoderBlockStruct.cs | 19 +- .../Heif/Av1/Tiling/Av1NeighborArrayUnit.cs | 2 +- .../Heif/Av1/Tiling/Av1PartitionContext.cs | 12 +- .../Heif/Av1/Tiling/Av1PictureControlSet.cs | 27 + .../Av1/Tiling/Av1PictureParentControlSet.cs | 2 + .../Heif/Av1/Tiling/Av1SequenceControlSet.cs | 2 + .../Formats/Heif/Av1/Tiling/Av1Superblock.cs | 16 +- .../Heif/Av1/Tiling/Av1SuperblockGeometry.cs | 9 + .../Formats/Heif/Av1/Tiling/Av1TileInfo.cs | 9 + .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 73 +- .../Formats/Heif/Av1/Tiling/Av1TileWriter.cs | 1050 +++++++++++++++++ 17 files changed, 1313 insertions(+), 134 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs index a4eb06bcb3..743708fc2b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs @@ -119,8 +119,18 @@ internal static class Av1DefaultDistributions new(711, 966, 1172, 32448, 32538, 32617, 32664) ]; + /// + /// Gets the skip . + /// + /// SVT: default_skip_cdfs public static Av1Distribution[] Skip => [new(31671), new(16515), new(4576)]; + /// + /// Gets the skip mode . + /// + /// SVT: default_skip_mode_cdfs + public static Av1Distribution[] SkipMode => [new(32621), new(20708), new(8127)]; + public static Av1Distribution DeltaLoopFilterAbsolute => new(28160, 32120, 32677); public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677); diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs index 951ee96bcb..04f458ebd2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; @@ -365,4 +366,94 @@ internal static class Av1SymbolContextHelper extra = endOfBlock - EndOfBlockGroupStart[t]; return t; } + + public static int GetSegmentId(Av1PartitionInfo partitionInfo, ObuFrameHeader frameHeader, int[][] segmentIds, int rowIndex, int columnIndex) + { + int modeInfoOffset = (rowIndex * frameHeader.ModeInfoColumnCount) + columnIndex; + int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); + int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); + int xMin = Math.Min(frameHeader.ModeInfoColumnCount - columnIndex, bw4); + int yMin = Math.Min(frameHeader.ModeInfoRowCount - rowIndex, bh4); + int segmentId = Av1Constants.MaxSegmentCount - 1; + for (int y = 0; y < yMin; y++) + { + for (int x = 0; x < xMin; x++) + { + segmentId = Math.Min(segmentId, segmentIds[y][x]); + } + } + + return segmentId; + } + + /// + /// SVT: svt_aom_get_segment_id + /// + public static int GetSegmentId(Av1Common cm, ReadOnlySpan segment_ids, Av1BlockSize bsize, Point modeInfoPosition) + { + int mi_offset = (modeInfoPosition.Y * cm.ModeInfoColumnCount) + modeInfoPosition.X; + int bw = bsize.GetWidth(); + int bh = bsize.GetHeight(); + int xmis = Math.Min(cm.ModeInfoColumnCount - modeInfoPosition.X, bw); + int ymis = Math.Min(cm.ModeInfoRowCount - modeInfoPosition.Y, bh); + int segment_id = Av1Constants.MaxSegmentCount; + + for (int y = 0; y < ymis; ++y) + { + int offset = mi_offset + (y * cm.ModeInfoColumnCount); + for (int x = 0; x < xmis; ++x) + { + segment_id = Math.Min(segment_id, segment_ids[offset + x]); + } + } + + Guard.IsTrue(segment_id is >= 0 and < Av1Constants.MaxSegmentCount, nameof(segment_id), "Segment ID needs to be in proper range."); + return segment_id; + } + + public static int NegativeDeinterleave(int diff, int reference, int max) + { + if (reference == 0) + { + return diff; + } + + if (reference >= max - 1) + { + return max - diff - 1; + } + + if (2 * reference < max) + { + if (diff <= 2 * reference) + { + if ((diff & 1) > 0) + { + return reference + ((diff + 1) >> 1); + } + else + { + return reference - (diff >> 1); + } + } + + return diff; + } + else + { + if (diff <= 2 * (max - reference - 1)) + { + if ((diff & 1) > 0) + { + return reference + ((diff + 1) >> 1); + } + else + { + return reference - (diff >> 1); + } + } + + return max - (diff + 1); + } + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index b8b1f5bba2..f96833fde0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -26,6 +26,7 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode; private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; + private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode; private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute; private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; @@ -132,6 +133,12 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(this.skip[ctx]) > 0; } + public bool ReadSkipMode(Av1BlockSize blockSize) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.skipMode[(int)blockSize]) > 0; + } + public int ReadDeltaLoopFilterAbsolute() { ref Av1SymbolReader r = ref this.reader; diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 1feed0fff8..3425c3d000 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -27,9 +27,13 @@ internal class Av1SymbolEncoder : IDisposable private readonly Av1Distribution[][][] coefficientsBaseRange; private readonly Av1Distribution[][][] coefficientsBase; private readonly Av1Distribution[][][] coefficientsBaseEndOfBlock; + private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; private readonly Av1Distribution[][] dcSign; private readonly Av1Distribution[][][] endOfBlockExtra; private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; + private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; + private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; + private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode; private bool isDisposed; private readonly Configuration configuration; private Av1SymbolWriter writer; @@ -309,4 +313,28 @@ internal class Av1SymbolEncoder : IDisposable this.intraExtendedTransform[extendedSet][(int)squareTransformSize][(int)intraMode]); } } + + internal void WriteSegmentId(int segmentId, int context) + { + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(segmentId, this.segmentId[context]); + } + + internal void WriteSkip(bool skip, int context) + { + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(skip, this.skip[context]); + } + + internal void WriteSkipMode(bool skip, int context) + { + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(skip, this.skipMode[context]); + } + + internal void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode) + { + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol((int)filterIntraMode, this.filterIntraMode); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs index a6e24821d6..0252ba2195 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs @@ -5,51 +5,58 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1BlockGeometry { - internal class Av1BlockGeometry + public Av1BlockGeometry() { - public Av1BlockGeometry() + this.TransformOrigin = new Point[Av1Constants.MaxVarTransform + 1][]; + for (int i = 0; i < this.TransformOrigin.Length; i++) { - this.TransformOrigin = new Point[Av1Constants.MaxVarTransform + 1][]; - for (int i = 0; i < this.TransformOrigin.Length; i++) - { - this.TransformOrigin[i] = new Point[Av1Constants.MaxTransformBlockCount]; - } + this.TransformOrigin[i] = new Point[Av1Constants.MaxTransformBlockCount]; } + } - public Av1BlockSize BlockSize { get; internal set; } + public Av1BlockSize BlockSize { get; internal set; } - public Av1BlockSize BlockSizeUv { get; internal set; } + public Av1BlockSize BlockSizeUv { get; internal set; } - /// - /// Gets or sets the Origin point from lop left of the superblock. - /// - public Point Origin { get; internal set; } + /// + /// Gets or sets the Origin point from lop left of the superblock. + /// + public Point Origin { get; internal set; } - public bool HasUv { get; internal set; } + public bool HasUv { get; internal set; } - /// - /// Gets or sets the blocks width. - /// - public int BlockWidth { get; internal set; } + /// + /// Gets or sets the blocks width. + /// + public int BlockWidth { get; internal set; } - /// - /// Gets or sets the blocks height. - /// - public int BlockHeight { get; internal set; } + /// + /// Gets or sets the blocks height. + /// + public int BlockHeight { get; internal set; } - public int[] TransformBlockCount { get; } = new int[Av1Constants.MaxVarTransform + 1]; + public int[] TransformBlockCount { get; } = new int[Av1Constants.MaxVarTransform + 1]; - public Av1TransformSize[] TransformSize { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; + public Av1TransformSize[] TransformSize { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; - public Av1TransformSize[] TransformSizeUv { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; + public Av1TransformSize[] TransformSizeUv { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; - public Point[][] TransformOrigin { get; private set; } + public Point[][] TransformOrigin { get; private set; } - /// - /// Gets or sets the block index in md scan. - /// - public int BlockIndex { get; set; } - } + /// + /// Gets or sets the blocks index in the Mode Decision scan. + /// + public int ModeDecisionIndex { get; set; } + + /// + /// Gets or sets the offset to the next nsq block (skip remaining d2 blocks). + /// + public int NextDepthSequenceOffset { get; set; } + + /// + /// Gets or sets the offset to the next d1 sq block + /// + public int NextDepth1Offset { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs deleted file mode 100644 index 27f8572360..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; - -internal class Av1BlockStruct -{ - public Av1TransformUnit[] TransformBlocks { get; } = new Av1TransformUnit[Av1Constants.MaxTransformUnitCount]; - - public required Av1MacroBlockD MacroBlock { get; set; } - - public int MdScanIndex { get; set; } - - public int QIndex { get; set; } - - public int SegmentId { get; set; } - - public Av1FilterIntraMode FilterIntraMode { get; set; } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs index d68e11f3fa..45bf6603c1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs @@ -3,14 +3,19 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1EncoderBlockStruct { - internal class Av1EncoderBlockStruct - { - public required Av1MacroBlockD MacroBlock { get; internal set; } + public Av1TransformUnit[] TransformBlocks { get; } = new Av1TransformUnit[Av1Constants.MaxTransformUnitCount]; - public required int[] PaletteSize { get; internal set; } + public required Av1MacroBlockD MacroBlock { get; set; } - public int QIndex { get; internal set; } - } + public int ModeDecisionScanIndex { get; set; } + + public int QuantizationIndex { get; set; } + + public int SegmentId { get; set; } + + public Av1FilterIntraMode FilterIntraMode { get; set; } + + public required int[] PaletteSize { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs index 215dc2ad02..4592a3066f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs @@ -49,7 +49,7 @@ internal class Av1NeighborArrayUnit public int GetTopLeftIndex(Point loc) => this.left.Length + (loc.X >> this.GranularityTopLeftLog2) - (loc.Y >> this.GranularityTopLeftLog2); - public void UnitModeWrite(Span value, Point origin, Size blockSize, UnitMask mask) + public void UnitModeWrite(ReadOnlySpan value, Point origin, Size blockSize, UnitMask mask) { int idx, j; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs index 2289d28d78..2d9c910d01 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs @@ -1,12 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +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 -internal class Av1PartitionContext +internal struct Av1PartitionContext : IMinMaxValue { 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]; @@ -17,6 +19,14 @@ internal class Av1PartitionContext // Mask to extract ModeInfo offset within max ModeInfoBlock public const int Mask = (1 << (7 - 2)) - 1; + public static Av1PartitionContext MaxValue => throw new NotImplementedException(); + + public static Av1PartitionContext MinValue => throw new NotImplementedException(); + + public byte Left { get; internal set; } + + public byte Above { get; internal set; } + public static int GetAboveContext(Av1BlockSize blockSize) => AboveLookup[(int)blockSize]; public static int GetLeftContext(Av1BlockSize blockSize) => LeftLookup[(int)blockSize]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs index 614cf59c42..bdffdeabc3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs @@ -5,6 +5,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1PictureControlSet { + public required Av1NeighborArrayUnit[] PartitionContexts { get; internal set; } + public required Av1NeighborArrayUnit[] LuminanceDcSignLevelCoefficientNeighbors { get; internal set; } public required Av1NeighborArrayUnit[] CrDcSignLevelCoefficientNeighbors { get; internal set; } @@ -16,4 +18,29 @@ internal class Av1PictureControlSet public required Av1SequenceControlSet Sequence { get; internal set; } public required Av1PictureParentControlSet Parent { get; internal set; } + + public required byte[] SegmentationNeighborMap { get; internal set; } + + public required Av1BlockModeInfo[] ModeInfoGrid { get; internal set; } + + /// + /// SVT: svt_av1_update_segmentation_map + /// + internal void UpdateSegmentation(Av1BlockSize blockSize, Point origin, int segmentId) + { + Av1Common cm = this.Parent.Common; + Span segment_ids = this.SegmentationNeighborMap; + int mi_col = origin.X >> Av1Constants.ModeInfoSizeLog2; + int mi_row = origin.Y >> Av1Constants.ModeInfoSizeLog2; + int mi_offset = (mi_row * cm.ModeInfoColumnCount) + mi_col; + int bw = blockSize.GetWidth(); + int bh = blockSize.GetHeight(); + int xmis = Math.Min(cm.ModeInfoColumnCount - mi_col, bw); + int ymis = Math.Min(cm.ModeInfoRowCount - mi_row, bh); + for (int y = 0; y < ymis; ++y) + { + int offset = mi_offset + (y * cm.ModeInfoColumnCount); + segment_ids.Slice(offset, xmis).Fill((byte)segmentId); + } + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs index b1a183048d..169a29b794 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs @@ -18,4 +18,6 @@ internal class Av1PictureParentControlSet public int AlignedWidth { get; internal set; } public int AlignedHeight { get; internal set; } + + public required Av1SuperblockGeometry[] SuperblockGeometry { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs index 39f7c2b197..e19b7ba7b4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs @@ -8,4 +8,6 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1SequenceControlSet { public required ObuSequenceHeader SequenceHeader { get; internal set; } + + 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 776451aeaf..a4d1465951 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs @@ -1,11 +1,17 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using static SixLabors.ImageSharp.Formats.Heif.Av1.Tiling.Av1TileWriter; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1Superblock { - internal class Av1Superblock - { - } + public required Av1EncoderBlockStruct[] FinalBlocks { get; set; } + + public required Av1TileInfo TileInfo { get; set; } + + public required Av1PartitionType[] CodingUnitPartitionTypes { get; internal set; } + + 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 new file mode 100644 index 0000000000..6422b88cde --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal class Av1SuperblockGeometry +{ + public bool IsComplete { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs index ec70fbf689..52a6d0d709 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs @@ -13,6 +13,15 @@ internal class Av1TileInfo this.SetTileColumn(frameHeader.TilesInfo, frameHeader.ModeInfoColumnCount, column); } + public Av1TileInfo(Av1TileInfo tileInfo) + { + this.ModeInfoColumnStart = tileInfo.ModeInfoColumnStart; + this.ModeInfoColumnEnd = tileInfo.ModeInfoColumnEnd; + this.ModeInfoRowStart = tileInfo.ModeInfoRowStart; + this.ModeInfoRowEnd = tileInfo.ModeInfoRowEnd; + this.TileIndex = tileInfo.TileIndex; + } + public int ModeInfoRowStart { get; private set; } public int ModeInfoRowEnd { get; private set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index d217ff4451..e4cf2f5fe4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -1153,17 +1153,17 @@ internal class Av1TileReader : IAv1TileReader int rowIndex = partitionInfo.RowIndex; if (partitionInfo.AvailableAbove && partitionInfo.AvailableLeft) { - prevUL = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex - 1); + prevUL = Av1SymbolContextHelper.GetSegmentId(partitionInfo, this.FrameHeader, this.segmentIds, rowIndex - 1, columnIndex - 1); } if (partitionInfo.AvailableAbove) { - prevU = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex); + prevU = Av1SymbolContextHelper.GetSegmentId(partitionInfo, this.FrameHeader, this.segmentIds, rowIndex - 1, columnIndex); } if (partitionInfo.AvailableLeft) { - prevU = this.GetSegmentId(partitionInfo, rowIndex, columnIndex - 1); + prevU = Av1SymbolContextHelper.GetSegmentId(partitionInfo, this.FrameHeader, this.segmentIds, rowIndex, columnIndex - 1); } if (prevU == -1) @@ -1189,72 +1189,7 @@ internal class Av1TileReader : IAv1TileReader : prevUL == prevU && prevUL == prevL ? 2 : prevUL == prevU || prevUL == prevL || prevU == prevL ? 1 : 0; int lastActiveSegmentId = this.FrameHeader.SegmentationParameters.LastActiveSegmentId; - partitionInfo.ModeInfo.SegmentId = NegativeDeinterleave(reader.ReadSegmentId(ctx), predictor, lastActiveSegmentId + 1); - } - } - - private int GetSegmentId(Av1PartitionInfo partitionInfo, int rowIndex, int columnIndex) - { - int modeInfoOffset = (rowIndex * this.FrameHeader.ModeInfoColumnCount) + columnIndex; - int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); - int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); - int xMin = Math.Min(this.FrameHeader.ModeInfoColumnCount - columnIndex, bw4); - int yMin = Math.Min(this.FrameHeader.ModeInfoRowCount - rowIndex, bh4); - int segmentId = Av1Constants.MaxSegmentCount - 1; - for (int y = 0; y < yMin; y++) - { - for (int x = 0; x < xMin; x++) - { - segmentId = Math.Min(segmentId, this.segmentIds[y][x]); - } - } - - return segmentId; - } - - private static int NegativeDeinterleave(int diff, int reference, int max) - { - if (reference == 0) - { - return diff; - } - - if (reference >= max - 1) - { - return max - diff - 1; - } - - if (2 * reference < max) - { - if (diff <= 2 * reference) - { - if ((diff & 1) > 0) - { - return reference + ((diff + 1) >> 1); - } - else - { - return reference - (diff >> 1); - } - } - - return diff; - } - else - { - if (diff <= 2 * (max - reference - 1)) - { - if ((diff & 1) > 0) - { - return reference + ((diff + 1) >> 1); - } - else - { - return reference - (diff >> 1); - } - } - - return max - (diff + 1); + partitionInfo.ModeInfo.SegmentId = Av1SymbolContextHelper.NegativeDeinterleave(reader.ReadSegmentId(ctx), predictor, lastActiveSegmentId + 1); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs new file mode 100644 index 0000000000..b1709d9589 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs @@ -0,0 +1,1050 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + /// + /// SVT: svt_aom_write_sb + /// + public static void WriteSuperblock( + Av1PictureControlSet pcs, + Av1EntropyCodingContext ec_ctx, + ref Av1SymbolEncoder writer, + Av1Superblock superblock, + Av1FrameBuffer frameBuffer, + ushort tileIndex) + { + Av1SequenceControlSet scs = pcs.Sequence; + Av1NeighborArrayUnit partition_context_na = pcs.PartitionContexts[tileIndex]; + + // CU Varaiables + int blk_index = 0; + uint final_blk_index = 0; + + ec_ctx.CodedAreaSuperblock = 0; + ec_ctx.CodedAreaSuperblockUv = 0; + Av1SuperblockGeometry sb_geom = pcs.Parent.SuperblockGeometry[superblock.Index]; + bool check_blk_out_of_bound = !sb_geom.IsComplete; + do + { + bool code_blk_cond = true; // Code cu only if it is inside the picture + Av1EncoderBlockStruct blk_ptr = superblock.FinalBlocks[final_blk_index]; + Av1BlockGeometry blk_geom = GetBlockGeometryByModeDecisionScanIndex(blk_index); + + Av1BlockSize bsize = blk_geom.BlockSize; + 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) + { + 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); + } + + if (code_blk_cond) + { + int hbs = bsize.Get4x4WideCount() >> 1; + int quarter_step = bsize.Get4x4WideCount() >> 2; + Av1Common cm = pcs.Parent.Common; + int mi_row = blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2; + int mi_col = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2; + + if (bsize >= Av1BlockSize.Block8x8) + { + for (int plane = 0; plane < 3; ++plane) + { + /* TODO: Implement + if (svt_av1_loop_restoration_corners_in_sb(cm, + scs.SequenceHeader, + plane, + mi_row, + mi_col, + bsize, + out int rcol0, + out int rcol1, + out int rrow0, + out int rrow1, + out int tile_tl_idx)) + { + int rstride = pcs.RestorationInfos[plane].HorizontalUnitCountPerTile; + for (int rrow = rrow0; rrow < rrow1; ++rrow) + { + for (int rcol = rcol0; rcol < rcol1; ++rcol) + { + int runit_idx = tile_tl_idx + rcol + (rrow * rstride); + Av1RestorationUnitInfo rui = pcs.RestorationUnitInfos[plane].UnitInfo[runit_idx]; + loop_restoration_write_sb_coeffs( + pcs, + ref writer, + tileIndex, + rui, + plane); + } + } + }*/ + } + + // Code Split Flag + EncodePartition( + pcs, + ec_ctx, + writer, + bsize, + superblock.CodingUnitPartitionTypes[blk_index], + blockOrigin, + partition_context_na); + } + + // assert(blk_geom.Shape == PART_N); + Guard.IsTrue(Av1Math.Implies(bsize == Av1BlockSize.Block4x4, superblock.CodingUnitPartitionTypes[blk_index] == Av1PartitionType.None), nameof(bsize), string.Empty); + switch (superblock.CodingUnitPartitionTypes[blk_index]) + { + case Av1PartitionType.None: + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + break; + + case Av1PartitionType.Horizontal: + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + if (mi_row + hbs < cm.ModeInfoRowCount) + { + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + } + + break; + + case Av1PartitionType.Vertical: + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + if (mi_col + hbs < cm.ModeInfoColumnCount) + { + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + } + + break; + case Av1PartitionType.Split: + break; + case Av1PartitionType.HorizontalA: + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + break; + case Av1PartitionType.HorizontalB: + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + break; + case Av1PartitionType.VerticalA: + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + break; + case Av1PartitionType.VerticalB: + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + + break; + case Av1PartitionType.Horizontal4: + for (int i = 0; i < 4; ++i) + { + int this_mi_row = mi_row + (i * quarter_step); + if (i > 0 && this_mi_row >= cm.ModeInfoRowCount) + { + // Only the last block is able to be outside the picture boundary. If one of the first + // 3 blocks is outside the boundary, H4 is not a valid partition (see AV1 spec 5.11.4) + Guard.IsTrue(i == 3, nameof(i), "Only the last block can be partial"); + break; + } + + if (i > 0) + { + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + } + + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + } + + break; + case Av1PartitionType.Vertical4: + for (int i = 0; i < 4; ++i) + { + int this_mi_col = mi_col + (i * quarter_step); + if (i > 0 && this_mi_col >= cm.ModeInfoColumnCount) + { + // Only the last block is able to be outside the picture boundary. If one of the first + // 3 blocks is outside the boundary, H4 is not a valid partition (see AV1 spec 5.11.4) + Guard.IsTrue(i == 3, nameof(i), "Only the last block can be partial"); + break; + } + + if (i > 0) + { + final_blk_index++; + blk_ptr = superblock.FinalBlocks[final_blk_index]; + } + + WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, frameBuffer); + } + + break; + } + + if (superblock.CodingUnitPartitionTypes[blk_index] != Av1PartitionType.Split) + { + final_blk_index++; + blk_index += blk_geom.NextDepthSequenceOffset; + } + else + { + blk_index += blk_geom.NextDepth1Offset; + } + } + else + { + blk_index += blk_geom.NextDepth1Offset; + } + } + while (blk_index < scs.MaxBlockCount); + } + + private static void EncodePartition(Av1PictureControlSet pcs, Av1EntropyCodingContext ec_ctx, Av1SymbolEncoder writer, Av1BlockSize bsize, object value, Point blockOrigin, Av1NeighborArrayUnit partition_context_na) => throw new NotImplementedException(); + + /// + /// SVT: encode_partition_av1 + /// + private static void EncodePartition( + Av1PictureControlSet pcs, + ref Av1SymbolEncoder writer, + Av1BlockSize blockSize, + Av1PartitionType partitionType, + Point blockOrigin, + Av1NeighborArrayUnit partition_context_na) + { + bool is_partition_point = blockSize >= Av1BlockSize.Block8x8; + + if (!is_partition_point) + { + return; + } + + int hbs = (blockSize.Get4x4WideCount() << 2) >> 1; + bool has_rows = (blockOrigin.Y + hbs) < pcs.Parent.AlignedHeight; + bool has_cols = (blockOrigin.X + hbs) < pcs.Parent.AlignedWidth; + + int partition_context_left_neighbor_index = partition_context_na.GetLeftIndex(blockOrigin); + int partition_context_top_neighbor_index = partition_context_na.GetTopIndex(blockOrigin); + + int context_index = 0; + + byte above_ctx = + (byte)(partition_context_na.Top[partition_context_top_neighbor_index].Above == byte.MaxValue + ? 0 + : partition_context_na.Top[partition_context_top_neighbor_index].Above); + byte left_ctx = + (byte)(partition_context_na.Left[partition_context_left_neighbor_index].Left == byte.MaxValue + ? 0 + : partition_context_na.Left[partition_context_left_neighbor_index].Left); + + int blockSizeLog2 = blockSize.Get4x4WidthLog2() - 1; + int above = (above_ctx >> blockSizeLog2) & 1, left = (left_ctx >> blockSizeLog2) & 1; + + 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."); + + context_index = ((left * 2) + above) + blockSizeLog2 * Av1Constants.PartitionProbabilitySet; + + if (!has_rows && !has_cols) + { + Guard.IsTrue(partitionType == Av1PartitionType.Split, nameof(partitionType), "Partition outside frame boundaries should have Split type."); + return; + } + + if (has_rows && has_cols) + { + writer.WritePartitionType(partitionType, context_index); + } + else if (!has_rows && has_cols) + { + writer.WriteSplitOrVertical(partitionType, blockSize, context_index); + } + else + { + writer.WriteSplitOrHorizontal(partitionType, blockSize, context_index); + } + + return; + } + + /// + /// SVT: write_modes_b + /// + private static void WriteModesBlock( + Av1PictureControlSet pcs, + Av1EntropyCodingContext entropyCodingContext, + ref Av1SymbolEncoder writer, + Av1Superblock tb_ptr, + Av1EncoderBlockStruct blk_ptr, + ushort tile_idx, + Av1FrameBuffer coeff_ptr) + { + Av1SequenceControlSet scs = pcs.Sequence; + ObuFrameHeader frm_hdr = pcs.Parent.FrameHeader; + /* + Av1NeighborArrayUnit luma_dc_sign_level_coeff_na = pcs.LuminanceDcSignLevelCoefficientNeighbors[tile_idx]; + Av1NeighborArrayUnit cr_dc_sign_level_coeff_na = pcs.CrDcSignLevelCoefficientNeighbors[tile_idx]; + Av1NeighborArrayUnit cb_dc_sign_level_coeff_na = pcs.CbDcSignLevelCoefficientNeighbors[tile_idx]; + Av1NeighborArrayUnit txfm_context_array = pcs.TransformFunctionContexts[tile_idx]; + Av1BlockGeometry blockGeometry = GetBlockGeometryMds(blk_ptr.ModeDecisionScanIndex); + Point blockOrigin = Point.Add(entropyCodingContext.SuperblockOrigin, (Size)blockGeometry.Origin); + Av1BlockSize blockSize = blockGeometry.BlockSize; + Av1MacroBlockModeInfo macroBlockModeInfo = GetMacroBlockModeInfo(pcs, blockOrigin); + bool skipWritingCoefficients = macroBlockModeInfo.Block.Skip; + entropyCodingContext.MacroBlockModeInfo = macroBlockModeInfo; + + bool skip_mode = macroBlockModeInfo.Block.SkipMode; + + Guard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize)); + int mi_row = blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2; + int mi_col = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2; + int mi_stride = pcs.Parent.Common.ModeInfoStride; + int offset = (mi_row * mi_stride) + mi_col; + Point modeInfoPosition = new(mi_col, mi_row); + blk_ptr.MacroBlock.ModeInfo = pcs.ModeInfoGrid[offset]; + blk_ptr.MacroBlock.Tile = new Av1TileInfo(tb_ptr.TileInfo); + blk_ptr.MacroBlock.IsUpAvailable = modeInfoPosition.Y > tb_ptr.TileInfo.ModeInfoRowStart; + blk_ptr.MacroBlock.IsLeftAvailable = modeInfoPosition.X > tb_ptr.TileInfo.ModeInfoColumnStart; + + if (blk_ptr.MacroBlock.IsUpAvailable) + { + blk_ptr.MacroBlock.AboveMacroBlock = blk_ptr.MacroBlock.ModeInfo[-mi_stride].mbmi; + } + else + { + blk_ptr.MacroBlock.AboveMacroBlock = null; + } + + if (blk_ptr.MacroBlock.IsLeftAvailable) + { + blk_ptr.MacroBlock.LeftMacroBlock = blk_ptr.MacroBlock.ModeInfo[-1].mbmi; + } + else + { + blk_ptr.MacroBlock.LeftMacroBlock = null; + } + + blk_ptr.MacroBlock.tile_ctx = frame_context; + + int bw = blockSize.GetWidth(); + int bh = blockSize.GetHeight(); + set_mi_row_col( + pcs, + blk_ptr.MacroBlock, + blk_ptr.MacroBlock.Tile, + mi_row, + bh, + mi_col, + bw, + mi_stride, + pcs.Parent.Common.ModeInfoRowCount, + pcs.Parent.Common.ModeInfoColumnCount); + + // if (pcs.slice_type == I_SLICE) + // We implement only INTRA frames. + { + + // const int32_t skip = write_skip(cm, xd, mbmi->segment_id, mi, w) + if (pcs.Parent.FrameHeader.SegmentationParameters.Enabled && pcs.Parent.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) + { + WriteSegmentId(pcs, ref writer, blockGeometry.BlockSize, blockOrigin, blk_ptr, skipWritingCoefficients); + } + + EncodeSkipCoefficients(ref writer, blk_ptr, skipWritingCoefficients); + + if (pcs.Parent.FrameHeader.SegmentationParameters.Enabled && !pcs.Parent.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) + { + WriteSegmentId(pcs, ref writer, blockGeometry.BlockSize, blockOrigin, blk_ptr, skipWritingCoefficients); + } + + WriteCdef( + scs, + pcs, + ref writer, + tile_idx, + blk_ptr.MacroBlock, + skipWritingCoefficients, + blockOrigin / (1 << Av1Constants.ModeInfoSizeLog2)); + + if (pcs.Parent.FrameHeader.DeltaQParameters.IsPresent) + { + int current_q_index = blk_ptr.QuantizationIndex; + bool super_block_upper_left = (((blockOrigin.Y >> 2) & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0) && + (((blockOrigin.X >> 2) & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0); + if ((blockSize != scs.SequenceHeader.SuperblockSize || !skipWritingCoefficients) && super_block_upper_left) + { + Guard.MustBeGreaterThan(current_q_index, 0, nameof(current_q_index)); + int reduced_delta_qindex = (current_q_index - pcs.Parent.PreviousQIndex[tile_idx]) / + frm_hdr.DeltaQParameters.Resolution; + + writer.WriteDeltaQIndex(reduced_delta_qindex); + pcs.Parent.PreviousQIndex[tile_idx] = current_q_index; + } + } + + Av1PredictionMode intra_luma_mode = macroBlockModeInfo.Block.Mode; + uint intra_chroma_mode = macroBlockModeInfo.Block.UvMode; + if (svt_aom_allow_intrabc(pcs.Parent.FrameHeader, pcs.Parent.SliceType)) + { + WriteIntraBlockCopyInfo(ref writer, macroBlockModeInfo, blk_ptr); + } + + if (!macroBlockModeInfo.Block.UseIntraBlockCopy) + { + EncodeIntraLumaMode(ref writer, macroBlockModeInfo, blk_ptr, blockSize, intra_luma_mode); + } + + if (!macroBlockModeInfo.Block.UseIntraBlockCopy) + { + if (blockGeometry.HasUv) + { + EncodeIntraChromaMode( + ref writer, + macroBlockModeInfo, + blk_ptr, + blockSize, + intra_luma_mode, + intra_chroma_mode, + blockGeometry.BlockWidth <= 32 && blockGeometry.BlockHeight <= 32); + } + } + + if (!macroBlockModeInfo.Block.UseIntraBlockCopy && svt_aom_allow_palette(frm_hdr.AllowScreenContentTools, blockGeometry.BlockSize)) + { + WritePaletteModeInfo( + pcs.Parent, + ref writer, + macroBlockModeInfo, + blk_ptr, + blockGeometry.BlockSize, + blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2, + blockOrigin.X >> Av1Constants.ModeInfoSizeLog2); + } + + if (!macroBlockModeInfo.Block.UseIntraBlockCopy && + svt_aom_filter_intra_allowed( + scs.SequenceHeader.FilterIntraLevel, blockSize, blk_ptr.PaletteSize[0], intra_luma_mode)) + { + writer.WriteSkip(blk_ptr.FilterIntraMode != Av1FilterIntraMode.AllFilterIntraModes, blockSize); + if (blk_ptr.FilterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) + { + writer.WriteFilterIntraMode(blk_ptr.FilterIntraMode); + } + } + + if (!macroBlockModeInfo.Block.UseIntraBlockCopy) + { + assert(blk_ptr.PaletteSize[1] == 0); + TOKENEXTRA tok = entropyCodingContext.tok; + for (int plane = 0; plane < 2; ++plane) + { + int palette_size_plane = blk_ptr.PaletteSize[plane]; + if (palette_size_plane > 0) + { + Av1TransformSize tx_size = + blockGeometry.TransformSize[macroBlockModeInfo.Block.TransformDepth]; // inherit tx_size from 1st transform block; + svt_av1_tokenize_color_map( + frame_context, + blk_ptr, + plane, + tok, + blockSize, + tx_size, + PALETTE_MAP, + 0); // NO CDF update in entropy, the update will take place in arithmetic encode + assert(macroBlockModeInfo.Block.UseIntraBlockCopy); + assert(svt_aom_allow_palette(pcs.Parent.FrameHeader.AllowScreenContentTools, blockGeometry.BlockSize)); + svt_aom_get_block_dimensions(blockGeometry.BlockSize, plane, blk_ptr.MacroBlock, null, null, out int rowCount, out int columnCount); + pack_map_tokens(ref writer, ref entropyCodingContext.tok, palette_size_plane, rowCount * columnCount); + + // advance the pointer + entropyCodingContext.tok = tok; + } + } + } + + if (frm_hdr.TransformMode == Av1TransformMode.Select) + { + // TODO: Implement when Selecting transform block size is supported. + // CodeTransformSize( + // pcs, + // ref writer, + // blockOrigin, + // blk_ptr, + // blockGeometry, + // txfm_context_array, + // skipWritingCoefficients); + } + + if (!skipWritingCoefficients) + { + // SVT: av1_encode_coeff_1d + EncodeCoefficients1d( + pcs, + entropyCodingContext, + ref writer, + entropyCodingContext.MacroBlockModeInfo, + blk_ptr, + blockOrigin, + intra_luma_mode, + blockSize, + coeff_ptr, + luma_dc_sign_level_coeff_na, + cr_dc_sign_level_coeff_na, + cb_dc_sign_level_coeff_na); + } + } + + // Update the neighbors + ec_update_neighbors(pcs, entropyCodingContext, blockOrigin, blk_ptr, tile_idx, blockSize, coeff_ptr); + + if (svt_av1_allow_palette(pcs.Parent.PaletteLevel, blockGeometry.BlockSize)) + { + // free ENCDEC palette info buffer + assert(blk_ptr.palette_info.color_idx_map != null && "free palette:Null"); + EB_FREE(blk_ptr.palette_info.color_idx_map); + blk_ptr.palette_info.color_idx_map = null; + EB_FREE(blk_ptr.palette_info); + }*/ + } + + /// + /// SVT: av1_encode_coeff_1d + /// + private static void EncodeCoefficients1d( + Av1PictureControlSet pcs, + Av1EntropyCodingContext ec_ctx, + ref Av1SymbolEncoder writer, + Av1MacroBlockModeInfo mbmi, + Av1EncoderBlockStruct blk_ptr, + Point blockOrigin, + Av1PredictionMode intraLumaDir, + Av1BlockSize planeBlockSize, + Av1FrameBuffer coeff_ptr, + Av1NeighborArrayUnit luma_dc_sign_level_coeff_na, + Av1NeighborArrayUnit cr_dc_sign_level_coeff_na, + Av1NeighborArrayUnit cb_dc_sign_level_coeff_na) + { + if (mbmi.Block.TransformDepth != 0) + { + EncodeTransformCoefficientsY( + pcs, + ec_ctx, + ref writer, + mbmi, + blk_ptr, + blockOrigin, + intraLumaDir, + planeBlockSize, + coeff_ptr, + luma_dc_sign_level_coeff_na); + + EncodeTransformCoefficientsUv( + pcs, + ec_ctx, + ref writer, + mbmi, + blk_ptr, + blockOrigin, + intraLumaDir, + planeBlockSize, + coeff_ptr, + cr_dc_sign_level_coeff_na, + cb_dc_sign_level_coeff_na); + } + else + { + throw new NotImplementedException("Only capable to encode Largest transform mode."); + } + } + + /// + /// SVT: av1_encode_tx_coef_y + /// + public static void EncodeTransformCoefficientsY( + Av1PictureControlSet pcs, + Av1EntropyCodingContext entropyCodingContext, + ref Av1SymbolEncoder writer, + Av1MacroBlockModeInfo mbmi, + Av1EncoderBlockStruct blk_ptr, + Point blockOrigin, + Av1PredictionMode intraLumaDir, + Av1BlockSize plane_bsize, + Av1FrameBuffer coeff_ptr, + Av1NeighborArrayUnit luma_dc_sign_level_coeff_na) + { + // Removed any code related to INTER frames. + Av1BlockGeometry blockGeometry = GetBlockGeometryByModeDecisionScanIndex(blk_ptr.ModeDecisionScanIndex); + int tx_depth = mbmi.Block.TransformDepth; + int txb_count = blockGeometry.TransformBlockCount[mbmi.Block.TransformDepth]; + ObuFrameHeader frameHeader = pcs.Parent.FrameHeader; + + for (int tx_index = 0; tx_index < txb_count; tx_index++) + { + int txb_itr = tx_index; + + Av1TransformSize tx_size = blockGeometry.TransformSize[tx_depth]; + + int coeff1d_offset = entropyCodingContext.CodedAreaSuperblock; + Span coeff_buffer = coeff_ptr.BufferY!.DangerousGetSingleSpan()[coeff1d_offset..]; + + Av1TransformBlockContext blockContext = new(); + Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][txb_itr]; + GetTransformBlockContexts( + pcs, + Av1ComponentType.Luminance, + luma_dc_sign_level_coeff_na, + blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin, + plane_bsize, + tx_size, + blockContext); + + Av1TransformType tx_type = blk_ptr.TransformBlocks[txb_itr].TransformType[(int)Av1ComponentType.Luminance]; + int eob = blk_ptr.TransformBlocks[txb_itr].NzCoefficientCount[0]; + if (eob == 0) + { + // INTRA + tx_type = blk_ptr.TransformBlocks[txb_itr].TransformType[(int)Av1PlaneType.Y] = Av1TransformType.DctDct; + Guard.IsTrue(tx_type == Av1TransformType.DctDct, nameof(tx_type), string.Empty); + } + + int cul_level_y = writer.WriteCoefficients( + tx_size, + tx_type, + intraLumaDir, + coeff_buffer, + Av1ComponentType.Luminance, + blockContext, + (ushort)eob, + frameHeader.UseReducedTransformSet, + blk_ptr.FilterIntraMode); + + // Update the luma Dc Sign Level Coeff Neighbor Array + Span culLevelSpan = new(ref cul_level_y); + ReadOnlySpan dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelSpan); + + int transformWidth = blockGeometry.TransformSize[tx_depth].GetWidth(); + int transformHeight = blockGeometry.TransformSize[tx_depth].GetHeight(); + luma_dc_sign_level_coeff_na.UnitModeWrite( + dc_sign_level_coeff, + blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin, + new Size(transformWidth, transformHeight), + Av1NeighborArrayUnit.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); + + entropyCodingContext.CodedAreaSuperblock += transformWidth * transformHeight; + } + } + + /// + /// SVT: av1_encode_tx_coef_uv + /// + private static void EncodeTransformCoefficientsUv( + Av1PictureControlSet pcs, + Av1EntropyCodingContext entropyCodingContext, + ref Av1SymbolEncoder writer, + Av1MacroBlockModeInfo mbmi, + Av1EncoderBlockStruct blk_ptr, + Point blockOrigin, + Av1PredictionMode intraLumaDir, + Av1BlockSize plane_bsize, + Av1FrameBuffer coeff_ptr, + Av1NeighborArrayUnit cr_dc_sign_level_coeff_na, + Av1NeighborArrayUnit cb_dc_sign_level_coeff_na) + { + Av1BlockGeometry blockGeometry = GetBlockGeometryByModeDecisionScanIndex(blk_ptr.ModeDecisionScanIndex); + + if (!blockGeometry.HasUv) + { + return; + } + + int tx_depth = mbmi.Block.TransformDepth; + uint txb_count = 1; + ObuFrameHeader frameHeader = pcs.Parent.FrameHeader; + int transformWidth = blockGeometry.TransformSize[tx_depth].GetWidth(); + int transformHeight = blockGeometry.TransformSize[tx_depth].GetHeight(); + + for (uint tx_index = 0; tx_index < txb_count; ++tx_index) + { + Av1TransformSize chroma_tx_size = blockGeometry.TransformSizeUv[tx_depth]; + + if (blockGeometry.HasUv) + { + // cb + Span coeff_buffer = coeff_ptr.BufferCb!.DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv); + Av1TransformBlockContext blockContext = new(); + Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][tx_index]; + GetTransformBlockContexts( + pcs, + Av1ComponentType.Chroma, + cb_dc_sign_level_coeff_na, + RoundUv(blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin) / 2, + blockGeometry.BlockSizeUv, + chroma_tx_size, + blockContext); + Av1TransformType chroma_tx_type = blk_ptr.TransformBlocks[tx_index].TransformType[(int)Av1ComponentType.Chroma]; + int endOfBlockCb = blk_ptr.TransformBlocks[tx_index].NzCoefficientCount[1]; + int cul_level_cb = writer.WriteCoefficients( + chroma_tx_size, + chroma_tx_type, + intraLumaDir, + coeff_buffer, + Av1ComponentType.Chroma, + blockContext, + (ushort)endOfBlockCb, + 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]; + + GetTransformBlockContexts( + pcs, + Av1ComponentType.Chroma, + cr_dc_sign_level_coeff_na, + RoundUv(blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin) / 2, + blockGeometry.BlockSizeUv, + chroma_tx_size, + blockContext); + + int cul_level_cr = writer.WriteCoefficients( + chroma_tx_size, + chroma_tx_type, + intraLumaDir, + coeff_buffer, + Av1ComponentType.Chroma, + blockContext, + (ushort)endOfBlockCr, + frameHeader.UseReducedTransformSet, + blk_ptr.FilterIntraMode); + + // Update the cb Dc Sign Level Coeff Neighbor Array + Span culLevelCbSpan = new(ref cul_level_cb); + ReadOnlySpan dc_sign_level_coeff = MemoryMarshal.AsBytes(culLevelCbSpan); + cb_dc_sign_level_coeff_na.UnitModeWrite( + dc_sign_level_coeff, + RoundUv(transformOrigin) / 2, + 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( + dc_sign_level_coeff, + RoundUv(transformOrigin) / 2, + new Size(transformWidth, transformHeight), + Av1NeighborArrayUnit.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); + } + + entropyCodingContext.CodedAreaSuperblockUv += transformWidth * transformHeight; + } + } + + private static Point RoundUv(Point point) => throw new NotImplementedException(); + + /// + /// SVT: svt_aom_get_txb_ctx + /// + private static void GetTransformBlockContexts( + Av1PictureControlSet pcs, + Av1ComponentType plane, + Av1NeighborArrayUnit dcSignLevelCoefficientNeighborArray, + Point blockOrigin, + Av1BlockSize planeBlockSize, + Av1TransformSize transformSize, + Av1TransformBlockContext blockContext) + { + int dcSignLevelCoefficientLeftNeighborIndex = dcSignLevelCoefficientNeighborArray.GetLeftIndex(blockOrigin); + int dcSignLevelCoefficientTopNeighborIndex = dcSignLevelCoefficientNeighborArray.GetTopIndex(blockOrigin); + + sbyte[] signs = [0, -1, 1]; + int transformBlockWidth; + int transformBlockHeight; + if (plane != Av1ComponentType.Luminance) + { + transformBlockWidth = Math.Min(transformSize.GetWidth(), ((pcs.Parent.AlignedWidth / 2) - blockOrigin.X) >> 2); + transformBlockHeight = Math.Min(transformSize.GetHeight(), ((pcs.Parent.AlignedHeight / 2) - blockOrigin.Y) >> 2); + } + else + { + transformBlockWidth = Math.Min(transformSize.GetWidth(), (pcs.Parent.AlignedWidth - blockOrigin.X) >> 2); + transformBlockHeight = Math.Min(transformSize.GetHeight(), (pcs.Parent.AlignedHeight - blockOrigin.Y) >> 2); + } + + short dc_sign = 0; + ushort k = 0; + + byte sign; + + if (dcSignLevelCoefficientNeighborArray.Top[dcSignLevelCoefficientTopNeighborIndex] != Av1NeighborArrayUnit.InvalidNeighborData) + { + do + { + sign = (byte)(dcSignLevelCoefficientNeighborArray.Top[k + dcSignLevelCoefficientTopNeighborIndex] >> + Av1Constants.CoefficientContextBitCount); + Guard.MustBeLessThanOrEqualTo(sign, (byte)2, nameof(sign)); + dc_sign += signs[sign]; + } + while (++k < transformBlockWidth); + } + + if (dcSignLevelCoefficientNeighborArray.Left[dcSignLevelCoefficientLeftNeighborIndex] != Av1NeighborArrayUnit.InvalidNeighborData) + { + k = 0; + do + { + sign = (byte)(dcSignLevelCoefficientNeighborArray.Left[k + dcSignLevelCoefficientLeftNeighborIndex] >> + Av1Constants.CoefficientContextBitCount); + Guard.MustBeLessThanOrEqualTo(sign, (byte)2, nameof(sign)); + dc_sign += signs[sign]; + } + while (++k < transformBlockHeight); + } + + if (dc_sign > 0) + { + blockContext.DcSignContext = 2; + } + else if (dc_sign < 0) + { + blockContext.DcSignContext = 1; + } + else + { + blockContext.DcSignContext = 0; + } + + if (plane == Av1ComponentType.Luminance) + { + if (planeBlockSize == transformSize.ToBlockSize()) + { + blockContext.SkipContext = 0; + } + else + { + 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] + ]; + int top = 0; + int left = 0; + + k = 0; + if (dcSignLevelCoefficientNeighborArray.Top[dcSignLevelCoefficientTopNeighborIndex] != + Av1NeighborArrayUnit.InvalidNeighborData) + { + do + { + top |= dcSignLevelCoefficientNeighborArray.Top[k + dcSignLevelCoefficientTopNeighborIndex]; + } + while (++k < transformBlockWidth); + } + + top &= Av1Constants.CoefficientContextMask; + + if (dcSignLevelCoefficientNeighborArray.Left[dcSignLevelCoefficientLeftNeighborIndex] != + Av1NeighborArrayUnit.InvalidNeighborData) + { + k = 0; + do + { + left |= dcSignLevelCoefficientNeighborArray.Left[k + dcSignLevelCoefficientLeftNeighborIndex]; + } + while (++k < transformBlockHeight); + } + + left &= Av1Constants.CoefficientContextMask; + int max = Math.Min(top | left, 4); + int min = Math.Min(Math.Min(top, left), 4); + + blockContext.SkipContext = skip_contexts[min][max]; + } + } + else + { + short ctx_base_left = 0; + short ctx_base_top = 0; + + if (dcSignLevelCoefficientNeighborArray.Top[dcSignLevelCoefficientTopNeighborIndex] != + Av1NeighborArrayUnit.InvalidNeighborData) + { + k = 0; + do + { + ctx_base_top += + (dcSignLevelCoefficientNeighborArray.Top[k + dcSignLevelCoefficientTopNeighborIndex] != 0) ? (short)1 : (short)0; + } + while (++k < transformBlockWidth); + } + + if (dcSignLevelCoefficientNeighborArray.Left[dcSignLevelCoefficientLeftNeighborIndex] != + Av1NeighborArrayUnit.InvalidNeighborData) + { + k = 0; + do + { + ctx_base_left += dcSignLevelCoefficientNeighborArray.Left[k + dcSignLevelCoefficientLeftNeighborIndex] != 0 ? (short)1 : (short)0; + } + while (++k < transformBlockHeight); + } + + int ctx_base = ((ctx_base_left != 0) ? 1 : 0) + ((ctx_base_top != 0) ? 1 : 0); + int ctx_offset = planeBlockSize.GetPelsLog2Count() > transformSize.ToBlockSize().GetPelsLog2Count() ? 10 : 7; + blockContext.SkipContext = (short)(ctx_base + ctx_offset); + } + } + + private static void WriteSegmentId(Av1PictureControlSet pcs, ref Av1SymbolEncoder writer, Av1BlockSize blockSize, Point blockOrigin, Av1EncoderBlockStruct block, bool skip) + { + ObuSegmentationParameters segmentation_params = pcs.Parent.FrameHeader.SegmentationParameters; + if (!segmentation_params.Enabled) + { + return; + } + + int spatial_pred = GetSpatialSegmentationPrediction(pcs, block.MacroBlock, blockOrigin, out int cdf_num); + if (skip) + { + pcs.UpdateSegmentation(blockSize, blockOrigin, spatial_pred); + block.SegmentId = spatial_pred; + return; + } + + int coded_id = Av1SymbolContextHelper.NegativeDeinterleave(block.SegmentId, spatial_pred, segmentation_params.LastActiveSegmentId + 1); + writer.WriteSegmentId(coded_id, cdf_num); + pcs.UpdateSegmentation(blockSize, blockOrigin, block.SegmentId); + } + + /// + /// SVT: svt_av1_get_spatial_seg_prediction + /// + 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 + + int mi_col = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2; + int mi_row = blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2; + bool left_available = xd.IsLeftAvailable; + bool up_available = xd.IsUpAvailable; + Av1Common cm = pcs.Parent.Common; + Span segmentation_map = pcs.SegmentationNeighborMap; + + if (up_available && left_available) + { + prev_ul = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_row - 1, mi_col - 1)); + } + + if (up_available) + { + prev_u = Av1SymbolContextHelper.GetSegmentId(cm, segmentation_map, Av1BlockSize.Block4x4, new Point(mi_row - 1, mi_col - 0)); + } + + if (left_available) + { + 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. + if (prev_ul < 0 || prev_u < 0 || prev_l < 0) /* Edge case */ + { + cdf_index = 0; + } + else if ((prev_ul == prev_u) && (prev_ul == prev_l)) + { + cdf_index = 2; + } + else if ((prev_ul == prev_u) || (prev_ul == prev_l) || (prev_u == prev_l)) + { + cdf_index = 1; + } + else + { + cdf_index = 0; + } + + // If 2 or more are identical returns that as predictor, otherwise prev_l. + if (prev_u == -1) // edge case + { + return prev_l == -1 ? 0 : prev_l; + } + + if (prev_l == -1) // edge case + { + return prev_u; + } + + return (prev_ul == prev_u) ? prev_u : prev_l; + } + + internal static void EncodeSkipCoefficients(ref Av1SymbolEncoder writer, Av1EncoderBlockStruct block, bool skip) + { + Av1MacroBlockModeInfo? above_mi = block.MacroBlock.AboveMacroBlock; + Av1MacroBlockModeInfo? left_mi = block.MacroBlock.LeftMacroBlock; + int above_skip = (above_mi != null && above_mi.Block.Skip) ? 1 : 0; + int left_skip = (left_mi != null && left_mi.Block.Skip) ? 1 : 0; + writer.WriteSkip(skip, above_skip + left_skip); + } + + /// + /// SVT: get_blk_geom_mds + /// + private static Av1BlockGeometry GetBlockGeometryByModeDecisionScanIndex(int modeDecisionScanIndex) => throw new NotImplementedException(); +}