diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index f379852d9f..21c6299a99 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -45,11 +45,13 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; private readonly Configuration configuration; private Av1SymbolReader reader; + private readonly int baseQIndex; public Av1SymbolDecoder(Configuration configuration, Span tileData, int qIndex) { this.configuration = configuration; this.reader = new Av1SymbolReader(tileData); + this.baseQIndex = qIndex; this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex); this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex); this.baseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex); @@ -209,7 +211,7 @@ internal ref struct Av1SymbolDecoder // 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); + Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSize, useReducedTransformSet) > 1 && baseQIndex > 0) { int extendedSet = Av1SymbolContextHelper.GetExtendedTransformSet(transformSize, useReducedTransformSet); @@ -224,7 +226,7 @@ internal ref struct Av1SymbolDecoder : 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]; + transformType = (Av1TransformType)ExtendedTransformIndicesInverse[(int)transformSetType][symbol]; } return transformType; @@ -304,6 +306,11 @@ internal ref struct Av1SymbolDecoder return 0; } + if (plane == (int)Av1Plane.Y) + { + this.ReadTransformType(transformSize, useReducedTransformSet, modeInfo.FilterIntraModeInfo.UseFilterIntra, this.baseQIndex, modeInfo.FilterIntraModeInfo.Mode, modeInfo.YMode); + } + transformInfo.Type = ComputeTransformType(planeType, modeInfo, isLossless, transformSize, transformInfo, useReducedTransformSet); Av1TransformClass transformClass = transformInfo.Type.ToClass(); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type); @@ -330,7 +337,7 @@ internal ref struct Av1SymbolDecoder } DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); - culLevel = this.ReadCoefficientsDc(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); + culLevel = this.ReadCoefficientsSign(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge); transformInfo.CodeBlockFlag = true; @@ -438,7 +445,7 @@ internal ref struct Av1SymbolDecoder } } - public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) + public int ReadCoefficientsSign(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) { int maxScanLine = 0; int culLevel = 0; @@ -521,7 +528,7 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); } - private int ReadGolomb() + internal int ReadGolomb() { int x = 1; int length = 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 7889a7ea57..d0e669bfba 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -33,6 +33,7 @@ internal class Av1SymbolEncoder : IDisposable private bool isDisposed; private readonly Configuration configuration; private Av1SymbolWriter writer; + private readonly int baseQIndex; public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex) { @@ -45,6 +46,7 @@ internal class Av1SymbolEncoder : IDisposable this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); this.configuration = configuration; this.writer = new(configuration, initialSize); + this.baseQIndex = qIndex; } public void WriteUseIntraBlockCopy(bool value) @@ -81,14 +83,12 @@ internal class Av1SymbolEncoder : IDisposable public int WriteCoefficients( Av1TransformSize transformSize, Av1TransformType transformType, - int txbIndex, // TODO: Doesn't seem to be used, remove. Av1PredictionMode intraDirection, Span coefficientBuffer, Av1ComponentType componentType, Av1TransformBlockContext transformBlockContext, - ushort eob, + ushort endOfBlock, bool useReducedTransformSet, - int baseQIndex, Av1FilterIntraMode filterIntraMode) { int c; @@ -107,9 +107,9 @@ internal class Av1SymbolEncoder : IDisposable Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); - this.WriteTransformBlockSkip(eob == 0, transformSizeContext, transformBlockContext.SkipContext); + this.WriteTransformBlockSkip(endOfBlock == 0, transformSizeContext, transformBlockContext.SkipContext); - if (eob == 0) + if (endOfBlock == 0) { return 0; } @@ -117,42 +117,27 @@ internal class Av1SymbolEncoder : IDisposable levels.Initialize(coefficientBuffer); if (componentType == Av1ComponentType.Luminance) { - this.WriteTransformType(transformType, transformSize, useReducedTransformSet, baseQIndex, filterIntraMode, intraDirection); + this.WriteTransformType(transformType, transformSize, useReducedTransformSet, this.baseQIndex, filterIntraMode, intraDirection); } - short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(eob, out int eobExtra); - this.WriteEndOfBlockFlag(componentType, transformClass, transformSize, endOfBlockPosition); + this.WriteEndOfBlockPosition(endOfBlock, componentType, transformClass, transformSize, transformSizeContext); - int eobOffsetBitCount = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPosition]; - if (eobOffsetBitCount > 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 < eobOffsetBitCount; i++) - { - eobShift = eobOffsetBitCount - 1 - i; - bit = (eobExtra & (1 << eobShift)) != 0 ? 1 : 0; - w.WriteLiteral((uint)bit, 1); - } - } - - Av1SymbolContextHelper.GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coefficientContexts); + Av1SymbolContextHelper.GetNzMapContexts(levels, scan, endOfBlock, transformSize, transformClass, coefficientContexts); int limitedTransformSizeContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); - for (c = eob - 1; c >= 0; --c) + for (c = endOfBlock - 1; c >= 0; --c) { short pos = scan[c]; int v = coefficientBuffer[pos]; - short coeff_ctx = coefficientContexts[pos]; + short coeffContext = coefficientContexts[pos]; int level = Math.Abs(v); - if (c == eob - 1) + if (c == endOfBlock - 1) { - w.WriteSymbol(Math.Min(level, 3) - 1, this.coefficientsBaseEndOfBlock[(int)transformSizeContext][(int)componentType][coeff_ctx]); + w.WriteSymbol(Math.Min(level, 3) - 1, this.coefficientsBaseEndOfBlock[(int)transformSizeContext][(int)componentType][coeffContext]); } else { - w.WriteSymbol(Math.Min(level, 3), this.coefficientsBase[(int)transformSizeContext][(int)componentType][coeff_ctx]); + w.WriteSymbol(Math.Min(level, 3), this.coefficientsBase[(int)transformSizeContext][(int)componentType][coeffContext]); } if (level > Av1Constants.BaseLevelsCount) @@ -175,7 +160,7 @@ internal class Av1SymbolEncoder : IDisposable // Loop to code all signs in the transform block, // starting with the sign of DC (if applicable) int cul_level = 0; - for (c = 0; c < eob; ++c) + for (c = 0; c < endOfBlock; ++c) { short pos = scan[c]; int v = coefficientBuffer[pos]; @@ -194,7 +179,7 @@ internal class Av1SymbolEncoder : IDisposable w.WriteLiteral(sign, 1); } - if (level > Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount) + if (level > (Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount)) { this.WriteGolomb(level - Av1Constants.CoefficientBaseRange - 1 - Av1Constants.BaseLevelsCount); } @@ -208,6 +193,27 @@ internal class Av1SymbolEncoder : IDisposable return cul_level; } + internal void WriteEndOfBlockPosition(ushort endOfBlock, Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, Av1TransformSize transformSizeContext) + { + short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(endOfBlock, out int eobExtra); + this.WriteEndOfBlockFlag(componentType, transformClass, transformSize, endOfBlockPosition); + + int eobOffsetBitCount = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPosition]; + if (eobOffsetBitCount > 0) + { + ref Av1SymbolWriter w = ref this.writer; + int eobShift = eobOffsetBitCount - 1; + uint bit = (eobExtra & (1 << eobShift)) != 0 ? 1u : 0u; + w.WriteSymbol((int)bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockPosition]); + for (int i = 1; i < eobOffsetBitCount; i++) + { + eobShift = eobOffsetBitCount - 1 - i; + bit = (eobExtra & (1 << eobShift)) != 0 ? 1u : 0u; + w.WriteLiteral(bit, 1); + } + } + } + internal void WriteTransformBlockSkip(bool skip, Av1TransformSize transformSizeContext, int skipContext) { ref Av1SymbolWriter w = ref this.writer; @@ -232,23 +238,22 @@ internal class Av1SymbolEncoder : IDisposable /// /// SVT: write_golomb /// - private void WriteGolomb(int level) + internal void WriteGolomb(int level) { - int x = level + 1; - int i = x; - int length = (int)Av1Math.Log2_32((uint)x) + 1; + uint x = (uint)level + 1u; + int length = (int)Av1Math.Log2_32(x) + 1; Guard.MustBeGreaterThan(length, 0, nameof(length)); ref Av1SymbolWriter w = ref this.writer; - for (i = 0; i < length - 1; ++i) + for (int i = 0; i < length - 1; ++i) { - w.WriteLiteral(0, 1); + w.WriteLiteral(0u, 1); } for (int j = length - 1; j >= 0; --j) { - w.WriteLiteral((uint)((x >> j) & 0x01), 1); + w.WriteLiteral((x >> j) & 0x01, 1); } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index 5e78a30405..43baea8c46 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using Microsoft.VisualBasic; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -21,7 +20,6 @@ public class Av1CoefficientsEntropyTests public void RoundTripZeroEndOfBlock() { // Assign - const int txbIndex = 0; Av1BlockSize blockSize = Av1BlockSize.Block4x4; Av1TransformSize transformSize = Av1TransformSize.Size4x4; Av1TransformType transformType = Av1TransformType.Identity; @@ -36,12 +34,12 @@ public class Av1CoefficientsEntropyTests 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 coefficientsBuffer = [1, 2, 3, 4, 5]; Span expected = new int[16]; Span actuals = new int[16]; // Act - encoder.WriteCoefficients(transformSize, transformType, txbIndex, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, BaseQIndex, filterIntraMode); + encoder.WriteCoefficients(transformSize, transformType, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, filterIntraMode); using IMemoryOwner encoded = encoder.Exit(); @@ -53,11 +51,10 @@ public class Av1CoefficientsEntropyTests Assert.Equal(expected, actuals); } - // [Fact] + [Fact] public void RoundTripFullBlock() { // Assign - const int txbIndex = 0; const Av1BlockSize blockSize = Av1BlockSize.Block4x4; const Av1TransformSize transformSize = Av1TransformSize.Size4x4; const Av1TransformType transformType = Av1TransformType.Identity; @@ -76,13 +73,14 @@ public class Av1CoefficientsEntropyTests Span actuals = new int[16]; // Act - encoder.WriteCoefficients(transformSize, transformType, txbIndex, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, BaseQIndex, filterIntraMode); + encoder.WriteCoefficients(transformSize, transformType, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, 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); + int plane = Math.Min((int)componentType, 1); + 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); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs index d15f22c40b..cabcc7ae4c 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs @@ -357,6 +357,67 @@ public class Av1EntropyTests Assert.Equal(values, actuals); } + [Fact] + public void RoundTripEndOfBlockPosition() + { + // Assign + const Av1TransformSize transformSize = Av1TransformSize.Size4x4; + const Av1TransformSize transformSizeContext = Av1TransformSize.Size4x4; + const Av1ComponentType componentType = Av1ComponentType.Luminance; + const Av1PlaneType planeType = Av1PlaneType.Y; + const Av1TransformClass transformClass = Av1TransformClass.Class2D; + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + + ushort[] values = [1, 2, 3, 4, 5]; + int[] actuals = new int[values.Length]; + + // Act + foreach (ushort value in values) + { + encoder.WriteEndOfBlockPosition(value, componentType, transformClass, transformSize, transformSizeContext); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); + } + + // Assert + Assert.Equal(values.Select(x => (int)x).ToArray(), actuals); + } + + [Fact] + public void RoundTripGolomb() + { + // Assign + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + + int[] values = Enumerable.Range(0, 16384).ToArray(); + int[] actuals = new int[values.Length]; + + // Act + foreach (int value in values) + { + encoder.WriteGolomb(value); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadGolomb(); + } + + // Assert + Assert.Equal(values, actuals); + } + [Fact] public void RoundTripUseIntraBlockCopy() {