Browse Source

spectral testing

pull/322/head
Anton Firszov 9 years ago
parent
commit
43bf873c4e
  1. 11
      src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs
  2. 4
      src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs
  3. 15
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs
  4. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  5. 129
      tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs
  6. 84
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  7. 4
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs

11
src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs

@ -164,5 +164,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return (this[0] * 31) + this[1]; 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;
}
} }
} }

4
src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs

@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
public Image<TPixel> Decode<TPixel>(Stream stream) public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
this.ParseStream(stream, false); this.ParseStream(stream);
this.ProcessBlocksIntoJpegImageChannels<TPixel>(); this.ProcessBlocksIntoJpegImageChannels<TPixel>();
Image<TPixel> image = this.ConvertJpegPixelsToImagePixels<TPixel>(); Image<TPixel> image = this.ConvertJpegPixelsToImagePixels<TPixel>();
@ -260,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary> /// </summary>
/// <param name="stream">The stream</param> /// <param name="stream">The stream</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param> /// <param name="metadataOnly">Whether to decode metadata only.</param>
public void ParseStream(Stream stream, bool metadataOnly) public void ParseStream(Stream stream, bool metadataOnly = false)
{ {
this.MetaData = new ImageMetaData(); this.MetaData = new ImageMetaData();
this.InputStream = stream; this.InputStream = stream;

15
tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs

@ -125,5 +125,20 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(42, value); 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);
}
} }
} }

4
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -294,8 +294,8 @@ namespace SixLabors.ImageSharp.Tests
ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult); ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult);
ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult); ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult);
this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentage}"); this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}");
this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentage}"); this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}");
} }
} }
} }

129
tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs

@ -9,8 +9,11 @@ namespace SixLabors.ImageSharp.Tests
using BitMiracle.LibJpeg.Classic; using BitMiracle.LibJpeg.Classic;
using SixLabors.ImageSharp.Formats.Jpeg.Common; 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;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -105,7 +108,14 @@ namespace SixLabors.ImageSharp.Tests
public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder) public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder)
{ {
FrameComponent[] srcComponents = decoder.Frame.Components; 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(); ComponentData[] destComponents = srcComponents.Select(ComponentData.Load).ToArray();
return new SpectralData(destComponents); return new SpectralData(destComponents);
@ -124,11 +134,11 @@ namespace SixLabors.ImageSharp.Tests
return null; return null;
} }
Image<Rgba32> result = new Image<Rgba32>(c0.XCount * 8, c0.YCount * 8); Image<Rgba32> result = new Image<Rgba32>(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); this.WriteToImage(bx, by, result);
} }
@ -142,9 +152,9 @@ namespace SixLabors.ImageSharp.Tests
ComponentData c1 = this.Components[1]; ComponentData c1 = this.Components[1];
ComponentData c2 = this.Components[2]; ComponentData c2 = this.Components[2];
Block8x8 block0 = c0.Blocks[by, bx]; Block8x8 block0 = c0.Blocks[bx, by];
Block8x8 block1 = c1.Blocks[by, bx]; Block8x8 block1 = c1.Blocks[bx, by];
Block8x8 block2 = c2.Blocks[by, bx]; Block8x8 block2 = c2.Blocks[bx, by];
float d0 = (c0.MaxVal - c0.MinVal); float d0 = (c0.MaxVal - c0.MinVal);
float d1 = (c1.MaxVal - c1.MinVal); float d1 = (c1.MaxVal - c1.MinVal);
@ -216,28 +226,28 @@ namespace SixLabors.ImageSharp.Tests
public class ComponentData : IEquatable<ComponentData> public class ComponentData : IEquatable<ComponentData>
{ {
public ComponentData(int yCount, int xCount, int index) public ComponentData(int blockCountY, int blockCountX, int index)
{ {
this.YCount = yCount; this.BlockCountY = blockCountY;
this.XCount = xCount; this.BlockCountX = blockCountX;
this.Index = index; this.Index = index;
this.Blocks = new Block8x8[this.YCount, this.XCount]; this.Blocks = new Buffer2D<Block8x8>(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 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<Block8x8> Blocks { get; private set; }
public short MinVal { get; private set; } = short.MaxValue; public short MinVal { get; private set; } = short.MaxValue;
public short MaxVal { get; private set; } = short.MinValue; public short MaxVal { get; private set; } = short.MinValue;
public static ComponentData Load(Array bloxSource, int index) public static ComponentData Load(Array bloxSource, int index)
{ {
int yCount = bloxSource.Length; int yCount = bloxSource.Length;
@ -266,39 +276,56 @@ namespace SixLabors.ImageSharp.Tests
{ {
this.MinVal = Math.Min(this.MinVal, data.Min()); this.MinVal = Math.Min(this.MinVal, data.Min());
this.MaxVal = Math.Max(this.MaxVal, data.Max()); 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( var result = new ComponentData(
sc.BlocksPerColumnForMcu, c.BlocksPerColumnForMcu,
sc.BlocksPerLineForMcu, c.BlocksPerLineForMcu,
index 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; 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(); short[] data = c.GetBlockReference(x, y).ToArray();
this.MakeBlock(data, y, x); result.MakeBlock(data, y, x);
} }
} }
return result;
} }
public Image<Rgba32> CreateGrayScaleImage() public Image<Rgba32> CreateGrayScaleImage()
{ {
Image<Rgba32> result = new Image<Rgba32>(this.XCount * 8, this.YCount * 8); Image<Rgba32> result = new Image<Rgba32>(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); this.WriteToImage(bx, by, result);
} }
@ -308,7 +335,7 @@ namespace SixLabors.ImageSharp.Tests
internal void WriteToImage(int bx, int by, Image<Rgba32> image) internal void WriteToImage(int bx, int by, Image<Rgba32> image)
{ {
Block8x8 block = this.Blocks[by, bx]; Block8x8 block = this.Blocks[bx, by];
for (int y = 0; y < 8; y++) for (int y = 0; y < 8; y++)
{ {
@ -340,17 +367,18 @@ namespace SixLabors.ImageSharp.Tests
{ {
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(this, other)) return true;
bool ok = this.Index == other.Index && this.YCount == other.YCount && this.XCount == other.XCount bool ok = this.Index == other.Index && this.BlockCountY == other.BlockCountY
&& this.MinVal == other.MinVal && this.BlockCountX == other.BlockCountX;
&& this.MaxVal == other.MaxVal; //&& this.MinVal == other.MinVal
//&& this.MaxVal == other.MaxVal;
if (!ok) return false; 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 a = this.Blocks[x, y];
Block8x8 b = other.Blocks[i, j]; Block8x8 b = other.Blocks[x, y];
if (!a.Equals(b)) return false; if (!a.Equals(b)) return false;
} }
} }
@ -370,8 +398,8 @@ namespace SixLabors.ImageSharp.Tests
unchecked unchecked
{ {
var hashCode = this.Index; var hashCode = this.Index;
hashCode = (hashCode * 397) ^ this.YCount; hashCode = (hashCode * 397) ^ this.BlockCountY;
hashCode = (hashCode * 397) ^ this.XCount; hashCode = (hashCode * 397) ^ this.BlockCountX;
hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode();
hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode();
return hashCode; return hashCode;
@ -387,6 +415,8 @@ namespace SixLabors.ImageSharp.Tests
{ {
return !Equals(left, right); return !Equals(left, right);
} }
} }
internal static FieldInfo GetNonPublicField(object obj, string fieldName) internal static FieldInfo GetNonPublicField(object obj, string fieldName)
@ -400,5 +430,28 @@ namespace SixLabors.ImageSharp.Tests
FieldInfo fi = GetNonPublicField(obj, fieldName); FieldInfo fi = GetNonPublicField(obj, fieldName);
return fi.GetValue(obj); 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);
}
} }
} }

