diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
index f6e7d6f3f8..9c16b63def 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
@@ -10,6 +10,35 @@ internal static class Av1BlockSizeExtensions
private static readonly int[] SizeWide = [1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16];
private static readonly int[] SizeHigh = [1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4];
+ private static readonly Av1BlockSize[][][] SubSampled =
+ [
+
+ // ss_x == 0 ss_x == 0 ss_x == 1 ss_x == 1
+ // ss_y == 0 ss_y == 1 ss_y == 0 ss_y == 1
+ [[Av1BlockSize.Block4x4, Av1BlockSize.Block4x4], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x4]],
+ [[Av1BlockSize.Block4x8, Av1BlockSize.Block4x4], [Av1BlockSize.Invalid, Av1BlockSize.Block4x4]],
+ [[Av1BlockSize.Block8x4, Av1BlockSize.Invalid], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x4]],
+ [[Av1BlockSize.Block8x8, Av1BlockSize.Block8x4], [Av1BlockSize.Block4x8, Av1BlockSize.Block4x4]],
+ [[Av1BlockSize.Block8x16, Av1BlockSize.Block8x8], [Av1BlockSize.Invalid, Av1BlockSize.Block4x8]],
+ [[Av1BlockSize.Block16x8, Av1BlockSize.Invalid], [Av1BlockSize.Block8x8, Av1BlockSize.Block8x4]],
+ [[Av1BlockSize.Block16x16, Av1BlockSize.Block16x8], [Av1BlockSize.Block8x16, Av1BlockSize.Block8x8]],
+ [[Av1BlockSize.Block16x32, Av1BlockSize.Block16x16], [Av1BlockSize.Invalid, Av1BlockSize.Block8x16]],
+ [[Av1BlockSize.Block32x16, Av1BlockSize.Invalid], [Av1BlockSize.Block16x16, Av1BlockSize.Block16x8]],
+ [[Av1BlockSize.Block32x32, Av1BlockSize.Block32x16], [Av1BlockSize.Block16x32, Av1BlockSize.Block16x16]],
+ [[Av1BlockSize.Block32x64, Av1BlockSize.Block32x32], [Av1BlockSize.Invalid, Av1BlockSize.Block16x32]],
+ [[Av1BlockSize.Block64x32, Av1BlockSize.Invalid], [Av1BlockSize.Block32x32, Av1BlockSize.Block32x16]],
+ [[Av1BlockSize.Block64x64, Av1BlockSize.Block64x32], [Av1BlockSize.Block32x64, Av1BlockSize.Block32x32]],
+ [[Av1BlockSize.Block64x128, Av1BlockSize.Block64x64], [Av1BlockSize.Invalid, Av1BlockSize.Block32x64]],
+ [[Av1BlockSize.Block128x64, Av1BlockSize.Invalid], [Av1BlockSize.Block64x64, Av1BlockSize.Block64x32]],
+ [[Av1BlockSize.Block128x128, Av1BlockSize.Block128x64], [Av1BlockSize.Block64x128, Av1BlockSize.Block64x64]],
+ [[Av1BlockSize.Block4x16, Av1BlockSize.Block4x8], [Av1BlockSize.Invalid, Av1BlockSize.Block4x8]],
+ [[Av1BlockSize.Block16x4, Av1BlockSize.Invalid], [Av1BlockSize.Block8x4, Av1BlockSize.Block8x4]],
+ [[Av1BlockSize.Block8x32, Av1BlockSize.Block8x16], [Av1BlockSize.Invalid, Av1BlockSize.Block4x16]],
+ [[Av1BlockSize.Block32x8, Av1BlockSize.Invalid], [Av1BlockSize.Block16x8, Av1BlockSize.Block16x4]],
+ [[Av1BlockSize.Block16x64, Av1BlockSize.Block16x32], [Av1BlockSize.Invalid, Av1BlockSize.Block8x32]],
+ [[Av1BlockSize.Block64x16, Av1BlockSize.Invalid], [Av1BlockSize.Block32x16, Av1BlockSize.Block32x8]]
+ ];
+
private static readonly Av1TransformSize[] MaxTransformSize = [
Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8,
Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32,
@@ -48,7 +77,20 @@ internal static class Av1BlockSizeExtensions
=> Get4x4HighCount(blockSize) << 2;
///
- /// Returns th largest transform size that can be used for blocks of given size.
+ /// Returns the block size of a sub sampled block.
+ ///
+ public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, bool subY)
+ {
+ if (blockSize == Av1BlockSize.Invalid)
+ {
+ return Av1BlockSize.Invalid;
+ }
+
+ return SubSampled[(int)blockSize][subX ? 1 : 0][subY ? 1 : 0];
+ }
+
+ ///
+ /// Returns the largest transform size that can be used for blocks of given size.
/// The can be either a square or rectangular block.
///
public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize)
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
index 35ce9c37ff..1fd5d3c2f2 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
@@ -2,61 +2,13 @@
// 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.Quantization;
using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
-using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal class Av1Decoder : IAv1TileDecoder
{
- private static readonly int[] SgrprojXqdMid = [-32, 31];
- private static readonly int[] WienerTapsMid = [3, -7, 15];
- private const int PartitionProbabilitySet = 4;
-
- private int[] deltaLoopFilter = [];
- private bool[][][] blockDecoded = [];
- private int[][] referenceSgrXqd = [];
- private int[][][] referenceLrWiener = [];
- private bool availableUp;
- private bool availableLeft;
- private bool availableUpForChroma;
- private bool availableLeftForChroma;
- private Av1ParseAboveContext aboveContext = new();
- private Av1ParseLeftContext leftContext = new();
- private bool skip;
- private bool readDeltas;
- private int currentQuantizerIndex;
- private Av1PredictionMode[][] YModes = [];
- private Av1PredictionMode YMode = Av1PredictionMode.DC;
- private Av1PredictionMode UvMode = Av1PredictionMode.DC;
- private Av1PredictionMode[][] UvModes = [];
- private object[] ReferenceFrame = [];
- private object[][][] ReferenceFrames = [];
- private bool HasChroma;
- private bool SubsamplingX;
- private bool SubsamplingY;
- private int PaletteSizeY;
- private int PaletteSizeUv;
- private object[][] aboveLevelContext = [];
- private object[][] aboveDcContext = [];
- private object[][] leftLevelContext = [];
- private object[][] leftDcContext = [];
- private Av1TransformSize TransformSize = Av1TransformSize.Size4x4;
- private bool skipMode;
- private bool IsChromaForLumaAllowed;
- private object FilterUltraMode = -1;
- private int AngleDeltaY;
- private int AngleDeltaUv;
- private bool Lossless;
- private int[][] SegmentIds = [];
- private int MaxLumaWidth;
- private int MaxLumaHeight;
- private int segmentId;
- private int[][] cdefIndex = [];
- private int deltaLoopFilterResolution;
- private int deltaQuantizerResolution;
+ private readonly Av1TileDecoder tileDecoder;
public Av1Decoder()
{
@@ -64,7 +16,7 @@ internal class Av1Decoder : IAv1TileDecoder
this.SequenceHeader = new ObuSequenceHeader();
this.TileInfo = new ObuTileInfo();
this.SeenFrameHeader = false;
- this.currentQuantizerIndex = -1;
+ this.tileDecoder = new Av1TileDecoder(this.SequenceHeader, this.FrameInfo, this.TileInfo);
}
public bool SequenceHeaderDone { get; set; }
@@ -86,751 +38,8 @@ internal class Av1Decoder : IAv1TileDecoder
}
public void DecodeTile(Span tileData, int tileNum)
- {
- Av1SymbolDecoder reader = new(tileData);
- int tileRowIndex = tileNum / this.TileInfo.TileColumnCount;
- int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount;
- this.aboveContext.Clear();
- this.ClearLoopFilterDelta();
- int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
- this.referenceSgrXqd = new int[planesCount][];
- this.referenceLrWiener = new int[planesCount][][];
- for (int plane = 0; plane < planesCount; plane++)
- {
- this.referenceSgrXqd[plane] = new int[2];
- Array.Copy(SgrprojXqdMid, this.referenceSgrXqd[plane], SgrprojXqdMid.Length);
- this.referenceLrWiener[plane] = new int[2][];
- for (int pass = 0; pass < 2; pass++)
- {
- this.referenceLrWiener[plane][pass] = new int[ObuConstants.WienerCoefficientCount];
- Array.Copy(WienerTapsMid, this.referenceLrWiener[plane][pass], WienerTapsMid.Length);
- }
- }
-
- Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
- int superBlock4x4Size = superBlockSize.Get4x4WideCount();
- for (int row = this.TileInfo.TileRowStartModeInfo[tileRowIndex]; row < this.TileInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize)
- {
- int superBlockRow = (row << ObuConstants.ModeInfoSizeLog2) >> this.SequenceHeader.SuperBlockSizeLog2;
- this.leftContext.Clear();
- for (int column = this.TileInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.TileInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize)
- {
- int superBlockColumn = (column << ObuConstants.ModeInfoSizeLog2) >> this.SequenceHeader.SuperBlockSizeLog2;
- bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX;
- bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY;
-
- bool ReadDeltas = this.FrameInfo.DeltaQParameters.IsPresent;
-
- // Nothing to do for CDEF
- // this.ClearCdef(row, column);
- this.ClearBlockDecodedFlags(row, column, superBlock4x4Size);
- this.ReadLoopRestoration(row, column, superBlockSize);
- this.DecodePartition(ref reader, row, column, superBlockSize);
- }
- }
- }
-
- private void ClearLoopFilterDelta() => this.deltaLoopFilter = new int[4];
-
- private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size)
- {
- int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
- for (int plane = 0; plane < planesCount; plane++)
- {
- int subX = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX) ? 1 : 0;
- int subY = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : 0;
- int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - column) >> subX;
- int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - row) >> subY;
- for (int y = -1; y <= (superBlock4x4Size >> subY); y++)
- {
- for (int x = -1; x <= (superBlock4x4Size >> subX); x++)
- {
- if (y < 0 && x < superBlock4x4Width)
- {
- this.blockDecoded[plane][y][x] = true;
- }
- else if (x < 0 && y < superBlock4x4Height)
- {
- this.blockDecoded[plane][y][x] = true;
- }
- else
- {
- this.blockDecoded[plane][y][x] = false;
- }
- }
- }
-
- this.blockDecoded[plane][superBlock4x4Size >> subY][-1] = false;
- }
- }
-
- private void ReadLoopRestoration(int row, int column, Av1BlockSize superBlockSize)
- {
- int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
- for (int plane = 0; plane < planesCount; plane++)
- {
- if (this.FrameInfo.LoopRestorationParameters[plane].Type != ObuRestorationType.None)
- {
- // TODO: Implement.
- throw new NotImplementedException("No loop restoration filter support.");
- }
- }
- }
+ => this.tileDecoder.DecodeTile(tileData, tileNum);
public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration)
- {
- // TODO: Implement
- }
-
- private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
- {
- if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] ||
- columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex])
- {
- return;
- }
-
- this.availableUp = this.IsInside(rowIndex - 1, columnIndex);
- this.availableLeft = this.IsInside(rowIndex, columnIndex - 1);
- int block4x4Size = blockSize.Get4x4WideCount();
- int halfBlock4x4Size = block4x4Size >> 1;
- int quarterBlock4x4Size = halfBlock4x4Size >> 2;
- bool hasRows = (rowIndex + halfBlock4x4Size) < this.TileInfo.TileRowCount;
- bool hasColumns = (columnIndex + halfBlock4x4Size) < this.TileInfo.TileColumnCount;
- Av1PartitionType partitionType = Av1PartitionType.Split;
- if (blockSize < Av1BlockSize.Block8x8)
- {
- partitionType = Av1PartitionType.None;
- }
- else if (hasRows && hasColumns)
- {
- int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
- partitionType = reader.ReadPartitionType(ctx);
- }
- else if (hasColumns)
- {
- int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
- bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx);
- partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal;
- }
- else if (hasRows)
- {
- int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
- bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx);
- partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical;
- }
-
- Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize);
- Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize);
- switch (partitionType)
- {
- case Av1PartitionType.Split:
- this.DecodePartition(ref reader, rowIndex, columnIndex, subSize);
- this.DecodePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize);
- this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize);
- this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize);
- break;
- case Av1PartitionType.None:
- this.DecodeBlock(ref reader, rowIndex, columnIndex, subSize);
- break;
- default:
- throw new NotImplementedException($"Partition type: {partitionType} is not supported.");
- }
- }
-
- private void DecodeBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
- {
- int block4x4Width = blockSize.Get4x4WideCount();
- int block4x4Height = blockSize.Get4x4HighCount();
- int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
- bool hasChroma = planesCount > 1;
- if (block4x4Height == 1 && this.SequenceHeader.ColorConfig.SubSamplingY && (rowIndex & 0x1) == 0)
- {
- hasChroma = false;
- }
-
- if (block4x4Width == 1 && this.SequenceHeader.ColorConfig.SubSamplingX && (columnIndex & 0x1) == 0)
- {
- hasChroma = false;
- }
-
- this.availableUp = this.IsInside(rowIndex - 1, columnIndex);
- this.availableLeft = this.IsInside(rowIndex, columnIndex - 1);
- this.availableUpForChroma = this.availableUp;
- this.availableLeftForChroma = this.availableLeft;
- if (hasChroma)
- {
- if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1)
- {
- this.availableUpForChroma = this.IsInside(rowIndex - 2, columnIndex);
- }
-
- if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1)
- {
- this.availableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2);
- }
- }
-
- this.ReadModeInfo(ref reader, rowIndex, columnIndex, blockSize);
- this.ReadPaletteTokens(ref reader);
- this.ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize);
- if (this.skip)
- {
- this.ResetBlockContext(rowIndex, columnIndex, block4x4Width, block4x4Height);
- }
-
- bool isCompound = false;
- for (int y = 0; y < block4x4Height; y++)
- {
- for (int x = 0; x < block4x4Width; x++)
- {
- this.YModes[rowIndex + y][columnIndex + x] = this.YMode;
- if (this.ReferenceFrame[0] == (object)ObuFrameType.IntraOnlyFrame && hasChroma)
- {
- this.UvModes[rowIndex + y][columnIndex + x] = this.UvMode;
- }
-
- for (int refList = 0; refList < 2; refList++)
- {
- this.ReferenceFrames[rowIndex + y][columnIndex + x][refList] = this.ReferenceFrame[refList];
- }
- }
- }
-
- this.ComputePrediction();
- this.Residual(rowIndex, columnIndex, blockSize);
- }
-
- private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize)
- {
- int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15;
- int widthChunks = Math.Max(1, blockSize.Get4x4WideCount() >> 6);
- int heightChunks = Math.Max(1, blockSize.Get4x4HighCount() >> 6);
- Av1BlockSize sizeChunk = (widthChunks > 1 || heightChunks > 1) ? Av1BlockSize.Block64x64 : blockSize;
-
- for (int chunkY = 0; chunkY < heightChunks; chunkY++)
- {
- for (int chunkX = 0; chunkX < widthChunks; chunkX++)
- {
- int rowChunk = rowIndex + (chunkY << 4);
- int columnChunk = columnIndex + (chunkX << 4);
- int subBlockRow = rowChunk & superBlockMask;
- int subBlockColumn = columnChunk & superBlockMask;
- for (int plane = 0; plane < 1 + (this.HasChroma ? 2 : 0); plane++)
- {
- Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, this.TransformSize);
- int stepX = transformSize.GetWidth() >> 2;
- int stepY = transformSize.GetHeight() >> 2;
- Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane);
- int num4x4Width = planeSize.Get4x4WideCount();
- int num4x4Height = planeSize.Get4x4HighCount();
- int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0;
- int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0;
- int baseX = (columnChunk >> subX) * (1 << ObuConstants.ModeInfoSizeLog2);
- int baseY = (rowChunk >> subY) * (1 << ObuConstants.ModeInfoSizeLog2);
- int baseXBlock = (columnIndex >> subX) * (1 << ObuConstants.ModeInfoSizeLog2);
- int baseYBlock = (rowIndex >> subY) * (1 << ObuConstants.ModeInfoSizeLog2);
- for (int y = 0; y < num4x4Height; y += stepY)
- {
- for (int x = 0; x < num4x4Width; x += stepX)
- {
- this.TransformBlock(plane, baseXBlock, baseYBlock, transformSize, x + ((chunkX << 4) >> subX), y + ((chunkY << 4) >> subY));
- }
- }
- }
- }
- }
- }
-
- private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException();
-
- private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) => throw new NotImplementedException();
-
- private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y)
- {
- int startX = baseX + (4 * x);
- int startY = baseY + (4 * y);
- int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0;
- int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0;
- int columnIndex = (startX << subX) >> ObuConstants.ModeInfoSizeLog2;
- int rowIndex = (startY << subY) >> ObuConstants.ModeInfoSizeLog2;
- int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15;
- int subBlockColumn = columnIndex & superBlockMask;
- int subBlockRow = rowIndex & superBlockMask;
- int stepX = transformSize.GetWidth() >> ObuConstants.ModeInfoSizeLog2;
- int stepY = transformSize.GetHeight() >> ObuConstants.ModeInfoSizeLog2;
- int maxX = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subX;
- int maxY = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subY;
- if (startX >= maxX || startY >= maxY)
- {
- return;
- }
-
- if ((plane == 0 && this.PaletteSizeY > 0) ||
- (plane != 0 && this.PaletteSizeUv > 0))
- {
- this.PredictPalette(plane, startX, startY, x, y, transformSize);
- }
- else
- {
- bool isChromaFromLuma = plane > 0 && this.UvMode == Av1PredictionMode.UvChromaFromLuma;
- Av1PredictionMode mode;
- if (plane == 0)
- {
- mode = this.YMode;
- }
- else
- {
- mode = isChromaFromLuma ? Av1PredictionMode.DC : this.UvMode;
- }
-
- int log2Width = transformSize.GetWidthLog2();
- int log2Height = transformSize.GetHeightLog2();
- bool leftAvailable = (x > 0) || plane == 0 ? this.availableLeft : this.availableLeftForChroma;
- bool upAvailable = (y > 0) || plane == 0 ? this.availableUp : this.availableUpForChroma;
- bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX];
- bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1];
- this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height);
- if (isChromaFromLuma)
- {
- this.PredictChromaFromLuma(plane, startX, startY, transformSize);
- }
- }
-
- if (plane == 0)
- {
- this.MaxLumaWidth = startX + (stepX * 4);
- this.MaxLumaHeight = startY + (stepY * 4);
- }
-
- if (!this.skip)
- {
- int eob = this.Coefficients(plane, startX, startY, transformSize);
- if (eob > 0)
- {
- this.Reconstruct(plane, startX, startY, transformSize);
- }
- }
-
- for (int i = 0; i < stepY; i++)
- {
- for (int j = 0; j < stepX; j++)
- {
- // Ignore loop filter.
- this.blockDecoded[plane][(subBlockRow >> subY) + i][(subBlockColumn >> subX) + j] = true;
- }
- }
- }
-
- private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
-
- private int Coefficients(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
-
- private void PredictChromaFromLuma(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
-
- private void PredictIntra(int plane, int startX, int startY, bool leftAvailable, bool upAvailable, bool haveAboveRight, bool haveBelowLeft, Av1PredictionMode mode, int log2Width, int log2Height) => throw new NotImplementedException();
-
- private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException();
-
- private void ComputePrediction()
- {
- // Not applicable for INTRA frames.
- }
-
- private void ResetBlockContext(int rowIndex, int columnIndex, int block4x4Width, int block4x4Height)
- {
- for (int plane = 0; plane < 1 + (this.HasChroma ? 2 : 0); plane++)
- {
- int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0;
- int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0;
- for (int i = columnIndex >> subX; i < ((columnIndex + block4x4Width) >> subX); i++)
- {
- this.aboveLevelContext[plane][i] = 0;
- this.aboveDcContext[plane][i] = 0;
- }
-
- for (int i = rowIndex >> subY; i < ((rowIndex + block4x4Height) >> subY); i++)
- {
- this.leftLevelContext[plane][i] = 0;
- this.leftDcContext[plane][i] = 0;
- }
- }
- }
-
- private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
- {
- int block4x4Width = blockSize.Get4x4WideCount();
- int block4x4Height = blockSize.Get4x4HighCount();
-
- // First condition in spec is for INTER frames, implemented only the INTRA condition.
- this.ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize);
- /*for (int row = rowIndex; row < rowIndex + block4x4Height; row++)
- {
- for (int column = columnIndex; column < columnIndex + block4x4Width; column++)
- {
- this.InterTransformSizes[row][column] = this.TransformSize;
- }
- }*/
- }
-
- private void ReadPaletteTokens(ref Av1SymbolDecoder reader)
- {
- if (this.PaletteSizeY != 0)
- {
- // Todo: Implement.
- throw new NotImplementedException();
- }
-
- if (this.PaletteSizeUv != 0)
- {
- // Todo: Implement.
- throw new NotImplementedException();
- }
- }
-
- private void ReadModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
- => this.ReadIntraFrameModeInfo(ref reader, rowIndex, columnIndex, blockSize);
-
- private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
- {
- this.skip = false;
- if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip)
- {
- this.ReadIntraSegmentId(ref reader);
- }
-
- this.skipMode = false;
- this.ReadSkip(ref reader);
- if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip)
- {
- this.IntraSegmentId(ref reader, rowIndex, columnIndex);
- }
-
- this.ReadCdef(ref reader, rowIndex, columnIndex, blockSize);
- this.ReadDeltaQuantizerIndex(ref reader, blockSize);
- this.ReadDeltaLoopFilter(ref reader, blockSize);
- this.readDeltas = false;
- this.ReferenceFrame[0] = -1; // IntraFrame;
- this.ReferenceFrame[1] = -1; // None;
- bool useIntraBlockCopy = false;
- if (this.FrameInfo.AllowIntraBlockCopy)
- {
- useIntraBlockCopy = reader.ReadUseIntraBlockCopy();
- }
-
- if (useIntraBlockCopy)
- {
- // TODO: Implement
- }
- else
- {
- // this.IsInter = false;
- this.YMode = reader.ReadIntraFrameYMode(blockSize);
- this.IntraAngleInfoY(ref reader, blockSize);
- if (this.HasChroma)
- {
- this.UvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed);
- if (this.UvMode == Av1PredictionMode.UvChromaFromLuma)
- {
- this.ReadChromaFromLumaAlphas(ref reader);
- }
-
- this.IntraAngleInfoUv(ref reader, blockSize);
- }
-
- this.PaletteSizeY = 0;
- this.PaletteSizeUv = 0;
- if (this.SequenceHeader.ModeInfoSize >= (int)Av1BlockSize.Block8x8 &&
- ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4WideCount() <= 64 &&
- ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4HighCount() <= 64 &&
- this.FrameInfo.AllowScreenContentTools)
- {
- this.PaletteModeInfo(ref reader);
- }
-
- this.FilterIntraModeInfo(ref reader, blockSize);
- }
- }
-
- private void ReadIntraSegmentId(ref Av1SymbolDecoder reader) => throw new NotImplementedException();
-
- private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
- {
- bool useFilterIntra = false;
- if (this.SequenceHeader.EnableFilterIntra &&
- this.YMode == Av1PredictionMode.DC && this.PaletteSizeY == 0 &&
- Math.Max(blockSize.GetWidth(), blockSize.GetHeight()) <= 32)
- {
- useFilterIntra = reader.ReadUseFilterUltra();
- if (useFilterIntra)
- {
- this.FilterUltraMode = reader.ReadFilterUltraMode();
- }
- }
- }
-
- private void PaletteModeInfo(ref Av1SymbolDecoder reader) =>
-
- // TODO: Implement.
- throw new NotImplementedException();
-
- private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader) =>
-
- // TODO: Implement.
- throw new NotImplementedException();
-
- private void IntraAngleInfoY(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
- {
- this.AngleDeltaY = 0;
- if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.YMode))
- {
- int angleDeltaY = reader.ReadAngleDelta(this.YMode);
- this.AngleDeltaY = angleDeltaY - ObuConstants.MaxAngleDelta;
- }
- }
-
- private void IntraAngleInfoUv(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
- {
- this.AngleDeltaUv = 0;
- if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.UvMode))
- {
- int angleDeltaUv = reader.ReadAngleDelta(this.UvMode);
- this.AngleDeltaUv = angleDeltaUv - ObuConstants.MaxAngleDelta;
- }
- }
-
- private static bool IsDirectionalMode(Av1PredictionMode mode)
- => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
-
- private void IntraSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex)
- {
- if (this.FrameInfo.SegmentationParameters.Enabled)
- {
- this.ReadSegmentId(ref reader, rowIndex, columnIndex);
- }
- else
- {
- this.segmentId = 0;
- }
-
- this.Lossless = this.FrameInfo.LosslessArray[this.segmentId];
- }
-
- private void ReadSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex)
- {
- int pred;
- int prevUL = -1;
- int prevU = -1;
- int prevL = -1;
- if (this.availableUp && this.availableLeft)
- {
- prevUL = this.SegmentIds[rowIndex - 1][columnIndex - 1];
- }
-
- if (this.availableUp)
- {
- prevU = this.SegmentIds[rowIndex - 1][columnIndex];
- }
-
- if (this.availableLeft)
- {
- prevU = this.SegmentIds[rowIndex][columnIndex - 1];
- }
-
- if (prevU == -1)
- {
- pred = (prevL == -1) ? 0 : prevL;
- }
- else if (prevL == -1)
- {
- pred = prevU;
- }
- else
- {
- pred = (prevU == prevUL) ? prevU : prevL;
- }
-
- if (this.skip)
- {
- this.segmentId = 0;
- }
- else
- {
- int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId;
- this.segmentId = NegativeDeinterleave(reader.ReadSegmentId(-1), pred, lastActiveSegmentId + 1);
- }
- }
-
- 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);
- }
- }
-
- private void ReadCdef(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
- {
- if (this.skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy)
- {
- return;
- }
-
- int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount();
- int cdefMask4 = ~(cdefSize4 - 1);
- int r = rowIndex & cdefMask4;
- int c = columnIndex & cdefMask4;
- if (this.cdefIndex[r][c] == -1)
- {
- this.cdefIndex[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount);
- int w4 = blockSize.Get4x4WideCount();
- int h4 = blockSize.Get4x4HighCount();
- for (int i = r; i < r + h4; i += cdefSize4)
- {
- for (int j = c; j < c + w4; j += cdefSize4)
- {
- this.cdefIndex[i][j] = this.cdefIndex[r][c];
- }
- }
- }
- }
-
- private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
- {
- Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
- if (blockSize == superBlockSize && this.skip)
- {
- return;
- }
-
- if (this.readDeltas && this.FrameInfo.DeltaLoopFilterParameters.IsPresent)
- {
- int frameLoopFilterCount = 1;
- if (this.FrameInfo.DeltaLoopFilterParameters.Multi)
- {
- frameLoopFilterCount = (this.SequenceHeader.ColorConfig.ChannelCount > 1) ? ObuConstants.FrameLoopFilterCount : ObuConstants.FrameLoopFilterCount - 2;
- }
-
- for (int i = 0; i < frameLoopFilterCount; i++)
- {
- int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute();
- if (deltaLoopFilterAbsolute == ObuConstants.DeltaLoopFilterSmall)
- {
- int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1;
- int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits);
- deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1;
- }
-
- if (deltaLoopFilterAbsolute != 0)
- {
- bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0;
- int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute;
- this.deltaLoopFilter[i] = Av1Math.Clip3(-ObuConstants.MaxLoopFilter, ObuConstants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution));
- }
- }
- }
- }
-
- private void ReadSkip(ref Av1SymbolDecoder reader)
- {
- if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip && this.FrameInfo.SegmentationParameters.IsFeatureActive(ObuSegmentationFeature.LevelSkip))
- {
- this.skip = true;
- }
- else
- {
- this.skip = reader.ReadSkip(-1);
- }
- }
-
- private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
- {
- Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
- if (blockSize == superBlockSize && this.skip)
- {
- return;
- }
-
- if (this.readDeltas)
- {
- int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute();
- if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall)
- {
- int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1;
- int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits);
- deltaQuantizerAbsolute = deltaQuantizerRemainingBits + (1 << deltaQuantizerRemainingBits) + 1;
- }
-
- if (deltaQuantizerAbsolute != 0)
- {
- bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0;
- int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute;
- this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution));
- }
- }
- }
-
- private bool IsInside(int rowIndex, int columnIndex) =>
- columnIndex >= this.TileInfo.TileColumnCount &&
- columnIndex < this.TileInfo.TileColumnCount &&
- rowIndex >= this.TileInfo.TileRowCount &&
- rowIndex < this.TileInfo.TileRowCount;
-
- private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY)
- {
- int block4x4Width = blockMode.BlockSize.Get4x4WideCount();
- int block4x4Height = blockMode.BlockSize.Get4x4HighCount();
- bool xPos = (columnIndex & 0x1) > 0 || (block4x4Width & 0x1) > 0 || !subSamplingX;
- bool yPos = (rowIndex & 0x1) > 0 || (block4x4Height & 0x1) > 0 || !subSamplingY;
- return xPos && yPos;
- }
-
- private int GetPartitionContext(int rowIndex, int columnIndex, Av1BlockSize blockSize)
- {
- // Maximum partition point is 8x8. Offset the log value occordingly.
- int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2();
- int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.TileInfo.TileColumnStartModeInfo[columnIndex];
- int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.TileInfo.TileRowStartModeInfo[rowIndex];
- int above = (aboveCtx >> blockSizeLog) & 0x1;
- int left = (leftCtx >> blockSizeLog) & 0x1;
- return ((left * 2) + above) + (blockSizeLog * PartitionProbabilitySet);
- }
+ => this.tileDecoder.FinishDecodeTiles(doCdef, doLoopRestoration);
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs
index f68c23adc9..be428efc32 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs
@@ -17,5 +17,6 @@ internal class ObuSegmentationParameters
public int LastActiveSegmentId { get; internal set; }
- internal bool IsFeatureActive(ObuSegmentationFeature feature) => false;
+ internal bool IsFeatureActive(int segmentId, ObuSegmentationFeature feature)
+ => this.FeatureEnabled[segmentId, (int)feature];
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs
new file mode 100644
index 0000000000..6c8a6bbff2
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs
@@ -0,0 +1,862 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1;
+using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
+
+internal class Av1TileDecoder : IAv1TileDecoder
+{
+ private static readonly int[] SgrprojXqdMid = [-32, 31];
+ private static readonly int[] WienerTapsMid = [3, -7, 15];
+ private const int PartitionProbabilitySet = 4;
+
+ private int[] deltaLoopFilter = [];
+ private bool[][][] blockDecoded = [];
+ private int[][] referenceSgrXqd = [];
+ private int[][][] referenceLrWiener = [];
+ private bool availableUp;
+ private bool availableLeft;
+ private bool availableUpForChroma;
+ private bool availableLeftForChroma;
+ private Av1ParseAboveContext aboveContext = new();
+ private Av1ParseLeftContext leftContext = new();
+ private bool skip;
+ private bool readDeltas;
+ private int currentQuantizerIndex;
+ private Av1PredictionMode[][] yModes = [];
+ private Av1PredictionMode yMode = Av1PredictionMode.DC;
+ private Av1PredictionMode uvMode = Av1PredictionMode.DC;
+ private Av1PredictionMode[][] uvModes = [];
+ private object[] referenceFrame = [];
+ private object[][][] referenceFrames = [];
+ private int paletteSizeY;
+ private int paletteSizeUv;
+ private object[][] aboveLevelContext = [];
+ private object[][] aboveDcContext = [];
+ private object[][] leftLevelContext = [];
+ private object[][] leftDcContext = [];
+ private Av1TransformSize transformSize = Av1TransformSize.Size4x4;
+ private object filterUltraMode = -1;
+ private int angleDeltaY;
+ private int angleDeltaUv;
+ private bool lossless;
+ private int[][] segmentIds = [];
+ private int maxLumaWidth;
+ private int maxLumaHeight;
+ private int segmentId;
+ private int[][] cdefIndex = [];
+ private int deltaLoopFilterResolution = -1;
+ private int deltaQuantizerResolution = -1;
+
+ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo)
+ {
+ this.FrameInfo = frameInfo;
+ this.SequenceHeader = sequenceHeader;
+ this.TileInfo = tileInfo;
+ }
+
+ public bool SequenceHeaderDone { get; set; }
+
+ public bool ShowExistingFrame { get; set; }
+
+ public bool SeenFrameHeader { get; set; }
+
+ public ObuFrameHeader FrameInfo { get; }
+
+ public ObuSequenceHeader SequenceHeader { get; }
+
+ public ObuTileInfo TileInfo { get; }
+
+ public void DecodeTile(Span tileData, int tileNum)
+ {
+ Av1SymbolDecoder reader = new(tileData);
+ int tileRowIndex = tileNum / this.TileInfo.TileColumnCount;
+ int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount;
+ this.aboveContext.Clear();
+ this.ClearLoopFilterDelta();
+ int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
+ this.referenceSgrXqd = new int[planesCount][];
+ this.referenceLrWiener = new int[planesCount][][];
+ for (int plane = 0; plane < planesCount; plane++)
+ {
+ this.referenceSgrXqd[plane] = new int[2];
+ Array.Copy(SgrprojXqdMid, this.referenceSgrXqd[plane], SgrprojXqdMid.Length);
+ this.referenceLrWiener[plane] = new int[2][];
+ for (int pass = 0; pass < 2; pass++)
+ {
+ this.referenceLrWiener[plane][pass] = new int[ObuConstants.WienerCoefficientCount];
+ Array.Copy(WienerTapsMid, this.referenceLrWiener[plane][pass], WienerTapsMid.Length);
+ }
+ }
+
+ Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
+ int superBlock4x4Size = superBlockSize.Get4x4WideCount();
+ for (int row = this.TileInfo.TileRowStartModeInfo[tileRowIndex]; row < this.TileInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize)
+ {
+ int superBlockRow = row << ObuConstants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2;
+ this.leftContext.Clear();
+ for (int column = this.TileInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.TileInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize)
+ {
+ int superBlockColumn = column << ObuConstants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2;
+ bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY;
+
+ bool readDeltas = this.FrameInfo.DeltaQParameters.IsPresent;
+
+ // Nothing to do for CDEF
+ // this.ClearCdef(row, column);
+ this.ClearBlockDecodedFlags(row, column, superBlock4x4Size);
+ this.ReadLoopRestoration(row, column, superBlockSize);
+ this.DecodePartition(ref reader, row, column, superBlockSize);
+ }
+ }
+ }
+
+ private void ClearLoopFilterDelta() => this.deltaLoopFilter = new int[4];
+
+ private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size)
+ {
+ int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
+ for (int plane = 0; plane < planesCount; plane++)
+ {
+ int subX = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0;
+ int subY = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0;
+ int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - column) >> subX;
+ int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - row) >> subY;
+ for (int y = -1; y <= superBlock4x4Size >> subY; y++)
+ {
+ for (int x = -1; x <= superBlock4x4Size >> subX; x++)
+ {
+ if (y < 0 && x < superBlock4x4Width)
+ {
+ this.blockDecoded[plane][y][x] = true;
+ }
+ else if (x < 0 && y < superBlock4x4Height)
+ {
+ this.blockDecoded[plane][y][x] = true;
+ }
+ else
+ {
+ this.blockDecoded[plane][y][x] = false;
+ }
+ }
+ }
+
+ int lastIndex = this.blockDecoded[plane][(superBlock4x4Size >> subY) - 1].Length - 1;
+ this.blockDecoded[plane][(superBlock4x4Size >> subY) - 1][lastIndex] = false;
+ }
+ }
+
+ private void ReadLoopRestoration(int row, int column, Av1BlockSize superBlockSize)
+ {
+ int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
+ for (int plane = 0; plane < planesCount; plane++)
+ {
+ if (this.FrameInfo.LoopRestorationParameters[plane].Type != ObuRestorationType.None)
+ {
+ // TODO: Implement.
+ throw new NotImplementedException("No loop restoration filter support.");
+ }
+ }
+ }
+
+ public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration)
+ {
+ // TODO: Implement
+ }
+
+ private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] ||
+ columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex])
+ {
+ return;
+ }
+
+ this.availableUp = this.IsInside(rowIndex - 1, columnIndex);
+ this.availableLeft = this.IsInside(rowIndex, columnIndex - 1);
+ int block4x4Size = blockSize.Get4x4WideCount();
+ int halfBlock4x4Size = block4x4Size >> 1;
+ int quarterBlock4x4Size = halfBlock4x4Size >> 2;
+ bool hasRows = rowIndex + halfBlock4x4Size < this.TileInfo.TileRowCount;
+ bool hasColumns = columnIndex + halfBlock4x4Size < this.TileInfo.TileColumnCount;
+ Av1PartitionType partitionType = Av1PartitionType.Split;
+ if (blockSize < Av1BlockSize.Block8x8)
+ {
+ partitionType = Av1PartitionType.None;
+ }
+ else if (hasRows && hasColumns)
+ {
+ int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
+ partitionType = reader.ReadPartitionType(ctx);
+ }
+ else if (hasColumns)
+ {
+ int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
+ bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx);
+ partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal;
+ }
+ else if (hasRows)
+ {
+ int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
+ bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx);
+ partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical;
+ }
+
+ Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize);
+ Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize);
+ switch (partitionType)
+ {
+ case Av1PartitionType.Split:
+ this.DecodePartition(ref reader, rowIndex, columnIndex, subSize);
+ this.DecodePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize);
+ this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize);
+ this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize);
+ break;
+ case Av1PartitionType.None:
+ this.DecodeBlock(ref reader, rowIndex, columnIndex, subSize);
+ break;
+ default:
+ throw new NotImplementedException($"Partition type: {partitionType} is not supported.");
+ }
+ }
+
+ private void DecodeBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ int block4x4Width = blockSize.Get4x4WideCount();
+ int block4x4Height = blockSize.Get4x4HighCount();
+ int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
+ bool hasChroma = planesCount > 1;
+ if (block4x4Height == 1 && this.SequenceHeader.ColorConfig.SubSamplingY && (rowIndex & 0x1) == 0)
+ {
+ hasChroma = false;
+ }
+
+ if (block4x4Width == 1 && this.SequenceHeader.ColorConfig.SubSamplingX && (columnIndex & 0x1) == 0)
+ {
+ hasChroma = false;
+ }
+
+ this.availableUp = this.IsInside(rowIndex - 1, columnIndex);
+ this.availableLeft = this.IsInside(rowIndex, columnIndex - 1);
+ this.availableUpForChroma = this.availableUp;
+ this.availableLeftForChroma = this.availableLeft;
+ if (hasChroma)
+ {
+ if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1)
+ {
+ this.availableUpForChroma = this.IsInside(rowIndex - 2, columnIndex);
+ }
+
+ if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1)
+ {
+ this.availableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2);
+ }
+ }
+
+ this.ReadModeInfo(ref reader, rowIndex, columnIndex, blockSize);
+ this.ReadPaletteTokens(ref reader);
+ ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize);
+ if (this.skip)
+ {
+ this.ResetBlockContext(rowIndex, columnIndex, blockSize);
+ }
+
+ // bool isCompound = false;
+ for (int y = 0; y < block4x4Height; y++)
+ {
+ for (int x = 0; x < block4x4Width; x++)
+ {
+ this.yModes[rowIndex + y][columnIndex + x] = this.yMode;
+ if (this.referenceFrame[0] == (object)ObuFrameType.IntraOnlyFrame && hasChroma)
+ {
+ this.uvModes[rowIndex + y][columnIndex + x] = this.uvMode;
+ }
+
+ for (int refList = 0; refList < 2; refList++)
+ {
+ this.referenceFrames[rowIndex + y][columnIndex + x][refList] = this.referenceFrame[refList];
+ }
+ }
+ }
+
+ ComputePrediction();
+ this.Residual(rowIndex, columnIndex, blockSize);
+ }
+
+ private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15;
+ int widthChunks = Math.Max(1, blockSize.Get4x4WideCount() >> 6);
+ int heightChunks = Math.Max(1, blockSize.Get4x4HighCount() >> 6);
+ Av1BlockSize sizeChunk = widthChunks > 1 || heightChunks > 1 ? Av1BlockSize.Block64x64 : blockSize;
+
+ for (int chunkY = 0; chunkY < heightChunks; chunkY++)
+ {
+ for (int chunkX = 0; chunkX < widthChunks; chunkX++)
+ {
+ int rowChunk = rowIndex + (chunkY << 4);
+ int columnChunk = columnIndex + (chunkX << 4);
+ int subBlockRow = rowChunk & superBlockMask;
+ int subBlockColumn = columnChunk & superBlockMask;
+ for (int plane = 0; plane < 1 + (this.HasChroma(rowIndex, columnIndex, blockSize) ? 2 : 0); plane++)
+ {
+ Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, this.transformSize);
+ int stepX = transformSize.GetWidth() >> 2;
+ int stepY = transformSize.GetHeight() >> 2;
+ Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane);
+ int num4x4Width = planeSize.Get4x4WideCount();
+ int num4x4Height = planeSize.Get4x4HighCount();
+ int subX = plane > 0 && subsamplingX ? 1 : 0;
+ int subY = plane > 0 && subsamplingY ? 1 : 0;
+ int baseX = (columnChunk >> subX) * (1 << ObuConstants.ModeInfoSizeLog2);
+ int baseY = (rowChunk >> subY) * (1 << ObuConstants.ModeInfoSizeLog2);
+ int baseXBlock = (columnIndex >> subX) * (1 << ObuConstants.ModeInfoSizeLog2);
+ int baseYBlock = (rowIndex >> subY) * (1 << ObuConstants.ModeInfoSizeLog2);
+ for (int y = 0; y < num4x4Height; y += stepY)
+ {
+ for (int x = 0; x < num4x4Width; x += stepX)
+ {
+ this.TransformBlock(plane, baseXBlock, baseYBlock, transformSize, x + (chunkX << 4 >> subX), y + (chunkY << 4 >> subY));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ int bw = blockSize.Get4x4WideCount();
+ int bh = blockSize.Get4x4HighCount();
+ bool subX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ bool hasChroma = ((rowIndex & 0x01) != 0 || (bh & 0x01) == 0 || !subY) &&
+ ((columnIndex & 0x01) != 0 || (bw & 0x01) == 0 || !subX);
+ return hasChroma;
+ }
+
+ private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException();
+
+ private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) => throw new NotImplementedException();
+
+ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y)
+ {
+ int startX = (baseX + 4) * x;
+ int startY = (baseY + 4) * y;
+ bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ int subX = plane > 0 && subsamplingX ? 1 : 0;
+ int subY = plane > 0 && subsamplingY ? 1 : 0;
+ int columnIndex = startX << subX >> ObuConstants.ModeInfoSizeLog2;
+ int rowIndex = startY << subY >> ObuConstants.ModeInfoSizeLog2;
+ int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15;
+ int subBlockColumn = columnIndex & superBlockMask;
+ int subBlockRow = rowIndex & superBlockMask;
+ int stepX = transformSize.GetWidth() >> ObuConstants.ModeInfoSizeLog2;
+ int stepY = transformSize.GetHeight() >> ObuConstants.ModeInfoSizeLog2;
+ int maxX = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subX;
+ int maxY = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subY;
+ if (startX >= maxX || startY >= maxY)
+ {
+ return;
+ }
+
+ if ((plane == 0 && this.paletteSizeY > 0) ||
+ (plane != 0 && this.paletteSizeUv > 0))
+ {
+ this.PredictPalette(plane, startX, startY, x, y, transformSize);
+ }
+ else
+ {
+ bool isChromaFromLuma = plane > 0 && this.uvMode == Av1PredictionMode.UvChromaFromLuma;
+ Av1PredictionMode mode;
+ if (plane == 0)
+ {
+ mode = this.yMode;
+ }
+ else
+ {
+ mode = isChromaFromLuma ? Av1PredictionMode.DC : this.uvMode;
+ }
+
+ int log2Width = transformSize.GetWidthLog2();
+ int log2Height = transformSize.GetHeightLog2();
+ bool leftAvailable = x > 0 || plane == 0 ? this.availableLeft : this.availableLeftForChroma;
+ bool upAvailable = y > 0 || plane == 0 ? this.availableUp : this.availableUpForChroma;
+ bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX];
+ bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1];
+ this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height);
+ if (isChromaFromLuma)
+ {
+ this.PredictChromaFromLuma(plane, startX, startY, transformSize);
+ }
+ }
+
+ if (plane == 0)
+ {
+ this.maxLumaWidth = startX + (stepX * 4);
+ this.maxLumaHeight = startY + (stepY * 4);
+ }
+
+ if (!this.skip)
+ {
+ int eob = this.Coefficients(plane, startX, startY, transformSize);
+ if (eob > 0)
+ {
+ this.Reconstruct(plane, startX, startY, transformSize);
+ }
+ }
+
+ for (int i = 0; i < stepY; i++)
+ {
+ for (int j = 0; j < stepX; j++)
+ {
+ // Ignore loop filter.
+ this.blockDecoded[plane][(subBlockRow >> subY) + i][(subBlockColumn >> subX) + j] = true;
+ }
+ }
+ }
+
+ private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
+
+ private int Coefficients(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
+
+ private void PredictChromaFromLuma(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
+
+ private void PredictIntra(int plane, int startX, int startY, bool leftAvailable, bool upAvailable, bool haveAboveRight, bool haveBelowLeft, Av1PredictionMode mode, int log2Width, int log2Height) => throw new NotImplementedException();
+
+ private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException();
+
+ private static void ComputePrediction()
+ {
+ // Not applicable for INTRA frames.
+ }
+
+ private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ int block4x4Width = blockSize.Get4x4WideCount();
+ int block4x4Height = blockSize.Get4x4HighCount();
+ bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ int endPlane = this.HasChroma(rowIndex, columnIndex, blockSize) ? 3 : 1;
+ for (int plane = 0; plane < endPlane; plane++)
+ {
+ int subX = plane > 0 && subsamplingX ? 1 : 0;
+ int subY = plane > 0 && subsamplingY ? 1 : 0;
+ for (int i = columnIndex >> subX; i < (columnIndex + block4x4Width) >> subX; i++)
+ {
+ this.aboveLevelContext[plane][i] = 0;
+ this.aboveDcContext[plane][i] = 0;
+ }
+
+ for (int i = rowIndex >> subY; i < (rowIndex + block4x4Height) >> subY; i++)
+ {
+ this.leftLevelContext[plane][i] = 0;
+ this.leftDcContext[plane][i] = 0;
+ }
+ }
+ }
+
+ private static void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ int block4x4Width = blockSize.Get4x4WideCount();
+ int block4x4Height = blockSize.Get4x4HighCount();
+
+ // First condition in spec is for INTER frames, implemented only the INTRA condition.
+ ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize);
+ /*for (int row = rowIndex; row < rowIndex + block4x4Height; row++)
+ {
+ for (int column = columnIndex; column < columnIndex + block4x4Width; column++)
+ {
+ this.InterTransformSizes[row][column] = this.TransformSize;
+ }
+ }*/
+ }
+
+ private void ReadPaletteTokens(ref Av1SymbolDecoder reader)
+ {
+ reader.ReadLiteral(-1);
+ if (this.paletteSizeY != 0)
+ {
+ // Todo: Implement.
+ throw new NotImplementedException();
+ }
+
+ if (this.paletteSizeUv != 0)
+ {
+ // Todo: Implement.
+ throw new NotImplementedException();
+ }
+ }
+
+ private void ReadModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ => this.ReadIntraFrameModeInfo(ref reader, rowIndex, columnIndex, blockSize);
+
+ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ this.skip = false;
+ if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip)
+ {
+ this.ReadIntraSegmentId(ref reader);
+ }
+
+ // this.skipMode = false;
+ this.ReadSkip(ref reader);
+ if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip)
+ {
+ this.IntraSegmentId(ref reader, rowIndex, columnIndex);
+ }
+
+ this.ReadCdef(ref reader, rowIndex, columnIndex, blockSize);
+ this.ReadDeltaQuantizerIndex(ref reader, blockSize);
+ this.ReadDeltaLoopFilter(ref reader, blockSize);
+ this.readDeltas = false;
+ this.referenceFrame[0] = -1; // IntraFrame;
+ this.referenceFrame[1] = -1; // None;
+ bool useIntraBlockCopy = false;
+ if (this.FrameInfo.AllowIntraBlockCopy)
+ {
+ useIntraBlockCopy = reader.ReadUseIntraBlockCopy();
+ }
+
+ if (useIntraBlockCopy)
+ {
+ // TODO: Implement
+ }
+ else
+ {
+ // this.IsInter = false;
+ this.yMode = reader.ReadIntraFrameYMode(blockSize);
+ this.IntraAngleInfoY(ref reader, blockSize);
+ if (this.HasChroma(rowIndex, columnIndex, blockSize))
+ {
+ this.uvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed(blockSize));
+ if (this.uvMode == Av1PredictionMode.UvChromaFromLuma)
+ {
+ this.ReadChromaFromLumaAlphas(ref reader);
+ }
+
+ this.IntraAngleInfoUv(ref reader, blockSize);
+ }
+
+ this.paletteSizeY = 0;
+ this.paletteSizeUv = 0;
+ if (this.SequenceHeader.ModeInfoSize >= (int)Av1BlockSize.Block8x8 &&
+ ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4WideCount() <= 64 &&
+ ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4HighCount() <= 64 &&
+ this.FrameInfo.AllowScreenContentTools)
+ {
+ this.PaletteModeInfo(ref reader);
+ }
+
+ this.FilterIntraModeInfo(ref reader, blockSize);
+ }
+ }
+
+ private bool IsChromaForLumaAllowed(Av1BlockSize blockSize)
+ {
+ if (this.lossless)
+ {
+ // In lossless, CfL is available when the partition size is equal to the
+ // transform size.
+ bool subX = this.SequenceHeader.ColorConfig.SubSamplingX;
+ bool subY = this.SequenceHeader.ColorConfig.SubSamplingY;
+ Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY);
+ return planeBlockSize == Av1BlockSize.Block4x4;
+ }
+
+ // Spec: CfL is available to luma partitions lesser than or equal to 32x32
+ return blockSize.Get4x4WideCount() <= 32 && blockSize.Get4x4HighCount() <= 32;
+ }
+
+ private void ReadIntraSegmentId(ref Av1SymbolDecoder reader) => throw new NotImplementedException();
+
+ private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
+ {
+ bool useFilterIntra = false;
+ if (this.SequenceHeader.EnableFilterIntra &&
+ this.yMode == Av1PredictionMode.DC && this.paletteSizeY == 0 &&
+ Math.Max(blockSize.GetWidth(), blockSize.GetHeight()) <= 32)
+ {
+ useFilterIntra = reader.ReadUseFilterUltra();
+ if (useFilterIntra)
+ {
+ this.filterUltraMode = reader.ReadFilterUltraMode();
+ }
+ }
+ }
+
+ private void PaletteModeInfo(ref Av1SymbolDecoder reader) =>
+
+ // TODO: Implement.
+ throw new NotImplementedException();
+
+ private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader) =>
+
+ // TODO: Implement.
+ throw new NotImplementedException();
+
+ private void IntraAngleInfoY(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
+ {
+ this.angleDeltaY = 0;
+ if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.yMode))
+ {
+ int angleDeltaY = reader.ReadAngleDelta(this.yMode);
+ this.angleDeltaY = angleDeltaY - ObuConstants.MaxAngleDelta;
+ }
+ }
+
+ private void IntraAngleInfoUv(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
+ {
+ this.angleDeltaUv = 0;
+ if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.uvMode))
+ {
+ int angleDeltaUv = reader.ReadAngleDelta(this.uvMode);
+ this.angleDeltaUv = angleDeltaUv - ObuConstants.MaxAngleDelta;
+ }
+ }
+
+ private static bool IsDirectionalMode(Av1PredictionMode mode)
+ => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
+
+ private void IntraSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex)
+ {
+ if (this.FrameInfo.SegmentationParameters.Enabled)
+ {
+ this.ReadSegmentId(ref reader, rowIndex, columnIndex);
+ }
+ else
+ {
+ this.segmentId = 0;
+ }
+
+ this.lossless = this.FrameInfo.LosslessArray[this.segmentId];
+ }
+
+ private void ReadSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex)
+ {
+ int pred;
+ int prevUL = -1;
+ int prevU = -1;
+ int prevL = -1;
+ if (this.availableUp && this.availableLeft)
+ {
+ prevUL = this.segmentIds[rowIndex - 1][columnIndex - 1];
+ }
+
+ if (this.availableUp)
+ {
+ prevU = this.segmentIds[rowIndex - 1][columnIndex];
+ }
+
+ if (this.availableLeft)
+ {
+ prevU = this.segmentIds[rowIndex][columnIndex - 1];
+ }
+
+ if (prevU == -1)
+ {
+ pred = prevL == -1 ? 0 : prevL;
+ }
+ else if (prevL == -1)
+ {
+ pred = prevU;
+ }
+ else
+ {
+ pred = prevU == prevUL ? prevU : prevL;
+ }
+
+ if (this.skip)
+ {
+ this.segmentId = 0;
+ }
+ else
+ {
+ int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId;
+ this.segmentId = NegativeDeinterleave(reader.ReadSegmentId(-1), pred, lastActiveSegmentId + 1);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ private void ReadCdef(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ if (this.skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy)
+ {
+ return;
+ }
+
+ int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount();
+ int cdefMask4 = ~(cdefSize4 - 1);
+ int r = rowIndex & cdefMask4;
+ int c = columnIndex & cdefMask4;
+ if (this.cdefIndex[r][c] == -1)
+ {
+ this.cdefIndex[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount);
+ int w4 = blockSize.Get4x4WideCount();
+ int h4 = blockSize.Get4x4HighCount();
+ for (int i = r; i < r + h4; i += cdefSize4)
+ {
+ for (int j = c; j < c + w4; j += cdefSize4)
+ {
+ this.cdefIndex[i][j] = this.cdefIndex[r][c];
+ }
+ }
+ }
+ }
+
+ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
+ {
+ Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
+ if (blockSize == superBlockSize && this.skip)
+ {
+ return;
+ }
+
+ if (this.readDeltas && this.FrameInfo.DeltaLoopFilterParameters.IsPresent)
+ {
+ int frameLoopFilterCount = 1;
+ if (this.FrameInfo.DeltaLoopFilterParameters.Multi)
+ {
+ frameLoopFilterCount = this.SequenceHeader.ColorConfig.ChannelCount > 1 ? ObuConstants.FrameLoopFilterCount : ObuConstants.FrameLoopFilterCount - 2;
+ }
+
+ for (int i = 0; i < frameLoopFilterCount; i++)
+ {
+ int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute();
+ if (deltaLoopFilterAbsolute == ObuConstants.DeltaLoopFilterSmall)
+ {
+ int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1;
+ int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits);
+ deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1;
+ }
+
+ if (deltaLoopFilterAbsolute != 0)
+ {
+ bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0;
+ int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute;
+ this.deltaLoopFilter[i] = Av1Math.Clip3(-ObuConstants.MaxLoopFilter, ObuConstants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution));
+ }
+ }
+ }
+ }
+
+ private void ReadSkip(ref Av1SymbolDecoder reader)
+ {
+ if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip &&
+ this.FrameInfo.SegmentationParameters.IsFeatureActive(-1, ObuSegmentationFeature.LevelSkip))
+ {
+ this.skip = true;
+ }
+ else
+ {
+ this.skip = reader.ReadSkip(-1);
+ }
+ }
+
+ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
+ {
+ Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
+ if (blockSize == superBlockSize && this.skip)
+ {
+ return;
+ }
+
+ if (this.readDeltas)
+ {
+ int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute();
+ if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall)
+ {
+ int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1;
+ int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits);
+ deltaQuantizerAbsolute = deltaQuantizerRemainingBits + (1 << deltaQuantizerRemainingBits) + 1;
+ }
+
+ if (deltaQuantizerAbsolute != 0)
+ {
+ bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0;
+ int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute;
+ this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution));
+ }
+ }
+ }
+
+ private bool IsInside(int rowIndex, int columnIndex) =>
+ columnIndex >= this.TileInfo.TileColumnCount &&
+ columnIndex < this.TileInfo.TileColumnCount &&
+ rowIndex >= this.TileInfo.TileRowCount &&
+ rowIndex < this.TileInfo.TileRowCount;
+
+ /*
+ private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY)
+ {
+ int block4x4Width = blockMode.BlockSize.Get4x4WideCount();
+ int block4x4Height = blockMode.BlockSize.Get4x4HighCount();
+ bool xPos = (columnIndex & 0x1) > 0 || (block4x4Width & 0x1) > 0 || !subSamplingX;
+ bool yPos = (rowIndex & 0x1) > 0 || (block4x4Height & 0x1) > 0 || !subSamplingY;
+ return xPos && yPos;
+ }*/
+
+ private int GetPartitionContext(int rowIndex, int columnIndex, Av1BlockSize blockSize)
+ {
+ // Maximum partition point is 8x8. Offset the log value occordingly.
+ int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2();
+ int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.TileInfo.TileColumnStartModeInfo[columnIndex];
+ int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.TileInfo.TileRowStartModeInfo[rowIndex];
+ int above = (aboveCtx >> blockSizeLog) & 0x1;
+ int left = (leftCtx >> blockSizeLog) & 0x1;
+ return (left * 2) + above + (blockSizeLog * PartitionProbabilitySet);
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs
index 69f1bf63e0..89b42535f4 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs
@@ -5,6 +5,7 @@ using System.Reflection;
using System.Text;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;