From e60b39fb256573499c0852926735e4e34852efe8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 3 Jul 2024 18:43:57 +0200 Subject: [PATCH] Implement Transform parsing --- .../Heif/Av1/Av1BlockSizeExtensions.cs | 19 ++ .../Formats/Heif/Av1/Av1Constants.cs | 5 + .../Heif/Av1/Tiling/Av1BlockModeInfo.cs | 10 +- .../Av1/Tiling/Av1DefaultDistributions.cs | 8 + .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 43 ++- .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 20 +- .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 20 +- .../Heif/Av1/Tiling/Av1PartitionInfo.cs | 46 ++++ .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 27 ++ .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 246 ++++++++++++++---- .../Heif/Av1/Tiling/Av1TransformInfo.cs | 18 ++ .../Heif/Av1/Transform/Av1TransformSize.cs | 2 +- .../Transform/Av1TransformSizeExtensions.cs | 38 ++- 13 files changed, 440 insertions(+), 62 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index a269c01071..34fe80da3b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -89,6 +90,24 @@ internal static class Av1BlockSizeExtensions return SubSampled[(int)blockSize][subX ? 1 : 0][subY ? 1 : 0]; } + public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize, bool subX, bool subY) + { + Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); + Av1TransformSize uvTransformSize = Av1TransformSize.Invalid; + if (planeBlockSize < Av1BlockSize.SizeS) + { + uvTransformSize = planeBlockSize.GetMaximumTransformSize(); + } + + return uvTransformSize switch + { + Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64 => Av1TransformSize.Size32x32, + Av1TransformSize.Size64x16 => Av1TransformSize.Size32x16, + Av1TransformSize.Size16x64 => Av1TransformSize.Size16x32, + _ => uvTransformSize, + }; + } + /// /// Returns the largest transform size that can be used for blocks of given size. /// The can be either a square or rectangular block. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 92810728a3..6fd6c536ac 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -133,4 +133,9 @@ internal static class Av1Constants /// Number of values for palette_size. /// public const int PaletteMaxSize = 8; + + /// + /// Maximum transform size categories. + /// + public const int MaxTransformCategories = 4; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index b8b8f8bb47..a78df4428e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs @@ -13,9 +13,11 @@ internal class Av1BlockModeInfo { this.BlockSize = blockSize; this.PositionInSuperblock = position; - this.AngleDelta = new int[numPlanes]; + this.AngleDelta = new int[numPlanes - 1]; this.paletteSize = new int[numPlanes - 1]; this.FilterIntraModeInfo = new(); + this.FirstTransformLocation = new int[numPlanes - 1]; + this.TusCount = new int[numPlanes - 1]; } public Av1BlockSize BlockSize { get; } @@ -24,7 +26,7 @@ internal class Av1BlockModeInfo public bool Skip { get; set; } - public Av1PartitionType PartitionType { get; } + public Av1PartitionType PartitionType { get; set; } public bool SkipMode { get; set; } @@ -44,6 +46,10 @@ internal class Av1BlockModeInfo public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } + public int[] FirstTransformLocation { get; } + + public int[] TusCount { get; internal set; } + public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType]; public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs index cb65bfafe4..fbd0d99856 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs @@ -171,4 +171,12 @@ internal static class Av1DefaultDistributions new(16384), new(16384), new(16384), new(16384), new(12770), new(10368), new(20229), new(18101), new(16384), new(16384) ]; + + public static Av1Distribution[][] TransformSize => + [ + [new(19968), new(19968), new(24320)], + [new(12272, 30172), new(12272, 30172), new(18677, 30848)], + [new(12986, 15180), new(12986, 15180), new(24302, 25602)], + [new(5782, 11475), new(5782, 11475), new(16803, 22759)], + ]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index db8b6ba18f..03e7100606 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -50,6 +51,7 @@ internal class Av1FrameBuffer // Initialize the arrays. int i = 0; int j = 0; + int k = 0; for (int y = 0; y < this.superblockRowCount; y++) { for (int x = 0; x < this.superblockColumnCount; x++) @@ -58,11 +60,16 @@ internal class Av1FrameBuffer this.superblockInfos[i] = new(this, point); for (int u = 0; u < this.modeInfoSizePerSuperblock; u++) { + this.transformInfosY[j] = new Av1TransformInfo(); + this.transformInfosY[j << 1] = new Av1TransformInfo(); + this.transformInfosY[(j << 1) + 1] = new Av1TransformInfo(); for (int v = 0; v < this.modeInfoSizePerSuperblock; v++) { - this.modeInfos[j] = new Av1BlockModeInfo(numPlanes, Av1BlockSize.Block4x4, new Point(u, v)); - j++; + this.modeInfos[k] = new Av1BlockModeInfo(numPlanes, Av1BlockSize.Block4x4, new Point(u, v)); + k++; } + + j++; } i++; @@ -87,6 +94,8 @@ internal class Av1FrameBuffer this.deltaLoopFilter = new int[superblockCount << this.deltaLoopFactorLog2]; } + public int ModeInfoCount => this.modeInfos.Length; + public Av1SuperblockInfo GetSuperblock(Point index) { Span span = this.superblockInfos; @@ -109,19 +118,35 @@ internal class Av1FrameBuffer return span[(superblock * this.modeInfoCountPerSuperblock) + modeInfo]; } - public Av1TransformInfo GetTransformY(Point index) + public ref Av1TransformInfo GetTransformY(int index) + { + Span span = this.transformInfosY; + return ref span[index]; + } + + public void SetTransformY(int index, Av1TransformInfo transformInfo) + { + Span span = this.transformInfosY; + span[index] = transformInfo; + } + + public ref Av1TransformInfo GetTransformY(Point index) { Span span = this.transformInfosY; int i = (index.Y * this.superblockColumnCount) + index.X; - return span[i * this.modeInfoCountPerSuperblock]; + return ref span[i * this.modeInfoCountPerSuperblock]; + } + + public ref Av1TransformInfo GetTransformUv(int index) + { + Span span = this.transformInfosUv; + return ref span[index]; } - public void GetTransformUv(Point index, out Av1TransformInfo transformU, out Av1TransformInfo transformV) + public void SetTransformUv(int index, Av1TransformInfo transformInfo) { Span span = this.transformInfosUv; - int i = 2 * ((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock; - transformU = span[i]; - transformV = span[i + 1]; + span[index] = transformInfo; } public Span GetCoefficientsY(Point index) @@ -175,5 +200,5 @@ internal class Av1FrameBuffer return span.Slice(i, 1 << this.deltaLoopFactorLog2); } - internal void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); + public void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index c883d618bf..2efbde6e4c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -44,6 +44,8 @@ internal class Av1ParseAboveNeighbor4x4Context public int[] AbovePartitionWidth => this.abovePartitionWidth; + public int[] AboveTransformWidth => this.aboveTransformWidth; + public void Clear(ObuSequenceHeader sequenceHeader) { int planeCount = sequenceHeader.ColorConfig.ChannelCount; @@ -70,6 +72,22 @@ internal class Av1ParseAboveNeighbor4x4Context } } + public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) + { + int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart; + int transformWidth = transformSize.GetWidth(); + int n4w = blockSize.Get4x4WideCount(); + if (skip) + { + transformWidth = n4w * (1 << Av1Constants.ModeInfoSizeLog2); + } + + for (int i = 0; i < n4w; i++) + { + this.aboveTransformWidth[startIndex + i] = transformWidth; + } + } + internal void ClearContext(int plane, int offset, int length) => Array.Fill(this.aboveContext[plane], 0, offset, length); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index 9030ea6012..18ea5b5f7f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -45,6 +45,8 @@ internal class Av1ParseLeftNeighbor4x4Context public int[] LeftPartitionHeight => this.leftPartitionHeight; + public int[] LeftTransformHeight => this.leftTransformHeight; + public void Clear(ObuSequenceHeader sequenceHeader) { int planeCount = sequenceHeader.ColorConfig.ChannelCount; @@ -71,6 +73,22 @@ internal class Av1ParseLeftNeighbor4x4Context } } + public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) + { + int startIndex = modeInfoLocation.Y - superblockInfo.Position.Y; + int transformHeight = transformSize.GetHeight(); + int n4h = blockSize.Get4x4HighCount(); + if (skip) + { + transformHeight = n4h * (1 << Av1Constants.ModeInfoSizeLog2); + } + + for (int i = 0; i < n4h; i++) + { + this.leftTransformHeight[startIndex + i] = transformHeight; + } + } + internal void ClearContext(int plane, int offset, int length) => Array.Fill(this.leftContext[plane], 0, offset, length); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index d6dc9ffa65..cfacff6d10 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs @@ -1,10 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1PartitionInfo { + private int modeBlockToLeftEdge; + private int modeBlockToRightEdge; + private int modeBlockToTopEdge; + private int modeBlockToBottomEdge; + public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType) { this.ModeInfo = modeInfo; @@ -42,4 +49,43 @@ internal class Av1PartitionInfo public int[][] CdefStrength { get; set; } public int[] ReferenceFrame { get; set; } + + public void ComputeBoundaryOffsets(ObuFrameHeader frameInfo, Av1TileInfo tileInfo) + { + Av1BlockSize blockSize = this.ModeInfo.BlockSize; + int bw4 = blockSize.Get4x4WideCount(); + int bh4 = blockSize.Get4x4HighCount(); + this.AvailableUp = this.RowIndex > tileInfo.ModeInfoRowStart; + this.AvailableLeft = this.ColumnIndex > tileInfo.ModeInfoColumnStart; + this.AvailableUpForChroma = this.AvailableUp; + this.AvailableLeftForChroma = this.AvailableLeft; + this.modeBlockToLeftEdge = -(this.ColumnIndex << Av1Constants.ModeInfoSizeLog2) << 3; + this.modeBlockToRightEdge = ((frameInfo.ModeInfoColumnCount - bw4 - this.ColumnIndex) << Av1Constants.ModeInfoSizeLog2) << 3; + this.modeBlockToTopEdge = -(this.RowIndex << Av1Constants.ModeInfoSizeLog2) << 3; + this.modeBlockToBottomEdge = ((frameInfo.ModeInfoRowCount - bh4 - this.RowIndex) << Av1Constants.ModeInfoSizeLog2) << 3; + } + + public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX) + { + int maxBlockWide = blockSize.GetWidth(); + if (this.modeBlockToRightEdge < 0) + { + int shift = subX ? 4 : 3; + maxBlockWide += this.modeBlockToRightEdge >> shift; + } + + return maxBlockWide >> 2; + } + + public int GetMaxBlockHigh(Av1BlockSize blockSize, bool subY) + { + int maxBlockHigh = blockSize.GetHeight(); + if (this.modeBlockToBottomEdge < 0) + { + int shift = subY ? 4 : 3; + maxBlockHigh += this.modeBlockToBottomEdge >> shift; + } + + return maxBlockHigh >> 2; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index 3d76994391..7d6eb4998f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Drawing; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -20,6 +22,7 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; + private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize; private Av1SymbolReader reader; public Av1SymbolDecoder(Span tileData) => this.reader = new Av1SymbolReader(tileData); @@ -149,6 +152,30 @@ internal ref struct Av1SymbolDecoder return (Av1FilterIntraMode)r.ReadSymbol(this.filterIntraMode); } + public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context) + { + ref Av1SymbolReader r = ref this.reader; + Av1TransformSize maxTransformSize = blockSize.GetMaximumTransformSize(); + int depth = 0; + while (maxTransformSize != Av1TransformSize.Size4x4) + { + depth++; + maxTransformSize = maxTransformSize.GetSubSize(); + DebugGuard.MustBeLessThan(depth, 10, nameof(depth)); + } + + DebugGuard.MustBeLessThanOrEqualTo(depth, Av1Constants.MaxTransformCategories, nameof(depth)); + int category = depth - 1; + int value = r.ReadSymbol(this.transformSize[category][context]); + Av1TransformSize transformSize = blockSize.GetMaximumTransformSize(); + for (int d = 0; d < value; ++d) + { + transformSize = transformSize.GetSubSize(); + } + + return transformSize; + } + private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index ea0af6ce9b..c286b34b6a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -2,10 +2,13 @@ // Licensed under the Six Labors Split License. using System.Data.Common; +using System.Drawing; +using System.Runtime.CompilerServices; 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; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using static SixLabors.ImageSharp.PixelFormats.Utils.Vector4Converters; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -31,6 +34,8 @@ internal class Av1TileDecoder : IAv1TileDecoder private int maxLumaHeight; private int deltaLoopFilterResolution = -1; private bool readDeltas; + private int[][] tusCount = []; + private int[] firstTransformOffset = new int[2]; public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { @@ -55,6 +60,10 @@ internal class Av1TileDecoder : IAv1TileDecoder modeInfoWideColumnCount = AlignPowerOfTwo(modeInfoWideColumnCount, sequenceHeader.SuperBlockSizeLog2 - 2); this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount); this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.ModeInfoSize); + this.tusCount = new int[Av1Constants.MaxPlanes][]; + this.tusCount[0] = new int[this.FrameBuffer.ModeInfoCount]; + this.tusCount[1] = new int[this.FrameBuffer.ModeInfoCount]; + this.tusCount[2] = new int[this.FrameBuffer.ModeInfoCount]; } public ObuFrameHeader FrameInfo { get; } @@ -71,7 +80,7 @@ internal class Av1TileDecoder : IAv1TileDecoder this.aboveNeighborContext.Clear(this.SequenceHeader); this.ClearLoopFilterDelta(); - int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; // Default initialization of Wiener and SGR Filter. this.referenceSgrXqd = new int[planesCount][]; @@ -102,9 +111,9 @@ internal class Av1TileDecoder : IAv1TileDecoder Point superblockPosition = new(superBlockColumn, superBlockRow); Av1SuperblockInfo superblockInfo = this.FrameBuffer.GetSuperblock(superblockPosition); - // this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); - this.FrameBuffer.ClearCdef(superblockPosition); + // this.ClearBlockDecodedFlags(modeInfoLocation, superBlock4x4Size); Point modeInfoLocation = new(column, row); + this.FrameBuffer.ClearCdef(superblockPosition); this.ReadLoopRestoration(modeInfoLocation, superBlockSize); this.ParsePartition(ref reader, modeInfoLocation, superBlockSize, superblockInfo, tileInfo); } @@ -119,7 +128,7 @@ internal class Av1TileDecoder : IAv1TileDecoder /// /// 5.11.3. Clear block decoded flags function. /// - private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) + private void ClearBlockDecodedFlags(Point modeInfoLocation, int superBlock4x4Size) { int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; this.blockDecoded = new bool[planesCount][][]; @@ -127,12 +136,12 @@ internal class Av1TileDecoder : IAv1TileDecoder { 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; - this.blockDecoded[plane] = new bool[superBlock4x4Size >> subY][]; + int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - modeInfoLocation.X) >> subX; + int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - modeInfoLocation.Y) >> subY; + this.blockDecoded[plane] = new bool[(superBlock4x4Size >> subY) + 3][]; for (int y = -1; y <= superBlock4x4Size >> subY; y++) { - this.blockDecoded[plane][y] = new bool[superBlock4x4Size >> subX]; + this.blockDecoded[plane][y] = new bool[(superBlock4x4Size >> subX) + 3]; for (int x = -1; x <= superBlock4x4Size >> subX; x++) { if (y < 0 && x < superBlock4x4Width) @@ -194,7 +203,7 @@ internal class Av1TileDecoder : IAv1TileDecoder bool availableLeft = this.IsInside(rowIndex, columnIndex - 1); int block4x4Size = blockSize.Get4x4WideCount(); int halfBlock4x4Size = block4x4Size >> 1; - int quarterBlock4x4Size = halfBlock4x4Size >> 2; + int quarterBlock4x4Size = halfBlock4x4Size >> 1; bool hasRows = (modeInfoLocation.Y + halfBlock4x4Size) < this.FrameInfo.ModeInfoRowCount; bool hasColumns = (modeInfoLocation.X + halfBlock4x4Size) < this.FrameInfo.ModeInfoColumnCount; Av1PartitionType partitionType = Av1PartitionType.Split; @@ -234,53 +243,53 @@ internal class Av1TileDecoder : IAv1TileDecoder this.ParsePartition(ref reader, loc3, subSize, superblockInfo, tileInfo); break; case Av1PartitionType.None: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.None); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.None); break; case Av1PartitionType.Horizontal: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.Horizontal); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal); if (hasRows) { Point halfLocation = new(columnIndex, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, Av1PartitionType.Horizontal); + this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal); } break; case Av1PartitionType.Vertical: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.Vertical); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical); if (hasRows) { Point halfLocation = new(columnIndex + halfBlock4x4Size, rowIndex); - this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, Av1PartitionType.Vertical); + this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical); } break; case Av1PartitionType.HorizontalA: - this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA); Point locHorA1 = new(columnIndex + halfBlock4x4Size, rowIndex); - this.ParseBlock(ref reader, locHorA1, splitSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, locHorA1, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA); Point locHorA2 = new(columnIndex, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locHorA2, subSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, locHorA2, subSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA); break; case Av1PartitionType.HorizontalB: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); Point locHorB1 = new(columnIndex + halfBlock4x4Size, rowIndex); - this.ParseBlock(ref reader, locHorB1, splitSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, locHorB1, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); Point locHorB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locHorB2, splitSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, locHorB2, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); break; case Av1PartitionType.VerticalA: - this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA); Point locVertA1 = new(columnIndex, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locVertA1, splitSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, locVertA1, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA); Point locVertA2 = new(columnIndex + halfBlock4x4Size, rowIndex); - this.ParseBlock(ref reader, locVertA2, subSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, locVertA2, subSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA); break; case Av1PartitionType.VerticalB: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); Point locVertB1 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locVertB1, splitSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, locVertB1, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); Point locVertB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locVertB2, splitSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, locVertB2, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); break; case Av1PartitionType.Horizontal4: for (int i = 0; i < 4; i++) @@ -292,7 +301,7 @@ internal class Av1TileDecoder : IAv1TileDecoder } Point currentLocation = new(modeInfoLocation.X, currentBlockRow); - this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, Av1PartitionType.Horizontal4); + this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal4); } break; @@ -306,7 +315,7 @@ internal class Av1TileDecoder : IAv1TileDecoder } Point currentLocation = new(currentBlockColumn, modeInfoLocation.Y); - this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, Av1PartitionType.Vertical4); + this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical4); } break; @@ -317,7 +326,7 @@ internal class Av1TileDecoder : IAv1TileDecoder this.UpdatePartitionContext(new Point(columnIndex, rowIndex), tileInfo, superblockInfo, subSize, blockSize, partitionType); } - private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1PartitionType partitionType) + private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1PartitionType partitionType) { int rowIndex = modeInfoLocation.Y; int columnIndex = modeInfoLocation.X; @@ -325,14 +334,12 @@ internal class Av1TileDecoder : IAv1TileDecoder int block4x4Height = blockSize.Get4x4HighCount(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; Av1BlockModeInfo blockModeInfo = superblockInfo.GetModeInfo(modeInfoLocation); + blockModeInfo.PartitionType = partitionType; bool hasChroma = this.HasChroma(rowIndex, columnIndex, blockSize); Av1PartitionInfo partitionInfo = new(blockModeInfo, superblockInfo, hasChroma, partitionType); partitionInfo.ColumnIndex = columnIndex; partitionInfo.RowIndex = rowIndex; - partitionInfo.AvailableUp = this.IsInside(rowIndex - 1, columnIndex); - partitionInfo.AvailableLeft = this.IsInside(rowIndex, columnIndex - 1); - partitionInfo.AvailableUpForChroma = partitionInfo.AvailableUp; - partitionInfo.AvailableLeftForChroma = partitionInfo.AvailableLeft; + partitionInfo.ComputeBoundaryOffsets(this.FrameInfo, tileInfo); if (hasChroma) { if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) @@ -358,7 +365,7 @@ internal class Av1TileDecoder : IAv1TileDecoder this.ReadModeInfo(ref reader, partitionInfo); ReadPaletteTokens(ref reader, partitionInfo); - ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); + this.ReadBlockTransformSize(ref reader, modeInfoLocation, partitionInfo, superblockInfo, tileInfo); if (partitionInfo.ModeInfo.Skip) { this.ResetSkipContext(partitionInfo); @@ -571,22 +578,171 @@ internal class Av1TileDecoder : IAv1TileDecoder } /// - /// 5.11.16. Block TX size syntax. + /// 5.11.15. TX size syntax. /// - private static void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private Av1TransformSize ReadTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, bool allowSelect) + { + Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo; + if (this.FrameInfo.LosslessArray[modeInfo.SegmentId]) + { + return Av1TransformSize.Size4x4; + } + + if (modeInfo.BlockSize > Av1BlockSize.Block4x4 && allowSelect && this.FrameInfo.TransformMode == Transform.Av1TransformMode.Select) + { + return this.ReadSelectedTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo); + } + + return modeInfo.BlockSize.GetMaximumTransformSize(); + } + + private Av1TransformSize ReadSelectedTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { + int context = 0; + Av1TransformSize maxTransformSize = partitionInfo.ModeInfo.BlockSize.GetMaximumTransformSize(); + int aboveWidth = this.aboveNeighborContext.AboveTransformWidth[partitionInfo.ColumnIndex - tileInfo.ModeInfoColumnStart]; + int above = (aboveWidth >= maxTransformSize.GetWidth()) ? 1 : 0; + int leftHeight = this.leftNeighborContext.LeftTransformHeight[partitionInfo.RowIndex - superblockInfo.Position.Y]; + int left = (leftHeight >= maxTransformSize.GetHeight()) ? 1 : 0; + bool hasAbove = partitionInfo.AvailableUp; + bool hasLeft = partitionInfo.AvailableLeft; + + if (hasAbove && hasLeft) + { + context = above + left; + } + else if (hasAbove) + { + context = above; + } + else if (hasLeft) + { + context = left; + } + else + { + context = 0; + } + + return reader.ReadTransformSize(partitionInfo.ModeInfo.BlockSize, context); + } + + /// + /// Section 5.11.16. Block TX size syntax. + /// + private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) + { + Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize; int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Height = blockSize.Get4x4HighCount(); // First condition in spec is for INTER frames, implemented only the INTRA condition. - ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); - /*for (int row = rowIndex; row < rowIndex + block4x4Height; row++) + bool allowSelect = !partitionInfo.ModeInfo.Skip || true; + Av1TransformSize transformSize = this.ReadTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo, allowSelect); + this.aboveNeighborContext.UpdateTransformation(modeInfoLocation, tileInfo, transformSize, blockSize, false); + this.leftNeighborContext.UpdateTransformation(modeInfoLocation, superblockInfo, transformSize, blockSize, false); + this.UpdateTransformInfo(partitionInfo, blockSize, transformSize); + } + + private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1BlockSize blockSize, Av1TransformSize transformSize) + { + int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; + int transformInfoUvIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; + Av1TransformInfo lumaTransformInfo = this.FrameBuffer.GetTransformY(transformInfoYIndex); + Av1TransformInfo chromaTransformInfo = this.FrameBuffer.GetTransformUv(transformInfoUvIndex); + int totalLumaTusCount = 0; + int totalChromaTusCount = 0; + int forceSplitCount = 0; + bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; + int maxBlockWide = partitionInfo.GetMaxBlockWide(blockSize, false); + int maxBlockHigh = partitionInfo.GetMaxBlockHigh(blockSize, false); + int width = 64 >> 2; + int height = 64 >> 2; + width = Math.Min(width, maxBlockWide); + height = Math.Min(height, maxBlockHigh); + + bool isLossLess = this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]; + Av1TransformSize transformSizeUv = isLossLess ? Av1TransformSize.Size4x4 : blockSize.GetMaxUvTransformSize(subX, subY); + + for (int idy = 0; idy < maxBlockHigh; idy += height) { - for (int column = columnIndex; column < columnIndex + block4x4Width; column++) + for (int idx = 0; idx < maxBlockWide; idx += width, forceSplitCount++) { - this.InterTransformSizes[row][column] = this.TransformSize; + int lumaTusCount = 0; + int chromaTusCount = 0; + + // Update Luminance Transform Info. + int stepColumn = transformSize.Get4x4WideCount(); + int stepRow = transformSize.Get4x4HighCount(); + + int unitHeight = Av1Math.Round2(Math.Min(height + idy, maxBlockHigh), 0); + int unitWidth = Av1Math.Round2(Math.Min(width + idx, maxBlockWide), 0); + for (int blockRow = idy; blockRow < unitHeight; blockRow += stepRow) + { + for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) + { + this.FrameBuffer.SetTransformY(transformInfoYIndex, new Av1TransformInfo + { + TransformSize = transformSize, + OffsetX = blockColumn, + OffsetY = blockRow + }); + transformInfoYIndex++; + lumaTusCount++; + totalLumaTusCount++; + } + } + + this.tusCount[(int)Av1Plane.Y][forceSplitCount] = lumaTusCount; + + if (this.SequenceHeader.ColorConfig.IsMonochrome || !partitionInfo.IsChroma) + { + continue; + } + + // Update Chroma Transform Info. + stepColumn = transformSizeUv.Get4x4WideCount(); + stepRow = transformSizeUv.Get4x4HighCount(); + + unitHeight = Av1Math.Round2(Math.Min(height + idx, maxBlockHigh), subY ? 1 : 0); + unitWidth = Av1Math.Round2(Math.Min(width + idx, maxBlockWide), subX ? 1 : 0); + for (int blockRow = idy; blockRow < unitHeight; blockRow += stepRow) + { + for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) + { + this.FrameBuffer.SetTransformUv(transformInfoUvIndex, new Av1TransformInfo + { + TransformSize = transformSizeUv, + OffsetX = blockColumn, + OffsetY = blockRow + }); + transformInfoUvIndex++; + chromaTusCount++; + totalChromaTusCount++; + } + } + + this.tusCount[(int)Av1Plane.U][forceSplitCount] = lumaTusCount; + this.tusCount[(int)Av1Plane.V][forceSplitCount] = lumaTusCount; } - }*/ + } + + // Cr Transform Info Update from Cb. + if (totalChromaTusCount != 0) + { + int originalIndex = transformInfoUvIndex - totalChromaTusCount; + for (int i = 0; i < totalChromaTusCount; i++) + { + this.FrameBuffer.SetTransformUv(transformInfoUvIndex + i, this.FrameBuffer.GetTransformUv(originalIndex + i)); + } + } + + partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Y] = totalLumaTusCount; + partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Uv] = totalChromaTusCount; + + this.firstTransformOffset[(int)Av1PlaneType.Y] += totalLumaTusCount; + this.firstTransformOffset[(int)Av1PlaneType.Uv] += totalChromaTusCount; } /// @@ -594,16 +750,15 @@ internal class Av1TileDecoder : IAv1TileDecoder /// private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - reader.ReadLiteral(-1); if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) != 0) { - // Todo: Implement. + // TODO: Implement. throw new NotImplementedException(); } if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0) { - // Todo: Implement. + // TODO: Implement. throw new NotImplementedException(); } } @@ -636,8 +791,7 @@ internal class Av1TileDecoder : IAv1TileDecoder this.ReadCdef(ref reader, partitionInfo); - bool readDeltas = false; - if (readDeltas) + if (this.readDeltas) { this.ReadDeltaQuantizerIndex(ref reader, partitionInfo); this.ReadDeltaLoopFilter(ref reader, partitionInfo); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs index 910309f873..ce91a4916f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs @@ -1,8 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1TransformInfo { + public Av1TransformInfo() + { + } + + public Av1TransformInfo(Av1TransformInfo originalInfo) + { + this.TransformSize = originalInfo.TransformSize; + this.OffsetX = originalInfo.OffsetX; + this.OffsetY = originalInfo.OffsetY; + } + + public Av1TransformSize TransformSize { get; internal set; } + + public int OffsetX { get; internal set; } + + public int OffsetY { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs index 267c55266d..e812778335 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal enum Av1TransformSize : byte { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 8366a8e887..1fc36fd100 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -1,12 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal static class Av1TransformSizeExtensions { private static readonly int[] Size2d = [ - 16, 64, 256, 1024, 4096, 32, 32, 128, 128, 512, 512, 2048, 2048, 64, 64, 256, 256, 1024, 1024]; + 16, 64, 256, 1024, 4096, 32, 32, 128, 128, 512, 512, 2048, 2048, 64, 64, 256, 256, 1024, 1024]; + + private static readonly Av1TransformSize[] SubTransformSize = [ + Av1TransformSize.Size4x4, // TX_4X4 + Av1TransformSize.Size4x4, // TX_8X8 + Av1TransformSize.Size8x8, // TX_16X16 + Av1TransformSize.Size16x16, // TX_32X32 + Av1TransformSize.Size32x32, // TX_64X64 + Av1TransformSize.Size4x4, // TX_4X8 + Av1TransformSize.Size4x4, // TX_8X4 + Av1TransformSize.Size8x8, // TX_8X16 + Av1TransformSize.Size8x8, // TX_16X8 + Av1TransformSize.Size16x16, // TX_16X32 + Av1TransformSize.Size16x16, // TX_32X16 + Av1TransformSize.Size32x32, // TX_32X64 + Av1TransformSize.Size32x32, // TX_64X32 + Av1TransformSize.Size4x8, // TX_4X16 + Av1TransformSize.Size8x4, // TX_16X4 + Av1TransformSize.Size8x16, // TX_8X32 + Av1TransformSize.Size16x8, // TX_32X8 + Av1TransformSize.Size16x32, // TX_16X64 + Av1TransformSize.Size32x16, // TX_64X16 + ]; + + // Transform block width in units. + private static readonly int[] WideUnit = [1, 2, 4, 8, 16, 1, 2, 2, 4, 4, 8, 8, 16, 1, 4, 2, 8, 4, 16]; + + // Transform block height in unit + private static readonly int[] HighUnit = [1, 2, 4, 8, 16, 2, 1, 4, 2, 8, 4, 16, 8, 4, 1, 8, 2, 16, 4]; public static int GetScale(this Av1TransformSize size) { @@ -21,4 +49,10 @@ internal static class Av1TransformSizeExtensions public static int GetWidthLog2(this Av1TransformSize size) => (int)size; public static int GetHeightLog2(this Av1TransformSize size) => (int)size; + + public static int Get4x4WideCount(this Av1TransformSize size) => WideUnit[(int)size]; + + public static int Get4x4HighCount(this Av1TransformSize size) => HighUnit[(int)size]; + + public static Av1TransformSize GetSubSize(this Av1TransformSize size) => SubTransformSize[(int)size]; }