diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index b058fad4ed..1839bf2f8d 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -342,6 +342,7 @@ True AC DC + DCT EOF FDCT IDCT diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs index 51016c8288..0a15c37f01 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -44,6 +44,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } + public short this[int y, int x] + { + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } + public static bool operator ==(Block8x8 left, Block8x8 right) { return left.Equals(right); @@ -83,9 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common short* fp = blockPtr->data; fp[idx] = value; } - - public short GetValueAt(int x, int y) => this[(y * 8) + x]; - + public Block8x8F AsFloatBlock() { // TODO: Optimize this @@ -112,6 +116,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common Unsafe.CopyBlock(ref destRef, ref selfRef, Size * sizeof(short)); } + public void CopyTo(Span destination) + { + for (int i = 0; i < Size; i++) + { + destination[i] = this[i]; + } + } + + public void LoadFrom(Span source) + { + for (int i = 0; i < Size; i++) + { + this[i] = (short)source[i]; + } + } + [Conditional("DEBUG")] private static void GuardBlockIndex(int idx) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 491908f92a..b3220dea69 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -83,6 +83,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } + public float this[int y, int x] + { + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } + /// /// Pointer-based "Indexer" (getter part) /// @@ -113,17 +119,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common fp[idx] = value; } - public Block8x8 AsInt16Block() - { - // TODO: Optimize this - var result = default(Block8x8); - for (int i = 0; i < Size; i++) - { - result[i] = (short)this[i]; - } + //public Block8x8 AsInt16Block() + //{ + // // TODO: Optimize this + // var result = default(Block8x8); + // for (int i = 0; i < Size; i++) + // { + // result[i] = (short)this[i]; + // } - return result; - } + // return result; + //} /// /// Fill the block with defaults (zeroes) @@ -413,6 +419,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common a.V7R = DivideRound(a.V7R, b.V7R); } + public void RoundInto(ref Block8x8 dest) + { + for (int i = 0; i < Size; i++) + { + float val = this[i]; + if (val < 0) + { + val -= 0.5f; + } + else + { + val += 0.5f; + } + dest[i] = (short)val; + } + } + + public Block8x8 RoundAsInt16Block() + { + var result = default(Block8x8); + this.RoundInto(ref result); + return result; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 15609991d2..edf3162d27 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -291,10 +291,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public unsafe void UnzigDivRound(int seed) { Block8x8F block = new Block8x8F(); - block.LoadFrom(Create8x8RandomFloatData(-2000, 2000, seed)); + block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); Block8x8F qt = new Block8x8F(); - qt.LoadFrom(Create8x8RandomFloatData(-2000, 2000, seed)); + qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); UnzigData unzig = UnzigData.Create(); @@ -314,19 +314,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } + //[Fact] + //public void AsInt16Block() + //{ + // float[] data = Create8x8FloatData(); + + // var source = default(Block8x8F); + // source.LoadFrom(data); + + // Block8x8 dest = source.AsInt16Block(); + + // for (int i = 0; i < Block8x8F.Size; i++) + // { + // Assert.Equal((short)data[i], dest[i]); + // } + //} + [Fact] - public void AsInt16Block() + public void RoundInto() { - float[] data = Create8x8FloatData(); + float[] data = Create8x8RandomFloatData(-1000, 1000); var source = default(Block8x8F); source.LoadFrom(data); + var dest = default(Block8x8); - Block8x8 dest = source.AsInt16Block(); + source.RoundInto(ref dest); - for (int i = 0; i < Block8x8F.Size; i++) + for (int i = 0; i < Block8x8.Size; i++) { - Assert.Equal((short)data[i], dest[i]); + float expectedFloat = data[i]; + short expectedShort = (short) Math.Round(expectedFloat); + short actualShort = dest[i]; + + Assert.Equal(expectedShort, actualShort); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index 45096a8b6a..8c1d5fb906 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -115,12 +115,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void GetValueAt() + public void IndexerYX() { var block = default(Block8x8); block[8 * 3 + 5] = 42; - short value = block.GetValueAt(5, 3); + short value = block[3, 5]; Assert.Equal(42, value); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 562027b88c..8c9c4bbca3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(3)] public void TransformIDCT(int seed) { - Span sourceArray = JpegUtilityTestFixture.Create8x8RandomFloatData(-200, 200, seed); + Span sourceArray = JpegUtilityTestFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); float[] expectedDestArray = new float[64]; float[] tempArray = new float[64]; @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void FDCT8x4_LeftPart(int seed) { - Span src = JpegUtilityTestFixture.Create8x8RandomFloatData(-200, 200, seed); + Span src = JpegUtilityTestFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); Block8x8F srcBlock = new Block8x8F(); srcBlock.LoadFrom(src); @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void FDCT8x4_RightPart(int seed) { - Span src = JpegUtilityTestFixture.Create8x8RandomFloatData(-200, 200, seed); + Span src = JpegUtilityTestFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); Block8x8F srcBlock = new Block8x8F(); srcBlock.LoadFrom(src); @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void TransformFDCT(int seed) { - Span src = JpegUtilityTestFixture.Create8x8RandomFloatData(-200, 200, seed); + Span src = JpegUtilityTestFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); Block8x8F srcBlock = new Block8x8F(); srcBlock.LoadFrom(src); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs new file mode 100644 index 0000000000..4d7ad8a7e1 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -0,0 +1,113 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System; + + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + + public partial class ReferenceImplementationsTests + { + public class FastFloatingPointDCT + { + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void ForwardThenInverse(int seed, int startAt) + { + int[] data = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + float[] src = data.ConvertAllToFloat(); + float[] dest = new float[64]; + float[] temp = new float[64]; + + 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)); + } + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void IDCT_IsEquivalentTo_StandardIntegerImplementation(int seed) + { + int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + Span floatSrc = intData.ConvertAllToFloat(); + + ReferenceImplementations.StandardIntegerDCT.TransformIDCTInplace(intData); + + float[] dest = new float[64]; + float[] temp = new float[64]; + + 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)); + } + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void IDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + float[] floatSrc = intData.ConvertAllToFloat(); + + ReferenceImplementations.AccurateDCT.TransformIDCTInplace(intData); + + float[] dest = new float[64]; + float[] temp = new float[64]; + + 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)); + } + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void FDCT_IsEquivalentTo_StandardIntegerImplementation(int seed) + { + int[] intData = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + float[] floatSrc = intData.ConvertAllToFloat(); + + ReferenceImplementations.StandardIntegerDCT.TransformFDCTInplace(intData); + + float[] dest = new float[64]; + float[] temp = new float[64]; + + ReferenceImplementations.FastFloatingPointDCT.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); + + for (int i = 0; i < 64; i++) + { + float expected = intData[i]; + float actual = dest[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(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 new file mode 100644 index 0000000000..49187d9bc8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -0,0 +1,73 @@ +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 StandardIntegerDCT + { + public StandardIntegerDCT(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void IDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + int[] data = Create8x8RandomIntData(-1000, 1000, seed); + + Block8x8 source = default(Block8x8); + source.LoadFrom(data); + + Block8x8 expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + Block8x8 actual = ReferenceImplementations.StandardIntegerDCT.TransformIDCT(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); + } + + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void ForwardThenInverse(int seed, int startAt) + { + Span original = JpegUtilityTestFixture.Create8x8RandomIntData(-200, 200, seed); + + Span block = original.AddScalarToAllValues(128); + + ReferenceImplementations.StandardIntegerDCT.TransformFDCTInplace(block); + + for (int i = 0; i < 64; i++) + { + block[i] /= 8; + } + + ReferenceImplementations.StandardIntegerDCT.TransformIDCTInplace(block); + + for (int i = startAt; i < 64; i++) + { + float expected = original[i]; + float actual = (float)block[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index fc9e84cecd..8b97f1208e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -3,124 +3,18 @@ using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - using System; - using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - public class ReferenceImplementationsTests : JpegUtilityTestFixture + public partial class ReferenceImplementationsTests : JpegUtilityTestFixture { public ReferenceImplementationsTests(ITestOutputHelper output) : base(output) { } - - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Idct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) - { - int[] intData = Create8x8RandomIntData(-200, 200, seed); - Span floatSrc = intData.ConvertAllToFloat(); - - ReferenceImplementations.IntegerDCT.TransformIDCTInplace(intData); - - float[] dest = new float[64]; - float[] temp = new float[64]; - - 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)); - } - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void IntegerDCT_ForwardThenInverse(int seed, int startAt) - { - Span original = Create8x8RandomIntData(-200, 200, seed); - - Span block = original.AddScalarToAllValues(128); - - ReferenceImplementations.IntegerDCT.TransformFDCTInplace(block); - - for (int i = 0; i < 64; i++) - { - block[i] /= 8; - } - - ReferenceImplementations.IntegerDCT.TransformIDCTInplace(block); - - for (int i = startAt; i < 64; i++) - { - float expected = original[i]; - float actual = (float)block[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); - } - - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt) - { - int[] data = Create8x8RandomIntData(-200, 200, seed); - float[] src = data.ConvertAllToFloat(); - float[] dest = new float[64]; - float[] temp = new float[64]; - - 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)); - } - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Fdct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) - { - int[] intData = Create8x8RandomIntData(-200, 200, seed); - float[] floatSrc = intData.ConvertAllToFloat(); - - ReferenceImplementations.IntegerDCT.TransformFDCTInplace(intData); - - float[] dest = new float[64]; - float[] temp = new float[64]; - - ReferenceImplementations.FastFloatingPointDCT.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); - - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs index 51c99d261a..19602579ab 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs @@ -76,8 +76,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils return result; } - internal static float[] Create8x8RandomFloatData(int minValue, int maxValue, int seed = 42) - => SpanExtensions.ConvertAllToFloat(Create8x8RandomIntData(minValue, maxValue, seed)); + 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) + { + Random rnd = new Random(seed); + float[] result = new float[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + double val = rnd.NextDouble(); + val *= maxValue - minValue; + val += minValue; + + result[i * 8 + j] = (float)val; + } + } + return result; + } internal void Print8x8Data(T[] data) => this.Print8x8Data(new Span(data)); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 3f1cd89b40..5798491ff4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal float GetBlockValue(Block8x8 block, int x, int y) { float d = (this.MaxVal - this.MinVal); - float val = block.GetValueAt(x, y); + float val = block[y, x]; val -= this.MinVal; val /= d; return val; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs new file mode 100644 index 0000000000..d6c2bc454a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs @@ -0,0 +1,121 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + internal static partial class ReferenceImplementations + { + /// + /// Reference implementations based on: + /// https://github.com/keithw/mympeg2enc/blob/master/idct.c#L222 + /// Claiming: + /// /* reference idct taken from "ieeetest.c" + /// * Written by Tom Lane (tgl@cs.cmu.edu). + /// * Released to public domain 11/22/93. + /// */ + /// + internal static class AccurateDCT + { + private static double[,] CosLut = InitCosLut(); + + + public static Block8x8 TransformIDCT(ref Block8x8 block) + { + Block8x8F temp = block.AsFloatBlock(); + Block8x8F res0 = TransformIDCT(ref temp); + return res0.RoundAsInt16Block(); + } + + public static void TransformIDCTInplace(Span span) + { + var temp = new Block8x8(); + temp.LoadFrom(span); + Block8x8 result = TransformIDCT(ref temp); + result.CopyTo(span); + } + + public static Block8x8 TransformFDCT(ref Block8x8 block) + { + Block8x8F temp = block.AsFloatBlock(); + Block8x8F res0 = TransformFDCT(ref temp); + return res0.RoundAsInt16Block(); + } + + public static void TransformFDCTInplace(Span span) + { + var temp = new Block8x8(); + temp.LoadFrom(span); + Block8x8 result = TransformFDCT(ref temp); + result.CopyTo(span); + } + + + + private static double[,] InitCosLut() + { + double[,] coslu = new double[8,8]; + int a, b; + double tmp; + + for (a = 0; a < 8; a++) + for (b = 0; b < 8; b++) + { + tmp = Math.Cos((double)((a + a + 1) * b) * (3.14159265358979323846 / 16.0)); + if (b == 0) + { + tmp /= Math.Sqrt(2.0); + } + coslu[a, b] = tmp * 0.5; + } + return coslu; + } + + public static Block8x8F TransformIDCT(ref Block8x8F block) + { + int x, y, u, v; + double tmp, tmp2; + Block8x8F res = default(Block8x8F); + + for (y=0; y<8; y++) { + for (x=0; x<8; x++) { + tmp = 0.0; + for (v=0; v<8; v++) { + tmp2 = 0.0; + for (u=0; u<8; u++) { + tmp2 += (double) block[v * 8 + u] * CosLut[x, u]; + } + tmp += CosLut[y, v] * tmp2; + } + res[y, x] = (float)tmp; + } + } + return res; + } + + + public static Block8x8F TransformFDCT(ref Block8x8F block) + { + int x, y, u, v; + 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]; + } + tmp += CosLut[y, v] * tmp2; + } + res[v, u] = (float)tmp; + } + } + + return res; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.IntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs similarity index 84% rename from tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.IntegerDCT.cs rename to tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs index 1c166748bd..243969cab4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.IntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs @@ -2,12 +2,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { using System; + using SixLabors.ImageSharp.Formats.Jpeg.Common; + internal static partial class ReferenceImplementations { /// - /// The "original" libjpeg/golang based DCT implementation is used as reference implementation for tests. + /// Contains the "original" golang based DCT/IDCT implementations as reference implementations. + /// 1. ===== Forward DCT ===== + /// **** The original golang source claims: + /// It is based on the code in jfdctint.c from the Independent JPEG Group, + /// found at http://www.ijg.org/files/jpegsrc.v8c.tar.gz. + /// + /// **** Could be found here as well: + /// https://github.com/mozilla/mozjpeg/blob/master/jfdctint.c + /// + /// 2. ===== Inverse DCT ===== + /// + /// The golang source claims: + /// http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_13818-4_2004_Conformance_Testing/Video/verifier/mpeg2decode_960109.tar.gz + /// The referenced MPEG2 code claims: + /// /**********************************************************/ + /// /* inverse two dimensional DCT, Chen-Wang algorithm */ + /// /* (cf. IEEE ASSP-32, pp. 803-816, Aug. 1984) */ + /// /* 32-bit integer arithmetic (8 bit coefficients) */ + /// /* 11 mults, 29 adds per DCT */ + /// /* sE, 18.8.91 */ + /// /**********************************************************/ + /// /* coefficients extended to 12 bit for IEEE1180-1990 */ + /// /* compliance sE, 2.1.94 */ + /// /**********************************************************/ + /// + /// **** The code looks pretty similar to the standard libjpeg IDCT, but without quantization: + /// https://github.com/mozilla/mozjpeg/blob/master/jidctint.c /// - public static class IntegerDCT + public static class StandardIntegerDCT { private const int fix_0_298631336 = 2446; private const int fix_0_390180644 = 3196; @@ -37,6 +65,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// private const int CenterJSample = 128; + public static Block8x8 TransformFDCT(ref Block8x8 block) + { + int[] temp = new int[Block8x8.Size]; + block.CopyTo(temp); + TransformFDCTInplace(temp); + var result = default(Block8x8); + result.LoadFrom(temp); + return result; + } + + public static Block8x8 TransformIDCT(ref Block8x8 block) + { + int[] temp = new int[Block8x8.Size]; + block.CopyTo(temp); + TransformIDCTInplace(temp); + var result = default(Block8x8); + result.LoadFrom(temp); + return result; + } + /// /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. /// Leave results scaled up by an overall factor of 8.