diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpanExtensions.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpanExtensions.cs index 9b89c8e821..451c7276e3 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpanExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpanExtensions.cs @@ -9,9 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils using System; /// - /// MutableSpan Extensions + /// Span Extensions /// - internal static class MutableSpanExtensions + internal static class SpanExtensions { /// /// Save to a Vector4 @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils /// /// Source /// A new with float values - public static Span ConvertToInt32MutableSpan(this Span src) + public static Span ConvertToInt32Span(this Span src) { int[] result = new int[src.Length]; for (int i = 0; i < src.Length; i++) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index a3a8d1218b..15609991d2 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -1,24 +1,23 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Diagnostics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg.Common; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; - -using Xunit; -using Xunit.Abstractions; // Uncomment this to turn unit tests into benchmarks: //#define BENCHMARKING // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { using System; + using System.Diagnostics; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; public class Block8x8FTests : JpegUtilityTestFixture { @@ -222,88 +221,7 @@ namespace SixLabors.ImageSharp.Tests sw.Stop(); this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms"); } - - [Fact] - public void iDCT2D8x4_LeftPart() - { - float[] sourceArray = Create8x8FloatData(); - float[] expectedDestArray = new float[64]; - - ReferenceImplementations.iDCT2D8x4_32f(sourceArray, expectedDestArray); - - Block8x8F source = new Block8x8F(); - source.LoadFrom(sourceArray); - - Block8x8F dest = new Block8x8F(); - - DCT.IDCT8x4_LeftPart(ref source, ref dest); - - float[] actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); - - this.Print8x8Data(expectedDestArray); - this.Output.WriteLine("**************"); - this.Print8x8Data(actualDestArray); - - Assert.Equal(expectedDestArray, actualDestArray); - } - - [Fact] - public void iDCT2D8x4_RightPart() - { - float[] sourceArray = Create8x8FloatData(); - float[] expectedDestArray = new float[64]; - - ReferenceImplementations.iDCT2D8x4_32f(sourceArray.AsSpan().Slice(4), expectedDestArray.AsSpan().Slice(4)); - - Block8x8F source = new Block8x8F(); - source.LoadFrom(sourceArray); - - Block8x8F dest = new Block8x8F(); - - DCT.IDCT8x4_RightPart(ref source, ref dest); - - float[] actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); - - this.Print8x8Data(expectedDestArray); - this.Output.WriteLine("**************"); - this.Print8x8Data(actualDestArray); - - Assert.Equal(expectedDestArray, actualDestArray); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void TransformIDCT(int seed) - { - Span sourceArray = Create8x8RandomFloatData(-200, 200, seed); - float[] expectedDestArray = new float[64]; - float[] tempArray = new float[64]; - - ReferenceImplementations.iDCT2D_llm(sourceArray, expectedDestArray, tempArray); - - // ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray); - Block8x8F source = new Block8x8F(); - source.LoadFrom(sourceArray); - - Block8x8F dest = new Block8x8F(); - Block8x8F tempBuffer = new Block8x8F(); - - DCT.TransformIDCT(ref source, ref dest, ref tempBuffer); - - float[] actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); - - this.Print8x8Data(expectedDestArray); - this.Output.WriteLine("**************"); - this.Print8x8Data(actualDestArray); - Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f)); - Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f)); - } - + [Fact] public unsafe void CopyColorsTo() { @@ -367,74 +285,6 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void FDCT8x4_LeftPart(int seed) - { - Span src = Create8x8RandomFloatData(-200, 200, seed); - Block8x8F srcBlock = new Block8x8F(); - srcBlock.LoadFrom(src); - - Block8x8F destBlock = new Block8x8F(); - - float[] expectedDest = new float[64]; - - ReferenceImplementations.fDCT2D8x4_32f(src, expectedDest); - DCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); - - float[] actualDest = new float[64]; - destBlock.CopyTo(actualDest); - - Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void FDCT8x4_RightPart(int seed) - { - Span src = Create8x8RandomFloatData(-200, 200, seed); - Block8x8F srcBlock = new Block8x8F(); - srcBlock.LoadFrom(src); - - Block8x8F destBlock = new Block8x8F(); - - float[] expectedDest = new float[64]; - - ReferenceImplementations.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan().Slice(4)); - DCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); - - float[] actualDest = new float[64]; - destBlock.CopyTo(actualDest); - - Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TransformFDCT(int seed) - { - Span src = Create8x8RandomFloatData(-200, 200, seed); - Block8x8F srcBlock = new Block8x8F(); - srcBlock.LoadFrom(src); - - Block8x8F destBlock = new Block8x8F(); - - float[] expectedDest = new float[64]; - float[] temp1 = new float[64]; - Block8x8F temp2 = new Block8x8F(); - - ReferenceImplementations.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); - DCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); - - float[] actualDest = new float[64]; - destBlock.CopyTo(actualDest); - - Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); - } - [Theory] [InlineData(1)] [InlineData(2)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index 6df413a850..45096a8b6a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -1,9 +1,8 @@ // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - using Moq; - using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs new file mode 100644 index 0000000000..562027b88c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -0,0 +1,175 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; + + public static class DCTTests + { + public class FastFloatingPoint : JpegUtilityTestFixture + { + public FastFloatingPoint(ITestOutputHelper output) + : base(output) + { + } + + + [Fact] + public void iDCT2D8x4_LeftPart() + { + float[] sourceArray = JpegUtilityTestFixture.Create8x8FloatData(); + float[] expectedDestArray = new float[64]; + + ReferenceImplementations.FastFloatingPointDCT.iDCT2D8x4_32f(sourceArray, expectedDestArray); + + Block8x8F source = new Block8x8F(); + source.LoadFrom(sourceArray); + + Block8x8F dest = new Block8x8F(); + + DCT.IDCT8x4_LeftPart(ref source, ref dest); + + float[] actualDestArray = new float[64]; + dest.CopyTo(actualDestArray); + + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); + + Assert.Equal(expectedDestArray, actualDestArray); + } + + [Fact] + public void iDCT2D8x4_RightPart() + { + float[] sourceArray = JpegUtilityTestFixture.Create8x8FloatData(); + float[] expectedDestArray = new float[64]; + + ReferenceImplementations.FastFloatingPointDCT.iDCT2D8x4_32f(sourceArray.AsSpan().Slice(4), expectedDestArray.AsSpan().Slice(4)); + + Block8x8F source = new Block8x8F(); + source.LoadFrom(sourceArray); + + Block8x8F dest = new Block8x8F(); + + DCT.IDCT8x4_RightPart(ref source, ref dest); + + float[] actualDestArray = new float[64]; + dest.CopyTo(actualDestArray); + + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); + + Assert.Equal(expectedDestArray, actualDestArray); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void TransformIDCT(int seed) + { + Span sourceArray = JpegUtilityTestFixture.Create8x8RandomFloatData(-200, 200, seed); + float[] expectedDestArray = new float[64]; + float[] tempArray = new float[64]; + + ReferenceImplementations.FastFloatingPointDCT.iDCT2D_llm(sourceArray, expectedDestArray, tempArray); + + // ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray); + Block8x8F source = new Block8x8F(); + source.LoadFrom(sourceArray); + + Block8x8F dest = new Block8x8F(); + Block8x8F tempBuffer = new Block8x8F(); + + DCT.TransformIDCT(ref source, ref dest, ref tempBuffer); + + float[] actualDestArray = new float[64]; + dest.CopyTo(actualDestArray); + + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); + Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f)); + Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f)); + } + + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void FDCT8x4_LeftPart(int seed) + { + Span src = JpegUtilityTestFixture.Create8x8RandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + Block8x8F destBlock = new Block8x8F(); + + float[] expectedDest = new float[64]; + + ReferenceImplementations.FastFloatingPointDCT.fDCT2D8x4_32f(src, expectedDest); + DCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); + + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void FDCT8x4_RightPart(int seed) + { + Span src = JpegUtilityTestFixture.Create8x8RandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + Block8x8F destBlock = new Block8x8F(); + + float[] expectedDest = new float[64]; + + ReferenceImplementations.FastFloatingPointDCT.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan().Slice(4)); + DCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); + + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TransformFDCT(int seed) + { + Span src = JpegUtilityTestFixture.Create8x8RandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + Block8x8F destBlock = new Block8x8F(); + + float[] expectedDest = new float[64]; + float[] temp1 = new float[64]; + Block8x8F temp2 = new Block8x8F(); + + ReferenceImplementations.FastFloatingPointDCT.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + DCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); + + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } + + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index df6cd69e70..9bbb2558b5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -1,16 +1,11 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.IO; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; + // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - using System; using System.IO; using System.Linq; @@ -19,8 +14,8 @@ namespace SixLabors.ImageSharp.Tests using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; using Xunit.Abstractions; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 60aaea8469..3bd1ed265e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,22 +1,23 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; -using System.IO; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; - -using Xunit; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System.Collections.Generic; + using System.IO; + + using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Processing; + using SixLabors.Primitives; + + using Xunit; + using Xunit.Abstractions; + public class JpegEncoderTests : MeasureFixture { public static IEnumerable AllBmpFiles => TestImages.Bmp.All; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index c87fce6d8d..113596ee8f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -1,18 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.IO; -using System.Linq; -using System.Numerics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System; + using System.IO; + using System.Linq; + using System.Numerics; + + using SixLabors.ImageSharp.Formats.Jpeg; + + using Xunit; + using Xunit.Abstractions; + public class JpegProfilingBenchmarks : MeasureFixture { public JpegProfilingBenchmarks(ITestOutputHelper output) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs index ad728d8dd8..887e9d7e95 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs @@ -1,16 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Numerics; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System.Numerics; + + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; + using SixLabors.ImageSharp.PixelFormats; + + using Xunit; + public class JpegUtilsTests { public static Image CreateTestImage() diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs deleted file mode 100644 index e3e517a8b2..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs +++ /dev/null @@ -1,483 +0,0 @@ -namespace SixLabors.ImageSharp.Tests -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Numerics; - using System.Reflection; - - using SixLabors.ImageSharp.Formats.Jpeg; - using SixLabors.ImageSharp.Formats.Jpeg.Common; - using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; - using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; - using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; - using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; - using SixLabors.ImageSharp.Memory; - using SixLabors.ImageSharp.PixelFormats; - using SixLabors.Primitives; - - using Xunit; - - internal static class LibJpegTools - { - public class SpectralData : IEquatable - { - public int ComponentCount { get; private set; } - - public ComponentData[] Components { get; private set; } - - private SpectralData(Array wholeImage) - { - this.ComponentCount = 0; - - for (int i = 0; i < wholeImage.Length && wholeImage.GetValue(i) != null; i++) - { - this.ComponentCount++; - } - - this.Components = new ComponentData[this.ComponentCount]; - - for (int i = 0; i < this.ComponentCount; i++) - { - object jVirtArray = wholeImage.GetValue(i); - Array bloxSource = (Array)GetNonPublicMember(jVirtArray, "m_buffer"); - - this.Components[i] = ComponentData.Load(bloxSource, i); - } - } - - internal SpectralData(ComponentData[] components) - { - this.ComponentCount = components.Length; - this.Components = components; - } - - public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder) - { - FrameComponent[] srcComponents = decoder.Frame.Components; - ComponentData[] destComponents = srcComponents.Select(ComponentData.Load).ToArray(); - - return new SpectralData(destComponents); - } - - public static SpectralData LoadFromImageSharpDecoder(OldJpegDecoderCore decoder) - { - OldComponent[] srcComponents = decoder.Components; - ComponentData[] destComponents = srcComponents.Select(ComponentData.Load).ToArray(); - - return new SpectralData(destComponents); - } - - public Image TryCreateRGBSpectralImage() - { - if (this.ComponentCount != 3) return null; - - ComponentData c0 = this.Components[0]; - ComponentData c1 = this.Components[1]; - ComponentData c2 = this.Components[2]; - - if (c0.Size != c1.Size || c1.Size != c2.Size) - { - return null; - } - - Image result = new Image(c0.WidthInBlocks * 8, c0.HeightInBlocks * 8); - - for (int by = 0; by < c0.HeightInBlocks; by++) - { - for (int bx = 0; bx < c0.WidthInBlocks; bx++) - { - this.WriteToImage(bx, by, result); - } - } - return result; - } - - internal void WriteToImage(int bx, int by, Image image) - { - ComponentData c0 = this.Components[0]; - ComponentData c1 = this.Components[1]; - ComponentData c2 = this.Components[2]; - - Block8x8 block0 = c0.Blocks[bx, by]; - Block8x8 block1 = c1.Blocks[bx, by]; - Block8x8 block2 = c2.Blocks[bx, by]; - - float d0 = (c0.MaxVal - c0.MinVal); - float d1 = (c1.MaxVal - c1.MinVal); - float d2 = (c2.MaxVal - c2.MinVal); - - for (int y = 0; y < 8; y++) - { - for (int x = 0; x < 8; x++) - { - float val0 = c0.GetBlockValue(block0, x, y); - float val1 = c0.GetBlockValue(block1, x, y); - float val2 = c0.GetBlockValue(block2, x, y); - - Vector4 v = new Vector4(val0, val1, val2, 1); - Rgba32 color = default(Rgba32); - color.PackFromVector4(v); - - int yy = by * 8 + y; - int xx = bx * 8 + x; - image[xx, yy] = color; - } - } - } - - public bool Equals(SpectralData other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - if (this.ComponentCount != other.ComponentCount) - { - return false; - } - - for (int i = 0; i < this.ComponentCount; i++) - { - ComponentData a = this.Components[i]; - ComponentData b = other.Components[i]; - if (!a.Equals(b)) return false; - } - return true; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((SpectralData)obj); - } - - public override int GetHashCode() - { - unchecked - { - return (this.ComponentCount * 397) ^ (this.Components != null ? this.Components[0].GetHashCode() : 0); - } - } - - public static bool operator ==(SpectralData left, SpectralData right) - { - return Equals(left, right); - } - - public static bool operator !=(SpectralData left, SpectralData right) - { - return !Equals(left, right); - } - } - - public class ComponentData : IEquatable, IJpegComponent - { - public ComponentData(int heightInBlocks, int widthInBlocks, int index) - { - this.HeightInBlocks = heightInBlocks; - this.WidthInBlocks = widthInBlocks; - this.Index = index; - this.Blocks = new Buffer2D(this.WidthInBlocks, this.HeightInBlocks); - } - - public Size Size => new Size(this.WidthInBlocks, this.HeightInBlocks); - - public int Index { get; } - - public int HeightInBlocks { get; } - - public int WidthInBlocks { get; } - - public Buffer2D Blocks { get; private set; } - - public short MinVal { get; private set; } = short.MaxValue; - - public short MaxVal { get; private set; } = short.MinValue; - - public static ComponentData Load(Array bloxSource, int index) - { - int yCount = bloxSource.Length; - Array row0 = (Array)bloxSource.GetValue(0); - int xCount = row0.Length; - ComponentData result = new ComponentData(yCount, xCount, index); - result.Init(bloxSource); - return result; - } - - private void Init(Array bloxSource) - { - for (int y = 0; y < bloxSource.Length; y++) - { - Array row = (Array)bloxSource.GetValue(y); - for (int x = 0; x < row.Length; x++) - { - object jBlock = row.GetValue(x); - short[] data = (short[])GetNonPublicMember(jBlock, "data"); - this.MakeBlock(data, y, x); - } - } - } - - internal void MakeBlock(short[] data, int y, int x) - { - this.MinVal = Math.Min(this.MinVal, data.Min()); - this.MaxVal = Math.Max(this.MaxVal, data.Max()); - this.Blocks[x, y] = new Block8x8(data); - } - - public static ComponentData Load(FrameComponent c, int index) - { - var result = new ComponentData( - c.BlocksPerColumnForMcu, - c.BlocksPerLineForMcu, - index - ); - - for (int y = 0; y < result.HeightInBlocks; y++) - { - for (int x = 0; x < result.WidthInBlocks; x++) - { - short[] data = c.GetBlockBuffer(y, x).ToArray(); - result.MakeBlock(data, y, x); - } - } - - return result; - } - - public static ComponentData Load(OldComponent c) - { - var result = new ComponentData( - c.HeightInBlocks, - c.WidthInBlocks, - c.Index - ); - - for (int y = 0; y < result.HeightInBlocks; y++) - { - for (int x = 0; x < result.WidthInBlocks; x++) - { - short[] data = c.GetBlockReference(x, y).ToArray(); - result.MakeBlock(data, y, x); - } - } - - return result; - } - - public Image CreateGrayScaleImage() - { - Image result = new Image(this.WidthInBlocks * 8, this.HeightInBlocks * 8); - - for (int by = 0; by < this.HeightInBlocks; by++) - { - for (int bx = 0; bx < this.WidthInBlocks; bx++) - { - this.WriteToImage(bx, by, result); - } - } - return result; - } - - internal void WriteToImage(int bx, int by, Image image) - { - Block8x8 block = this.Blocks[bx, by]; - - for (int y = 0; y < 8; y++) - { - for (int x = 0; x < 8; x++) - { - var val = this.GetBlockValue(block, x, y); - - Vector4 v = new Vector4(val, val, val, 1); - Rgba32 color = default(Rgba32); - color.PackFromVector4(v); - - int yy = by * 8 + y; - int xx = bx * 8 + x; - image[xx, yy] = color; - } - } - } - - internal float GetBlockValue(Block8x8 block, int x, int y) - { - float d = (this.MaxVal - this.MinVal); - float val = block.GetValueAt(x, y); - val -= this.MinVal; - val /= d; - return val; - } - - public bool Equals(ComponentData other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks - && this.WidthInBlocks == other.WidthInBlocks; - //&& this.MinVal == other.MinVal - //&& this.MaxVal == other.MaxVal; - if (!ok) return false; - - for (int y = 0; y < this.HeightInBlocks; y++) - { - for (int x = 0; x < this.WidthInBlocks; x++) - { - Block8x8 a = this.Blocks[x, y]; - Block8x8 b = other.Blocks[x, y]; - if (!a.Equals(b)) return false; - } - } - return true; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ComponentData)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = this.Index; - hashCode = (hashCode * 397) ^ this.HeightInBlocks; - hashCode = (hashCode * 397) ^ this.WidthInBlocks; - hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); - hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(ComponentData left, ComponentData right) - { - return Equals(left, right); - } - - public static bool operator !=(ComponentData left, ComponentData right) - { - return !Equals(left, right); - } - - - } - - internal static FieldInfo GetNonPublicField(object obj, string fieldName) - { - Type type = obj.GetType(); - return type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); - } - - internal static object GetNonPublicMember(object obj, string fieldName) - { - FieldInfo fi = GetNonPublicField(obj, fieldName); - return fi.GetValue(obj); - } - - public static (double total, double average) CalculateDifference(ComponentData expected, ComponentData actual) - { - BigInteger totalDiff = 0; - if (actual.WidthInBlocks < expected.WidthInBlocks) - { - throw new Exception("actual.WidthInBlocks < expected.WidthInBlocks"); - } - - if (actual.HeightInBlocks < expected.HeightInBlocks) - { - throw new Exception("actual.HeightInBlocks < expected.HeightInBlocks"); - } - - int w = expected.WidthInBlocks; - int h = expected.HeightInBlocks; - for (int y = 0; y < h; y++) - { - for (int x = 0; x < w; x++) - { - Block8x8 aa = expected.Blocks[x, y]; - Block8x8 bb = actual.Blocks[x, y]; - - long diff = Block8x8.TotalDifference(ref aa, ref bb); - totalDiff += diff; - } - } - - int count = w * h; - double total = (double)totalDiff; - double average = (double)totalDiff / (count * Block8x8.Size); - return (total, average); - } - - private static string DumpToolFullPath => Path.Combine( - TestEnvironment.ToolsDirectoryFullPath, - @"jpeg\dump-jpeg-coeffs.exe"); - - public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) - { - string args = $@"""{sourceFile}"" ""{destFile}"""; - var process = Process.Start(DumpToolFullPath, args); - process.WaitForExit(); - } - - public static SpectralData ExtractSpectralData(string inputFile) - { - TestFile testFile = TestFile.Create(inputFile); - - string outDir = TestEnvironment.CreateOutputDirectory(".Temp", $"JpegCoeffs"); - string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; - string coeffFileFullPath = Path.Combine(outDir, fn); - - try - { - RunDumpJpegCoeffsTool(testFile.FullPath, coeffFileFullPath); - - using (var dumpStream = new FileStream(coeffFileFullPath, FileMode.Open)) - using (var rdr = new BinaryReader(dumpStream)) - { - int componentCount = rdr.ReadInt16(); - ComponentData[] result = new ComponentData[componentCount]; - - for (int i = 0; i < componentCount; i++) - { - int widthInBlocks = rdr.ReadInt16(); - int heightInBlocks = rdr.ReadInt16(); - ComponentData resultComponent = new ComponentData(heightInBlocks, widthInBlocks, i); - result[i] = resultComponent; - } - - byte[] buffer = new byte[64*sizeof(short)]; - - for (int i = 0; i < result.Length; i++) - { - ComponentData c = result[i]; - - for (int y = 0; y < c.HeightInBlocks; y++) - { - for (int x = 0; x < c.WidthInBlocks; x++) - { - rdr.Read(buffer, 0, buffer.Length); - - short[] block = buffer.AsSpan().NonPortableCast().ToArray(); - c.MakeBlock(block, y, x); - } - } - } - - return new SpectralData(result); - } - } - finally - { - if (File.Exists(coeffFileFullPath)) - { - File.Delete(coeffFileFullPath); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs index 99163ae5f4..58923d198e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs @@ -1,8 +1,9 @@ -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { using System.IO; using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs deleted file mode 100644 index e86f96572b..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ /dev/null @@ -1,918 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg.Common; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; - -// ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests - /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd - /// - internal static class ReferenceImplementations - { - /// - /// Transpose 8x8 block stored linearly in a (inplace) - /// - /// - internal static void Transpose8x8(Span data) - { - for (int i = 1; i < 8; i++) - { - int i8 = i * 8; - for (int j = 0; j < i; j++) - { - float tmp = data[i8 + j]; - data[i8 + j] = data[j * 8 + i]; - data[j * 8 + i] = tmp; - } - } - } - - /// - /// Transpose 8x8 block stored linearly in a - /// - internal static void Transpose8x8(Span src, Span dest) - { - for (int i = 0; i < 8; i++) - { - int i8 = i * 8; - for (int j = 0; j < 8; j++) - { - dest[j * 8 + i] = src[i8 + j]; - } - } - } - - /// - /// The "original" libjpeg/golang based DCT implementation is used as reference implementation for tests. - /// - public static class IntegerReferenceDCT - { - private const int fix_0_298631336 = 2446; - private const int fix_0_390180644 = 3196; - private const int fix_0_541196100 = 4433; - private const int fix_0_765366865 = 6270; - private const int fix_0_899976223 = 7373; - private const int fix_1_175875602 = 9633; - private const int fix_1_501321110 = 12299; - private const int fix_1_847759065 = 15137; - private const int fix_1_961570560 = 16069; - private const int fix_2_053119869 = 16819; - private const int fix_2_562915447 = 20995; - private const int fix_3_072711026 = 25172; - - /// - /// The number of bits - /// - private const int Bits = 13; - - /// - /// The number of bits to shift by on the first pass. - /// - private const int Pass1Bits = 2; - - /// - /// The value to shift by - /// - private const int CenterJSample = 128; - - /// - /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. - /// Leave results scaled up by an overall factor of 8. - /// - /// The block of coefficients. - public static void TransformFDCTInplace(Span block) - { - // Pass 1: process rows. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - int x0 = block[y8]; - int x1 = block[y8 + 1]; - int x2 = block[y8 + 2]; - int x3 = block[y8 + 3]; - int x4 = block[y8 + 4]; - int x5 = block[y8 + 5]; - int x6 = block[y8 + 6]; - int x7 = block[y8 + 7]; - - int tmp0 = x0 + x7; - int tmp1 = x1 + x6; - int tmp2 = x2 + x5; - int tmp3 = x3 + x4; - - int tmp10 = tmp0 + tmp3; - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = x0 - x7; - tmp1 = x1 - x6; - tmp2 = x2 - x5; - tmp3 = x3 - x4; - - block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; - block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; - z1 += 1 << (Bits - Pass1Bits - 1); - block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); - block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * fix_1_175875602; - z1 += 1 << (Bits - Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); - block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); - } - - // Pass 2: process columns. - // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. - for (int x = 0; x < 8; x++) - { - int tmp0 = block[x] + block[56 + x]; - int tmp1 = block[8 + x] + block[48 + x]; - int tmp2 = block[16 + x] + block[40 + x]; - int tmp3 = block[24 + x] + block[32 + x]; - - int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = block[x] - block[56 + x]; - tmp1 = block[8 + x] - block[48 + x]; - tmp2 = block[16 + x] - block[40 + x]; - tmp3 = block[24 + x] - block[32 + x]; - - block[x] = (tmp10 + tmp11) >> Pass1Bits; - block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - - int z1 = (tmp12 + tmp13) * fix_0_541196100; - z1 += 1 << (Bits + Pass1Bits - 1); - block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); - block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * fix_1_175875602; - z1 += 1 << (Bits + Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); - block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); - block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); - block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); - } - - } - private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) - private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) - private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) - private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) - private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) - private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) - - private const int w1pw7 = w1 + w7; - private const int w1mw7 = w1 - w7; - private const int w2pw6 = w2 + w6; - private const int w2mw6 = w2 - w6; - private const int w3pw5 = w3 + w5; - private const int w3mw5 = w3 - w5; - - private const int r2 = 181; // 256/sqrt(2) - - /// - /// Performs a 2-D Inverse Discrete Cosine Transformation. - /// - /// The input coefficients should already have been multiplied by the - /// appropriate quantization table. We use fixed-point computation, with the - /// number of bits for the fractional component varying over the intermediate - /// stages. - /// - /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the - /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on - /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. - /// - /// The source block of coefficients - public static void TransformIDCTInplace(Span src) - { - // Horizontal 1-D IDCT. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - // If all the AC components are zero, then the IDCT is trivial. - if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && - src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) - { - int dc = src[y8 + 0] << 3; - src[y8 + 0] = dc; - src[y8 + 1] = dc; - src[y8 + 2] = dc; - src[y8 + 3] = dc; - src[y8 + 4] = dc; - src[y8 + 5] = dc; - src[y8 + 6] = dc; - src[y8 + 7] = dc; - continue; - } - - // Prescale. - int x0 = (src[y8 + 0] << 11) + 128; - int x1 = src[y8 + 4] << 11; - int x2 = src[y8 + 6]; - int x3 = src[y8 + 2]; - int x4 = src[y8 + 1]; - int x5 = src[y8 + 7]; - int x6 = src[y8 + 5]; - int x7 = src[y8 + 3]; - - // Stage 1. - int x8 = w7 * (x4 + x5); - x4 = x8 + (w1mw7 * x4); - x5 = x8 - (w1pw7 * x5); - x8 = w3 * (x6 + x7); - x6 = x8 - (w3mw5 * x6); - x7 = x8 - (w3pw5 * x7); - - // Stage 2. - x8 = x0 + x1; - x0 -= x1; - x1 = w6 * (x3 + x2); - x2 = x1 - (w2pw6 * x2); - x3 = x1 + (w2mw6 * x3); - x1 = x4 + x6; - x4 -= x6; - x6 = x5 + x7; - x5 -= x7; - - // Stage 3. - x7 = x8 + x3; - x8 -= x3; - x3 = x0 + x2; - x0 -= x2; - x2 = ((r2 * (x4 + x5)) + 128) >> 8; - x4 = ((r2 * (x4 - x5)) + 128) >> 8; - - // Stage 4. - src[y8 + 0] = (x7 + x1) >> 8; - src[y8 + 1] = (x3 + x2) >> 8; - src[y8 + 2] = (x0 + x4) >> 8; - src[y8 + 3] = (x8 + x6) >> 8; - src[y8 + 4] = (x8 - x6) >> 8; - src[y8 + 5] = (x0 - x4) >> 8; - src[y8 + 6] = (x3 - x2) >> 8; - src[y8 + 7] = (x7 - x1) >> 8; - } - - // Vertical 1-D IDCT. - for (int x = 0; x < 8; x++) - { - // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. - // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so - // we do not bother to check for the all-zero case. - - // Prescale. - int y0 = (src[x] << 8) + 8192; - int y1 = src[32 + x] << 8; - int y2 = src[48 + x]; - int y3 = src[16 + x]; - int y4 = src[8 + x]; - int y5 = src[56 + x]; - int y6 = src[40 + x]; - int y7 = src[24 + x]; - - // Stage 1. - int y8 = (w7 * (y4 + y5)) + 4; - y4 = (y8 + (w1mw7 * y4)) >> 3; - y5 = (y8 - (w1pw7 * y5)) >> 3; - y8 = (w3 * (y6 + y7)) + 4; - y6 = (y8 - (w3mw5 * y6)) >> 3; - y7 = (y8 - (w3pw5 * y7)) >> 3; - - // Stage 2. - y8 = y0 + y1; - y0 -= y1; - y1 = (w6 * (y3 + y2)) + 4; - y2 = (y1 - (w2pw6 * y2)) >> 3; - y3 = (y1 + (w2mw6 * y3)) >> 3; - y1 = y4 + y6; - y4 -= y6; - y6 = y5 + y7; - y5 -= y7; - - // Stage 3. - y7 = y8 + y3; - y8 -= y3; - y3 = y0 + y2; - y0 -= y2; - y2 = ((r2 * (y4 + y5)) + 128) >> 8; - y4 = ((r2 * (y4 - y5)) + 128) >> 8; - - // Stage 4. - src[x] = (y7 + y1) >> 14; - src[8 + x] = (y3 + y2) >> 14; - src[16 + x] = (y0 + y4) >> 14; - src[24 + x] = (y8 + y6) >> 14; - src[32 + x] = (y8 - y6) >> 14; - src[40 + x] = (y0 - y4) >> 14; - src[48 + x] = (y3 - y2) >> 14; - src[56 + x] = (y7 - y1) >> 14; - } - } - } - - /// - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 - /// - /// - /// - private static void iDCT1Dllm_32f(Span y, Span x) - { - float a0, a1, a2, a3, b0, b1, b2, b3; - float z0, z1, z2, z3, z4; - - //float r0 = 1.414214f; - float r1 = 1.387040f; - float r2 = 1.306563f; - float r3 = 1.175876f; - //float r4 = 1.000000f; - float r5 = 0.785695f; - float r6 = 0.541196f; - float r7 = 0.275899f; - - z0 = y[1] + y[7]; - z1 = y[3] + y[5]; - z2 = y[3] + y[7]; - z3 = y[1] + y[5]; - z4 = (z0 + z1) * r3; - - z0 = z0 * (-r3 + r7); - z1 = z1 * (-r3 - r1); - z2 = z2 * (-r3 - r5) + z4; - z3 = z3 * (-r3 + r5) + z4; - - b3 = y[7] * (-r1 + r3 + r5 - r7) + z0 + z2; - b2 = y[5] * (r1 + r3 - r5 + r7) + z1 + z3; - b1 = y[3] * (r1 + r3 + r5 - r7) + z1 + z2; - b0 = y[1] * (r1 + r3 - r5 - r7) + z0 + z3; - - z4 = (y[2] + y[6]) * r6; - z0 = y[0] + y[4]; - z1 = y[0] - y[4]; - z2 = z4 - y[6] * (r2 + r6); - z3 = z4 + y[2] * (r2 - r6); - a0 = z0 + z3; - a3 = z0 - z3; - a1 = z1 + z2; - a2 = z1 - z2; - - x[0] = a0 + b0; - x[7] = a0 - b0; - x[1] = a1 + b1; - x[6] = a1 - b1; - x[2] = a2 + b2; - x[5] = a2 - b2; - x[3] = a3 + b3; - x[4] = a3 - b3; - } - - /// - /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 - /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporal block "temp" - /// - /// - /// - /// - internal static void iDCT2D_llm(Span s, Span d, Span temp) - { - int j; - - for (j = 0; j < 8; j++) - { - iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); - } - - Transpose8x8(temp, d); - - for (j = 0; j < 8; j++) - { - iDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); - } - - Transpose8x8(temp, d); - - for (j = 0; j < 64; j++) - { - d[j] *= 0.125f; - } - } - - /// - /// Original: - /// - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 - /// - /// - /// Source - /// Destination - public static void fDCT2D8x4_32f(Span s, Span d) - { - Vector4 c0 = _mm_load_ps(s, 0); - Vector4 c1 = _mm_load_ps(s, 56); - Vector4 t0 = (c0 + c1); - Vector4 t7 = (c0 - c1); - - c1 = _mm_load_ps(s, 48); - c0 = _mm_load_ps(s, 8); - Vector4 t1 = (c0 + c1); - Vector4 t6 = (c0 - c1); - - c1 = _mm_load_ps(s, 40); - c0 = _mm_load_ps(s, 16); - Vector4 t2 = (c0 + c1); - Vector4 t5 = (c0 - c1); - - c0 = _mm_load_ps(s, 24); - c1 = _mm_load_ps(s, 32); - Vector4 t3 = (c0 + c1); - Vector4 t4 = (c0 - c1); - - /* - c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; - c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; - c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; - c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; - */ - - c0 = (t0 + t3); - Vector4 c3 = (t0 - t3); - c1 = (t1 + t2); - Vector4 c2 = (t1 - t2); - - /* - c0 = t0 + t3; c3 = t0 - t3; - c1 = t1 + t2; c2 = t1 - t2; - */ - - _mm_store_ps(d, 0, (c0 + c1)); - - _mm_store_ps(d, 32, (c0 - c1)); - - /*y[0] = c0 + c1; - y[4] = c0 - c1;*/ - - Vector4 w0 = new Vector4(0.541196f); - Vector4 w1 = new Vector4(1.306563f); - - _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3))); - - _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); - /* - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; - */ - - w0 = new Vector4(1.175876f); - w1 = new Vector4(0.785695f); - c3 = ((w0 * t4) + (w1 * t7)); - c0 = ((w0 * t7) - (w1 * t4)); - /* - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - */ - - w0 = new Vector4(1.387040f); - w1 = new Vector4(0.275899f); - c2 = ((w0 * t5) + (w1 * t6)); - c1 = ((w0 * t6) - (w1 * t5)); - /* - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; - */ - - _mm_store_ps(d, 24, (c0 - c2)); - - _mm_store_ps(d, 40, (c3 - c1)); - //y[5] = c3 - c1; y[3] = c0 - c2; - - Vector4 invsqrt2 = new Vector4(0.707107f); - c0 = ((c0 + c2) * invsqrt2); - c3 = ((c3 + c1) * invsqrt2); - //c0 = (c0 + c2) * invsqrt2; - //c3 = (c3 + c1) * invsqrt2; - - _mm_store_ps(d, 8, (c0 + c3)); - - _mm_store_ps(d, 56, (c0 - c3)); - //y[1] = c0 + c3; y[7] = c0 - c3; - - /*for(i = 0;i < 8;i++) - { - y[i] *= invsqrt2h; - }*/ - } - - public static void fDCT8x8_llm_sse(Span s, Span d, Span temp) - { - Transpose8x8(s, temp); - - fDCT2D8x4_32f(temp, d); - - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - - Transpose8x8(d, temp); - - fDCT2D8x4_32f(temp, d); - - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - - Vector4 c = new Vector4(0.1250f); - - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//0 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//1 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//2 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//3 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//4 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//5 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//6 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//7 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//8 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//9 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//10 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//11 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//12 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//13 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//14 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//15 - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 _mm_load_ps(Span src, int offset) - { - src = src.Slice(offset); - return new Vector4(src[0], src[1], src[2], src[3]); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void _mm_store_ps(Span dest, int offset, Vector4 src) - { - dest = dest.Slice(offset); - dest[0] = src.X; - dest[1] = src.Y; - dest[2] = src.Z; - dest[3] = src.W; - } - - private static readonly Vector4 _1_175876 = new Vector4(1.175876f); - - private static readonly Vector4 _1_961571 = new Vector4(-1.961571f); - - private static readonly Vector4 _0_390181 = new Vector4(-0.390181f); - - private static readonly Vector4 _0_899976 = new Vector4(-0.899976f); - - private static readonly Vector4 _2_562915 = new Vector4(-2.562915f); - - private static readonly Vector4 _0_298631 = new Vector4(0.298631f); - - private static readonly Vector4 _2_053120 = new Vector4(2.053120f); - - private static readonly Vector4 _3_072711 = new Vector4(3.072711f); - - private static readonly Vector4 _1_501321 = new Vector4(1.501321f); - - private static readonly Vector4 _0_541196 = new Vector4(0.541196f); - - private static readonly Vector4 _1_847759 = new Vector4(-1.847759f); - - private static readonly Vector4 _0_765367 = new Vector4(0.765367f); - - /// - /// Original: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// Does a part of the IDCT job on the given parts of the blocks - /// - /// - /// - internal static void iDCT2D8x4_32f(Span y, Span x) - { - /* - float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; - for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } - */ - /* - 0: 1.414214 - 1: 1.387040 - 2: 1.306563 - 3: - 4: 1.000000 - 5: 0.785695 - 6: - 7: 0.275899 - */ - - Vector4 my1 = _mm_load_ps(y, 8); - Vector4 my7 = _mm_load_ps(y, 56); - Vector4 mz0 = my1 + my7; - - Vector4 my3 = _mm_load_ps(y, 24); - Vector4 mz2 = my3 + my7; - Vector4 my5 = _mm_load_ps(y, 40); - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = ((mz0 + mz1) * _1_175876); - //z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; - //z4 = (z0 + z1) * r[3]; - - mz2 = mz2 * _1_961571 + mz4; - mz3 = mz3 * _0_390181 + mz4; - mz0 = mz0 * _0_899976; - mz1 = mz1 * _2_562915; - - /* - -0.899976 - -2.562915 - -1.961571 - -0.390181 - z0 = z0 * (-r[3] + r[7]); - z1 = z1 * (-r[3] - r[1]); - z2 = z2 * (-r[3] - r[5]) + z4; - z3 = z3 * (-r[3] + r[5]) + z4;*/ - - Vector4 mb3 = my7 * _0_298631 + mz0 + mz2; - Vector4 mb2 = my5 * _2_053120 + mz1 + mz3; - Vector4 mb1 = my3 * _3_072711 + mz1 + mz2; - Vector4 mb0 = my1 * _1_501321 + mz0 + mz3; - - /* - 0.298631 - 2.053120 - 3.072711 - 1.501321 - b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2; - b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3; - b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2; - b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3; - */ - - Vector4 my2 = _mm_load_ps(y, 16); - Vector4 my6 = _mm_load_ps(y, 48); - mz4 = (my2 + my6) * _0_541196; - Vector4 my0 = _mm_load_ps(y, 0); - Vector4 my4 = _mm_load_ps(y, 32); - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + my6 * _1_847759; - mz3 = mz4 + my2 * _0_765367; - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - /* - 1.847759 - 0.765367 - z4 = (y[2] + y[6]) * r[6]; - z0 = y[0] + y[4]; z1 = y[0] - y[4]; - z2 = z4 - y[6] * (r[2] + r[6]); - z3 = z4 + y[2] * (r[2] - r[6]); - a0 = z0 + z3; a3 = z0 - z3; - a1 = z1 + z2; a2 = z1 - z2; - */ - - _mm_store_ps(x, 0, my0 + mb0); - - _mm_store_ps(x, 56, my0 - mb0); - - _mm_store_ps(x, 8, my1 + mb1); - - _mm_store_ps(x, 48, my1 - mb1); - - _mm_store_ps(x, 16, my2 + mb2); - - _mm_store_ps(x, 40, my2 - mb2); - - _mm_store_ps(x, 24, my3 + mb3); - - _mm_store_ps(x, 32, my3 - mb3); - /* - x[0] = a0 + b0; x[7] = a0 - b0; - x[1] = a1 + b1; x[6] = a1 - b1; - x[2] = a2 + b2; x[5] = a2 - b2; - x[3] = a3 + b3; x[4] = a3 - b3; - for(i = 0;i < 8;i++){ x[i] *= 0.353554f; } - */ - } - - /// - /// Copies color values from block to the destination image buffer. - /// - /// - /// - /// - internal static unsafe void CopyColorsTo(ref Block8x8F block, Span buffer, int stride) - { - fixed (Block8x8F* p = &block) - { - float* b = (float*)p; - - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - int yStride = y * stride; - - for (int x = 0; x < 8; x++) - { - float c = b[y8 + x]; - - if (c < -128) - { - c = 0; - } - else if (c > 127) - { - c = 255; - } - else - { - c += 128; - } - - buffer[yStride + x] = (byte)c; - } - } - } - } - - internal static void fDCT1Dllm_32f(Span x, Span y) - { - float t0, t1, t2, t3, t4, t5, t6, t7; - float c0, c1, c2, c3; - float[] r = new float[8]; - - //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } - r[0] = 1.414214f; - r[1] = 1.387040f; - r[2] = 1.306563f; - r[3] = 1.175876f; - r[4] = 1.000000f; - r[5] = 0.785695f; - r[6] = 0.541196f; - r[7] = 0.275899f; - - const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2); - //const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; - - c1 = x[0]; - c2 = x[7]; - t0 = c1 + c2; - t7 = c1 - c2; - c1 = x[1]; - c2 = x[6]; - t1 = c1 + c2; - t6 = c1 - c2; - c1 = x[2]; - c2 = x[5]; - t2 = c1 + c2; - t5 = c1 - c2; - c1 = x[3]; - c2 = x[4]; - t3 = c1 + c2; - t4 = c1 - c2; - - c0 = t0 + t3; - c3 = t0 - t3; - c1 = t1 + t2; - c2 = t1 - t2; - - y[0] = c0 + c1; - y[4] = c0 - c1; - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; - - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; - - y[5] = c3 - c1; - y[3] = c0 - c2; - c0 = (c0 + c2) * invsqrt2; - c3 = (c3 + c1) * invsqrt2; - y[1] = c0 + c3; - y[7] = c0 - c3; - } - - internal static void fDCT2D_llm( - Span s, - Span d, - Span temp, - bool downscaleBy8 = false, - bool offsetSourceByNeg128 = false) - { - Span sWorker = offsetSourceByNeg128 ? s.AddScalarToAllValues(-128f) : s; - - for (int j = 0; j < 8; j++) - { - fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); - } - - Transpose8x8(temp, d); - - for (int j = 0; j < 8; j++) - { - fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); - } - - Transpose8x8(temp, d); - - if (downscaleBy8) - { - for (int j = 0; j < 64; j++) - { - d[j] *= 0.125f; - } - } - } - - /// - /// Reference implementation to test . - /// Rounding is done used an integer-based algorithm defined in . - /// - /// The input block - /// The destination block of integers - /// The quantization table - /// Pointer to - public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) - { - float* s = (float*)src; - float* q = (float*)qt; - - for (int zig = 0; zig < Block8x8F.Size; zig++) - { - int a = (int)s[unzigPtr[zig]]; - int b = (int)q[zig]; - - int val = RationalRound(a, b); - dest[zig] = val; - } - } - - /// - /// Rounds a rational number defined as dividend/divisor into an integer - /// - /// The dividend - /// The divisior - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RationalRound(int dividend, int divisor) - { - if (dividend >= 0) - { - return (dividend + (divisor >> 1)) / divisor; - } - - return -((-dividend + (divisor >> 1)) / divisor); - } - } -} \ 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 16e5631d12..fc9e84cecd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -11,6 +11,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { using System; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + public class ReferenceImplementationsTests : JpegUtilityTestFixture { public ReferenceImplementationsTests(ITestOutputHelper output) @@ -28,12 +30,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int[] intData = Create8x8RandomIntData(-200, 200, seed); Span floatSrc = intData.ConvertAllToFloat(); - ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(intData); + ReferenceImplementations.IntegerDCT.TransformIDCTInplace(intData); float[] dest = new float[64]; float[] temp = new float[64]; - ReferenceImplementations.iDCT2D_llm(floatSrc, dest, temp); + ReferenceImplementations.FastFloatingPointDCT.iDCT2D_llm(floatSrc, dest, temp); for (int i = 0; i < 64; i++) { @@ -54,14 +56,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Span block = original.AddScalarToAllValues(128); - ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block); + ReferenceImplementations.IntegerDCT.TransformFDCTInplace(block); for (int i = 0; i < 64; i++) { block[i] /= 8; } - ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(block); + ReferenceImplementations.IntegerDCT.TransformIDCTInplace(block); for (int i = startAt; i < 64; i++) { @@ -84,8 +86,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] dest = new float[64]; float[] temp = new float[64]; - ReferenceImplementations.fDCT2D_llm(src, dest, temp, true); - ReferenceImplementations.iDCT2D_llm(dest, src, temp); + ReferenceImplementations.FastFloatingPointDCT.fDCT2D_llm(src, dest, temp, true); + ReferenceImplementations.FastFloatingPointDCT.iDCT2D_llm(dest, src, temp); for (int i = startAt; i < 64; i++) { @@ -105,12 +107,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int[] intData = Create8x8RandomIntData(-200, 200, seed); float[] floatSrc = intData.ConvertAllToFloat(); - ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(intData); + ReferenceImplementations.IntegerDCT.TransformFDCTInplace(intData); float[] dest = new float[64]; float[] temp = new float[64]; - ReferenceImplementations.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); + ReferenceImplementations.FastFloatingPointDCT.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); for (int i = 0; i < 64; i++) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 8b1ab8bfe3..ad6182d222 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,19 +1,15 @@ // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { using System; - using System.Collections.Generic; using System.IO; using System.Linq; - using System.Runtime.CompilerServices; - using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; - using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs similarity index 91% rename from tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs rename to tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs index c3ccba8f61..ecebc58a59 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegUtilityTestFixture.cs @@ -1,16 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Diagnostics; -using System.Text; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; -using Xunit.Abstractions; + // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { + using System; + using System.Diagnostics; + using System.Text; + + using Xunit.Abstractions; + public class JpegUtilityTestFixture : MeasureFixture { public JpegUtilityTestFixture(ITestOutputHelper output) : base(output) @@ -75,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests } internal static float[] Create8x8RandomFloatData(int minValue, int maxValue, int seed = 42) - => Create8x8RandomIntData(minValue, maxValue, seed).ConvertAllToFloat(); + => ImageSharp.Formats.Jpeg.GolangPort.Utils.SpanExtensions.ConvertAllToFloat(Create8x8RandomIntData(minValue, maxValue, seed)); 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 new file mode 100644 index 0000000000..3f1cd89b40 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -0,0 +1,184 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Linq; + using System.Numerics; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; + using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; + using SixLabors.ImageSharp.Memory; + using SixLabors.Primitives; + + internal static partial class LibJpegTools + { + public class ComponentData : IEquatable, IJpegComponent + { + public ComponentData(int heightInBlocks, int widthInBlocks, int index) + { + this.HeightInBlocks = heightInBlocks; + this.WidthInBlocks = widthInBlocks; + this.Index = index; + this.Blocks = new Buffer2D(this.WidthInBlocks, this.HeightInBlocks); + } + + public Size Size => new Size(this.WidthInBlocks, this.HeightInBlocks); + + public int Index { get; } + + public int HeightInBlocks { get; } + + public int WidthInBlocks { get; } + + public Buffer2D Blocks { get; private set; } + + public short MinVal { get; private set; } = short.MaxValue; + + public short MaxVal { get; private set; } = short.MinValue; + + internal void MakeBlock(short[] data, int y, int x) + { + this.MinVal = Math.Min((short)this.MinVal, data.Min()); + this.MaxVal = Math.Max((short)this.MaxVal, data.Max()); + this.Blocks[x, y] = new Block8x8(data); + } + + public static ComponentData Load(FrameComponent c, int index) + { + var result = new ComponentData( + c.BlocksPerColumnForMcu, + c.BlocksPerLineForMcu, + index + ); + + for (int y = 0; y < result.HeightInBlocks; y++) + { + for (int x = 0; x < result.WidthInBlocks; x++) + { + short[] data = c.GetBlockBuffer(y, x).ToArray(); + result.MakeBlock(data, y, x); + } + } + + return result; + } + + public static ComponentData Load(OldComponent c) + { + var result = new ComponentData( + c.HeightInBlocks, + c.WidthInBlocks, + c.Index + ); + + for (int y = 0; y < result.HeightInBlocks; y++) + { + for (int x = 0; x < result.WidthInBlocks; x++) + { + short[] data = c.GetBlockReference(x, y).ToArray(); + result.MakeBlock(data, y, x); + } + } + + return result; + } + + public Image CreateGrayScaleImage() + { + Image result = new Image(this.WidthInBlocks * 8, this.HeightInBlocks * 8); + + for (int by = 0; by < this.HeightInBlocks; by++) + { + for (int bx = 0; bx < this.WidthInBlocks; bx++) + { + this.WriteToImage(bx, by, result); + } + } + return result; + } + + internal void WriteToImage(int bx, int by, Image image) + { + Block8x8 block = this.Blocks[bx, by]; + + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + var val = this.GetBlockValue(block, x, y); + + Vector4 v = new Vector4(val, val, val, 1); + Rgba32 color = default(Rgba32); + color.PackFromVector4(v); + + int yy = by * 8 + y; + int xx = bx * 8 + x; + image[xx, yy] = color; + } + } + } + + internal float GetBlockValue(Block8x8 block, int x, int y) + { + float d = (this.MaxVal - this.MinVal); + float val = block.GetValueAt(x, y); + val -= this.MinVal; + val /= d; + return val; + } + + public bool Equals(ComponentData other) + { + if (Object.ReferenceEquals(null, other)) return false; + if (Object.ReferenceEquals(this, other)) return true; + bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks + && this.WidthInBlocks == other.WidthInBlocks; + //&& this.MinVal == other.MinVal + //&& this.MaxVal == other.MaxVal; + if (!ok) return false; + + for (int y = 0; y < this.HeightInBlocks; y++) + { + for (int x = 0; x < this.WidthInBlocks; x++) + { + Block8x8 a = this.Blocks[x, y]; + Block8x8 b = other.Blocks[x, y]; + if (!a.Equals(b)) return false; + } + } + return true; + } + + public override bool Equals(object obj) + { + if (Object.ReferenceEquals(null, obj)) return false; + if (Object.ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return this.Equals((ComponentData)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = this.Index; + hashCode = (hashCode * 397) ^ this.HeightInBlocks; + hashCode = (hashCode * 397) ^ this.WidthInBlocks; + hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); + hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ComponentData left, ComponentData right) + { + return Object.Equals(left, right); + } + + public static bool operator !=(ComponentData left, ComponentData right) + { + return !Object.Equals(left, right); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs new file mode 100644 index 0000000000..39465a69d4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -0,0 +1,146 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Linq; + using System.Numerics; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; + using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; + using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; + + internal static partial class LibJpegTools + { + public class SpectralData : IEquatable + { + public int ComponentCount { get; private set; } + + public LibJpegTools.ComponentData[] Components { get; private set; } + + internal SpectralData(LibJpegTools.ComponentData[] components) + { + this.ComponentCount = components.Length; + this.Components = components; + } + + public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder) + { + FrameComponent[] srcComponents = decoder.Frame.Components; + LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray(); + + return new SpectralData(destComponents); + } + + public static SpectralData LoadFromImageSharpDecoder(OldJpegDecoderCore decoder) + { + OldComponent[] srcComponents = decoder.Components; + LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray(); + + return new SpectralData(destComponents); + } + + public Image TryCreateRGBSpectralImage() + { + if (this.ComponentCount != 3) return null; + + LibJpegTools.ComponentData c0 = this.Components[0]; + LibJpegTools.ComponentData c1 = this.Components[1]; + LibJpegTools.ComponentData c2 = this.Components[2]; + + if (c0.Size != c1.Size || c1.Size != c2.Size) + { + return null; + } + + Image result = new Image(c0.WidthInBlocks * 8, c0.HeightInBlocks * 8); + + for (int by = 0; by < c0.HeightInBlocks; by++) + { + for (int bx = 0; bx < c0.WidthInBlocks; bx++) + { + this.WriteToImage(bx, by, result); + } + } + return result; + } + + internal void WriteToImage(int bx, int by, Image image) + { + LibJpegTools.ComponentData c0 = this.Components[0]; + LibJpegTools.ComponentData c1 = this.Components[1]; + LibJpegTools.ComponentData c2 = this.Components[2]; + + Block8x8 block0 = c0.Blocks[bx, by]; + Block8x8 block1 = c1.Blocks[bx, by]; + Block8x8 block2 = c2.Blocks[bx, by]; + + float d0 = (c0.MaxVal - c0.MinVal); + float d1 = (c1.MaxVal - c1.MinVal); + float d2 = (c2.MaxVal - c2.MinVal); + + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + float val0 = c0.GetBlockValue(block0, x, y); + float val1 = c0.GetBlockValue(block1, x, y); + float val2 = c0.GetBlockValue(block2, x, y); + + Vector4 v = new Vector4(val0, val1, val2, 1); + Rgba32 color = default(Rgba32); + color.PackFromVector4(v); + + int yy = by * 8 + y; + int xx = bx * 8 + x; + image[xx, yy] = color; + } + } + } + + public bool Equals(SpectralData other) + { + if (Object.ReferenceEquals(null, other)) return false; + if (Object.ReferenceEquals(this, other)) return true; + if (this.ComponentCount != other.ComponentCount) + { + return false; + } + + for (int i = 0; i < this.ComponentCount; i++) + { + LibJpegTools.ComponentData a = this.Components[i]; + LibJpegTools.ComponentData b = other.Components[i]; + if (!a.Equals(b)) return false; + } + return true; + } + + public override bool Equals(object obj) + { + if (Object.ReferenceEquals(null, obj)) return false; + if (Object.ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return this.Equals((SpectralData)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (this.ComponentCount * 397) ^ (this.Components != null ? this.Components[0].GetHashCode() : 0); + } + } + + public static bool operator ==(SpectralData left, SpectralData right) + { + return Object.Equals(left, right); + } + + public static bool operator !=(SpectralData left, SpectralData right) + { + return !Object.Equals(left, right); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs new file mode 100644 index 0000000000..3747528d0a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -0,0 +1,113 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Numerics; + using System.Reflection; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + internal static partial class LibJpegTools + { + public static (double total, double average) CalculateDifference(ComponentData expected, ComponentData actual) + { + BigInteger totalDiff = 0; + if (actual.WidthInBlocks < expected.WidthInBlocks) + { + throw new Exception("actual.WidthInBlocks < expected.WidthInBlocks"); + } + + if (actual.HeightInBlocks < expected.HeightInBlocks) + { + throw new Exception("actual.HeightInBlocks < expected.HeightInBlocks"); + } + + int w = expected.WidthInBlocks; + int h = expected.HeightInBlocks; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + Block8x8 aa = expected.Blocks[x, y]; + Block8x8 bb = actual.Blocks[x, y]; + + long diff = Block8x8.TotalDifference(ref aa, ref bb); + totalDiff += diff; + } + } + + int count = w * h; + double total = (double)totalDiff; + double average = (double)totalDiff / (count * Block8x8.Size); + return (total, average); + } + + private static string DumpToolFullPath => Path.Combine( + TestEnvironment.ToolsDirectoryFullPath, + @"jpeg\dump-jpeg-coeffs.exe"); + + public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) + { + string args = $@"""{sourceFile}"" ""{destFile}"""; + var process = Process.Start(DumpToolFullPath, args); + process.WaitForExit(); + } + + public static SpectralData ExtractSpectralData(string inputFile) + { + TestFile testFile = TestFile.Create(inputFile); + + string outDir = TestEnvironment.CreateOutputDirectory(".Temp", $"JpegCoeffs"); + string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; + string coeffFileFullPath = Path.Combine(outDir, fn); + + try + { + RunDumpJpegCoeffsTool(testFile.FullPath, coeffFileFullPath); + + using (var dumpStream = new FileStream(coeffFileFullPath, FileMode.Open)) + using (var rdr = new BinaryReader(dumpStream)) + { + int componentCount = rdr.ReadInt16(); + ComponentData[] result = new ComponentData[componentCount]; + + for (int i = 0; i < componentCount; i++) + { + int widthInBlocks = rdr.ReadInt16(); + int heightInBlocks = rdr.ReadInt16(); + ComponentData resultComponent = new ComponentData(heightInBlocks, widthInBlocks, i); + result[i] = resultComponent; + } + + byte[] buffer = new byte[64*sizeof(short)]; + + for (int i = 0; i < result.Length; i++) + { + ComponentData c = result[i]; + + for (int y = 0; y < c.HeightInBlocks; y++) + { + for (int x = 0; x < c.WidthInBlocks; x++) + { + rdr.Read(buffer, 0, buffer.Length); + + short[] block = buffer.AsSpan().NonPortableCast().ToArray(); + c.MakeBlock(block, y, x); + } + } + } + + return new SpectralData(result); + } + } + finally + { + if (File.Exists(coeffFileFullPath)) + { + File.Delete(coeffFileFullPath); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.FastFloatingPointDCT.cs new file mode 100644 index 0000000000..5c3b65a1ec --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.FastFloatingPointDCT.cs @@ -0,0 +1,490 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; + + internal static partial class ReferenceImplementations + { + internal static class FastFloatingPointDCT + { + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 + /// + /// + /// + private static void iDCT1Dllm_32f(Span y, Span x) + { + float a0, a1, a2, a3, b0, b1, b2, b3; + float z0, z1, z2, z3, z4; + + //float r0 = 1.414214f; + float r1 = 1.387040f; + float r2 = 1.306563f; + float r3 = 1.175876f; + //float r4 = 1.000000f; + float r5 = 0.785695f; + float r6 = 0.541196f; + float r7 = 0.275899f; + + z0 = y[1] + y[7]; + z1 = y[3] + y[5]; + z2 = y[3] + y[7]; + z3 = y[1] + y[5]; + z4 = (z0 + z1) * r3; + + z0 = z0 * (-r3 + r7); + z1 = z1 * (-r3 - r1); + z2 = z2 * (-r3 - r5) + z4; + z3 = z3 * (-r3 + r5) + z4; + + b3 = y[7] * (-r1 + r3 + r5 - r7) + z0 + z2; + b2 = y[5] * (r1 + r3 - r5 + r7) + z1 + z3; + b1 = y[3] * (r1 + r3 + r5 - r7) + z1 + z2; + b0 = y[1] * (r1 + r3 - r5 - r7) + z0 + z3; + + z4 = (y[2] + y[6]) * r6; + z0 = y[0] + y[4]; + z1 = y[0] - y[4]; + z2 = z4 - y[6] * (r2 + r6); + z3 = z4 + y[2] * (r2 - r6); + a0 = z0 + z3; + a3 = z0 - z3; + a1 = z1 + z2; + a2 = z1 - z2; + + x[0] = a0 + b0; + x[7] = a0 - b0; + x[1] = a1 + b1; + x[6] = a1 - b1; + x[2] = a2 + b2; + x[5] = a2 - b2; + x[3] = a3 + b3; + x[4] = a3 - b3; + } + + /// + /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 + /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporal block "temp" + /// + /// + /// + /// + internal static void iDCT2D_llm(Span s, Span d, Span temp) + { + int j; + + for (j = 0; j < 8; j++) + { + iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); + } + + ReferenceImplementations.Transpose8x8(temp, d); + + for (j = 0; j < 8; j++) + { + iDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + } + + ReferenceImplementations.Transpose8x8(temp, d); + + for (j = 0; j < 64; j++) + { + d[j] *= 0.125f; + } + } + + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + public static void fDCT2D8x4_32f(Span s, Span d) + { + Vector4 c0 = _mm_load_ps(s, 0); + Vector4 c1 = _mm_load_ps(s, 56); + Vector4 t0 = (c0 + c1); + Vector4 t7 = (c0 - c1); + + c1 = _mm_load_ps(s, 48); + c0 = _mm_load_ps(s, 8); + Vector4 t1 = (c0 + c1); + Vector4 t6 = (c0 - c1); + + c1 = _mm_load_ps(s, 40); + c0 = _mm_load_ps(s, 16); + Vector4 t2 = (c0 + c1); + Vector4 t5 = (c0 - c1); + + c0 = _mm_load_ps(s, 24); + c1 = _mm_load_ps(s, 32); + Vector4 t3 = (c0 + c1); + Vector4 t4 = (c0 - c1); + + /* + c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; + c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; + c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; + c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; + */ + + c0 = (t0 + t3); + Vector4 c3 = (t0 - t3); + c1 = (t1 + t2); + Vector4 c2 = (t1 - t2); + + /* + c0 = t0 + t3; c3 = t0 - t3; + c1 = t1 + t2; c2 = t1 - t2; + */ + + _mm_store_ps(d, 0, (c0 + c1)); + + _mm_store_ps(d, 32, (c0 - c1)); + + /*y[0] = c0 + c1; + y[4] = c0 - c1;*/ + + Vector4 w0 = new Vector4(0.541196f); + Vector4 w1 = new Vector4(1.306563f); + + _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3))); + + _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); + /* + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + */ + + w0 = new Vector4(1.175876f); + w1 = new Vector4(0.785695f); + c3 = ((w0 * t4) + (w1 * t7)); + c0 = ((w0 * t7) - (w1 * t4)); + /* + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + */ + + w0 = new Vector4(1.387040f); + w1 = new Vector4(0.275899f); + c2 = ((w0 * t5) + (w1 * t6)); + c1 = ((w0 * t6) - (w1 * t5)); + /* + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + */ + + _mm_store_ps(d, 24, (c0 - c2)); + + _mm_store_ps(d, 40, (c3 - c1)); + //y[5] = c3 - c1; y[3] = c0 - c2; + + Vector4 invsqrt2 = new Vector4(0.707107f); + c0 = ((c0 + c2) * invsqrt2); + c3 = ((c3 + c1) * invsqrt2); + //c0 = (c0 + c2) * invsqrt2; + //c3 = (c3 + c1) * invsqrt2; + + _mm_store_ps(d, 8, (c0 + c3)); + + _mm_store_ps(d, 56, (c0 - c3)); + //y[1] = c0 + c3; y[7] = c0 - c3; + + /*for(i = 0;i < 8;i++) + { + y[i] *= invsqrt2h; + }*/ + } + + public static void fDCT8x8_llm_sse(Span s, Span d, Span temp) + { + ReferenceImplementations.Transpose8x8(s, temp); + + fDCT2D8x4_32f(temp, d); + + fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + + ReferenceImplementations.Transpose8x8(d, temp); + + fDCT2D8x4_32f(temp, d); + + fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + + Vector4 c = new Vector4(0.1250f); + + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//0 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//1 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//2 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//3 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//4 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//5 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//6 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//7 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//8 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//9 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//10 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//11 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//12 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//13 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//14 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//15 + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 _mm_load_ps(Span src, int offset) + { + src = src.Slice(offset); + return new Vector4(src[0], src[1], src[2], src[3]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _mm_store_ps(Span dest, int offset, Vector4 src) + { + dest = dest.Slice(offset); + dest[0] = src.X; + dest[1] = src.Y; + dest[2] = src.Z; + dest[3] = src.W; + } + + private static readonly Vector4 _1_175876 = new Vector4(1.175876f); + + private static readonly Vector4 _1_961571 = new Vector4(-1.961571f); + + private static readonly Vector4 _0_390181 = new Vector4(-0.390181f); + + private static readonly Vector4 _0_899976 = new Vector4(-0.899976f); + + private static readonly Vector4 _2_562915 = new Vector4(-2.562915f); + + private static readonly Vector4 _0_298631 = new Vector4(0.298631f); + + private static readonly Vector4 _2_053120 = new Vector4(2.053120f); + + private static readonly Vector4 _3_072711 = new Vector4(3.072711f); + + private static readonly Vector4 _1_501321 = new Vector4(1.501321f); + + private static readonly Vector4 _0_541196 = new Vector4(0.541196f); + + private static readonly Vector4 _1_847759 = new Vector4(-1.847759f); + + private static readonly Vector4 _0_765367 = new Vector4(0.765367f); + + /// + /// Original: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// Does a part of the IDCT job on the given parts of the blocks + /// + /// + /// + internal static void iDCT2D8x4_32f(Span y, Span x) + { + /* + float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; + for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + */ + /* + 0: 1.414214 + 1: 1.387040 + 2: 1.306563 + 3: + 4: 1.000000 + 5: 0.785695 + 6: + 7: 0.275899 + */ + + Vector4 my1 = _mm_load_ps(y, 8); + Vector4 my7 = _mm_load_ps(y, 56); + Vector4 mz0 = my1 + my7; + + Vector4 my3 = _mm_load_ps(y, 24); + Vector4 mz2 = my3 + my7; + Vector4 my5 = _mm_load_ps(y, 40); + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + + Vector4 mz4 = ((mz0 + mz1) * _1_175876); + //z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; + //z4 = (z0 + z1) * r[3]; + + mz2 = mz2 * _1_961571 + mz4; + mz3 = mz3 * _0_390181 + mz4; + mz0 = mz0 * _0_899976; + mz1 = mz1 * _2_562915; + + /* + -0.899976 + -2.562915 + -1.961571 + -0.390181 + z0 = z0 * (-r[3] + r[7]); + z1 = z1 * (-r[3] - r[1]); + z2 = z2 * (-r[3] - r[5]) + z4; + z3 = z3 * (-r[3] + r[5]) + z4;*/ + + Vector4 mb3 = my7 * _0_298631 + mz0 + mz2; + Vector4 mb2 = my5 * _2_053120 + mz1 + mz3; + Vector4 mb1 = my3 * _3_072711 + mz1 + mz2; + Vector4 mb0 = my1 * _1_501321 + mz0 + mz3; + + /* + 0.298631 + 2.053120 + 3.072711 + 1.501321 + b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2; + b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3; + b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2; + b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3; + */ + + Vector4 my2 = _mm_load_ps(y, 16); + Vector4 my6 = _mm_load_ps(y, 48); + mz4 = (my2 + my6) * _0_541196; + Vector4 my0 = _mm_load_ps(y, 0); + Vector4 my4 = _mm_load_ps(y, 32); + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + my6 * _1_847759; + mz3 = mz4 + my2 * _0_765367; + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + /* + 1.847759 + 0.765367 + z4 = (y[2] + y[6]) * r[6]; + z0 = y[0] + y[4]; z1 = y[0] - y[4]; + z2 = z4 - y[6] * (r[2] + r[6]); + z3 = z4 + y[2] * (r[2] - r[6]); + a0 = z0 + z3; a3 = z0 - z3; + a1 = z1 + z2; a2 = z1 - z2; + */ + + _mm_store_ps(x, 0, my0 + mb0); + + _mm_store_ps(x, 56, my0 - mb0); + + _mm_store_ps(x, 8, my1 + mb1); + + _mm_store_ps(x, 48, my1 - mb1); + + _mm_store_ps(x, 16, my2 + mb2); + + _mm_store_ps(x, 40, my2 - mb2); + + _mm_store_ps(x, 24, my3 + mb3); + + _mm_store_ps(x, 32, my3 - mb3); + /* + x[0] = a0 + b0; x[7] = a0 - b0; + x[1] = a1 + b1; x[6] = a1 - b1; + x[2] = a2 + b2; x[5] = a2 - b2; + x[3] = a3 + b3; x[4] = a3 - b3; + for(i = 0;i < 8;i++){ x[i] *= 0.353554f; } + */ + } + + internal static void fDCT1Dllm_32f(Span x, Span y) + { + float t0, t1, t2, t3, t4, t5, t6, t7; + float c0, c1, c2, c3; + float[] r = new float[8]; + + //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + r[0] = 1.414214f; + r[1] = 1.387040f; + r[2] = 1.306563f; + r[3] = 1.175876f; + r[4] = 1.000000f; + r[5] = 0.785695f; + r[6] = 0.541196f; + r[7] = 0.275899f; + + const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2); + //const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; + + c1 = x[0]; + c2 = x[7]; + t0 = c1 + c2; + t7 = c1 - c2; + c1 = x[1]; + c2 = x[6]; + t1 = c1 + c2; + t6 = c1 - c2; + c1 = x[2]; + c2 = x[5]; + t2 = c1 + c2; + t5 = c1 - c2; + c1 = x[3]; + c2 = x[4]; + t3 = c1 + c2; + t4 = c1 - c2; + + c0 = t0 + t3; + c3 = t0 - t3; + c1 = t1 + t2; + c2 = t1 - t2; + + y[0] = c0 + c1; + y[4] = c0 - c1; + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + + y[5] = c3 - c1; + y[3] = c0 - c2; + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; + y[1] = c0 + c3; + y[7] = c0 - c3; + } + + internal static void fDCT2D_llm( + Span s, + Span d, + Span temp, + bool downscaleBy8 = false, + bool offsetSourceByNeg128 = false) + { + Span sWorker = offsetSourceByNeg128 ? s.AddScalarToAllValues(-128f) : s; + + for (int j = 0; j < 8; j++) + { + fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); + } + + ReferenceImplementations.Transpose8x8(temp, d); + + for (int j = 0; j < 8; j++) + { + fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + } + + ReferenceImplementations.Transpose8x8(temp, d); + + if (downscaleBy8) + { + for (int j = 0; j < 64; j++) + { + d[j] *= 0.125f; + } + } + } + } + } +} \ 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.IntegerDCT.cs new file mode 100644 index 0000000000..1c166748bd --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.IntegerDCT.cs @@ -0,0 +1,314 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + + internal static partial class ReferenceImplementations + { + /// + /// The "original" libjpeg/golang based DCT implementation is used as reference implementation for tests. + /// + public static class IntegerDCT + { + private const int fix_0_298631336 = 2446; + private const int fix_0_390180644 = 3196; + private const int fix_0_541196100 = 4433; + private const int fix_0_765366865 = 6270; + private const int fix_0_899976223 = 7373; + private const int fix_1_175875602 = 9633; + private const int fix_1_501321110 = 12299; + private const int fix_1_847759065 = 15137; + private const int fix_1_961570560 = 16069; + private const int fix_2_053119869 = 16819; + private const int fix_2_562915447 = 20995; + private const int fix_3_072711026 = 25172; + + /// + /// The number of bits + /// + private const int Bits = 13; + + /// + /// The number of bits to shift by on the first pass. + /// + private const int Pass1Bits = 2; + + /// + /// The value to shift by + /// + private const int CenterJSample = 128; + + /// + /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. + /// Leave results scaled up by an overall factor of 8. + /// + /// The block of coefficients. + public static void TransformFDCTInplace(Span block) + { + // Pass 1: process rows. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + int x0 = block[y8]; + int x1 = block[y8 + 1]; + int x2 = block[y8 + 2]; + int x3 = block[y8 + 3]; + int x4 = block[y8 + 4]; + int x5 = block[y8 + 5]; + int x6 = block[y8 + 6]; + int x7 = block[y8 + 7]; + + int tmp0 = x0 + x7; + int tmp1 = x1 + x6; + int tmp2 = x2 + x5; + int tmp3 = x3 + x4; + + int tmp10 = tmp0 + tmp3; + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = x0 - x7; + tmp1 = x1 - x6; + tmp2 = x2 - x5; + tmp3 = x3 - x4; + + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits - Pass1Bits - 1); + block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits - Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); + block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); + } + + // Pass 2: process columns. + // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. + for (int x = 0; x < 8; x++) + { + int tmp0 = block[x] + block[56 + x]; + int tmp1 = block[8 + x] + block[48 + x]; + int tmp2 = block[16 + x] + block[40 + x]; + int tmp3 = block[24 + x] + block[32 + x]; + + int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = block[x] - block[56 + x]; + tmp1 = block[8 + x] - block[48 + x]; + tmp2 = block[16 + x] - block[40 + x]; + tmp3 = block[24 + x] - block[32 + x]; + + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; + + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits + Pass1Bits - 1); + block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits + Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); + block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); + block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); + block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); + } + + } + private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) + private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) + private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) + private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) + private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) + private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) + + private const int w1pw7 = w1 + w7; + private const int w1mw7 = w1 - w7; + private const int w2pw6 = w2 + w6; + private const int w2mw6 = w2 - w6; + private const int w3pw5 = w3 + w5; + private const int w3mw5 = w3 - w5; + + private const int r2 = 181; // 256/sqrt(2) + + /// + /// Performs a 2-D Inverse Discrete Cosine Transformation. + /// + /// The input coefficients should already have been multiplied by the + /// appropriate quantization table. We use fixed-point computation, with the + /// number of bits for the fractional component varying over the intermediate + /// stages. + /// + /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the + /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on + /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. + /// + /// The source block of coefficients + public static void TransformIDCTInplace(Span src) + { + // Horizontal 1-D IDCT. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + // If all the AC components are zero, then the IDCT is trivial. + if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && + src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) + { + int dc = src[y8 + 0] << 3; + src[y8 + 0] = dc; + src[y8 + 1] = dc; + src[y8 + 2] = dc; + src[y8 + 3] = dc; + src[y8 + 4] = dc; + src[y8 + 5] = dc; + src[y8 + 6] = dc; + src[y8 + 7] = dc; + continue; + } + + // Prescale. + int x0 = (src[y8 + 0] << 11) + 128; + int x1 = src[y8 + 4] << 11; + int x2 = src[y8 + 6]; + int x3 = src[y8 + 2]; + int x4 = src[y8 + 1]; + int x5 = src[y8 + 7]; + int x6 = src[y8 + 5]; + int x7 = src[y8 + 3]; + + // Stage 1. + int x8 = w7 * (x4 + x5); + x4 = x8 + (w1mw7 * x4); + x5 = x8 - (w1pw7 * x5); + x8 = w3 * (x6 + x7); + x6 = x8 - (w3mw5 * x6); + x7 = x8 - (w3pw5 * x7); + + // Stage 2. + x8 = x0 + x1; + x0 -= x1; + x1 = w6 * (x3 + x2); + x2 = x1 - (w2pw6 * x2); + x3 = x1 + (w2mw6 * x3); + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + // Stage 3. + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = ((r2 * (x4 + x5)) + 128) >> 8; + x4 = ((r2 * (x4 - x5)) + 128) >> 8; + + // Stage 4. + src[y8 + 0] = (x7 + x1) >> 8; + src[y8 + 1] = (x3 + x2) >> 8; + src[y8 + 2] = (x0 + x4) >> 8; + src[y8 + 3] = (x8 + x6) >> 8; + src[y8 + 4] = (x8 - x6) >> 8; + src[y8 + 5] = (x0 - x4) >> 8; + src[y8 + 6] = (x3 - x2) >> 8; + src[y8 + 7] = (x7 - x1) >> 8; + } + + // Vertical 1-D IDCT. + for (int x = 0; x < 8; x++) + { + // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. + // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so + // we do not bother to check for the all-zero case. + + // Prescale. + int y0 = (src[x] << 8) + 8192; + int y1 = src[32 + x] << 8; + int y2 = src[48 + x]; + int y3 = src[16 + x]; + int y4 = src[8 + x]; + int y5 = src[56 + x]; + int y6 = src[40 + x]; + int y7 = src[24 + x]; + + // Stage 1. + int y8 = (w7 * (y4 + y5)) + 4; + y4 = (y8 + (w1mw7 * y4)) >> 3; + y5 = (y8 - (w1pw7 * y5)) >> 3; + y8 = (w3 * (y6 + y7)) + 4; + y6 = (y8 - (w3mw5 * y6)) >> 3; + y7 = (y8 - (w3pw5 * y7)) >> 3; + + // Stage 2. + y8 = y0 + y1; + y0 -= y1; + y1 = (w6 * (y3 + y2)) + 4; + y2 = (y1 - (w2pw6 * y2)) >> 3; + y3 = (y1 + (w2mw6 * y3)) >> 3; + y1 = y4 + y6; + y4 -= y6; + y6 = y5 + y7; + y5 -= y7; + + // Stage 3. + y7 = y8 + y3; + y8 -= y3; + y3 = y0 + y2; + y0 -= y2; + y2 = ((r2 * (y4 + y5)) + 128) >> 8; + y4 = ((r2 * (y4 - y5)) + 128) >> 8; + + // Stage 4. + src[x] = (y7 + y1) >> 14; + src[8 + x] = (y3 + y2) >> 14; + src[16 + x] = (y0 + y4) >> 14; + src[24 + x] = (y8 + y6) >> 14; + src[32 + x] = (y8 - y6) >> 14; + src[40 + x] = (y0 - y4) >> 14; + src[48 + x] = (y3 - y2) >> 14; + src[56 + x] = (y7 - y1) >> 14; + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs new file mode 100644 index 0000000000..c8240bf083 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -0,0 +1,135 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + + + +// ReSharper disable InconsistentNaming + + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + /// + /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests + /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd + /// + internal static partial class ReferenceImplementations + { + /// + /// Transpose 8x8 block stored linearly in a (inplace) + /// + /// + internal static void Transpose8x8(Span data) + { + for (int i = 1; i < 8; i++) + { + int i8 = i * 8; + for (int j = 0; j < i; j++) + { + float tmp = data[i8 + j]; + data[i8 + j] = data[j * 8 + i]; + data[j * 8 + i] = tmp; + } + } + } + + /// + /// Transpose 8x8 block stored linearly in a + /// + internal static void Transpose8x8(Span src, Span dest) + { + for (int i = 0; i < 8; i++) + { + int i8 = i * 8; + for (int j = 0; j < 8; j++) + { + dest[j * 8 + i] = src[i8 + j]; + } + } + } + + /// + /// Copies color values from block to the destination image buffer. + /// + /// + /// + /// + internal static unsafe void CopyColorsTo(ref Block8x8F block, Span buffer, int stride) + { + fixed (Block8x8F* p = &block) + { + float* b = (float*)p; + + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + int yStride = y * stride; + + for (int x = 0; x < 8; x++) + { + float c = b[y8 + x]; + + if (c < -128) + { + c = 0; + } + else if (c > 127) + { + c = 255; + } + else + { + c += 128; + } + + buffer[yStride + x] = (byte)c; + } + } + } + } + + /// + /// Reference implementation to test . + /// Rounding is done used an integer-based algorithm defined in . + /// + /// The input block + /// The destination block of integers + /// The quantization table + /// Pointer to + public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) + { + float* s = (float*)src; + float* q = (float*)qt; + + for (int zig = 0; zig < Block8x8F.Size; zig++) + { + int a = (int)s[unzigPtr[zig]]; + int b = (int)q[zig]; + + int val = RationalRound(a, b); + dest[zig] = val; + } + } + + /// + /// Rounds a rational number defined as dividend/divisor into an integer + /// + /// The dividend + /// The divisior + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int RationalRound(int dividend, int divisor) + { + if (dividend >= 0) + { + return (dividend + (divisor >> 1)) / divisor; + } + + return -((-dividend + (divisor >> 1)) / divisor); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/VerifyJpeg.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs similarity index 97% rename from tests/ImageSharp.Tests/Formats/Jpg/VerifyJpeg.cs rename to tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs index d80870dda2..7ceb013440 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/VerifyJpeg.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs @@ -1,4 +1,4 @@ -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { using System.Collections.Generic; using System.Linq; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs index 9d0bcb2d4f..79e00711ae 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs @@ -1,13 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; -using SixLabors.Primitives; -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; + using SixLabors.Primitives; + + using Xunit; + using Xunit.Abstractions; + public class YCbCrImageTests { public YCbCrImageTests(ITestOutputHelper output) diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index e8a6e8c596..90d4a1f75c 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -40,5 +40,6 @@ + \ No newline at end of file