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];
}
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)
where TPixel : struct, IPixel<TPixel>
{
this.ParseStream(stream, false);
this.ParseStream(stream);
this.ProcessBlocksIntoJpegImageChannels<TPixel>();
Image<TPixel> image = this.ConvertJpegPixelsToImagePixels<TPixel>();
@ -260,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
/// <param name="stream">The stream</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.InputStream = stream;

15
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);
}
}
}

4
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}");
}
}
}

129
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<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);
}
@ -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<ComponentData>
{
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<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 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 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<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);
}
@ -308,7 +335,7 @@ namespace SixLabors.ImageSharp.Tests
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++)
{
@ -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);
}
}
}

84
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
@ -77,7 +79,63 @@ namespace SixLabors.ImageSharp.Tests
[Theory]
[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>
{
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<TPixel>(ms, imageSharpData);
}
}
bool equality = libJpegData.Equals(imageSharpData);
this.Output.WriteLine("Spectral data equality: " + equality);
[Theory]
[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 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);

Loading…
Cancel
Save