From d6d172025b05f56153aebffb3f968e342fadfc92 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 26 Aug 2017 19:07:02 +0200 Subject: [PATCH] playing with DCT implementations --- .../Formats/Jpeg/Common/Block8x8F.cs | 7 ++ ...ferenceImplementationsTests.AccurateDCT.cs | 36 +++++++++ ...plementationsTests.FastFloatingPointDCT.cs | 78 +++++++++++-------- ...ImplementationsTests.StandardIntegerDCT.cs | 30 ++++++- .../Jpg/Utils/JpegUtilityTestFixture.cs | 33 ++++++++ .../ReferenceImplementations.AccurateDCT.cs | 26 ++++--- ...nceImplementations.FastFloatingPointDCT.cs | 27 +++++++ ...renceImplementations.StandardIntegerDCT.cs | 2 + 8 files changed, 195 insertions(+), 44 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index b3220dea6..9a1acecfa 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -250,6 +250,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } + public float[] ToArray() + { + float[] result = new float[Size]; + this.CopyTo(result); + return result; + } + /// /// Multiply all elements of the block. /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs new file mode 100644 index 000000000..b716146e8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs @@ -0,0 +1,36 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; + + public partial class ReferenceImplementationsTests + { + public class AccurateDCT : JpegUtilityTestFixture + { + public AccurateDCT(ITestOutputHelper output) + : base(output) + { + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void ForwardThenInverse(int seed) + { + float[] data = JpegUtilityTestFixture.Create8x8RandomFloatData(-1000, 1000, seed); + + var b0 = default(Block8x8F); + b0.LoadFrom(data); + + Block8x8F b1 = ReferenceImplementations.AccurateDCT.TransformFDCT(ref b0); + Block8x8F b2 = ReferenceImplementations.AccurateDCT.TransformIDCT(ref b1); + + this.CompareBlocks(b0, b2, 1e-4f); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 4d7ad8a7e..05fe178cb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -3,21 +3,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { using System; + using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; + using Xunit.Abstractions; public partial class ReferenceImplementationsTests { - public class FastFloatingPointDCT + public class FastFloatingPointDCT : JpegUtilityTestFixture { + public FastFloatingPointDCT(ITestOutputHelper output) + : base(output) + { + } + [Theory] [InlineData(42, 0)] [InlineData(1, 0)] [InlineData(2, 0)] public void ForwardThenInverse(int seed, int startAt) { - int[] data = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + int[] data = JpegUtilityTestFixture.Create8x8RandomIntData(-1000, 1000, seed); float[] src = data.ConvertAllToFloat(); float[] dest = new float[64]; float[] temp = new float[64]; @@ -25,13 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ReferenceImplementations.FastFloatingPointDCT.fDCT2D_llm(src, dest, temp, true); ReferenceImplementations.FastFloatingPointDCT.iDCT2D_llm(dest, src, temp); - for (int i = startAt; i < 64; i++) - { - float expected = data[i]; - float actual = (float)src[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(2f)); - } + this.CompareBlocks(data.ConvertAllToFloat(), src, 2f); } [Theory] @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void IDCT_IsEquivalentTo_StandardIntegerImplementation(int seed) { - int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-1000, 1000, seed); Span floatSrc = intData.ConvertAllToFloat(); ReferenceImplementations.StandardIntegerDCT.TransformIDCTInplace(intData); @@ -50,13 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ReferenceImplementations.FastFloatingPointDCT.iDCT2D_llm(floatSrc, dest, temp); - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } + this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); } [Theory] @@ -65,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void IDCT_IsEquivalentTo_AccurateImplementation(int seed) { - int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-1000, 1000, seed); float[] floatSrc = intData.ConvertAllToFloat(); ReferenceImplementations.AccurateDCT.TransformIDCTInplace(intData); @@ -75,13 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ReferenceImplementations.FastFloatingPointDCT.iDCT2D_llm(floatSrc, dest, temp); - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } + this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); } [Theory] @@ -90,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void FDCT_IsEquivalentTo_StandardIntegerImplementation(int seed) { - int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-1000, 1000, seed); float[] floatSrc = intData.ConvertAllToFloat(); ReferenceImplementations.StandardIntegerDCT.TransformFDCTInplace(intData); @@ -100,14 +89,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ReferenceImplementations.FastFloatingPointDCT.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; + this.CompareBlocks(intData.ConvertAllToFloat(), dest, 2f); + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void FDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + float[] floatData = JpegUtilityTestFixture.Create8x8RandomFloatData(-1000, 1000); + + Block8x8F source = default(Block8x8F); + source.LoadFrom(floatData); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); + Block8x8F actual = ReferenceImplementations.FastFloatingPointDCT.TransformFDCT(ref source); + + this.CompareBlocks(expected, actual, 1f); + + //int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-1000, 1000, seed); + //float[] floatSrc = intData.ConvertAllToFloat(); - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } + //ReferenceImplementations.AccurateDCT.TransformFDCTInplace(intData); + + //float[] dest = new float[64]; + //float[] temp = new float[64]; + + //ReferenceImplementations.FastFloatingPointDCT.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: false); + + //this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); } + + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs index 49187d9bc..4fdfd62f3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -10,7 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public partial class ReferenceImplementationsTests { - public class StandardIntegerDCT { public StandardIntegerDCT(ITestOutputHelper output) @@ -34,10 +33,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8 expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); Block8x8 actual = ReferenceImplementations.StandardIntegerDCT.TransformIDCT(ref source); + Block8x8F sourceF = source.AsFloatBlock(); + Block8x8F wut0 = ReferenceImplementations.FastFloatingPointDCT.TransformIDCT(ref sourceF); + Block8x8 wut1 = wut0.RoundAsInt16Block(); + long diff = Block8x8.TotalDifference(ref expected, ref actual); this.Output.WriteLine(expected.ToString()); this.Output.WriteLine(actual.ToString()); + this.Output.WriteLine(wut1.ToString()); this.Output.WriteLine("DIFFERENCE: "+diff); + + Assert.True(diff < 4); + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void FDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + int[] data = Create8x8RandomIntData(-1000, 1000, seed); + + Block8x8 source = default(Block8x8); + source.LoadFrom(data); + + Block8x8 expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); + Block8x8 actual = ReferenceImplementations.StandardIntegerDCT.TransformFDCT(ref source); + + long diff = Block8x8.TotalDifference(ref expected, ref actual); + this.Output.WriteLine(expected.ToString()); + this.Output.WriteLine(actual.ToString()); + this.Output.WriteLine("DIFFERENCE: " + diff); + + Assert.True(diff < 4); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs index 19602579a..057a84fde 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs @@ -11,6 +11,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils using System.Diagnostics; using System.Text; + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + using Xunit; using Xunit.Abstractions; public class JpegUtilityTestFixture : MeasureFixture @@ -133,5 +136,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Debug.WriteLine(msg); this.Output.WriteLine(msg); } + + internal void CompareBlocks(Block8x8 a, Block8x8 b, float tolerance) => + this.CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), tolerance); + + internal void CompareBlocks(Block8x8F a, Block8x8F b, float tolerance) + => this.CompareBlocks(a.ToArray(), b.ToArray(), tolerance); + + internal void CompareBlocks(Span a, Span b, float tolerance) + { + ApproximateFloatComparer comparer = new ApproximateFloatComparer(tolerance); + double totalDifference = 0.0; + + bool failed = false; + + for (int i = 0; i < 64; i++) + { + float expected = a[i]; + float actual = b[i]; + totalDifference += Math.Abs(expected - actual); + + if (!comparer.Equals(expected, actual)) + { + failed = true; + this.Output.WriteLine($"Difference too large at index {i}"); + } + } + + this.Output.WriteLine("TOTAL DIFF: "+totalDifference); + Assert.False(failed); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs index d6c2bc454..7f80b4f98 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs @@ -100,20 +100,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils double tmp, tmp2; Block8x8F res = default(Block8x8F); - for (v=0; v<8; v++) { - for (u=0; u<8; u++) { - tmp = 0.0; - for (y=0; y<8; y++) { - tmp2 = 0.0; - for (x=0; x<8; x++) { - tmp2 += (double) block[y, x] * CosLut[x, u]; + for (v = 0; v < 8; v++) + { + for (u = 0; u < 8; u++) + { + tmp = 0.0; + for (y = 0; y < 8; y++) + { + tmp2 = 0.0; + for (x = 0; x < 8; x++) + { + tmp2 += (double)block[y,x] * CosLut[x,u]; } - tmp += CosLut[y, v] * tmp2; + tmp += CosLut[y,v] * tmp2; } - res[v, u] = (float)tmp; - } + res[v,u] = (float) tmp; + } } - + return res; } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.FastFloatingPointDCT.cs index 5c3b65a1e..6aea87330 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.FastFloatingPointDCT.cs @@ -4,12 +4,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils using System.Numerics; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; internal static partial class ReferenceImplementations { internal static class FastFloatingPointDCT { + public static Block8x8F TransformIDCT(ref Block8x8F source) + { + float[] s = new float[64]; + source.CopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; + + iDCT2D_llm(s, d, temp); + Block8x8F result = default(Block8x8F); + result.LoadFrom(d); + return result; + } + + public static Block8x8F TransformFDCT(ref Block8x8F source) + { + float[] s = new float[64]; + source.CopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; + + fDCT2D_llm(s, d, temp); + Block8x8F result = default(Block8x8F); + result.LoadFrom(d); + return result; + } + /// /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs index 243969cab..a1f70bcdd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs @@ -75,6 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils return result; } + [Obsolete("Looks like this method produces really bad results for bigger values!")] public static Block8x8 TransformIDCT(ref Block8x8 block) { int[] temp = new int[Block8x8.Size]; @@ -231,6 +232,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. /// /// The source block of coefficients + [Obsolete("Looks like this method produces really bad results for bigger values!")] public static void TransformIDCTInplace(Span src) { // Horizontal 1-D IDCT.