Skip to content

Commit

Permalink
Add coefficients unit test for multiple transform types
Browse files Browse the repository at this point in the history
  • Loading branch information
ynse01 committed Jan 1, 2025
1 parent 7085a28 commit 0b38ce3
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 38 deletions.
36 changes: 14 additions & 22 deletions src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;

internal static class Av1NzMap
{
private static readonly int[] ClipMax3 = [
0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
];

// SIG_COEF_CONTEXTS_2D = 26
private const int NzMapContext0 = 26;
private const int NzMapContext5 = NzMapContext0 + 5;
Expand Down Expand Up @@ -351,35 +341,35 @@ public static int GetNzMagnitude(Av1LevelBuffer levels, Point position, Av1Trans
Span<byte> row2 = levels.GetRow(position.Y + 2)[position.X..];

// Note: AOMMIN(level, 3) is useless for decoder since level < 3.
mag = ClipMax3[row0[1]]; // { 0, 1 }
mag += ClipMax3[row1[0]]; // { 1, 0 }
mag = ClipMax3(row0[1]); // { 0, 1 }
mag += ClipMax3(row1[0]); // { 1, 0 }

switch (transformClass)
{
case Av1TransformClass.Class2D:
mag += ClipMax3[row1[1]]; // { 1, 1 }
mag += ClipMax3[row0[2]]; // { 0, 2 }
mag += ClipMax3[row2[0]]; // { 2, 0 }
mag += ClipMax3(row1[1]); // { 1, 1 }
mag += ClipMax3(row0[2]); // { 0, 2 }
mag += ClipMax3(row2[0]); // { 2, 0 }
break;

case Av1TransformClass.ClassVertical:
Span<byte> row3 = levels.GetRow(position.Y + 3)[position.X..];
Span<byte> row4 = levels.GetRow(position.Y + 4)[position.X..];
mag += ClipMax3[row2[0]]; // { 2, 0 }
mag += ClipMax3[row3[0]]; // { 3, 0 }
mag += ClipMax3[row4[0]]; // { 4, 0 }
mag += ClipMax3(row2[0]); // { 2, 0 }
mag += ClipMax3(row3[0]); // { 3, 0 }
mag += ClipMax3(row4[0]); // { 4, 0 }
break;
case Av1TransformClass.ClassHorizontal:
mag += ClipMax3[row0[2]]; // { 0, 2 }
mag += ClipMax3[row0[3]]; // { 0, 3 }
mag += ClipMax3[row0[4]]; // { 0, 4 }
mag += ClipMax3(row0[2]); // { 0, 2 }
mag += ClipMax3(row0[3]); // { 0, 3 }
mag += ClipMax3(row0[4]); // { 0, 4 }
break;
}

return mag;
}

public static int GetNzMapContextFromStats(int stats, Av1LevelBuffer levels, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
public static int GetNzMapContextFromStats(int stats, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
{
// tx_class == 0(TX_CLASS_2D)
if (position.Y == 0 && ((int)transformClass | position.X) == 0)
Expand Down Expand Up @@ -418,4 +408,6 @@ public static int GetNzMapContextFromStats(int stats, Av1LevelBuffer levels, Poi
public static int GetNzMapContext(Av1TransformSize transformSize, Point pos) => GetNzMapContext(transformSize, pos.X + (pos.Y * transformSize.GetWidth()));

public static int GetNzMapContext(Av1TransformSize transformSize, int pos) => NzMapContextOffset[(int)transformSize][pos];

private static int ClipMax3(int value) => Math.Min(value, 3);
}
17 changes: 11 additions & 6 deletions src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, Point po
return ctx + Av1NzMap.GetNzMapContext(transformSize, position);
}

/// <summary>
/// Section 8.3.2 in the spec, under coeff_br. Optimized for end of block based
/// on the fact that {0, 1}, {1, 0}, {1, 1}, {0, 2} and {2, 0} will all be 0 in
/// the end of block case.
/// </summary>
internal static int GetBaseRangeContextEndOfBlock(Point pos, Av1TransformClass transformClass)
{
if (pos.X == 0 && pos.Y == 0)
Expand All @@ -143,6 +148,7 @@ internal static int GetBaseRangeContextEndOfBlock(Point pos, Av1TransformClass t
/// <summary>
/// SVT: get_br_ctx
/// </summary>
/// <remarks>Spec section 8.2.3, under 'coeff_br'.</remarks>
internal static int GetBaseRangeContext(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass)
{
Span<byte> row0 = levels.GetRow(position.Y);
Expand Down Expand Up @@ -224,7 +230,7 @@ internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, Point position)
internal static int GetLowerLevelsContext(Av1LevelBuffer levels, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
{
int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass);
return Av1NzMap.GetNzMapContextFromStats(stats, levels, position, transformSize, transformClass);
return Av1NzMap.GetNzMapContextFromStats(stats, position, transformSize, transformClass);
}

/// <summary>
Expand Down Expand Up @@ -265,18 +271,17 @@ internal static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInf
internal static sbyte GetNzMapContext(
Av1LevelBuffer levels,
Point position,
int scan_idx,
bool is_eob,
bool isEndOfBlock,
Av1TransformSize transformSize,
Av1TransformClass transformClass)
{
if (is_eob)
if (isEndOfBlock)
{
return (sbyte)GetLowerLevelContextEndOfBlock(levels, position);
}

int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass);
return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, levels, position, transformSize, transformClass);
return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, position, transformSize, transformClass);
}

/// <summary>
Expand All @@ -294,7 +299,7 @@ internal static void GetNzMapContexts(
{
int pos = scan[i];
Point position = levels.GetPosition(pos);
coefficientContexts[pos] = GetNzMapContext(levels, position, i, i == eob - 1, transformSize, transformClass);
coefficientContexts[pos] = GetNzMapContext(levels, position, i == eob - 1, transformSize, transformClass);
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,22 @@ private int ReadCoefficientsBase(Av1TransformSize transformSizeContext, Av1Plane
return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]);
}

private void ReadCoefficientsBaseRangeLoop(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext, ref int level)
{
ref Av1SymbolReader r = ref this.reader;
Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
Av1Distribution distribution = this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext];
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
{
int coefficientBaseRange = r.ReadSymbol(distribution);
level += coefficientBaseRange;
if (coefficientBaseRange < Av1Constants.BaseRangeSizeMinus1)
{
break;
}
}
}

internal int ReadGolomb()
{
ref Av1SymbolReader r = ref this.reader;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ internal static class Av1TransformSizeExtensions
Av1TransformSize.Size64x64, // TX_64X16
];

// This is computed as:
// min(transform_width_log2, 5) + min(transform_height_log2, 5) - 4.
private static readonly int[] Log2Minus4 = [
0, // TX_4X4
2, // TX_8X8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,26 @@ public void RoundTripFullBlock(ushort endOfBlock)
Assert.Equal(endOfBlock, actuals[0]);
}

[Fact]
public void RoundTripFullCoefficients()
[Theory]
[MemberData(nameof(GetBlockSize4x4Data))]
public void RoundTripFullCoefficientsYSize4x4(int bSize, int txSize, int txType)
{
// Assign
const ushort endOfBlock = 16;
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;
Av1BlockSize blockSize = (Av1BlockSize)bSize;
Av1TransformSize transformSize = (Av1TransformSize)txSize;
Av1TransformType transformType = (Av1TransformType)txType;
Av1PredictionMode intraDirection = Av1PredictionMode.DC;
Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.DC;
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];
int[] aboveContexts = new int[transformSize.Get4x4WideCount()];
int[] leftContexts = new int[transformSize.Get4x4HighCount()];
Av1TransformBlockContext transformBlockContext = new();
Configuration configuration = Configuration.Default;
Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex);
Span<int> coefficientsBuffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
Span<int> coefficientsBuffer = Enumerable.Range(0, blockSize.GetHeight() * blockSize.GetWidth()).ToArray();
Span<int> actuals = new int[16 + 1];

