From 8192ff2a17e78d7f458703ad5cff00da6f2dcda2 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 29 Mar 2022 21:09:22 +0300 Subject: [PATCH] Initial processor implementation, code base for tests --- .../DownScalingComponentProcessor2.cs | 157 +++++++++++++++++ .../DownScalingComponentProcessor4.cs | 127 ++++++++++++++ .../DownScalingComponentProcessor8.cs | 16 +- .../Decoder/SpectralConverter{TPixel}.cs | 20 +++ .../ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 159 +++++++++++++++++- ...plementationsTests.FastFloatingPointDCT.cs | 19 +-- .../Formats/Jpg/Utils/JpegFixture.cs | 21 +-- 7 files changed, 474 insertions(+), 45 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs new file mode 100644 index 000000000..60ded7f06 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs @@ -0,0 +1,157 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + internal sealed class DownScalingComponentProcessor2 : ComponentProcessor + { + private readonly IRawJpegData rawJpeg; + + public DownScalingComponentProcessor2(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) + : base(memoryAllocator, frame, postProcessorBufferSize, component, 4) + => this.rawJpeg = rawJpeg; + + public override void CopyBlocksToColorBuffer(int spectralStep) + { + Buffer2D spectralBuffer = this.Component.SpectralBlocks; + + float maximumValue = this.Frame.MaxColorChannelValue; + float normalizationValue = MathF.Ceiling(maximumValue / 2); + + int destAreaStride = this.ColorBuffer.Width; + + int blocksRowsPerStep = this.Component.SamplingFactors.Height; + Size subSamplingDivisors = this.Component.SubSamplingDivisors; + + Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.Component.QuantizationTableIndex]; + Block8x8F workspaceBlock = default; + + int yBlockStart = spectralStep * blocksRowsPerStep; + + for (int y = 0; y < blocksRowsPerStep; y++) + { + int yBuffer = y * this.BlockAreaSize.Height; + + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); + + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) + { + // Integer to float + workspaceBlock.LoadFrom(ref blockRow[xBlock]); + + // IDCT/Normalization/Range + TransformIDCT_4x4(ref workspaceBlock, ref dequantTable, normalizationValue, maximumValue); + + // Save to the intermediate buffer + int xColorBufferStart = xBlock * this.BlockAreaSize.Width; + ScaledCopyTo( + ref workspaceBlock, + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); + } + } + } + + public static void TransformIDCT_4x4(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) + { + const int DCTSIZE = 8; + const float FIX_0_541196100 = 0.541196100f; + const float FIX_0_765366865 = 0.765366865f; + const float FIX_1_847759065 = 1.847759065f; + + // input block is transposed so term indices must be tranposed too + float tmp0, tmp2, tmp10, tmp12; + float z1, z2, z3; + + for (int ctr = 0; ctr < 4; ctr++) + { + // Even part + tmp0 = block[ctr * DCTSIZE] * dequantTable[ctr * DCTSIZE]; + tmp2 = block[(ctr * DCTSIZE) + 2] * dequantTable[(ctr * DCTSIZE) + 2]; + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + // Odd part + z2 = block[(ctr * DCTSIZE) + 1] * dequantTable[(ctr * DCTSIZE) + 1]; + z3 = block[(ctr * DCTSIZE) + 3] * dequantTable[(ctr * DCTSIZE) + 3]; + + z1 = (z2 + z3) * FIX_0_541196100; + tmp0 = z1 + (z2 * FIX_0_765366865); + tmp2 = z1 - (z3 * FIX_1_847759065); + + /* Final output stage */ + block[ctr + 4] = tmp10 + tmp0; + block[ctr + 28] = tmp10 - tmp0; + block[ctr + 12] = tmp12 + tmp2; + block[ctr + 20] = tmp12 - tmp2; + } + + for (int ctr = 0; ctr < 4; ctr++) + { + // Even part + tmp0 = block[(ctr * 8) + 0 + 4]; + tmp2 = block[(ctr * 8) + 2 + 4]; + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + // Odd part + z2 = block[(ctr * 8) + 1 + 4]; + z3 = block[(ctr * 8) + 3 + 4]; + + z1 = (z2 + z3) * FIX_0_541196100; + tmp0 = z1 + (z2 * FIX_0_765366865); + tmp2 = z1 - (z3 * FIX_1_847759065); + + /* Final output stage */ + block[(ctr * 8) + 0] = (float)Math.Round(Numerics.Clamp(tmp10 + tmp0 + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 3] = (float)Math.Round(Numerics.Clamp(tmp10 - tmp0 + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 1] = (float)Math.Round(Numerics.Clamp(tmp12 + tmp2 + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 2] = (float)Math.Round(Numerics.Clamp(tmp12 - tmp2 + normalizationValue, 0, maxValue)); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) + { + // TODO: Optimize: implement all cases with scale-specific, loopless code! + CopyArbitraryScale(ref block, ref destRef, destStrideWidth, horizontalScale, verticalScale); + + [MethodImpl(InliningOptions.ColdPath)] + static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + for (int y = 0; y < 4; y++) + { + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 4; x++) + { + int xx = x * horizontalScale; + + float value = block[y8 + x]; + + for (int i = 0; i < verticalScale; i++) + { + int baseIdx = ((yy + i) * areaStride) + xx; + + for (int j = 0; j < horizontalScale; j++) + { + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; + } + } + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs new file mode 100644 index 000000000..499c7d92b --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs @@ -0,0 +1,127 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + internal sealed class DownScalingComponentProcessor4 : ComponentProcessor + { + private readonly IRawJpegData rawJpeg; + + public DownScalingComponentProcessor4(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) + : base(memoryAllocator, frame, postProcessorBufferSize, component, 2) + => this.rawJpeg = rawJpeg; + + public override void CopyBlocksToColorBuffer(int spectralStep) + { + Buffer2D spectralBuffer = this.Component.SpectralBlocks; + + float maximumValue = this.Frame.MaxColorChannelValue; + float normalizationValue = MathF.Ceiling(maximumValue / 2); + + int destAreaStride = this.ColorBuffer.Width; + + int blocksRowsPerStep = this.Component.SamplingFactors.Height; + Size subSamplingDivisors = this.Component.SubSamplingDivisors; + + Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.Component.QuantizationTableIndex]; + Block8x8F workspaceBlock = default; + + int yBlockStart = spectralStep * blocksRowsPerStep; + + for (int y = 0; y < blocksRowsPerStep; y++) + { + int yBuffer = y * this.BlockAreaSize.Height; + + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); + + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) + { + // Integer to float + workspaceBlock.LoadFrom(ref blockRow[xBlock]); + + // IDCT/Normalization/Range + TransformIDCT_2x2(ref workspaceBlock, ref dequantTable, normalizationValue, maximumValue); + + // Save to the intermediate buffer + int xColorBufferStart = xBlock * this.BlockAreaSize.Width; + ScaledCopyTo( + ref workspaceBlock, + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); + } + } + } + + public static void TransformIDCT_2x2(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) + { + // input block is transposed so term indices must be tranposed too + float tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + + // 0 + // => 0 1 + // 8 + tmp4 = block[0] * dequantTable[0]; + tmp5 = block[1] * dequantTable[1]; + tmp0 = tmp4 + tmp5; + tmp2 = tmp4 - tmp5; + + // 1 + // => 8 9 + // 9 + tmp4 = block[8] * dequantTable[8]; + tmp5 = block[9] * dequantTable[9]; + tmp1 = tmp4 + tmp5; + tmp3 = tmp4 - tmp5; + + // Row 0 + block[0] = (float)Math.Round(Numerics.Clamp(tmp0 + tmp1 + normalizationValue, 0, maxValue)); + block[1] = (float)Math.Round(Numerics.Clamp(tmp0 - tmp1 + normalizationValue, 0, maxValue)); + + // Row 1 + block[8] = (float)Math.Round(Numerics.Clamp(tmp2 + tmp3 + normalizationValue, 0, maxValue)); + block[9] = (float)Math.Round(Numerics.Clamp(tmp2 - tmp3 + normalizationValue, 0, maxValue)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) + { + // TODO: Optimize: implement all cases with scale-specific, loopless code! + CopyArbitraryScale(ref block, ref destRef, destStrideWidth, horizontalScale, verticalScale); + + [MethodImpl(InliningOptions.ColdPath)] + static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + for (int y = 0; y < 2; y++) + { + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 2; x++) + { + int xx = x * horizontalScale; + + float value = block[y8 + x]; + + for (int i = 0; i < verticalScale; i++) + { + int baseIdx = ((yy + i) * areaStride) + xx; + + for (int j = 0; j < horizontalScale; j++) + { + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; + } + } + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs index a854ac416..2aafcfa80 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs @@ -38,14 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { - // get direct current term - averaged 8x8 pixel value - float dc = blockRow[xBlock][0]; - - // dequantization - dc *= this.dcDequantizer; - - // Normalize & round - dc = (float)Math.Round(Numerics.Clamp(dc + normalizationValue, 0, maximumValue)); + float dc = TransformIDCT_1x1(blockRow[xBlock][0], this.dcDequantizer, normalizationValue, maximumValue); // Save to the intermediate buffer int xColorBufferStart = xBlock * this.BlockAreaSize.Width; @@ -59,6 +52,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } + [MethodImpl(InliningOptions.ShortMethod)] + public static float TransformIDCT_1x1(float dc, float dequantizer, float normalizationValue, float maxValue) + { + dc *= dequantizer; + return (float)Math.Round(Numerics.Clamp(dc + normalizationValue, 0, maxValue)); + } + [MethodImpl(InliningOptions.ShortMethod)] public static void ScaledCopyTo(float value, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 30139a3a7..547da83d7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -33,6 +33,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // 8 => 8, no scaling 8, + // 8 => 4, 1/2 of the original size + 4, + + // 8 => 2, 1/4 of the original size + 2, + // 8 => 1, 1/8 of the original size 1, }; @@ -256,6 +262,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.componentProcessors[i] = new DirectComponentProcessor(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]); } + break; + case 4: + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i] = new DownScalingComponentProcessor2(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]); + } + + break; + case 2: + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i] = new DownScalingComponentProcessor4(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]); + } + break; case 1: for (int i = 0; i < this.componentProcessors.Length; i++) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 9c467a1cc..159444ad8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -3,6 +3,7 @@ using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; @@ -14,8 +15,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public static class DCTTests { - private const int MaxAllowedValue = short.MaxValue; - private const int MinAllowedValue = short.MinValue; + // 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) { @@ -41,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(3)] public void LLM_TransformIDCT_CompareToNonOptimized(int seed) { - float[] sourceArray = Create8x8RoundedRandomFloatData(MinAllowedValue, MaxAllowedValue, seed); + float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); var srcBlock = Block8x8F.Load(sourceArray); @@ -74,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(3)] public void LLM_TransformIDCT_CompareToAccurate(int seed) { - float[] sourceArray = Create8x8RoundedRandomFloatData(MinAllowedValue, MaxAllowedValue, seed); + float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); var srcBlock = Block8x8F.Load(sourceArray); @@ -113,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { int seed = FeatureTestRunner.Deserialize(serialized); - Span src = Create8x8RoundedRandomFloatData(MinAllowedValue, MaxAllowedValue, seed); + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); @@ -156,6 +162,147 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSIMD); } + //[Theory] + //[InlineData(1)] + //[InlineData(2)] + //public void TranformIDCT_4x4(int seed) + //{ + // Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 4, 4); + // var srcBlock = default(Block8x8F); + // srcBlock.LoadFrom(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 + // FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + + // // testee + // // IDCT implementation tranforms blocks after transposition + // srcBlock.TransposeInplace(); + // DownScalingComponentProcessor2.TransformIDCT_4x4(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); + + // var comparer = new ApproximateFloatComparer(1f); + + // Span expectedSpan = expectedDest.AsSpan(); + // Span actualSpan = srcBlock.ToArray().AsSpan(); + + // AssertEquality(expectedSpan, actualSpan, comparer); + // AssertEquality(expectedSpan.Slice(8), actualSpan.Slice(8), comparer); + // AssertEquality(expectedSpan.Slice(16), actualSpan.Slice(16), comparer); + // AssertEquality(expectedSpan.Slice(24), actualSpan.Slice(24), comparer); + + // static void AssertEquality(Span expected, Span actual, ApproximateFloatComparer comparer) + // { + // for (int x = 0; x < 4; x++) + // { + // float expectedValue = (float)Math.Round(Numerics.Clamp(expected[x] + NormalizationValue, 0, MaxOutputValue)); + // Assert.Equal(expectedValue, actual[x], comparer); + // } + // } + //} + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TranformIDCT_2x2(int seed) + { + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 2, 2); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(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 + FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + + // testee + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); + DownScalingComponentProcessor4.TransformIDCT_2x2(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); + + var comparer = new ApproximateFloatComparer(0.1f); + + // top-left + float topLeftExpected = (float)Math.Round(Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue)); + Assert.Equal(topLeftExpected, srcBlock[0], comparer); + + // top-right + float topRightExpected = (float)Math.Round(Numerics.Clamp(expectedDest[7] + NormalizationValue, 0, MaxOutputValue)); + Assert.Equal(topRightExpected, srcBlock[1], comparer); + + // bot-left + float botLeftExpected = (float)Math.Round(Numerics.Clamp(expectedDest[56] + NormalizationValue, 0, MaxOutputValue)); + Assert.Equal(botLeftExpected, srcBlock[8], comparer); + + // bot-right + float botRightExpected = (float)Math.Round(Numerics.Clamp(expectedDest[63] + NormalizationValue, 0, MaxOutputValue)); + Assert.Equal(botRightExpected, srcBlock[9], comparer); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TranformIDCT_1x1(int seed) + { + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 1, 1); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(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 + FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + + // testee + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); + float actual = DownScalingComponentProcessor8.TransformIDCT_1x1( + srcBlock[0], + dequantMatrix[0], + NormalizationValue, + MaxOutputValue); + + float expected = (float)Math.Round(Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue)); + + Assert.Equal(actual, expected, new ApproximateFloatComparer(0.1f)); + } + // Forward transform // This test covers entire FDCT conversion chain // This test checks all hardware implementations @@ -168,7 +315,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { int seed = FeatureTestRunner.Deserialize(serialized); - Span src = Create8x8RoundedRandomFloatData(MinAllowedValue, MaxAllowedValue, seed); + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); var block = default(Block8x8F); block.LoadFrom(src); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 9eb3c0103..fddbb828c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2, 200)] public void LLM_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) { - float[] sourceArray = Create8x8RoundedRandomFloatData(-range, range, seed); + float[] sourceArray = Create8x8RandomFloatData(-range, range, seed); var source = Block8x8F.Load(sourceArray); @@ -64,23 +64,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 0.1f); } - [Theory] - [InlineData(42, 1000)] - [InlineData(1, 1000)] - [InlineData(2, 1000)] - public void LLM_IDCT_CompareToIntegerRoundedAccurateImplementation(int seed, int range) - { - Block8x8F fSource = CreateRoundedRandomFloatBlock(-range, range, seed); - Block8x8 iSource = fSource.RoundAsInt16Block(); - - Block8x8 iExpected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref iSource); - Block8x8F fExpected = iExpected.AsFloatBlock(); - - Block8x8F fActual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref fSource); - - this.CompareBlocks(fExpected, fActual, 2); - } - [Theory] [InlineData(42)] [InlineData(1)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index 1bdfc6eca..b5214cc3f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -89,33 +89,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils return result; } - internal static float[] Create8x8RoundedRandomFloatData(int minValue, int maxValue, int seed = 42) - => Create8x8RandomIntData(minValue, maxValue, seed).ConvertAllToFloat(); - - public static float[] Create8x8RandomFloatData(float minValue, float maxValue, int seed = 42) + public static float[] Create8x8RandomFloatData(float minValue, float maxValue, int seed = 42, int xBorder = 8, int yBorder = 8) { var rnd = new Random(seed); - var result = new float[64]; - for (int i = 0; i < 8; i++) + float[] result = new float[64]; + for (int y = 0; y < yBorder; y++) { - for (int j = 0; j < 8; j++) + int y8 = y * 8; + for (int x = 0; x < xBorder; x++) { double val = rnd.NextDouble(); val *= maxValue - minValue; val += minValue; - result[(i * 8) + j] = (float)val; + result[y8 + x] = (float)val; } } return result; } - internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42) => - Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed)); - - internal static Block8x8F CreateRoundedRandomFloatBlock(int minValue, int maxValue, int seed = 42) => - Block8x8F.Load(Create8x8RoundedRandomFloatData(minValue, maxValue, seed)); + internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42, int xBorder = 8, int yBorder = 8) => + Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed, xBorder, yBorder)); internal void Print8x8Data(T[] data) => this.Print8x8Data(new Span(data));