📷 A modern, cross-platform, 2D Graphics library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

365 lines
14 KiB

// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg;
[Trait("Format", "Jpg")]
public static class DCTTests
{
// size of input values is 10 bit max
private const float MaxInputValue = 1023;
private const float MinInputValue = -1024;
// output value range is 12 bit max
private const float MaxOutputValue = 4096;
private const float NormalizationValue = MaxOutputValue / 2;
internal static Block8x8F CreateBlockFromScalar(float value)
{
Block8x8F result = default;
for (int i = 0; i < Block8x8F.Size; i++)
{
result[i] = value;
}
return result;
}
public class FastFloatingPoint : JpegFixture
{
public FastFloatingPoint(ITestOutputHelper output)
: base(output)
{
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void LLM_TransformIDCT_CompareToNonOptimized(int seed)
{
float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed);
Block8x8F srcBlock = Block8x8F.Load(sourceArray);
// reference
Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref srcBlock);
// testee
// Part of the IDCT calculations is fused into the quantization step
// We must multiply input block with adjusted no-quantization matrix
// before applying IDCT
// Dequantization using unit matrix - no values are upscaled
Block8x8F dequantMatrix = CreateBlockFromScalar(1);
// This step is needed to apply adjusting multipliers to the input block
FloatingPointDCT.AdjustToIDCT(ref dequantMatrix);
// IDCT implementation tranforms blocks after transposition
srcBlock.TransposeInPlace();
srcBlock.MultiplyInPlace(ref dequantMatrix);
// IDCT calculation
FloatingPointDCT.TransformIDCT(ref srcBlock);
this.CompareBlocks(expected, srcBlock, 1f);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void LLM_TransformIDCT_CompareToAccurate(int seed)
{
float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed);
Block8x8F srcBlock = Block8x8F.Load(sourceArray);
// reference
Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref srcBlock);
// testee
// Part of the IDCT calculations is fused into the quantization step
// We must multiply input block with adjusted no-quantization matrix
// before applying IDCT
// Dequantization using unit matrix - no values are upscaled
Block8x8F dequantMatrix = CreateBlockFromScalar(1);
// This step is needed to apply adjusting multipliers to the input block
FloatingPointDCT.AdjustToIDCT(ref dequantMatrix);
// IDCT implementation tranforms blocks after transposition
srcBlock.TransposeInPlace();
srcBlock.MultiplyInPlace(ref dequantMatrix);
// IDCT calculation
FloatingPointDCT.TransformIDCT(ref srcBlock);
this.CompareBlocks(expected, srcBlock, 1f);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TransformIDCT(int seed)
{
static void RunTest(string serialized)
{
int seed = FeatureTestRunner.Deserialize<int>(serialized);
Span<float> src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed);
Block8x8F srcBlock = Block8x8F.Load(src);
float[] expectedDest = new float[64];
float[] temp = new float[64];
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp);
// testee
// Part of the IDCT calculations is fused into the quantization step
// We must multiply input block with adjusted no-quantization matrix
// before applying IDCT
Block8x8F dequantMatrix = CreateBlockFromScalar(1);
// Dequantization using unit matrix - no values are upscaled
// as quant matrix is all 1's
// This step is needed to apply adjusting multipliers to the input block
FloatingPointDCT.AdjustToIDCT(ref dequantMatrix);
srcBlock.MultiplyInPlace(ref dequantMatrix);
// testee
// IDCT implementation tranforms blocks after transposition
srcBlock.TransposeInPlace();
FloatingPointDCT.TransformIDCT(ref srcBlock);
float[] actualDest = srcBlock.ToArray();
Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f));
}
// 4 paths:
// 1. AllowAll - call avx/fma implementation
// 2. DisableFMA - call avx without fma implementation
// 3. DisableAvx - call sse implementation
// 4. DisableHWIntrinsic - call Vector4 fallback implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TranformIDCT_4x4(int seed)
{
Span<float> src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 4, 4);
Block8x8F srcBlock = Block8x8F.Load(src);
float[] expectedDest = new float[64];
float[] temp = new float[64];
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp);
// testee
// Part of the IDCT calculations is fused into the quantization step
// We must multiply input block with adjusted no-quantization matrix
// before applying IDCT
Block8x8F dequantMatrix = CreateBlockFromScalar(1);
// Dequantization using unit matrix - no values are upscaled
// as quant matrix is all 1's
// This step is needed to apply adjusting multipliers to the input block
ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix);
// testee
// IDCT implementation tranforms blocks after transposition
srcBlock.TransposeInPlace();
ScaledFloatingPointDCT.TransformIDCT_4x4(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue);
Span<float> expectedSpan = expectedDest.AsSpan();
Span<float> actualSpan = srcBlock.ToArray().AsSpan();
// resulting matrix is 4x4
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
AssertScaledElementEquality(expectedSpan.Slice((y * 16) + (x * 2)), actualSpan.Slice((y * 8) + x));
}
}
static void AssertScaledElementEquality(Span<float> expected, Span<float> actual)
{
float average2x2 = 0f;
for (int y = 0; y < 2; y++)
{
int y8 = y * 8;
for (int x = 0; x < 2; x++)
{
float clamped = Numerics.Clamp(expected[y8 + x] + NormalizationValue, 0, MaxOutputValue);
average2x2 += clamped;
}
}
average2x2 = MathF.Round(average2x2 / 4f);
Assert.Equal((int)average2x2, (int)actual[0]);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TranformIDCT_2x2(int seed)
{
Span<float> src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 2, 2);
Block8x8F srcBlock = Block8x8F.Load(src);
float[] expectedDest = new float[64];
float[] temp = new float[64];
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp);
// testee
// Part of the IDCT calculations is fused into the quantization step
// We must multiply input block with adjusted no-quantization matrix
// before applying IDCT
Block8x8F dequantMatrix = CreateBlockFromScalar(1);
// Dequantization using unit matrix - no values are upscaled
// as quant matrix is all 1's
// This step is needed to apply adjusting multipliers to the input block
ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix);
// testee
// IDCT implementation tranforms blocks after transposition
srcBlock.TransposeInPlace();
ScaledFloatingPointDCT.TransformIDCT_2x2(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue);
Span<float> expectedSpan = expectedDest.AsSpan();
Span<float> actualSpan = srcBlock.ToArray().AsSpan();
// resulting matrix is 2x2
for (int y = 0; y < 2; y++)
{
for (int x = 0; x < 2; x++)
{
AssertScaledElementEquality(expectedSpan.Slice((y * 32) + (x * 4)), actualSpan.Slice((y * 8) + x));
}
}
static void AssertScaledElementEquality(Span<float> expected, Span<float> actual)
{
float average4x4 = 0f;
for (int y = 0; y < 4; y++)
{
int y8 = y * 8;
for (int x = 0; x < 4; x++)
{
float clamped = Numerics.Clamp(expected[y8 + x] + NormalizationValue, 0, MaxOutputValue);
average4x4 += clamped;
}
}
average4x4 = MathF.Round(average4x4 / 16f);
Assert.Equal((int)average4x4, (int)actual[0]);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TranformIDCT_1x1(int seed)
{
Span<float> src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 1, 1);
Block8x8F srcBlock = Block8x8F.Load(src);
float[] expectedDest = new float[64];
float[] temp = new float[64];
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp);
// testee
// Part of the IDCT calculations is fused into the quantization step
// We must multiply input block with adjusted no-quantization matrix
// before applying IDCT
Block8x8F dequantMatrix = CreateBlockFromScalar(1);
// Dequantization using unit matrix - no values are upscaled
// as quant matrix is all 1's
// This step is needed to apply adjusting multipliers to the input block
ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix);
// testee
// IDCT implementation tranforms blocks after transposition
// But DC lays on main diagonal which is not changed by transposition
float actual = ScaledFloatingPointDCT.TransformIDCT_1x1(
srcBlock[0],
dequantMatrix[0],
NormalizationValue,
MaxOutputValue);
float expected = MathF.Round(Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue));
Assert.Equal((int)actual, (int)expected);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TransformFDCT(int seed)
{
static void RunTest(string serialized)
{
int seed = FeatureTestRunner.Deserialize<int>(serialized);
Span<float> src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed);
Block8x8F block = Block8x8F.Load(src);
float[] expectedDest = new float[64];
float[] temp1 = new float[64];
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true);
// testee
// Second transpose call is done by Quantize step
// Do this manually here just to be complient to the reference implementation
FloatingPointDCT.TransformFDCT(ref block);
block.TransposeInPlace();
// Part of the IDCT calculations is fused into the quantization step
// We must multiply input block with adjusted no-quantization matrix
// after applying FDCT
Block8x8F quantMatrix = CreateBlockFromScalar(1);
FloatingPointDCT.AdjustToFDCT(ref quantMatrix);
block.MultiplyInPlace(ref quantMatrix);
float[] actualDest = block.ToArray();
Assert.Equal(expectedDest, actualDest, new ApproximateFloatComparer(1f));
}
// 3 paths:
// 1. AllowAll - call avx implementation
// 2. DisableAvx - call Vector4 implementation
// 3. DisableHWIntrinsic - call scalar fallback implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
}
}