diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 4a44d0006e..d1783d323b 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -560,6 +560,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } + // TODO: Optimize this! + public void RoundInplace() + { + for (int i = 0; i < Size; i++) + { + this[i] = MathF.Round(this[i]); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs index 4837e190f6..92ed621e97 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/PostProcessing/JpegImagePostProcessor.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorSapce; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -16,8 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing public const int PixelRowsPerStep = 4 * 8; - private JpegComponentPostProcessor[] componentProcessors; - public JpegImagePostProcessor(IRawJpegData rawJpeg) { this.RawJpeg = rawJpeg; @@ -25,9 +22,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); - this.componentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray(); + this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray(); } + public JpegComponentPostProcessor[] ComponentProcessors { get; } + public IRawJpegData RawJpeg { get; } public int NumberOfPostProcessorSteps { get; } @@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing public void Dispose() { - foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) { cpp.Dispose(); } @@ -52,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing throw new NotImplementedException(); } - foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) { cpp.CopyBlocksToColorBuffer(); } @@ -76,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.PostProcessing { int maxY = Math.Min(destination.Height, this.CurrentImageRowInPixels + PixelRowsPerStep); - JpegComponentPostProcessor[] cp = this.componentProcessors; + JpegComponentPostProcessor[] cp = this.ComponentProcessors; YCbCrAndRgbConverter converter = new YCbCrAndRgbConverter(); diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs index 7baf545342..be03b5dd6e 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockPostProcessor.cs @@ -10,6 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { using System.Runtime.CompilerServices; + using SixLabors.Primitives; + /// /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. /// @@ -74,7 +76,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.QuantizeAndTransform(decoder, component, ref sourceBlock); this.data.ResultBlock.NormalizeColorsInplace(); - this.data.ResultBlock.CopyTo(destArea); + Size divs = component.SubSamplingDivisors; + + this.data.ResultBlock.RoundInplace(); + + this.data.ResultBlock.CopyTo(destArea, divs.Width, divs.Height); } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index 6bc087f9ed..c1544e5b19 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -37,8 +37,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } + private static void SaveBuffer(JpegComponentPostProcessor cp, TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = cp.ColorBuffer.ToGrayscaleImage(1f / 255f)) + { + image.DebugSave(provider, $"-C{cp.Component.Index}-"); + } + + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void DoProcessorStep(TestImageProvider provider) where TPixel : struct, IPixel { @@ -49,7 +60,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { pp.DoPostProcessorStep(image); - image.DebugSave(provider); + JpegComponentPostProcessor[] cp = pp.ComponentProcessors; + + SaveBuffer(cp[0], provider); + SaveBuffer(cp[1], provider); + SaveBuffer(cp[2], provider); } } @@ -78,7 +93,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); - this.Output.WriteLine("Difference: "+ report.DifferencePercentageString); + this.Output.WriteLine($"*** {imageFile} ***"); + this.Output.WriteLine($"Difference: "+ report.DifferencePercentageString); // ReSharper disable once PossibleInvalidOperationException Assert.True(report.TotalNormalizedDifference.Value < 0.005f); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 7ff2a3923c..1fc47726b5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2, 200)] public void LLM_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) { - float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-1000, 1000, seed); + float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-range, range, seed); var source = Block8x8F.Load(sourceArray); @@ -61,7 +61,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 0.1f); } - + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + public void LLM_IDCT_CompareToIntegerRoundedAccurateImplementation(int seed, int range) + { + Block8x8F fSource = CreateRoundedRandomFloatBlock(-range, range, seed); + Block8x8 iSource = fSource.RoundAsInt16Block(); + + Block8x8 iExpected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref iSource); + Block8x8F fExpected = iExpected.AsFloatBlock(); + + Block8x8F fActual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref fSource); + + this.CompareBlocks(fExpected, fActual, 2); + } + + [Theory] [InlineData(42)] [InlineData(1)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index 2049b3f946..07268ef214 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -106,6 +106,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42) => Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed)); + internal static Block8x8F CreateRoundedRandomFloatBlock(int minValue, int maxValue, int seed = 42) => + Block8x8F.Load(Create8x8RoundedRandomFloatData(minValue, maxValue, seed)); + internal void Print8x8Data(T[] data) => this.Print8x8Data(new Span(data)); internal void Print8x8Data(Span data) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index b8d1dbf41f..8a992b17d3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -20,7 +20,7 @@ } public static ImageSimilarityReport Empty => - new ImageSimilarityReport(null, null, Enumerable.Empty(), null); + new ImageSimilarityReport(null, null, Enumerable.Empty(), 0f); // TODO: This should not be a nullable value! public float? TotalNormalizedDifference { get; } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index cd2a223886..774fd4f7bd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -13,6 +13,10 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests { + using System.Numerics; + + using SixLabors.ImageSharp.Memory; + public static class TestImageExtensions { /// @@ -187,5 +191,21 @@ namespace SixLabors.ImageSharp.Tests return image; } + + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) + { + var image = new Image(buffer.Width, buffer.Height); + + Span pixels = image.Pixels; + + for (int i = 0; i < buffer.Length; i++) + { + float value = buffer[i] * scale; + var v = new Vector4(value, value, value, 1f); + pixels[i].PackFromVector4(v); + } + + return image; + } } } \ No newline at end of file