From 7edc7dc9f677eea79fe5accbfcd6ced61a2406df Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Nov 2024 19:35:10 +0100 Subject: [PATCH] Unit test for entropy round trips --- .../Av1/Entropy/Av1DefaultDistributions.cs | 3 +- .../Av1/Entropy/Av1SymbolContextHelper.cs | 9 ++ .../Heif/Av1/Entropy/Av1SymbolDecoder.cs | 54 ++++++++++- .../Heif/Av1/Entropy/Av1SymbolEncoder.cs | 96 +++++++++---------- .../Heif/Av1/Entropy/Av1SymbolWriter.cs | 3 + .../Formats/Heif/Av1/Av1BitStreamTests.cs | 6 +- .../Heif/Av1/Av1CoefficientsEntropyTests.cs | 42 +++++++- .../Formats/Heif/Av1/Av1EntropyTests.cs | 78 +++++++++++++-- .../Formats/Heif/Av1/Av1TilingTests.cs | 2 +- 9 files changed, 223 insertions(+), 70 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs index 6e03564721..a4eb06bcb3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal static class Av1DefaultDistributions @@ -1610,6 +1608,7 @@ internal static class Av1DefaultDistributions ], ]; + // SVT: av1_default_txb_skip_cdfs private static Av1Distribution[][][] TransformBlockSkip => [ [ diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs index ea321205f3..6bcca093ac 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs @@ -9,6 +9,15 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal static class Av1SymbolContextHelper { + public static readonly int[][] ExtendedTransformIndices = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 3, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 5, 6, 4, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0], + [3, 4, 5, 8, 6, 7, 9, 10, 11, 0, 1, 2, 0, 0, 0, 0], + [7, 8, 9, 12, 10, 11, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6], + ]; + public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; private static readonly int[] TransformCountInSet = [1, 2, 5, 7, 12, 16]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index efb49c07f4..f379852d9f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -9,6 +9,15 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal ref struct Av1SymbolDecoder { + private static readonly int[][] ExtendedTransformIndicesInverse = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [9, 0, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [9, 0, 10, 11, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [9, 10, 11, 0, 1, 2, 4, 5, 3, 6, 7, 8, 0, 0, 0, 0], + [9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 4, 5, 3, 6, 7, 8], + ]; + private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5]; @@ -33,7 +42,8 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[][][] endOfBlockExtra; private readonly Av1Distribution chromeForLumaSign = Av1DefaultDistributions.ChromeForLumaSign; private readonly Av1Distribution[] chromeForLumaAlpha = Av1DefaultDistributions.ChromeForLumaAlpha; - private Configuration configuration; + private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; + private readonly Configuration configuration; private Av1SymbolReader reader; public Av1SymbolDecoder(Configuration configuration, Span tileData, int qIndex) @@ -180,6 +190,46 @@ internal ref struct Av1SymbolDecoder return transformSize; } + public Av1TransformType ReadTransformType( + Av1TransformSize transformSize, + bool useReducedTransformSet, + bool useFilterIntra, + int baseQIndex, + Av1FilterIntraMode filterIntraMode, + Av1PredictionMode intraDirection) + { + Av1TransformType transformType = Av1TransformType.DctDct; + + /* + // No need to read transform type if block is skipped. + if (mbmi.Skip || + svt_aom_seg_feature_active(&parse_ctxt->frame_header->segmentation_params, mbmi->segment_id, SEG_LVL_SKIP)) + return; + */ + + // Ignoring INTER blocks here, as these should not end up here. + // int inter_block = is_inter_block_dec(mbmi); + Av1TransformSetType tx_set_type = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); + if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSize, useReducedTransformSet) > 1 && baseQIndex > 0) + { + int extendedSet = Av1SymbolContextHelper.GetExtendedTransformSet(transformSize, useReducedTransformSet); + + // eset == 0 should correspond to a set with only DCT_DCT and + // there is no need to read the tx_type + Guard.IsFalse(extendedSet == 0, nameof(extendedSet), string.Empty); + + Av1TransformSize squareTransformSize = transformSize.GetSquareSize(); + Av1PredictionMode intraMode = useFilterIntra + ? filterIntraMode.ToIntraDirection() + : intraDirection; + ref Av1SymbolReader r = ref this.reader; + int symbol = r.ReadSymbol(this.intraExtendedTransform[extendedSet][(int)squareTransformSize][(int)intraMode]); + transformType = (Av1TransformType)ExtendedTransformIndicesInverse[(int)tx_set_type][symbol]; + } + + return transformType; + } + public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext) { ref Av1SymbolReader r = ref this.reader; @@ -233,7 +283,7 @@ 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 = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); int culLevel = 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index ac442eb3bf..7889a7ea57 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -22,7 +22,7 @@ internal class Av1SymbolEncoder : IDisposable private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; - private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; + private readonly Av1Distribution[][] transformBlockSkip; private readonly Av1Distribution[][][] endOfBlockFlag; private readonly Av1Distribution[][][] coefficientsBaseRange; private readonly Av1Distribution[][][] coefficientsBase; @@ -36,6 +36,7 @@ internal class Av1SymbolEncoder : IDisposable public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex) { + this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex); this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex); this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex); this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex); @@ -49,7 +50,7 @@ internal class Av1SymbolEncoder : IDisposable public void WriteUseIntraBlockCopy(bool value) { ref Av1SymbolWriter w = ref this.writer; - w.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy); + w.WriteSymbol(value, this.tileIntraBlockCopy); } public void WritePartitionType(Av1PartitionType partitionType, int context) @@ -82,10 +83,9 @@ internal class Av1SymbolEncoder : IDisposable Av1TransformType transformType, int txbIndex, // TODO: Doesn't seem to be used, remove. Av1PredictionMode intraDirection, - Span coeffBuffer, + Span coefficientBuffer, Av1ComponentType componentType, - short transformBlockSkipContext, - short dcSignContext, + Av1TransformBlockContext transformBlockContext, ushort eob, bool useReducedTransformSet, int baseQIndex, @@ -97,54 +97,53 @@ internal class Av1SymbolEncoder : IDisposable Av1TransformClass transformClass = transformType.ToClass(); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); ReadOnlySpan scan = scanOrder.Scan; - int bwl = transformSize.GetBlockWidthLog2(); + int blockWidthLog2 = transformSize.GetBlockWidthLog2(); Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); ref Av1SymbolWriter w = ref this.writer; Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); - Span coeff_contexts = new sbyte[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; + Span coefficientContexts = new sbyte[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); - bool hasEndOfBlock = eob != 0; - this.WriteSkip(!hasEndOfBlock, transformBlockSkipContext); + this.WriteTransformBlockSkip(eob == 0, transformSizeContext, transformBlockContext.SkipContext); if (eob == 0) { return 0; } - levels.Initialize(coeffBuffer); + levels.Initialize(coefficientBuffer); if (componentType == Av1ComponentType.Luminance) { this.WriteTransformType(transformType, transformSize, useReducedTransformSet, baseQIndex, filterIntraMode, intraDirection); } - short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(eob, out int eob_extra); + short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(eob, out int eobExtra); this.WriteEndOfBlockFlag(componentType, transformClass, transformSize, endOfBlockPosition); - int eob_offset_bits = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPosition]; - if (eob_offset_bits > 0) + int eobOffsetBitCount = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPosition]; + if (eobOffsetBitCount > 0) { - int eob_shift = eob_offset_bits - 1; - int bit = (eob_extra & (1 << eob_shift)) != 0 ? 1 : 0; + int eobShift = eobOffsetBitCount - 1; + int bit = (eobExtra & (1 << eobShift)) != 0 ? 1 : 0; w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockPosition]); - for (int i = 1; i < eob_offset_bits; i++) + for (int i = 1; i < eobOffsetBitCount; i++) { - eob_shift = eob_offset_bits - 1 - i; - bit = (eob_extra & (1 << eob_shift)) != 0 ? 1 : 0; + eobShift = eobOffsetBitCount - 1 - i; + bit = (eobExtra & (1 << eobShift)) != 0 ? 1 : 0; w.WriteLiteral((uint)bit, 1); } } - Av1SymbolContextHelper.GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coeff_contexts); + Av1SymbolContextHelper.GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coefficientContexts); int limitedTransformSizeContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); for (c = eob - 1; c >= 0; --c) { short pos = scan[c]; - int v = coeffBuffer[pos]; - short coeff_ctx = coeff_contexts[pos]; + int v = coefficientBuffer[pos]; + short coeff_ctx = coefficientContexts[pos]; int level = Math.Abs(v); if (c == eob - 1) @@ -160,7 +159,7 @@ internal class Av1SymbolEncoder : IDisposable { // level is above 1. int baseRange = level - 1 - Av1Constants.BaseLevelsCount; - int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, bwl, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, blockWidthLog2, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1); @@ -179,7 +178,7 @@ internal class Av1SymbolEncoder : IDisposable for (c = 0; c < eob; ++c) { short pos = scan[c]; - int v = coeffBuffer[pos]; + int v = coefficientBuffer[pos]; int level = Math.Abs(v); cul_level += level; @@ -188,7 +187,7 @@ internal class Av1SymbolEncoder : IDisposable { if (c == 0) { - w.WriteSymbol((int)sign, this.dcSign[(int)componentType][dcSignContext]); + w.WriteSymbol((int)sign, this.dcSign[(int)componentType][transformBlockContext.DcSignContext]); } else { @@ -205,15 +204,14 @@ internal class Av1SymbolEncoder : IDisposable cul_level = Math.Min(Av1Constants.CoefficientContextMask, cul_level); // DC value - Av1SymbolContextHelper.SetDcSign(ref cul_level, coeffBuffer[0]); + Av1SymbolContextHelper.SetDcSign(ref cul_level, coefficientBuffer[0]); return cul_level; } - private void WriteSkip(bool hasEndOfBlock, int context) + internal void WriteTransformBlockSkip(bool skip, Av1TransformSize transformSizeContext, int skipContext) { - // Has EOB, means we won't skip, negating the logic. ref Av1SymbolWriter w = ref this.writer; - w.WriteSymbol(hasEndOfBlock ? 0 : 1, this.skip[context]); + w.WriteSymbol(skip, this.transformBlockSkip[(int)transformSizeContext][skipContext]); } public IMemoryOwner Exit() @@ -250,7 +248,7 @@ internal class Av1SymbolEncoder : IDisposable for (int j = length - 1; j >= 0; --j) { - w.WriteLiteral((uint)(x >> j & 0x01), 1); + w.WriteLiteral((uint)((x >> j) & 0x01), 1); } } @@ -265,7 +263,7 @@ internal class Av1SymbolEncoder : IDisposable /// /// SVT: av1_write_tx_type /// - private void WriteTransformType( + internal void WriteTransformType( Av1TransformType transformType, Av1TransformSize transformSize, bool useReducedTransformSet, @@ -277,34 +275,32 @@ internal class Av1SymbolEncoder : IDisposable ref Av1SymbolWriter w = ref this.writer; if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSize, useReducedTransformSet) > 1 && baseQIndex > 0) { - Av1TransformSize square_tx_size = transformSize.GetSquareSize(); - Guard.MustBeLessThanOrEqualTo((int)square_tx_size, Av1Constants.ExtendedTransformCount, nameof(square_tx_size)); + Av1TransformSize squareTransformSize = transformSize.GetSquareSize(); + Guard.MustBeLessThanOrEqualTo((int)squareTransformSize, Av1Constants.ExtendedTransformCount, nameof(squareTransformSize)); - Av1TransformSetType tx_set_type = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); - int eset = Av1SymbolContextHelper.GetExtendedTransformSet(transformSize, useReducedTransformSet); + Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); + int extendedSet = Av1SymbolContextHelper.GetExtendedTransformSet(transformSize, useReducedTransformSet); // eset == 0 should correspond to a set with only DCT_DCT and there // is no need to send the tx_type - Guard.MustBeGreaterThan(eset, 0, nameof(eset)); + Guard.MustBeGreaterThan(extendedSet, 0, nameof(extendedSet)); // assert(av1_ext_tx_used[tx_set_type][transformType]); + Av1PredictionMode intraMode; + if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) { - Av1PredictionMode intra_dir; - if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) - { - intra_dir = filterIntraMode.ToIntraDirection(); - } - else - { - intra_dir = intraDirection; - } - - Guard.MustBeLessThan((int)intra_dir, 13, nameof(intra_dir)); - Guard.MustBeLessThan((int)square_tx_size, 4, nameof(square_tx_size)); - w.WriteSymbol( - ExtendedTransformIndices[(int)tx_set_type][(int)transformType], - this.intraExtendedTransform[eset][(int)square_tx_size][(int)intra_dir]); + intraMode = filterIntraMode.ToIntraDirection(); } + else + { + intraMode = intraDirection; + } + + Guard.MustBeLessThan((int)intraMode, 13, nameof(intraMode)); + Guard.MustBeLessThan((int)squareTransformSize, 4, nameof(squareTransformSize)); + w.WriteSymbol( + ExtendedTransformIndices[(int)transformSetType][(int)transformType], + this.intraExtendedTransform[extendedSet][(int)squareTransformSize][(int)intraMode]); } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs index fef565bbe4..c5e31ded60 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs @@ -25,6 +25,9 @@ internal class Av1SymbolWriter : IDisposable public void Dispose() => this.memory.Dispose(); + public void WriteSymbol(bool symbol, Av1Distribution distribution) + => this.WriteSymbol(symbol ? 1 : 0, distribution); + public void WriteSymbol(int symbol, Av1Distribution distribution) { DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol)); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 94d442b12d..d412f9fdd5 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -95,9 +95,9 @@ public class Av1BitStreamTests } [Theory] - //[InlineData(6, 4)] - //[InlineData(42, 8)] - //[InlineData(52, 8)] + [InlineData(6, 4)] + [InlineData(42, 8)] + [InlineData(52, 8)] [InlineData(4050, 16)] public void WriteAsLiteral(uint value, int bitCount) { diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index 3be90137a5..5e78a30405 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -21,8 +21,6 @@ public class Av1CoefficientsEntropyTests public void RoundTripZeroEndOfBlock() { // Assign - const short transformBlockSkipContext = 0; - const short dcSignContext = 0; const int txbIndex = 0; Av1BlockSize blockSize = Av1BlockSize.Block4x4; Av1TransformSize transformSize = Av1TransformSize.Size4x4; @@ -30,7 +28,7 @@ public class Av1CoefficientsEntropyTests Av1PredictionMode intraDirection = Av1PredictionMode.DC; Av1ComponentType componentType = Av1ComponentType.Luminance; Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.DC; - ushort endOfBlock = 16; + ushort endOfBlock = 0; Av1BlockModeInfo modeInfo = new(Av1Constants.MaxPlanes, blockSize, new Point(0, 0)); Av1TransformInfo transformInfo = new(transformSize, 0, 0); int[] aboveContexts = new int[1]; @@ -43,11 +41,11 @@ public class Av1CoefficientsEntropyTests Span actuals = new int[16]; // Act - encoder.WriteCoefficients(transformSize, transformType, txbIndex, intraDirection, coefficientsBuffer, componentType, transformBlockSkipContext, dcSignContext, endOfBlock, true, BaseQIndex, filterIntraMode); + encoder.WriteCoefficients(transformSize, transformType, txbIndex, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, BaseQIndex, filterIntraMode); using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); Av1SymbolReader reader = new(encoded.GetSpan()); decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, 0, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); @@ -55,4 +53,38 @@ public class Av1CoefficientsEntropyTests Assert.Equal(expected, actuals); } + // [Fact] + public void RoundTripFullBlock() + { + // Assign + const int txbIndex = 0; + const Av1BlockSize blockSize = Av1BlockSize.Block4x4; + const Av1TransformSize transformSize = Av1TransformSize.Size4x4; + const Av1TransformType transformType = Av1TransformType.Identity; + const Av1PredictionMode intraDirection = Av1PredictionMode.DC; + const Av1ComponentType componentType = Av1ComponentType.Luminance; + const Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.DC; + const ushort endOfBlock = 16; + Av1BlockModeInfo modeInfo = new(Av1Constants.MaxPlanes, blockSize, new Point(0, 0)); + Av1TransformInfo transformInfo = new(transformSize, 0, 0); + int[] aboveContexts = new int[1]; + int[] leftContexts = new int[1]; + Av1TransformBlockContext transformBlockContext = new(); + 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]; + + // Act + encoder.WriteCoefficients(transformSize, transformType, txbIndex, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, BaseQIndex, filterIntraMode); + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + Av1SymbolReader reader = new(encoded.GetSpan()); + decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, (int)componentType, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); + + // Assert + Assert.Equal(coefficientsBuffer, actuals); + } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs index a23acfc046..d15f22c40b 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs @@ -4,6 +4,9 @@ using System.Buffers; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -208,8 +211,7 @@ public class Av1EntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); - Av1SymbolReader reader = new(encoded.GetSpan()); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadPartitionType(ctx); @@ -245,8 +247,7 @@ public class Av1EntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); - Av1SymbolReader reader = new(encoded.GetSpan()); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadSplitOrHorizontal((Av1BlockSize)blockSize, context); @@ -282,8 +283,7 @@ public class Av1EntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); - Av1SymbolReader reader = new(encoded.GetSpan()); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadSplitOrVertical((Av1BlockSize)blockSize, context); @@ -293,6 +293,70 @@ public class Av1EntropyTests Assert.Equal(values, actuals); } + [Fact] + public void RoundTripTransformBlockSkip() + { + // Assign + const Av1TransformSize transformSizeContext = Av1TransformSize.Size4x4; + const int skipContext = 0; + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + bool[] values = [true, true, false, false, false, false, false, false, true]; + bool[] actuals = new bool[values.Length]; + + // Act + foreach (bool value in values) + { + encoder.WriteTransformBlockSkip(value, transformSizeContext, skipContext); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadTransformBlockSkip(transformSizeContext, skipContext); + } + + // Assert + Assert.Equal(values, actuals); + } + + [Fact] + public void RoundTripTransformType() + { + // Assign + const Av1TransformSize transformSizeContext = Av1TransformSize.Size4x4; + const Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.DC; + const Av1PredictionMode intraDirection = Av1PredictionMode.DC; + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + + // TODO: Include AdstFlipAdst, which is currently mapped to Identity. + Av1TransformType[] values = [ + Av1TransformType.DctDct, Av1TransformType.DctDct, Av1TransformType.Identity, Av1TransformType.AdstDct, + Av1TransformType.DctDct, Av1TransformType.AdstAdst, Av1TransformType.Identity, Av1TransformType.DctAdst + ]; + Av1TransformType[] actuals = new Av1TransformType[values.Length]; + + // Act + foreach (Av1TransformType value in values) + { + encoder.WriteTransformType(value, transformSizeContext, true, BaseQIndex, filterIntraMode, intraDirection); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadTransformType(transformSizeContext, true, false, BaseQIndex, filterIntraMode, intraDirection); + } + + // Assert + Assert.Equal(values, actuals); + } + [Fact] public void RoundTripUseIntraBlockCopy() { @@ -310,7 +374,7 @@ public class Av1EntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadUseIntraBlockCopy(); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index fe756dd7c3..1464673777 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -38,7 +38,7 @@ public class Av1TilingTests } [Theory] - // [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 16)] + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 16)] [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d, 21, 1)] public void DecodeFirstTile(string filename, int dataOffset, int dataSize, int tileOffset, int superblockCount) {