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