// Act
Expand All @@ -134,4 +135,17 @@ public void RoundTripFullCoefficients()
Assert.Equal(endOfBlock, actuals[0]);
Assert.Equal(coefficientsBuffer[..endOfBlock], actuals[1..(endOfBlock + 1)]);
}

public static TheoryData<int, int, int> GetBlockSize4x4Data()
{
TheoryData<int, int, int> result = [];
Av1BlockSize blockSize = Av1BlockSize.Block4x4;
Av1TransformSize transformSize = blockSize.GetMaximumTransformSize();
for (Av1TransformType transformType = Av1TransformType.DctDct; transformType < Av1TransformType.VerticalDct; transformType++)
{
result.Add((int)blockSize, (int)transformSize, (int)transformType);
}

return result;
}
}
22 changes: 22 additions & 0 deletions tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformSizeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ internal void ToBlockSizeReturnsSameWidthAndHeight(int s)
Assert.Equal(transformHeight, blockHeight);
}

[Theory]
[MemberData(nameof(GetAllSizes))]
internal void LogMinus4ReturnsReferenceValues(int s)
{
// Assign
Av1TransformSize transformSize = (Av1TransformSize)s;
int expected = ReferenceLog2Minus4(transformSize);

// Act
int actual = transformSize.GetLog2Minus4();

// Assert
Assert.Equal(expected, actual);
}

public static TheoryData<int> GetAllSizes()
{
TheoryData<int> combinations = [];
Expand All @@ -149,4 +164,11 @@ private static int GetRatio(Av1TransformSize transformSize)
int ratio = width > height ? width / height : height / width;
return ratio;
}

private static int ReferenceLog2Minus4(Av1TransformSize transformSize)
{
int widthLog2 = Av1Math.Log2(transformSize.GetWidth());
int heightLog2 = Av1Math.Log2(transformSize.GetHeight());
return Math.Min(widthLog2, 5) + Math.Min(heightLog2, 5) - 4;
}
}

0 comments on commit 0b38ce3

Please sign in to comment.