Browse Source

More symbol round trip tests

pull/2633/head
Ynse Hoornenborg 1 year ago
parent
commit
99fd1419ad
  1. 17
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
  2. 79
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
  3. 14
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs
  4. 61
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs

17
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<byte> 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<int> coefficientBuffer, int endOfBlock, ReadOnlySpan<short> scan, int blockWidthLog2, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType)
public int ReadCoefficientsSign(Span<int> coefficientBuffer, int endOfBlock, ReadOnlySpan<short> 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;

79
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<int> 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
/// <summary>
/// SVT: write_golomb
/// </summary>
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);
}
}

14
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<int> coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
Span<int> coefficientsBuffer = [1, 2, 3, 4, 5];
Span<int> expected = new int[16];
Span<int> 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<byte> 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<int> 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<byte> 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);

61
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<byte> 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<byte> 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()
{

Loading…
Cancel
Save