From 41a11fd3392c5cd9d61eace70e2c33d1f22ed574 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 30 Nov 2024 11:34:31 +0100 Subject: [PATCH] Refactor position inside transform block --- .../Formats/Heif/Av1/Entropy/Av1NzMap.cs | 32 ++--- .../Av1/Entropy/Av1SymbolContextHelper.cs | 131 +++++++++--------- .../Heif/Av1/Entropy/Av1SymbolDecoder.cs | 57 ++++---- .../Heif/Av1/Entropy/Av1SymbolEncoder.cs | 7 +- .../Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs | 17 ++- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 2 +- .../Heif/Av1/Transform/Av1TransformSize.cs | 40 +++--- .../Heif/Av1/Av1CoefficientsEntropyTests.cs | 6 +- .../Formats/Heif/Av1/Av1LevelBufferTests.cs | 91 ++++++++++++ 9 files changed, 238 insertions(+), 145 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1LevelBufferTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs index da2c05f20a..e92a4a6507 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs @@ -286,18 +286,12 @@ internal static class Av1NzMap /// /// SVT: get_nz_mag /// - public static int GetNzMagnitude(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) - => GetNzMagnitude(levels, index >> blockWidthLog2, transformClass); - - /// - /// SVT: get_nz_mag - /// - public static int GetNzMagnitude(Av1LevelBuffer levels, int y, Av1TransformClass transformClass) + public static int GetNzMagnitude(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass) { int mag; - Span row0 = levels.GetRow(y); - Span row1 = levels.GetRow(y + 1); - Span row2 = levels.GetRow(y + 2); + Span row0 = levels.GetRow(position.Y); + Span row1 = levels.GetRow(position.Y + 1); + Span row2 = levels.GetRow(position.Y + 2); // Note: AOMMIN(level, 3) is useless for decoder since level < 3. mag = ClipMax3[row0[1]]; // { 0, 1 } @@ -312,8 +306,8 @@ internal static class Av1NzMap break; case Av1TransformClass.ClassVertical: - Span row3 = levels.GetRow(y + 3); - Span row4 = levels.GetRow(y + 4); + Span row3 = levels.GetRow(position.Y + 3); + Span row4 = levels.GetRow(position.Y + 4); mag += ClipMax3[row2[0]]; // { 2, 0 } mag += ClipMax3[row3[0]]; // { 3, 0 } mag += ClipMax3[row4[0]]; // { 4, 0 } @@ -328,10 +322,10 @@ internal static class Av1NzMap return mag; } - public static int GetNzMapContextFromStats(int stats, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) + public static int GetNzMapContextFromStats(int stats, Av1LevelBuffer levels, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass) { // tx_class == 0(TX_CLASS_2D) - if (((int)transformClass | pos) == 0) + if (position.Y == 0 && ((int)transformClass | position.X) == 0) { return 0; } @@ -352,14 +346,12 @@ internal static class Av1NzMap // if (row + col < 2) return ctx + 1; // if (row + col < 4) return 5 + ctx + 1; // return 21 + ctx; - return ctx + NzMapContextOffset[(int)transformSize][pos]; + int index = position.X + (levels.Size.Width * position.Y); + return ctx + NzMapContextOffset[(int)transformSize][index]; case Av1TransformClass.ClassHorizontal: - int row = pos >> bwl; - int col = pos - (row << bwl); - return ctx + NzMapContextOffset1d[col]; + return ctx + NzMapContextOffset1d[position.X]; case Av1TransformClass.ClassVertical: - int row2 = pos >> bwl; - return ctx + NzMapContextOffset1d[row2]; + return ctx + NzMapContextOffset1d[position.Y]; default: break; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs index 6bcca093ac..6712036e76 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs @@ -52,6 +52,9 @@ internal static class Av1SymbolContextHelper // Maps tx set types to the indices. INTRA values only private static readonly int[] ExtendedTransformSetToIndex = [0, -1, 2, 1, -1, -1]; + internal static Av1TransformSize GetTransformSizeContext(Av1TransformSize originalSize) + => (Av1TransformSize)(((int)originalSize.GetSquareSize() + (int)originalSize.GetSquareUpSize() + 1) >> 1); + internal static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) { int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; @@ -63,18 +66,16 @@ internal static class Av1SymbolContextHelper return endOfBlock; } - internal static int GetBaseRangeContextEndOfBlock(int index, int blockWidthLog2, Av1TransformClass transformClass) + internal static int GetBaseRangeContextEndOfBlock(Point pos, Av1TransformClass transformClass) { - int row = index >> blockWidthLog2; - int col = index - (row << blockWidthLog2); - if (index == 0) + if (pos.X == 0 && pos.Y == 0) { return 0; } - if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || - (transformClass == Av1TransformClass.ClassHorizontal && col == 0) || - (transformClass == Av1TransformClass.ClassVertical && row == 0)) + if ((transformClass == Av1TransformClass.Class2D && pos.Y < 2 && pos.X < 2) || + (transformClass == Av1TransformClass.ClassHorizontal && pos.X == 0) || + (transformClass == Av1TransformClass.ClassVertical && pos.Y == 0)) { return 7; } @@ -82,6 +83,31 @@ internal static class Av1SymbolContextHelper return 14; } + /// + /// SVT: get_lower_levels_ctx_eob + /// + internal static int GetLowerLevelContextEndOfBlock(Av1LevelBuffer levels, Point position) + { + if (position.X == 0 && position.Y == 0) + { + return 0; + } + + int total = levels.Size.Height * levels.Size.Width; + int index = position.X + (position.Y * levels.Size.Width); + if (index <= total >> 3) + { + return 1; + } + + if (index <= total >> 2) + { + return 2; + } + + return 3; + } + /// /// SVT: get_lower_levels_ctx_eob /// @@ -108,21 +134,17 @@ internal static class Av1SymbolContextHelper /// /// SVT: get_br_ctx_2d /// - internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, int index, int blockWidthLog2) + internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, Point position) { - DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); - int y = index >> blockWidthLog2; - int x = index - (y << blockWidthLog2); - int stride = (1 << blockWidthLog2) + Av1Constants.TransformPadHorizontal; - int pos = (y * stride) + x; - Span row0 = levels.GetRow(y); - Span row1 = levels.GetRow(y + 1); + DebugGuard.MustBeGreaterThan(position.X + position.Y, 0, nameof(position)); + Span row0 = levels.GetRow(position.Y); + Span row1 = levels.GetRow(position.Y + 1); int mag = Math.Min((int)row0[1], Av1Constants.MaxBaseRange) + Math.Min((int)row1[0], Av1Constants.MaxBaseRange) + Math.Min((int)row1[1], Av1Constants.MaxBaseRange); mag = Math.Min((mag + 1) >> 1, 6); - if ((y | x) < 2) + if ((position.Y | position.X) < 2) { return mag + 7; } @@ -133,15 +155,13 @@ internal static class Av1SymbolContextHelper /// /// SVT: get_lower_levels_ctx_2d /// - internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, int index, int blockWidthLog2, Av1TransformSize transformSize) + internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, Point pos, Av1TransformSize transformSize) { - DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); - int y = index >> blockWidthLog2; - int x = index - (y << blockWidthLog2); + DebugGuard.MustBeGreaterThan(pos.X + pos.Y, 0, nameof(pos)); int mag; - Span row0 = levelBuffer.GetRow(y); - Span row1 = levelBuffer.GetRow(y + 1); - Span row2 = levelBuffer.GetRow(y + 2); + Span row0 = levelBuffer.GetRow(pos.Y); + Span row1 = levelBuffer.GetRow(pos.Y + 1); + Span row2 = levelBuffer.GetRow(pos.Y + 2); mag = Math.Min((int)row0[1], 3); // { 0, 1 } mag += Math.Min((int)row1[0], 3); // { 1, 0 } mag += Math.Min((int)row1[1], 3); // { 1, 1 } @@ -149,31 +169,30 @@ internal static class Av1SymbolContextHelper mag += Math.Min((int)row2[0], 3); // { 2, 0 } int ctx = Math.Min((mag + 1) >> 1, 4); + int index = pos.X + (pos.Y * levelBuffer.Size.Width); return ctx + Av1NzMap.GetNzMapContext(transformSize, index); } /// /// SVT: get_br_ctx /// - internal static int GetBaseRangeContext(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) + internal static int GetBaseRangeContext(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass) { - int y = index >> blockWidthLog2; - int x = index - (y << blockWidthLog2); - Span row0 = levels.GetRow(y); - Span row1 = levels.GetRow(y + 1); - int mag = row0[x + 1]; - mag += row1[x]; + Span row0 = levels.GetRow(position.Y); + Span row1 = levels.GetRow(position.Y + 1); + int mag = row0[position.X + 1]; + mag += row1[position.X]; switch (transformClass) { case Av1TransformClass.Class2D: - mag += row1[x + 1]; + mag += row1[position.X + 1]; mag = Math.Min((mag + 1) >> 1, 6); - if (index == 0) + if ((position.X + position.Y) == 0) { return mag; } - if (y < 2 && x < 2) + if (position.Y < 2 && position.X < 2) { return mag + 7; } @@ -182,26 +201,26 @@ internal static class Av1SymbolContextHelper case Av1TransformClass.ClassHorizontal: mag += row0[2]; mag = Math.Min((mag + 1) >> 1, 6); - if (index == 0) + if ((position.X + position.Y) == 0) { return mag; } - if (x == 0) + if (position.X == 0) { return mag + 7; } break; case Av1TransformClass.ClassVertical: - mag += levels.GetRow(y + 2)[0]; + mag += levels.GetRow(position.Y + 2)[0]; mag = Math.Min((mag + 1) >> 1, 6); - if (index == 0) + if ((position.X + position.Y) == 0) { return mag; } - if (y == 0) + if (position.Y == 0) { return mag + 7; } @@ -214,10 +233,10 @@ internal static class Av1SymbolContextHelper return mag + 14; } - internal static int GetLowerLevelsContext(Av1LevelBuffer levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) + internal static int GetLowerLevelsContext(Av1LevelBuffer levels, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass) { - int stats = Av1NzMap.GetNzMagnitude(levels, pos >> bwl, transformClass); - return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); + int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass); + return Av1NzMap.GetNzMapContextFromStats(stats, levels, position, transformSize, transformClass); } internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) @@ -254,9 +273,7 @@ internal static class Av1SymbolContextHelper /// internal static sbyte GetNzMapContext( Av1LevelBuffer levels, - int index, - int blockWidthLog2, - int height, + Point position, int scan_idx, bool is_eob, Av1TransformSize transformSize, @@ -264,26 +281,11 @@ internal static class Av1SymbolContextHelper { if (is_eob) { - if (scan_idx == 0) - { - return 0; - } - - if (scan_idx <= (height << blockWidthLog2) / 8) - { - return 1; - } - - if (scan_idx <= (height << blockWidthLog2) / 4) - { - return 2; - } - - return 3; + return (sbyte)GetLowerLevelContextEndOfBlock(levels, position); } - int stats = Av1NzMap.GetNzMagnitude(levels, index, blockWidthLog2, transformClass); - return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, index, blockWidthLog2, transformSize, transformClass); + int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass); + return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, levels, position, transformSize, transformClass); } /// @@ -297,12 +299,11 @@ internal static class Av1SymbolContextHelper Av1TransformClass transformClass, Span coefficientContexts) { - int blockWidthLog2 = transformSize.GetBlockWidthLog2(); - int height = transformSize.GetHeight(); for (int i = 0; i < eob; ++i) { int pos = scan[i]; - coefficientContexts[pos] = GetNzMapContext(levels, pos, blockWidthLog2, height, i, i == eob - 1, transformSize, transformClass); + Point position = levels.GetPosition(pos); + coefficientContexts[pos] = GetNzMapContext(levels, position, i, i == eob - 1, transformSize, transformClass); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index 21c6299a99..b421676885 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -259,11 +259,8 @@ internal ref struct Av1SymbolDecoder } /// - /// 5.11.39. Coefficients syntax. + /// SVT: parse_coeffs /// - /// - /// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification. - /// public int ReadCoefficients( Av1BlockModeInfo modeInfo, Point blockPosition, @@ -285,14 +282,13 @@ internal ref struct Av1SymbolDecoder { int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); - Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); + Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); int culLevel = 0; Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); - int bwl = transformSize.GetBlockWidthLog2(); int endOfBlock; if (allZero) { @@ -322,22 +318,24 @@ internal ref struct Av1SymbolDecoder levels.Clear(); } - this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); + this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, scan, levels, transformSizeContext, planeType); if (endOfBlock > 1) { if (transformClass == Av1TransformClass.Class2D) { - this.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); - this.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, bwl, levels, transformSizeContext, planeType); + this.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, levels, transformSizeContext, planeType); + this.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, levels, transformSizeContext, planeType); } else { - this.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + this.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, levels, transformSizeContext, planeType); } } + coefficientBuffer[0] = endOfBlock; + DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); - culLevel = this.ReadCoefficientsSign(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); + culLevel = this.ReadCoefficientsSign(coefficientBuffer, endOfBlock, scan, levels, transformBlockContext.DcSignContext, planeType); UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge); transformInfo.CodeBlockFlag = true; @@ -372,15 +370,15 @@ internal ref struct Av1SymbolDecoder return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); } - public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, ReadOnlySpan scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int i = endOfBlock - 1; - int pos = scan[i]; - int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(blockWidthLog2, height, i); + Point position = levels.GetPosition(scan[i]); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(levels, position); int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(pos, blockWidthLog2, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(position, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++) { int coefficinetBaseRange = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -392,19 +390,19 @@ internal ref struct Av1SymbolDecoder } } - levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; + levels.GetRow(position)[0] = (byte)level; } - public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endScanIndex; c >= startScanIndex; --c) { - int pos = scan[c]; - int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext2d(levels, pos, blockWidthLog2, transformSize); + Point position = levels.GetPosition(scan[c]); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext2d(levels, position, transformSize); int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext2d(levels, pos, blockWidthLog2); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext2d(levels, position); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -416,20 +414,21 @@ internal ref struct Av1SymbolDecoder } } - levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; + levels.GetRow(position)[0] = (byte)level; } } - public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endScanIndex; c >= startScanIndex; --c) { int pos = scan[c]; - int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext(levels, pos, blockWidthLog2, transformSize, transformClass); + Point position = levels.GetPosition(pos); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext(levels, position, transformSize, transformClass); int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, blockWidthLog2, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -441,11 +440,11 @@ internal ref struct Av1SymbolDecoder } } - levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; + levels.GetRow(position)[0] = (byte)level; } } - public int ReadCoefficientsSign(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) + public int ReadCoefficientsSign(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) { int maxScanLine = 0; int culLevel = 0; @@ -454,7 +453,8 @@ internal ref struct Av1SymbolDecoder for (int c = 0; c < endOfBlock; c++) { int sign = 0; - int level = levels.GetPaddedRow(scan[c], blockWidthLog2)[0]; + Point position = levels.GetPosition(scan[c]); + int level = levels.GetRow(position)[0]; if (level != 0) { maxScanLine = Math.Max(maxScanLine, scan[c]); @@ -507,7 +507,8 @@ internal ref struct Av1SymbolDecoder private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) { ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]); + int transformContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); + return r.ReadSymbol(this.coefficientsBaseRange[transformContext][(int)planeType][baseRangeContext]); } private int ReadDcSign(Av1PlaneType planeType, int dcSignContext) diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index d0e669bfba..ab12516e17 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -98,12 +98,12 @@ internal class Av1SymbolEncoder : IDisposable Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); ReadOnlySpan scan = scanOrder.Scan; int blockWidthLog2 = transformSize.GetBlockWidthLog2(); - Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); + Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize); ref Av1SymbolWriter w = ref this.writer; Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); - Span coefficientContexts = new sbyte[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; + Span coefficientContexts = new sbyte[width * height]; Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); @@ -129,6 +129,7 @@ internal class Av1SymbolEncoder : IDisposable short pos = scan[c]; int v = coefficientBuffer[pos]; short coeffContext = coefficientContexts[pos]; + Point position = levels.GetPosition(pos); int level = Math.Abs(v); if (c == endOfBlock - 1) @@ -144,7 +145,7 @@ internal class Av1SymbolEncoder : IDisposable { // level is above 1. int baseRange = level - 1 - Av1Constants.BaseLevelsCount; - int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, blockWidthLog2, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs index 2fa4c03f03..77fd0cc8ba 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; @@ -45,6 +46,16 @@ internal sealed class Av1LevelBuffer : IDisposable } } + public Point GetPosition(int index) + { + int x = index % this.Size.Width; + int y = index / this.Size.Width; + return new Point(x, y); + } + + public Span GetRow(Point pos) + => this.GetRow(pos.Y); + public Span GetRow(int y) { ObjectDisposedException.ThrowIf(this.memory == null, this); @@ -64,10 +75,4 @@ internal sealed class Av1LevelBuffer : IDisposable ObjectDisposedException.ThrowIf(this.memory == null, this); this.memory.Memory.Span.Clear(); } - - internal Span GetPaddedRow(int index, int blockWidthLog2) - { - int y = index >> blockWidthLog2; - return this.GetRow(y); - } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 63827c5e98..d217ff4451 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -545,7 +545,7 @@ internal class Av1TileReader : IAv1TileReader Span coefficientBuffer = this.FrameInfo.GetCoefficients(plane); int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); - Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); + Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); Point blockPosition = new(blockColumn, blockRow); bool isLossless = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs index e812778335..f4af96c5bc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs @@ -5,26 +5,26 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal enum Av1TransformSize : byte { - Size4x4, - Size8x8, - Size16x16, - Size32x32, - Size64x64, - Size4x8, - Size8x4, - Size8x16, - Size16x8, - Size16x32, - Size32x16, - Size32x64, - Size64x32, - Size4x16, - Size16x4, - Size8x32, - Size32x8, - Size16x64, - Size64x16, - AllSizes, + Size4x4 = 0, + Size8x8 = 1, + Size16x16 = 2, + Size32x32 = 3, + Size64x64 = 4, + Size4x8 = 5, + Size8x4 = 6, + Size8x16 = 7, + Size16x8 = 8, + Size16x32 = 9, + Size32x16 = 10, + Size32x64 = 11, + Size64x32 = 12, + Size4x16 = 13, + Size16x4 = 14, + Size8x32 = 15, + Size32x8 = 16, + Size16x64 = 17, + Size64x16 = 18, + AllSizes = 19, SquareSizes = Size4x8, Invalid = 255, } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index 43baea8c46..add6619cf7 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -48,6 +48,7 @@ public class Av1CoefficientsEntropyTests decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, 0, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); // Assert + Assert.Equal(endOfBlock, actuals[0]); Assert.Equal(expected, actuals); } @@ -70,7 +71,7 @@ public class Av1CoefficientsEntropyTests Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Span coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; - Span actuals = new int[16]; + Span actuals = new int[16 + 1]; // Act encoder.WriteCoefficients(transformSize, transformType, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, filterIntraMode); @@ -83,6 +84,7 @@ public class Av1CoefficientsEntropyTests decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, plane, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); // Assert - Assert.Equal(coefficientsBuffer, actuals); + Assert.Equal(endOfBlock, actuals[0]); + Assert.Equal(coefficientsBuffer, actuals[1..]); } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1LevelBufferTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1LevelBufferTests.cs new file mode 100644 index 0000000000..6eabd6c7e2 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1LevelBufferTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1LevelBufferTests +{ + [Theory] + [InlineData(4, 4, 4, 1)] + [InlineData(4, 4, 5, 1)] + [InlineData(4, 4, 6, 1)] + [InlineData(4, 4, 7, 1)] + [InlineData(8, 4, 7, 0)] + [InlineData(8, 8, 16, 2)] + [InlineData(8, 4, 16, 2)] + public void TestGetPaddedRow(int width, int height, int index, byte expected) + { + // Arrange + Size size = new(width, height); + Av1LevelBuffer levels = new(Configuration.Default, size); + for (byte i = 0; i < 4; i++) + { + levels.GetRow(i).Fill(i); + } + + // Act + Point pos = levels.GetPosition(index); + + // Assert + Assert.Equal(expected, pos.Y); + Assert.Equal(expected, levels.GetRow(pos)[0]); + } + + [Theory] + [InlineData(4, 4)] + [InlineData(8, 4)] + [InlineData(8, 8)] + [InlineData(16, 4)] + public void TestGetRow(int width, int height) + { + // Arrange + Size size = new(width, height); + Av1LevelBuffer levels = new(Configuration.Default, size); + for (byte i = 0; i < height; i++) + { + levels.GetRow(i).Fill(i); + } + + for (int j = 0; j < height; j++) + { + // Act + Span actual = levels.GetRow(j); + + // Assert + Assert.Equal(j, actual[0]); + Assert.True(actual.Length >= width); + } + } + + [Theory] + [InlineData(4, 4)] + [InlineData(8, 4)] + [InlineData(8, 8)] + [InlineData(16, 4)] + public void TestClear(int width, int height) + { + // Arrange + Size size = new(width, height); + Av1LevelBuffer levels = new(Configuration.Default, size); + for (byte i = 0; i < height; i++) + { + levels.GetRow(i).Fill(i); + } + + // Act + levels.Clear(); + + // Assert + for (int j = 0; j < height; j++) + { + Span rowSpan = levels.GetRow(j); + for (int k = 0; k < width; k++) + { + Assert.Equal(0, rowSpan[k]); + } + } + } +}