84
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -1,12 +1,14 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests
{ {
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -58,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests
} }
[Theory] [Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void PdfJsDecoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider) public void PdfJsDecoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
@ -77,7 +79,63 @@ namespace SixLabors.ImageSharp.Tests
[Theory] [Theory]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void CompareSpectralResults_PdfJs<TPixel>(TestImageProvider<TPixel> provider) public void OriginalDecoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>(
MemoryStream ms,
LibJpegTools.SpectralData imageSharpData)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
JpegDecoderCore decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); JpegDecoderCore decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
@ -89,13 +147,25 @@ namespace SixLabors.ImageSharp.Tests
decoder.ParseStream(ms); decoder.ParseStream(ms);
var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
ms.Seek(0, SeekOrigin.Begin); this.VerifySpectralCorrectness<TPixel>(ms, imageSharpData);
var libJpegData = LibJpegTools.SpectralData.Load(ms); }
}
bool equality = libJpegData.Equals(imageSharpData); [Theory]
this.Output.WriteLine("Spectral data equality: " + equality); [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void VerifySpectralResults_OriginalDecoder<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>(ms, imageSharpData);
} }
} }

4
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs

@ -24,7 +24,7 @@
public float? TotalNormalizedDifference { get; } public float? TotalNormalizedDifference { get; }
public string DifferencePercentage => this.TotalNormalizedDifference.HasValue public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue
? $"{this.TotalNormalizedDifference.Value * 100:0.0000}%" ? $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"
: "?"; : "?";
@ -46,7 +46,7 @@
var sb = new StringBuilder(); var sb = new StringBuilder();
if (this.TotalNormalizedDifference.HasValue) if (this.TotalNormalizedDifference.HasValue)
{ {
sb.AppendLine($"Total difference: {this.DifferencePercentage}"); sb.AppendLine($"Total difference: {this.DifferencePercentageString}");
} }
int max = Math.Min(5, this.Differences.Length); int max = Math.Min(5, this.Differences.Length);

Loading…
Cancel
Save