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();
+}