From 6eceb6c0ebd389ad9e4862603f951b445b4096a8 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 1 Apr 2022 15:13:49 +0300 Subject: [PATCH] Moved quantization table initialization to component post processors --- .../DirectComponentProcessor.cs | 10 +- .../DownScalingComponentProcessor2.cs | 10 +- .../DownScalingComponentProcessor4.cs | 10 +- .../DownScalingComponentProcessor8.cs | 6 +- .../Jpeg/Components/ScaledFloatingPointDCT.cs | 31 ++-- .../Formats/Jpeg/JpegDecoderCore.cs | 3 - .../ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 135 ++++++++++-------- 7 files changed, 110 insertions(+), 95 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs index 99a1a33c04..be9fd98c11 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs @@ -8,11 +8,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { internal sealed class DirectComponentProcessor : ComponentProcessor { - private readonly IRawJpegData rawJpeg; + private Block8x8F dequantizationTable; public DirectComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) : base(memoryAllocator, frame, postProcessorBufferSize, component, blockSize: 8) - => this.rawJpeg = rawJpeg; + { + this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; + FloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); + } public override void CopyBlocksToColorBuffer(int spectralStep) { @@ -28,7 +31,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder Size subSamplingDivisors = this.Component.SubSamplingDivisors; - Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.Component.QuantizationTableIndex]; Block8x8F workspaceBlock = default; for (int y = 0; y < blocksRowsPerStep; y++) @@ -44,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder workspaceBlock.LoadFrom(ref blockRow[xBlock]); // Dequantize - workspaceBlock.MultiplyInPlace(ref dequantTable); + workspaceBlock.MultiplyInPlace(ref this.dequantizationTable); // Convert from spectral to color FloatingPointDCT.TransformIDCT(ref workspaceBlock); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs index 9376f408da..81fdced66f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs @@ -9,11 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { internal sealed class DownScalingComponentProcessor2 : ComponentProcessor { - private readonly IRawJpegData rawJpeg; + private Block8x8F dequantizationTable; public DownScalingComponentProcessor2(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) : base(memoryAllocator, frame, postProcessorBufferSize, component, 4) - => this.rawJpeg = rawJpeg; + { + this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; + ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); + } public override void CopyBlocksToColorBuffer(int spectralStep) { @@ -27,7 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder 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; @@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder workspaceBlock.LoadFrom(ref blockRow[xBlock]); // IDCT/Normalization/Range - ScaledFloatingPointDCT.TransformIDCT_4x4(ref workspaceBlock, ref dequantTable, normalizationValue, maximumValue); + ScaledFloatingPointDCT.TransformIDCT_4x4(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); // Save to the intermediate buffer int xColorBufferStart = xBlock * this.BlockAreaSize.Width; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs index b63f103a6b..505e37fa40 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs @@ -9,11 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { internal sealed class DownScalingComponentProcessor4 : ComponentProcessor { - private readonly IRawJpegData rawJpeg; + private Block8x8F dequantizationTable; public DownScalingComponentProcessor4(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) : base(memoryAllocator, frame, postProcessorBufferSize, component, 2) - => this.rawJpeg = rawJpeg; + { + this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; + ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); + } public override void CopyBlocksToColorBuffer(int spectralStep) { @@ -27,7 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder 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; @@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder workspaceBlock.LoadFrom(ref blockRow[xBlock]); // IDCT/Normalization/Range - ScaledFloatingPointDCT.TransformIDCT_2x2(ref workspaceBlock, ref dequantTable, normalizationValue, maximumValue); + ScaledFloatingPointDCT.TransformIDCT_2x2(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); // Save to the intermediate buffer int xColorBufferStart = xBlock * this.BlockAreaSize.Width; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs index 24cf669ac6..7961b59170 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs @@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { internal sealed class DownScalingComponentProcessor8 : ComponentProcessor { - private readonly float dcDequantizer; + private readonly float dcDequantizatizer; public DownScalingComponentProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) : base(memoryAllocator, frame, postProcessorBufferSize, component, 1) - => this.dcDequantizer = rawJpeg.QuantizationTables[component.QuantizationTableIndex][0]; + => this.dcDequantizatizer = 0.125f * rawJpeg.QuantizationTables[component.QuantizationTableIndex][0]; public override void CopyBlocksToColorBuffer(int spectralStep) { @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { - float dc = ScaledFloatingPointDCT.TransformIDCT_1x1(blockRow[xBlock][0], this.dcDequantizer, normalizationValue, maximumValue); + float dc = ScaledFloatingPointDCT.TransformIDCT_1x1(blockRow[xBlock][0], this.dcDequantizatizer, normalizationValue, maximumValue); // Save to the intermediate buffer int xColorBufferStart = xBlock * this.BlockAreaSize.Width; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs index 51ee6cb585..855dda2f4b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs @@ -14,6 +14,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// internal static class ScaledFloatingPointDCT { +#pragma warning disable SA1310 // naming rules violation warnings + private const float F32_0_541196100 = 0.541196100f; + private const float F32_0_765366865 = 0.765366865f; + private const float F32_1_847759065 = 1.847759065f; +#pragma warning restore SA1310 + /// /// Adjusts given quantization table for usage with IDCT algorithms /// from . @@ -56,27 +62,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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; - for (int ctr = 0; ctr < 4; ctr++) { // Even part - float tmp0 = block[ctr * DCTSIZE] * dequantTable[ctr * DCTSIZE]; - float tmp2 = block[(ctr * DCTSIZE) + 2] * dequantTable[(ctr * DCTSIZE) + 2]; + float tmp0 = block[ctr * 8] * dequantTable[ctr * 8]; + float tmp2 = block[(ctr * 8) + 2] * dequantTable[(ctr * 8) + 2]; float tmp10 = tmp0 + tmp2; float tmp12 = tmp0 - tmp2; // Odd part - float z2 = block[(ctr * DCTSIZE) + 1] * dequantTable[(ctr * DCTSIZE) + 1]; - float z3 = block[(ctr * DCTSIZE) + 3] * dequantTable[(ctr * DCTSIZE) + 3]; + float z2 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; + float z3 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; - float z1 = (z2 + z3) * FIX_0_541196100; - tmp0 = z1 + (z2 * FIX_0_765366865); - tmp2 = z1 - (z3 * FIX_1_847759065); + float z1 = (z2 + z3) * F32_0_541196100; + tmp0 = z1 + (z2 * F32_0_765366865); + tmp2 = z1 - (z3 * F32_1_847759065); /* Final output stage */ block[ctr + 4] = tmp10 + tmp0; @@ -98,9 +99,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components float z2 = block[(ctr * 8) + 1 + 4]; float z3 = block[(ctr * 8) + 3 + 4]; - float z1 = (z2 + z3) * FIX_0_541196100; - tmp0 = z1 + (z2 * FIX_0_765366865); - tmp2 = z1 - (z3 * FIX_1_847759065); + float z1 = (z2 + z3) * F32_0_541196100; + tmp0 = z1 + (z2 * F32_0_765366865); + tmp2 = z1 - (z3 * F32_1_847759065); /* Final output stage */ block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(tmp10 + tmp0 + normalizationValue, 0, maxValue)); diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 5abe17d338..8ae4dbd917 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1025,9 +1025,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg break; } } - - // Adjusting table for IDCT step during decompression - FloatingPointDCT.AdjustToIDCT(ref table); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 159444ad8f..c14d38b07d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -62,14 +62,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F dequantMatrix = CreateBlockFromScalar(1); // This step is needed to apply adjusting multipliers to the input block - FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); // IDCT implementation tranforms blocks after transposition srcBlock.TransposeInplace(); srcBlock.MultiplyInPlace(ref dequantMatrix); // IDCT calculation - FastFloatingPointDCT.TransformIDCT(ref srcBlock); + FloatingPointDCT.TransformIDCT(ref srcBlock); this.CompareBlocks(expected, srcBlock, 1f); } @@ -95,14 +95,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F dequantMatrix = CreateBlockFromScalar(1); // This step is needed to apply adjusting multipliers to the input block - FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); // IDCT implementation tranforms blocks after transposition srcBlock.TransposeInplace(); srcBlock.MultiplyInPlace(ref dequantMatrix); // IDCT calculation - FastFloatingPointDCT.TransformIDCT(ref srcBlock); + FloatingPointDCT.TransformIDCT(ref srcBlock); this.CompareBlocks(expected, srcBlock, 1f); } @@ -138,13 +138,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // 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); + FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); srcBlock.MultiplyInPlace(ref dequantMatrix); // testee // IDCT implementation tranforms blocks after transposition srcBlock.TransposeInplace(); - FastFloatingPointDCT.TransformIDCT(ref srcBlock); + FloatingPointDCT.TransformIDCT(ref srcBlock); float[] actualDest = srcBlock.ToArray(); @@ -162,56 +162,65 @@ 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_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 + ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + + // testee + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); + ScaledFloatingPointDCT.TransformIDCT_4x4(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); + + Block8x8F DEBUG_VARIABLE = Block8x8F.Load(expectedDest); + + var comparer = new ApproximateFloatComparer(1f); + + Span expectedSpan = expectedDest.AsSpan(); + Span actualSpan = srcBlock.ToArray().AsSpan(); + + AssertEquality_4x4(expectedSpan, actualSpan, comparer); + AssertEquality_4x4(expectedSpan.Slice(2), actualSpan.Slice(8), comparer); + AssertEquality_4x4(expectedSpan.Slice(4), actualSpan.Slice(16), comparer); + AssertEquality_4x4(expectedSpan.Slice(6), actualSpan.Slice(24), comparer); + + static void AssertEquality_4x4(Span expected, Span actual, ApproximateFloatComparer comparer) + { + float average_4x4 = 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); + average_4x4 += clamped; + } + } + + average_4x4 /= 4f; + } + } [Theory] [InlineData(1)] @@ -237,12 +246,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // 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); + ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); // testee // IDCT implementation tranforms blocks after transposition srcBlock.TransposeInplace(); - DownScalingComponentProcessor4.TransformIDCT_2x2(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); + ScaledFloatingPointDCT.TransformIDCT_2x2(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); + + Block8x8F DEBUG_VARIABLE = Block8x8F.Load(expectedDest); var comparer = new ApproximateFloatComparer(0.1f); @@ -287,12 +298,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // 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); + ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); // testee // IDCT implementation tranforms blocks after transposition srcBlock.TransposeInplace(); - float actual = DownScalingComponentProcessor8.TransformIDCT_1x1( + float actual = ScaledFloatingPointDCT.TransformIDCT_1x1( srcBlock[0], dequantMatrix[0], NormalizationValue, @@ -328,14 +339,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // testee // Second transpose call is done by Quantize step // Do this manually here just to be complient to the reference implementation - FastFloatingPointDCT.TransformFDCT(ref block); + 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); - FastFloatingPointDCT.AdjustToFDCT(ref quantMatrix); + FloatingPointDCT.AdjustToFDCT(ref quantMatrix); block.MultiplyInPlace(ref quantMatrix); float[] actualDest = block.ToArray();