diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index d378f8e03..f47ca720e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -171,4 +172,14 @@ internal ref struct Av1BitStreamReader uint temp = this.data[this.wordPosition]; this.nextWord = (temp << 24) | ((temp & 0x0000ff00U) << 8) | ((temp & 0x00ff0000U) >> 8) | (temp >> 24); } + + public Span GetSymbolReader(int tileDataSize) + { + DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Symbol reading needs to start on byte boundary."); + + // TODO: Pass exact byte iso Word start. + Span span = this.data.Slice(this.bitOffset >> WordSize, tileDataSize); + this.bitOffset += tileDataSize << 8; + return MemoryMarshal.Cast(span); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs index c288919ce..4f41d4708 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs @@ -11,7 +11,7 @@ internal class Av1BlockModeInfo public Av1PredictionMode PredictionMode { get; } - public Av1PartitionType Partition { get; } + public Av1PartitionType Partition { get; set; } public int SegmentId { get; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs index 6eca5bc63..5a524c261 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -5,30 +5,74 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal enum Av1BlockSize { - Block4x4, - Block4x8, - Block8x4, - Block8x8, - Block8x16, - Block16x8, - Block16x16, - Block16x32, - Block32x16, - Block32x32, - Block32x64, - Block64x32, - Block64x64, - Block64x128, - Block128x64, - Block128x128, - Block4x16, - Block16x4, - Block8x32, - Block32x8, - Block16x64, - Block64x16, - BlockSizeSAll, - BlockSizeS = Block4x16, - BlockInvalid = 255, - BlockLargest = BlockSizeS - 1, + // See sction 6.10.4 of the Av1 Specification. + + /// A block of samples, 4 samples wide and 4 samples high. + Block4x4 = 0, + + /// A block of samples, 4 samples wide and 8 samples high. + Block4x8 = 1, + + /// A block of samples, 8 samples wide and 4 samples high. + Block8x4 = 2, + + /// A block of samples, 8 samples wide and 8 samples high. + Block8x8 = 3, + + /// A block of samples, 8 samples wide and 16 samples high. + Block8x16 = 4, + + /// A block of samples, 16 samples wide and 8 samples high. + Block16x8 = 5, + + /// A block of samples, 16 samples wide and 16 samples high. + Block16x16 = 6, + + /// A block of samples, 16 samples wide and 32 samples high. + Block16x32 = 7, + + /// A block of samples, 32 samples wide and 16 samples high. + Block32x16 = 8, + + /// A block of samples, 32 samples wide and 32 samples high. + Block32x32 = 9, + + /// A block of samples, 32 samples wide and 64 samples high. + Block32x64 = 10, + + /// A block of samples, 64 samples wide and 32 samples high. + Block64x32 = 11, + + /// A block of samples, 64 samples wide and 64 samples high. + Block64x64 = 12, + + /// A block of samples, 64 samples wide and 128 samples high. + Block64x128 = 13, + + /// A block of samples, 128 samples wide and 64 samples high. + Block128x64 = 14, + + /// A block of samples, 128 samples wide and 128 samples high. + Block128x128 = 15, + + /// A block of samples, 4 samples wide and 16 samples high. + Block4x16 = 16, + + /// A block of samples, 16 samples wide and 4 samples high. + Block16x4 = 17, + + /// A block of samples, 8 samples wide and 32 samples high. + Block8x32 = 18, + + /// A block of samples, 32 samples wide and 8 samples high. + Block32x8 = 19, + + /// A block of samples, 16 samples wide and 64 samples high. + Block16x64 = 20, + + /// A block of samples, 64 samples wide and 16 samples high. + Block64x16 = 21, + Invalid = 22, + SizeS = Block4x16, + Largest = SizeS - 1, } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index 16190dd6b..f6e7d6f3f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -1,14 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + namespace SixLabors.ImageSharp.Formats.Heif.Av1; 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 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 Av1TransformSize[] MaxTransformSize = [ + Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8, + Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32, + Av1TransformSize.Size32x16, Av1TransformSize.Size32x32, Av1TransformSize.Size32x64, Av1TransformSize.Size64x32, + Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, + Av1TransformSize.Size4x16, Av1TransformSize.Size16x4, Av1TransformSize.Size8x32, Av1TransformSize.Size32x8, + Av1TransformSize.Size16x64, Av1TransformSize.Size64x16 + ]; public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize]; public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize]; + + /// + /// Returns the width of the block in samples. + /// + public static int GetWidth(this Av1BlockSize blockSize) + => Get4x4WideCount(blockSize) << 2; + + /// + /// Returns of the height of the block in 4 samples. + /// + public static int GetHeight(this Av1BlockSize blockSize) + => Get4x4HighCount(blockSize) << 2; + + /// + /// Returns base 2 logarithm of the width of the block in units of 4 samples. + /// + public static int Get4x4WidthLog2(this Av1BlockSize blockSize) + => Get4x4WideCount(blockSize) << 2; + + /// + /// Returns base 2 logarithm of the height of the block in units of 4 samples. + /// + public static int Get4x4HeightLog2(this Av1BlockSize blockSize) + => Get4x4HighCount(blockSize) << 2; + + /// + /// Returns th 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) + => MaxTransformSize[(int)blockSize]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index edd4940bf..0a8fac5d0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -2,16 +2,69 @@ // 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; + public Av1Decoder() { this.FrameInfo = new ObuFrameHeader(); this.SequenceHeader = new ObuSequenceHeader(); this.TileInfo = new ObuTileInfo(); + this.SeenFrameHeader = false; + this.currentQuantizerIndex = -1; } public bool SequenceHeaderDone { get; set; } @@ -32,9 +85,96 @@ internal class Av1Decoder : IAv1TileDecoder ObuReader.Read(ref reader, buffer.Length, this, false); } - public void DecodeTile(int tileNum) + public void DecodeTile(Span tileData, int tileNum) { - // TODO: Implement + 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."); + } + } } public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) @@ -42,9 +182,655 @@ internal class Av1Decoder : IAv1TileDecoder // TODO: Implement } - private static void DecodeBlock(Av1BlockModeInfo blockMode, int rowIndex, int columnIndex) + 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.ReadPartitionSymbol(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); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs index e5aa1f5b1..0a6092ce8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs @@ -5,55 +5,57 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal enum Av1PartitionType { + // See section 6.10.4 of Avi Spcification + /// /// Not partitioned any further. /// - None, + None = 0, /// /// Horizontally split in 2 partitions. /// - Horizontal, + Horizontal = 1, /// /// Vertically split in 2 partitions. /// - Vertical, + Vertical = 2, /// /// 4 equally sized partitions. /// - Split, + Split = 3, /// /// Horizontal split and the top partition is split again. /// - HorizontalA, + HorizontalA = 4, /// /// Horizontal split and the bottom partition is split again. /// - HorizontalB, + HorizontalB = 5, /// /// Vertical split and the left partition is split again. /// - VerticalA, + VerticalA = 6, /// /// Vertical split and the right partitino is split again. /// - VerticalB, + VerticalB = 7, /// /// 4:1 horizontal partition. /// - Horizontal4, + Horizontal4 = 8, /// /// 4:1 vertical partition. /// - Vertical4, + Vertical4 = 9, /// /// Invalid value. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs new file mode 100644 index 000000000..99ba78c3b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal static class Av1PartitionTypeExtensions +{ + private static readonly Av1BlockSize[][] PartitionSubSize = [ + [ + Av1BlockSize.Block4x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x128, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Block4x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + ] + ]; + + public static Av1BlockSize GetBlockSubSize(this Av1PartitionType partition, Av1BlockSize blockSize) + => PartitionSubSize[(int)partition][(int)blockSize]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs index 345cd29fe..c7aa943f8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs @@ -43,8 +43,11 @@ internal interface IAv1TileDecoder /// /// Decode a single tile. /// + /// + /// The bytes of encoded data in the bitstream dedicated to this tile. + /// /// The index of the tile that is to be decoded. - void DecodeTile(int tileNum); + void DecodeTile(Span tileData, int tileNum); /// /// Finshed decoding all tiles of a frame. diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs index c89e747f3..274226449 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal static class ObuConstants @@ -84,4 +86,31 @@ internal static class ObuConstants /// Maximum size of a loop restoration tile. /// public const int RestorationMaxTileSize = 256; + + /// + /// Number of Wiener coefficients to read. + /// + public const int WienerCoefficientCount = 3; + + public const int FrameLoopFilterCount = 4; + + /// + /// Value indicating alternative encoding of quantizer index delta values. + /// + public const int DeltaQuantizerSmall = 3; + + /// + /// Value indicating alternative encoding of loop filter delta values. + /// + public const int DeltaLoopFilterSmall = 3; + + /// + /// Maximum value used for loop filtering. + /// + public const int MaxLoopFilter = 63; + + /// + /// Maximum magnitude of AngleDeltaY and AngleDeltaUV. + /// + public const int MaxAngleDelta = 3; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 2c018101d..756f71f1f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -899,7 +899,7 @@ internal class ObuReader } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) - => segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) { @@ -980,18 +980,16 @@ internal class ObuReader for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++) { - int tileRow = tileNum / tileInfo.TileColumnCount; - int tileColumn = tileNum % tileInfo.TileColumnCount; bool isLastTile = tileNum == tileGroupEnd; - int tileSize = header.PayloadSize; + int tileDataSize = header.PayloadSize; if (!isLastTile) { - tileSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1; - header.PayloadSize -= tileSize + tileInfo.TileSizeBytes; + tileDataSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1; + header.PayloadSize -= tileDataSize + tileInfo.TileSizeBytes; } - // TODO: Pass more info to the decoder. - decoder.DecodeTile(tileNum); + Span tileData = reader.GetSymbolReader(tileDataSize); + decoder.DecodeTile(tileData, tileNum); } if (tileGroupEnd != tileCount - 1) @@ -1101,8 +1099,8 @@ internal class ObuReader private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { - frameInfo.SegmentationParameters.SegmentationEnabled = reader.ReadBoolean(); - Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentation not supported yet."); + frameInfo.SegmentationParameters.Enabled = reader.ReadBoolean(); + Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentation not supported yet."); // TODO: Parse more stuff. } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs new file mode 100644 index 000000000..1cfade406 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuSegmentationFeature +{ + None = 0, + LevelSkip, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs index ed92abf4c..f68c23adc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -9,7 +9,13 @@ internal class ObuSegmentationParameters public bool[,] FeatureEnabled { get; internal set; } = new bool[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; - public bool SegmentationEnabled { get; internal set; } + public bool Enabled { get; internal set; } public int[,] FeatureData { get; internal set; } = new int[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; + + public bool SegmentIdPrecedesSkip { get; internal set; } + + public int LastActiveSegmentId { get; internal set; } + + internal bool IsFeatureActive(ObuSegmentationFeature feature) => false; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 9437cc6ea..80b067585 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -428,7 +428,7 @@ internal class ObuWriter } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) - => segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) { @@ -560,7 +560,7 @@ internal class ObuWriter private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { - Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentatino not supported yet."); + Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentatino not supported yet."); writer.WriteBoolean(false); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs index a46733b00..dc35c96f3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs @@ -19,6 +19,7 @@ internal enum Av1PredictionMode SmoothVertical, SmoothHorizontal, Paeth, + UvChromaFromLuma, IntraModeStart = DC, IntraModeEnd = Paeth + 1, IntraModes = Paeth, diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs index 84832bb7b..3b9f555ef 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs @@ -5,23 +5,125 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal static class Av1DefaultDistributions { - public static uint[] YMode => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] FrameYMode => + [ + new(22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349), + new(18673, 19845, 22631, 23318, 23950, 24649, 25527, 27364, 28152, 29701, 29984, 30852), + new(19770, 20979, 23396, 23939, 24241, 24654, 25136, 27073, 27830, 29360, 29730, 30659), + new(20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049) + ]; - public static uint[] UvModeCflNotAllowed => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[][] FilterYMode => + [ + [ + new(15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466), + new(12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409), + new(10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567), + new(14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462), + new(12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475) + ], [ + new(10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436), + new(5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794), + new(7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492), + new(8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210), + new(7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994) + ], [ + new(12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077), + new(9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907), + new(6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086), + new(10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252), + new(9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463) + ], [ + new(12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409), + new(8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342), + new(8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018), + new(7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528), + new(8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485) + ], [ + new(12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477), + new(9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277), + new(8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261), + new(10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482), + new(7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593) + ] + ]; - public static uint[] UvModeCflAllowed => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[][] UvMode => + [ + [ + new(22631, 24152, 25378, 25661, 25986, 26520, 27055, 27923, 28244, 30059, 30941, 31961), + new(9513, 26881, 26973, 27046, 27118, 27664, 27739, 27824, 28359, 29505, 29800, 31796), + new(9845, 9915, 28663, 28704, 28757, 28780, 29198, 29822, 29854, 30764, 31777, 32029), + new(13639, 13897, 14171, 25331, 25606, 25727, 25953, 27148, 28577, 30612, 31355, 32493), + new(9764, 9835, 9930, 9954, 25386, 27053, 27958, 28148, 28243, 31101, 31744, 32363), + new(11825, 13589, 13677, 13720, 15048, 29213, 29301, 29458, 29711, 31161, 31441, 32550), + new(14175, 14399, 16608, 16821, 17718, 17775, 28551, 30200, 30245, 31837, 32342, 32667), + new(12885, 13038, 14978, 15590, 15673, 15748, 16176, 29128, 29267, 30643, 31961, 32461), + new(12026, 13661, 13874, 15305, 15490, 15726, 15995, 16273, 28443, 30388, 30767, 32416), + new(19052, 19840, 20579, 20916, 21150, 21467, 21885, 22719, 23174, 28861, 30379, 32175), + new(18627, 19649, 20974, 21219, 21492, 21816, 22199, 23119, 23527, 27053, 31397, 32148), + new(17026, 19004, 19997, 20339, 20586, 21103, 21349, 21907, 22482, 25896, 26541, 31819), + new(12124, 13759, 14959, 14992, 15007, 15051, 15078, 15166, 15255, 15753, 16039, 16606) + ], [ + new(10407, 11208, 12900, 13181, 13823, 14175, 14899, 15656, 15986, 20086, 20995, 22455, 24212), + new(4532, 19780, 20057, 20215, 20428, 21071, 21199, 21451, 22099, 24228, 24693, 27032, 29472), + new(5273, 5379, 20177, 20270, 20385, 20439, 20949, 21695, 21774, 23138, 24256, 24703, 26679), + new(6740, 7167, 7662, 14152, 14536, 14785, 15034, 16741, 18371, 21520, 22206, 23389, 24182), + new(4987, 5368, 5928, 6068, 19114, 20315, 21857, 22253, 22411, 24911, 25380, 26027, 26376), + new(5370, 6889, 7247, 7393, 9498, 21114, 21402, 21753, 21981, 24780, 25386, 26517, 27176), + new(4816, 4961, 7204, 7326, 8765, 8930, 20169, 20682, 20803, 23188, 23763, 24455, 24940), + new(6608, 6740, 8529, 9049, 9257, 9356, 9735, 18827, 19059, 22336, 23204, 23964, 24793), + new(5998, 7419, 7781, 8933, 9255, 9549, 9753, 10417, 18898, 22494, 23139, 24764, 25989), + new(10660, 11298, 12550, 12957, 13322, 13624, 14040, 15004, 15534, 20714, 21789, 23443, 24861), + new(10522, 11530, 12552, 12963, 13378, 13779, 14245, 15235, 15902, 20102, 22696, 23774, 25838), + new(10099, 10691, 12639, 13049, 13386, 13665, 14125, 15163, 15636, 19676, 20474, 23519, 25208), + new(3144, 5087, 7382, 7504, 7593, 7690, 7801, 8064, 8232, 9248, 9875, 10521, 29048) + ] + ]; - public static uint[] AngleDelta => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] AngleDelta => + [ + new(2180, 5032, 7567, 22776, 26989, 30217), + new(2301, 5608, 8801, 23487, 26974, 30330), + new(3780, 11018, 13699, 19354, 23083, 31286), + new(4581, 11226, 15147, 17138, 21834, 28397), + new(1737, 10927, 14509, 19588, 22745, 28823), + new(2664, 10176, 12485, 17650, 21600, 30495), + new(2240, 11096, 15453, 20341, 22561, 28917), + new(3605, 10428, 12459, 17676, 21244, 30655) + ]; - public static uint[] IntraBlockCopy => [30531, 0, 0]; + public static Av1Distribution IntraBlockCopy => new(30531); - public static uint[] PartitionWidth8 => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] PartitionTypes => + [ + new(19132, 25510, 30392), + new(13928, 19855, 28540), + new(12522, 23679, 28629), + new(9896, 18783, 25853), + new(15597, 20929, 24571, 26706, 27664, 28821, 29601, 30571, 31902), + new(7925, 11043, 16785, 22470, 23971, 25043, 26651, 28701, 29834), + new(5414, 13269, 15111, 20488, 22360, 24500, 25537, 26336, 32117), + new(2662, 6362, 8614, 20860, 23053, 24778, 26436, 27829, 31171), + new(18462, 20920, 23124, 27647, 28227, 29049, 29519, 30178, 31544), + new(7689, 9060, 12056, 24992, 25660, 26182, 26951, 28041, 29052), + new(6015, 9009, 10062, 24544, 25409, 26545, 27071, 27526, 32047), + new(1394, 2208, 2796, 28614, 29061, 29466, 29840, 30185, 31899), + new(20137, 21547, 23078, 29566, 29837, 30261, 30524, 30892, 31724), + new(6732, 7490, 9497, 27944, 28250, 28515, 28969, 29630, 30104), + new(5945, 7663, 8348, 28683, 29117, 29749, 30064, 30298, 32238), + new(870, 1212, 1487, 31198, 31394, 31574, 31743, 31881, 32332), + new(27899, 28219, 28529, 32484, 32539, 32619, 32639), + new(6607, 6990, 8268, 32060, 32219, 32338, 32371), + new(5429, 6676, 7122, 32027, 32227, 32531, 32582), + new(711, 966, 1172, 32448, 32538, 32617, 32664) + ]; - public static uint[] PartitionWidth16 => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] Skip => [new(31671), new(16515), new(4576)]; - public static uint[] PartitionWidth32 => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution DeltaLoopFilterAbsolute => new(28160, 32120, 32677); - public static uint[] PartitionWidth64 => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677); - public static uint[] SegmentId => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] SegmentId => [new(128 * 128), new(128 * 128), new(128 * 128)]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs new file mode 100644 index 000000000..c3d33a440 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +/// +/// Class representing the probability distribution used for symbol coding. +/// +internal class Av1Distribution +{ + internal const int ProbabilityTop = 1 << ProbabilityBitCount; + internal const int ProbabilityMinimum = 4; + internal const int CdfShift = 15 - ProbabilityBitCount; + internal const int ProbabilityShift = 6; + + private const int ProbabilityBitCount = 15; + + private readonly uint[] probabilities; + private readonly int speed; + private int updateCount; + + public Av1Distribution(uint p0) + : this([p0, 0], 1) + { + } + + public Av1Distribution(uint p0, uint p1) + : this([p0, p1, 0], 1) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2) + : this([p0, p1, p2, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3) + : this([p0, p1, p2, p3, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4) + : this([p0, p1, p2, p3, p4, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5) + : this([p0, p1, p2, p3, p4, p5, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6) + : this([p0, p1, p2, p3, p4, p5, p6, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8) + : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11) + : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11, uint p12) + : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, 0], 2) + { + } + + private Av1Distribution(uint[] props, int speed) + { + // this.probabilities = props; + this.probabilities = new uint[props.Length]; + for (int i = 0; i < props.Length - 1; i++) + { + this.probabilities[i] = ProbabilityTop - props[i]; + } + + this.NumberOfSymbols = props.Length; + this.speed = speed; + } + + public int NumberOfSymbols { get; } + + public uint this[int index] => this.probabilities[index]; + + public void Update(int value) + { + int rate15 = this.updateCount > 15 ? 1 : 0; + int rate31 = this.updateCount > 31 ? 1 : 0; + int rate = 3 + rate15 + rate31 + this.speed; // + get_msb(nsymbs); + int tmp = ProbabilityTop; + + // Single loop (faster) + for (int i = 0; i < this.NumberOfSymbols - 1; i++) + { + tmp = (i == value) ? 0 : tmp; + uint p = this.probabilities[i]; + if (tmp < p) + { + this.probabilities[i] -= (ushort)((p - tmp) >> rate); + } + else + { + this.probabilities[i] += (ushort)((tmp - p) >> rate); + } + } + + int rate32 = this.updateCount < 32 ? 1 : 0; + this.updateCount += rate32; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs new file mode 100644 index 000000000..f783c9cdd --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1ParseAboveContext +{ + public int PartitionWidth { get; internal set; } + + internal void Clear() => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs new file mode 100644 index 000000000..7f521c273 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1ParseLeftContext +{ + public int PartitionHeight { get; internal set; } + + internal void Clear() => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs index 53ab65522..d0193031e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs @@ -1,12 +1,128 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -internal class Av1SymbolDecoder +internal ref struct Av1SymbolDecoder { - private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; + private readonly Av1Distribution[] frameYMode = Av1DefaultDistributions.FrameYMode; + private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; + private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; + private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute; + private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; + private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; + private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; + private Av1SymbolReader reader; + + public Av1SymbolDecoder(Span tileData) => this.reader = new Av1SymbolReader(tileData); + + public int ReadLiteral(int bitCount) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadLiteral(bitCount); + } + + public bool ReadUseIntraBlockCopy() + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.tileIntraBlockCopy) > 0; + } + + public Av1PartitionType ReadPartitionSymbol(int context) + { + ref Av1SymbolReader r = ref this.reader; + return (Av1PartitionType)r.ReadSymbol(this.tilePartitionTypes[context]); + } + + public bool ReadSplitOrHorizontal(Av1BlockSize blockSize, int context) + { + Av1Distribution input = this.tilePartitionTypes[context]; + uint p = Av1Distribution.ProbabilityTop; + p -= GetElementProbability(input, Av1PartitionType.Horizontal); + p -= GetElementProbability(input, Av1PartitionType.Split); + p -= GetElementProbability(input, Av1PartitionType.HorizontalA); + p -= GetElementProbability(input, Av1PartitionType.HorizontalB); + p -= GetElementProbability(input, Av1PartitionType.VerticalA); + if (blockSize != Av1BlockSize.Block128x128) + { + p -= GetElementProbability(input, Av1PartitionType.Horizontal4); + } + + Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p); + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(distribution) > 0; + } + + public bool ReadSplitOrVertical(Av1BlockSize blockSize, int context) + { + Av1Distribution input = this.tilePartitionTypes[context]; + uint p = Av1Distribution.ProbabilityTop; + p -= GetElementProbability(input, Av1PartitionType.Vertical); + p -= GetElementProbability(input, Av1PartitionType.Split); + p -= GetElementProbability(input, Av1PartitionType.HorizontalA); + p -= GetElementProbability(input, Av1PartitionType.VerticalA); + p -= GetElementProbability(input, Av1PartitionType.VerticalB); + if (blockSize != Av1BlockSize.Block128x128) + { + p -= GetElementProbability(input, Av1PartitionType.Vertical4); + } + + Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p); + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(distribution) > 0; + } + + public Av1PredictionMode ReadIntraFrameYMode(Av1BlockSize blockSize) + { + ref Av1SymbolReader r = ref this.reader; + return (Av1PredictionMode)r.ReadSymbol(this.frameYMode[(int)blockSize]); + } + + public Av1PredictionMode ReadUvMode(Av1BlockSize blockSize, bool chromaFromLumaAllowed) + { + int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0; + ref Av1SymbolReader r = ref this.reader; + return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)blockSize]); + } + + public bool ReadSkip(int ctx) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.skip[ctx]) > 0; + } + + public int ReadDeltaLoopFilterAbsolute() + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.deltaLoopFilterAbsolute); + } + + public int ReadDeltaQuantizerAbsolute() + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.deltaQuantizerAbsolute); + } + + public int ReadSegmentId(int ctx) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.segmentId[ctx]); + } + + public int ReadAngleDelta(Av1PredictionMode mode) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.angleDelta[((int)mode) - 1]); + } + + public bool ReadUseFilterUltra() => throw new NotImplementedException(); + + public object ReadFilterUltraMode() => throw new NotImplementedException(); - public bool ReadUseIntraBlockCopySymbol(ref Av1SymbolReader reader) - => reader.ReadSymbol(this.tileIntraBlockCopy, 2) > 0; + private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) + => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs index 6ff2807ec..7ed6ff745 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs @@ -1,12 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -internal class Av1SymbolEncoder +internal class Av1SymbolEncoder : IDisposable { - private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + + private Av1SymbolWriter? writer; + + public Av1SymbolEncoder(Configuration configuration, int initialSize) + => this.writer = new(configuration, initialSize); + + public void WriteUseIntraBlockCopySymbol(bool value) + => this.writer!.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2); + + public IMemoryOwner Exit() => this.writer!.Exit(); - public void WriteUseIntraBlockCopySymbol(Av1SymbolWriter writer, bool value) - => writer.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2); + public void Dispose() + { + this.writer?.Dispose(); + this.writer = null; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs index 91e00676a..19aed6386 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs @@ -5,13 +5,6 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal ref struct Av1SymbolReader { - internal const int CdfProbabilityTop = 1 << CdfProbabilityBitCount; - internal const int ProbabilityMinimum = 4; - internal const int CdfShift = 15 - CdfProbabilityBitCount; - internal const int ProbabilityShift = 6; - internal static readonly int[] NumberOfSymbols2Speed = [0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]; - - private const int CdfProbabilityBitCount = 15; private const int DecoderWindowsSize = 32; private const int LotsOfBits = 0x4000; @@ -45,10 +38,12 @@ internal ref struct Av1SymbolReader this.Refill(); } - public int ReadSymbol(uint[] probabilities, int numberOfSymbols) + public int ReadSymbol(Av1Distribution distribution) { - int value = this.DecodeIntegerQ15(probabilities, numberOfSymbols); - UpdateCdf(probabilities, value, numberOfSymbols); + int value = this.DecodeIntegerQ15(distribution); + + // UpdateCdf(probabilities, value, numberOfSymbols); + distribution.Update(value); return value; } @@ -87,8 +82,8 @@ internal ref struct Av1SymbolReader // assert(dif >> (DecoderWindowsSize - 16) < r); // assert(32768U <= r); - v = ((range >> 8) * (frequency >> ProbabilityShift)) >> (7 - ProbabilityShift); - v += ProbabilityMinimum; + v = ((range >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift); + v += Av1Distribution.ProbabilityMinimum; vw = v << (DecoderWindowsSize - 16); ret = true; newRange = v; @@ -106,17 +101,13 @@ internal ref struct Av1SymbolReader /// /// Decodes a symbol given an inverse cumulative distribution function(CDF) table in Q15. /// - /// + /// /// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]). /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0. /// - /// - /// The number of symbols in the alphabet. - /// This should be at most 16. - /// /// The decoded symbol. - private int DecodeIntegerQ15(uint[] probabilities, int numberOfSymbols) + private int DecodeIntegerQ15(Av1Distribution distribution) { uint c; uint u; @@ -125,20 +116,20 @@ internal ref struct Av1SymbolReader uint dif = this.difference; uint r = this.range; - int n = numberOfSymbols - 1; + int n = distribution.NumberOfSymbols - 1; DebugGuard.MustBeLessThan(dif >> (DecoderWindowsSize - 16), r, nameof(r)); - DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last value in probability array needs to be zero."); + DebugGuard.IsTrue(distribution[n] == 0, "Last value in probability array needs to be zero."); DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); - DebugGuard.MustBeGreaterThanOrEqualTo(7 - ProbabilityShift - CdfShift, 0, nameof(CdfShift)); + DebugGuard.MustBeGreaterThanOrEqualTo(7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift, 0, nameof(Av1Distribution.CdfShift)); c = dif >> (DecoderWindowsSize - 16); v = r; ret = -1; do { u = v; - v = ((r >> 8) * (probabilities[++ret] >> ProbabilityShift)) >> (7 - ProbabilityShift - CdfShift); - v += (uint)(ProbabilityMinimum * (n - ret)); + v = ((r >> 8) * (distribution[++ret] >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift); + v += (uint)(Av1Distribution.ProbabilityMinimum * (n - ret)); } while (c < v); @@ -213,31 +204,4 @@ internal ref struct Av1SymbolReader this.count = cnt; this.position = position; } - - internal static void UpdateCdf(uint[] probabilities, int value, int numberOfSymbols) - { - DebugGuard.MustBeLessThan(numberOfSymbols, 17, nameof(numberOfSymbols)); - int rate15 = probabilities[numberOfSymbols] > 15 ? 1 : 0; - int rate31 = probabilities[numberOfSymbols] > 31 ? 1 : 0; - int rate = 3 + rate15 + rate31 + NumberOfSymbols2Speed[numberOfSymbols]; // + get_msb(nsymbs); - int tmp = CdfProbabilityTop; - - // Single loop (faster) - for (int i = 0; i < numberOfSymbols - 1; i++) - { - tmp = (i == value) ? 0 : tmp; - uint p = probabilities[i]; - if (tmp < p) - { - probabilities[i] -= (ushort)((p - tmp) >> rate); - } - else - { - probabilities[i] += (ushort)((tmp - p) >> rate); - } - } - - uint rate32 = probabilities[numberOfSymbols] < 32 ? 1U : 0U; - probabilities[numberOfSymbols] += rate32; - } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs index 296b516fb..cf36b04a1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs @@ -25,14 +25,14 @@ internal class Av1SymbolWriter : IDisposable public void Dispose() => this.memory.Dispose(); - public void WriteSymbol(int symbol, uint[] probabilities, int numberOfSymbols) + public void WriteSymbol(int symbol, Av1Distribution distribution, int numberOfSymbols) { DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol)); DebugGuard.MustBeLessThan(symbol, numberOfSymbols, nameof(symbol)); - DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero."); + DebugGuard.IsTrue(distribution[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero."); - this.EncodeIntegerQ15(symbol, probabilities, numberOfSymbols); - Av1SymbolReader.UpdateCdf(probabilities, symbol, numberOfSymbols); + this.EncodeIntegerQ15(symbol, distribution, numberOfSymbols); + distribution.Update(symbol); } public void WriteLiteral(uint value, int bitCount) @@ -104,8 +104,8 @@ internal class Av1SymbolWriter : IDisposable l = this.low; r = this.rng; DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); - v = ((r >> 8) * (frequency >> Av1SymbolReader.ProbabilityShift)) >> (7 - Av1SymbolReader.ProbabilityShift); - v += Av1SymbolReader.ProbabilityMinimum; + v = ((r >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift); + v += Av1Distribution.ProbabilityMinimum; if (val) { l += r - v; @@ -123,7 +123,7 @@ internal class Av1SymbolWriter : IDisposable /// Encodes a symbol given an inverse cumulative distribution function(CDF) table in Q15. /// /// The value to encode. - /// + /// /// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]). /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0. @@ -132,12 +132,12 @@ internal class Av1SymbolWriter : IDisposable /// The number of symbols in the alphabet. /// This should be at most 16. /// - private void EncodeIntegerQ15(int symbol, uint[] probabilities, int numberOfSymbols) - => this.EncodeIntegerQ15(symbol > 0 ? probabilities[symbol - 1] : Av1SymbolReader.CdfProbabilityTop, probabilities[symbol], symbol, numberOfSymbols); + private void EncodeIntegerQ15(int symbol, Av1Distribution distribution, int numberOfSymbols) + => this.EncodeIntegerQ15(symbol > 0 ? distribution[symbol - 1] : Av1Distribution.ProbabilityTop, distribution[symbol], symbol, numberOfSymbols); private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols) { - const int totalShift = 7 - Av1SymbolReader.ProbabilityShift - Av1SymbolReader.CdfShift; + const int totalShift = 7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift; uint l = this.low; uint r = this.rng; DebugGuard.MustBeLessThanOrEqualTo(32768U, r, nameof(r)); @@ -145,21 +145,21 @@ internal class Av1SymbolWriter : IDisposable DebugGuard.MustBeLessThanOrEqualTo(lowFrequency, 32768U, nameof(lowFrequency)); DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, nameof(totalShift)); int n = numberOfSymbols - 1; - if (lowFrequency < Av1SymbolReader.CdfProbabilityTop) + if (lowFrequency < Av1Distribution.ProbabilityTop) { uint u; uint v; - u = (uint)((((r >> 8) * (lowFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + - (Av1SymbolReader.ProbabilityMinimum * (n - (symbol - 1)))); - v = (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + - (Av1SymbolReader.ProbabilityMinimum * (n - symbol))); + u = (uint)((((r >> 8) * (lowFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + + (Av1Distribution.ProbabilityMinimum * (n - (symbol - 1)))); + v = (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + + (Av1Distribution.ProbabilityMinimum * (n - symbol))); l += r - u; r = u - v; } else { - r -= (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + - (Av1SymbolReader.ProbabilityMinimum * (n - symbol))); + r -= (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + + (Av1Distribution.ProbabilityMinimum * (n - symbol))); } this.Normalize(l, r); diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 3db631e0b..8366a8e88 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -13,4 +13,12 @@ internal static class Av1TransformSizeExtensions int pels = Size2d[(int)size]; return (pels > 1024) ? 2 : (pels > 256) ? 1 : 0; } + + public static int GetWidth(this Av1TransformSize size) => (int)size; + + public static int GetHeight(this Av1TransformSize size) => (int)size; + + public static int GetWidthLog2(this Av1TransformSize size) => (int)size; + + public static int GetHeightLog2(this Av1TransformSize size) => (int)size; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs index 36ae4cfe8..9f0dbc284 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs @@ -20,7 +20,7 @@ internal class Av1TileDecoderStub : IAv1TileDecoder public ObuTileInfo TileInfo { get; } = new ObuTileInfo(); - public void DecodeTile(int tileNum) + public void DecodeTile(Span tileData, int tileNum) { } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs index 99f38c118..36b66a8b7 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; using SixLabors.ImageSharp.Memory; @@ -185,30 +186,53 @@ public class SymbolTest Assert.Equal(expectedValues, values); } + [Theory] + [InlineData(0, 255, 255, 255, 158)] + + // [InlineData(1, 255, 255, 255, 158)] + // [InlineData(4, 255, 207, 254, 18)] + public void ReadPartitionTypeSymbols(int ctx, byte b0, byte b1, byte b2, byte b3) + { + // Assign + byte[] values = [b0, b1, b2, b3, 0]; + Av1SymbolDecoder decoder = new(values); + Av1PartitionType[] expected = [ + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None ]; + Av1PartitionType[] actuals = new Av1PartitionType[expected.Length]; + + // Act + for (int i = 0; i < expected.Length; i++) + { + actuals[i] = decoder.ReadPartitionSymbol(ctx); + } + + // Assert + Assert.Equal(expected, actuals); + } + [Fact] public void RoundTripUseIntraBlockCopy() { // Assign bool[] values = [true, true, false, true, false, false, false]; - MemoryStream output = new(100); Configuration configuration = Configuration.Default; - using Av1SymbolWriter writer = new(configuration, 100 / 8); - Av1SymbolEncoder encoder = new(); - Av1SymbolDecoder decoder = new(); + Av1SymbolEncoder encoder = new(configuration, 100 / 8); bool[] actuals = new bool[values.Length]; // Act foreach (bool value in values) { - encoder.WriteUseIntraBlockCopySymbol(writer, value); + encoder.WriteUseIntraBlockCopySymbol(value); } - using IMemoryOwner encoded = writer.Exit(); + using IMemoryOwner encoded = encoder.Exit(); + Av1SymbolDecoder decoder = new(encoded.GetSpan()); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { - actuals[i] = decoder.ReadUseIntraBlockCopySymbol(ref reader); + actuals[i] = decoder.ReadUseIntraBlockCopy(); } // Assert