diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs index d686a5e258..51016c8288 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -164,5 +164,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return (this[0] * 31) + this[1]; } + public static long TotalDifference(ref Block8x8 a, ref Block8x8 b) + { + long result = 0; + for (int i = 0; i < Size; i++) + { + int d = a[i] - b[i]; + result += Math.Abs(d); + } + + return result; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs index 856e31b331..ec19a4fe9d 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs @@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort public Image Decode(Stream stream) where TPixel : struct, IPixel { - this.ParseStream(stream, false); + this.ParseStream(stream); this.ProcessBlocksIntoJpegImageChannels(); Image image = this.ConvertJpegPixelsToImagePixels(); @@ -260,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// The stream /// Whether to decode metadata only. - public void ParseStream(Stream stream, bool metadataOnly) + public void ParseStream(Stream stream, bool metadataOnly = false) { this.MetaData = new ImageMetaData(); this.InputStream = stream; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index d77a468872..6df413a850 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -125,5 +125,20 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(42, value); } + + [Fact] + public void TotalDifference() + { + short[] data = Create8x8ShortData(); + var block1 = new Block8x8(data); + var block2 = new Block8x8(data); + + block2[10] += 7; + block2[63] += 8; + + long d = Block8x8.TotalDifference(ref block1, ref block2); + + Assert.Equal(15, d); + } } } \ 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 e3351d963c..a8128ca80b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -294,8 +294,8 @@ namespace SixLabors.ImageSharp.Tests ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult); ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult); - this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentage}"); - this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentage}"); + this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}"); + this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs index 11b7691033..f9cb316814 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs @@ -9,8 +9,11 @@ namespace SixLabors.ImageSharp.Tests using BitMiracle.LibJpeg.Classic; 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; @@ -105,7 +108,14 @@ namespace SixLabors.ImageSharp.Tests 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); @@ -124,11 +134,11 @@ namespace SixLabors.ImageSharp.Tests return null; } - Image result = new Image(c0.XCount * 8, c0.YCount * 8); + Image result = new Image(c0.BlockCountX * 8, c0.BlockCountY * 8); - for (int by = 0; by < c0.YCount; by++) + for (int by = 0; by < c0.BlockCountY; by++) { - for (int bx = 0; bx < c0.XCount; bx++) + for (int bx = 0; bx < c0.BlockCountX; bx++) { this.WriteToImage(bx, by, result); } @@ -142,9 +152,9 @@ namespace SixLabors.ImageSharp.Tests ComponentData c1 = this.Components[1]; ComponentData c2 = this.Components[2]; - Block8x8 block0 = c0.Blocks[by, bx]; - Block8x8 block1 = c1.Blocks[by, bx]; - Block8x8 block2 = c2.Blocks[by, bx]; + 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); @@ -216,28 +226,28 @@ namespace SixLabors.ImageSharp.Tests public class ComponentData : IEquatable { - public ComponentData(int yCount, int xCount, int index) + public ComponentData(int blockCountY, int blockCountX, int index) { - this.YCount = yCount; - this.XCount = xCount; + this.BlockCountY = blockCountY; + this.BlockCountX = blockCountX; this.Index = index; - this.Blocks = new Block8x8[this.YCount, this.XCount]; + this.Blocks = new Buffer2D(this.BlockCountX, this.BlockCountY); } - public Size Size => new Size(this.XCount, this.YCount); + public Size Size => new Size(this.BlockCountX, this.BlockCountY); public int Index { get; } - public int YCount { get; } + public int BlockCountY { get; } - public int XCount { get; } + public int BlockCountX { get; } - public Block8x8[,] Blocks { get; private set; } + 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; @@ -266,39 +276,56 @@ namespace SixLabors.ImageSharp.Tests { this.MinVal = Math.Min(this.MinVal, data.Min()); this.MaxVal = Math.Max(this.MaxVal, data.Max()); - this.Blocks[y, x] = new Block8x8(data); + this.Blocks[x, y] = new Block8x8(data); } - public static ComponentData Load(FrameComponent sc, int index) + public static ComponentData Load(FrameComponent c, int index) { var result = new ComponentData( - sc.BlocksPerColumnForMcu, - sc.BlocksPerLineForMcu, + c.BlocksPerColumnForMcu, + c.BlocksPerLineForMcu, index ); - result.Init(sc); + + for (int y = 0; y < result.BlockCountY; y++) + { + for (int x = 0; x < result.BlockCountX; x++) + { + short[] data = c.GetBlockBuffer(y, x).ToArray(); + result.MakeBlock(data, y, x); + } + } + return result; } - private void Init(FrameComponent sc) + public static ComponentData Load(OldComponent c) { - for (int y = 0; y < this.YCount; y++) + var result = new ComponentData( + c.BlockCountY, + c.BlockCountX, + c.Index + ); + + for (int y = 0; y < result.BlockCountY; y++) { - for (int x = 0; x < this.XCount; x++) + for (int x = 0; x < result.BlockCountX; x++) { - short[] data = sc.GetBlockBuffer(y, x).ToArray(); - this.MakeBlock(data, y, x); + short[] data = c.GetBlockReference(x, y).ToArray(); + result.MakeBlock(data, y, x); } } + + return result; } public Image CreateGrayScaleImage() { - Image result = new Image(this.XCount * 8, this.YCount * 8); + Image result = new Image(this.BlockCountX * 8, this.BlockCountY * 8); - for (int by = 0; by < this.YCount; by++) + for (int by = 0; by < this.BlockCountY; by++) { - for (int bx = 0; bx < this.XCount; bx++) + for (int bx = 0; bx < this.BlockCountX; bx++) { this.WriteToImage(bx, by, result); } @@ -308,7 +335,7 @@ namespace SixLabors.ImageSharp.Tests internal void WriteToImage(int bx, int by, Image image) { - Block8x8 block = this.Blocks[by, bx]; + Block8x8 block = this.Blocks[bx, by]; for (int y = 0; y < 8; y++) { @@ -340,17 +367,18 @@ namespace SixLabors.ImageSharp.Tests { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - bool ok = this.Index == other.Index && this.YCount == other.YCount && this.XCount == other.XCount - && this.MinVal == other.MinVal - && this.MaxVal == other.MaxVal; + bool ok = this.Index == other.Index && this.BlockCountY == other.BlockCountY + && this.BlockCountX == other.BlockCountX; + //&& this.MinVal == other.MinVal + //&& this.MaxVal == other.MaxVal; if (!ok) return false; - for (int i = 0; i < this.YCount; i++) + for (int y = 0; y < this.BlockCountY; y++) { - for (int j = 0; j < this.XCount; j++) + for (int x = 0; x < this.BlockCountX; x++) { - Block8x8 a = this.Blocks[i, j]; - Block8x8 b = other.Blocks[i, j]; + Block8x8 a = this.Blocks[x, y]; + Block8x8 b = other.Blocks[x, y]; if (!a.Equals(b)) return false; } } @@ -370,8 +398,8 @@ namespace SixLabors.ImageSharp.Tests unchecked { var hashCode = this.Index; - hashCode = (hashCode * 397) ^ this.YCount; - hashCode = (hashCode * 397) ^ this.XCount; + hashCode = (hashCode * 397) ^ this.BlockCountY; + hashCode = (hashCode * 397) ^ this.BlockCountX; hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); return hashCode; @@ -387,6 +415,8 @@ namespace SixLabors.ImageSharp.Tests { return !Equals(left, right); } + + } internal static FieldInfo GetNonPublicField(object obj, string fieldName) @@ -400,5 +430,28 @@ namespace SixLabors.ImageSharp.Tests FieldInfo fi = GetNonPublicField(obj, fieldName); return fi.GetValue(obj); } + + public static double CalculateAverageDifference(ComponentData a, ComponentData b) + { + BigInteger totalDiff = 0; + if (a.Size != b.Size) + { + throw new Exception("a.Size != b.Size"); + } + + int count = a.Blocks.Length; + + for (int i = 0; i < count; i++) + { + Block8x8 aa = a.Blocks[i]; + Block8x8 bb = b.Blocks[i]; + + long diff = Block8x8.TotalDifference(ref aa, ref bb); + totalDiff += diff; + } + + double result = (double)totalDiff; + return result / (count * Block8x8.Size); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 84a900f32d..90c6eeb587 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,12 +1,14 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { + using System; using System.Collections.Generic; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -58,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void PdfJsDecoder_ParseStream_SaveSpectralResult(TestImageProvider provider) where TPixel : struct, IPixel { @@ -77,7 +79,63 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] - public void CompareSpectralResults_PdfJs(TestImageProvider provider) + public void OriginalDecoder_ParseStream_SaveSpectralResult(TestImageProvider provider) + where TPixel : struct, IPixel + { + OldJpegDecoderCore decoder = new OldJpegDecoderCore(Configuration.Default, new JpegDecoder()); + + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + + using (var ms = new MemoryStream(sourceBytes)) + { + decoder.ParseStream(ms, false); + + var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + this.SaveSpectralImage(provider, data); + } + } + + private void VerifySpectralCorrectness( + MemoryStream ms, + LibJpegTools.SpectralData imageSharpData) + where TPixel : struct, IPixel + { + ms.Seek(0, SeekOrigin.Begin); + var libJpegData = LibJpegTools.SpectralData.Load(ms); + + bool equality = libJpegData.Equals(imageSharpData); + this.Output.WriteLine("Spectral data equality: " + equality); + //if (!equality) + //{ + int componentCount = imageSharpData.ComponentCount; + if (libJpegData.ComponentCount != componentCount) + { + throw new Exception("libJpegData.ComponentCount != componentCount"); + } + + double totalDifference = 0; + this.Output.WriteLine("*** Differences ***"); + for (int i = 0; i < componentCount; i++) + { + LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i]; + LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i]; + + double d = LibJpegTools.CalculateAverageDifference(libJpegComponent, imageSharpComponent); + + this.Output.WriteLine($"Component{i}: {d}"); + totalDifference += d; + } + totalDifference /= componentCount; + + this.Output.WriteLine($"AVERAGE: {totalDifference}"); + //} + + Assert.Equal(libJpegData, imageSharpData); + } + + [Theory] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void VerifySpectralCorrectness_PdfJs(TestImageProvider provider) where TPixel : struct, IPixel { JpegDecoderCore decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); @@ -89,13 +147,25 @@ namespace SixLabors.ImageSharp.Tests decoder.ParseStream(ms); var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - ms.Seek(0, SeekOrigin.Begin); - var libJpegData = LibJpegTools.SpectralData.Load(ms); + this.VerifySpectralCorrectness(ms, imageSharpData); + } + } - bool equality = libJpegData.Equals(imageSharpData); - this.Output.WriteLine("Spectral data equality: " + equality); + [Theory] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void VerifySpectralResults_OriginalDecoder(TestImageProvider provider) + where TPixel : struct, IPixel + { + OldJpegDecoderCore decoder = new OldJpegDecoderCore(Configuration.Default, new JpegDecoder()); + + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + + using (var ms = new MemoryStream(sourceBytes)) + { + decoder.ParseStream(ms); + var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - Assert.Equal(libJpegData, imageSharpData); + this.VerifySpectralCorrectness(ms, imageSharpData); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 22a8d2cff6..a4c540c5e1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -24,7 +24,7 @@ public float? TotalNormalizedDifference { get; } - public string DifferencePercentage => this.TotalNormalizedDifference.HasValue + public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue ? $"{this.TotalNormalizedDifference.Value * 100:0.0000}%" : "?"; @@ -46,7 +46,7 @@ var sb = new StringBuilder(); if (this.TotalNormalizedDifference.HasValue) { - sb.AppendLine($"Total difference: {this.DifferencePercentage}"); + sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); } int max = Math.Min(5, this.Differences.Length);