Browse Source

Use Rgba64 for image comparison.

af/merge-core
James Jackson-South 8 years ago
parent
commit
ae66072668
  1. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  2. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
  3. 2
      tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs
  4. 2
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  5. 2
      tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
  6. 14
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs
  7. 2
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs
  8. 19
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs
  9. 4
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs
  10. 10
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs
  11. 35
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs
  12. 19
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  13. 18
      tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs

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

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (!CustomToleranceValues.TryGetValue(file, out float tolerance))
{
bool baseline = file.ToLower().Contains("baseline");
bool baseline = file.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) >= 0;
tolerance = baseline ? BaselineTolerance : ProgressiveTolerance;
}

2
tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
public class DetectEdgesTest : FileTestBase
{
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.001f);
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F);
public static readonly string[] CommonTestImages = { TestImages.Png.Bike };

2
tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
[GroupOutput("Filters")]
public class FilterTest
{
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f, 3);
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0218f, 3);
// Testing the generic FilterProcessor with more than one pixel type intentionally.
// There is no need to do this with the specialized ones.

2
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial };
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f);
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.069F);
public static readonly TheoryData<string, IResampler> AllReSamplers =
new TheoryData<string, IResampler>

2
tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
private readonly ITestOutputHelper Output;
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f, 3);
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0085f, 3);
/// <summary>
/// angleDeg, sx, sy, tx, ty

14
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs

@ -22,10 +22,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
int width = actual.Width;
// TODO: Comparing through Rgba32 is not robust enough because of the existance of super high precision pixel types.
// TODO: Comparing through Rgba64 may not be robust enough because of the existance of super high precision pixel types.
var aBuffer = new Rgba32[width];
var bBuffer = new Rgba32[width];
var aBuffer = new Rgba64[width];
var bBuffer = new Rgba64[width];
var differences = new List<PixelDifference>();
@ -34,13 +34,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
Span<TPixelA> aSpan = expected.GetPixelRowSpan(y);
Span<TPixelB> bSpan = actual.GetPixelRowSpan(y);
PixelOperations<TPixelA>.Instance.ToRgba32(aSpan, aBuffer, width);
PixelOperations<TPixelB>.Instance.ToRgba32(bSpan, bBuffer, width);
PixelOperations<TPixelA>.Instance.ToRgba64(aSpan, aBuffer, width);
PixelOperations<TPixelB>.Instance.ToRgba64(bSpan, bBuffer, width);
for (int x = 0; x < width; x++)
{
Rgba32 aPixel = aBuffer[x];
Rgba32 bPixel = bBuffer[x];
Rgba64 aPixel = aBuffer[x];
Rgba64 bPixel = bBuffer[x];
if (aPixel != bPixel)
{

2
tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
int i = 0;
foreach (ImageSimilarityReport r in reports)
{
sb.Append($"Report{i}: ");
sb.Append($"Report ImageFrame {i}: ");
sb.Append(r);
sb.Append(Environment.NewLine);
i++;

19
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs

@ -28,9 +28,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
/// <summary>
/// Returns Tolerant(imageThresholdInPercents/100)
/// </summary>
public static ImageComparer TolerantPercentage(float imageThresholdInPercents,
int perPixelManhattanThreshold = 0) =>
Tolerant(imageThresholdInPercents / 100f, perPixelManhattanThreshold);
public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0)
=> Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold);
public abstract ImageSimilarityReport<TPixelA, TPixelB> CompareImagesOrFrames<TPixelA, TPixelB>(
ImageFrame<TPixelA> expected,
@ -120,18 +119,20 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
var cleanedReports = new List<ImageSimilarityReport<TPixelA, TPixelB>>(reports.Count());
foreach (ImageSimilarityReport<TPixelA, TPixelB> r in reports)
{
IEnumerable<PixelDifference> outsideChanges = r.Differences.Where(x => !(
ignoredRegion.X <= x.Position.X &&
x.Position.X <= ignoredRegion.Right &&
ignoredRegion.Y <= x.Position.Y &&
x.Position.Y <= ignoredRegion.Bottom));
IEnumerable<PixelDifference> outsideChanges = r.Differences.Where(
x =>
!(ignoredRegion.X <= x.Position.X
&& x.Position.X <= ignoredRegion.Right
&& ignoredRegion.Y <= x.Position.Y
&& x.Position.Y <= ignoredRegion.Bottom));
if (outsideChanges.Any())
{
cleanedReports.Add(new ImageSimilarityReport<TPixelA, TPixelB>(r.ExpectedImage, r.ActualImage, outsideChanges, null));
}
}
if (cleanedReports.Any())
if (cleanedReports.Count > 0)
{
throw new ImageDifferenceIsOverThresholdException(cleanedReports);
}

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

@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
this.TotalNormalizedDifference = totalNormalizedDifference;
this.Differences = differences.ToArray();
}
public object ExpectedImage { get; }
public object ActualImage { get; }
@ -59,6 +60,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
var sb = new StringBuilder();
if (this.TotalNormalizedDifference.HasValue)
{
sb.AppendLine();
sb.AppendLine($"Total difference: {this.DifferencePercentageString}");
}
int max = Math.Min(5, this.Differences.Length);
@ -68,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
sb.Append(this.Differences[i]);
if (i < max - 1)
{
sb.Append("; ");
sb.AppendFormat(";{0}", Environment.NewLine);
}
}
if (this.Differences.Length >= 5)

10
tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs

@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
this.AlphaDifference = alphaDifference;
}
public PixelDifference(Point position, Rgba32 expected, Rgba32 actual)
public PixelDifference(Point position, Rgba64 expected, Rgba64 actual)
: this(position,
(int)actual.R - (int)expected.R,
(int)actual.G - (int)expected.G,
(int)actual.B - (int)expected.B,
(int)actual.A - (int)expected.A)
actual.R - expected.R,
actual.G - expected.G,
actual.B - expected.B,
actual.A - expected.A)
{
}

35
tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs

@ -12,7 +12,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
public class TolerantImageComparer : ImageComparer
{
// 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit
public const float DefaultImageThreshold = 1.0f / (100 * 100 * 255);
// 257 = (1 / 255) * 65535.
public const float DefaultImageThreshold = 257F / (100 * 100 * 65535);
/// <summary>
/// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'.
@ -28,23 +29,23 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
/// <summary>
/// The maximal tolerated difference represented by a value between 0.0 and 1.0.
/// Examples of percentage differences on a single pixel:
/// 1. PixelA = (255,255,255,0) PixelB =(0,0,0,255) leads to 100% difference on a single pixel
/// 2. PixelA = (255,255,255,0) PixelB =(255,255,255,255) leads to 25% difference on a single pixel
/// 3. PixelA = (255,255,255,0) PixelB =(128,128,128,128) leads to 50% difference on a single pixel
/// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel
/// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel
/// 3. PixelA = (65535,65535,65535,0) PixelB =(128,128,128,128) leads to 50% difference on a single pixel
///
/// The total differences is the sum of all pixel differences normalized by image dimensions!
/// The individual distances are calculated using the Manhattan function:
/// <see>
/// <cref>https://en.wikipedia.org/wiki/Taxicab_geometry</cref>
/// </see>
/// ImageThresholdInPercents = 1.0/255 means that we allow one byte difference per channel on a 1x1 image
/// ImageThresholdInPercents = 1.0/(100*100*255) means that we allow only one byte difference per channel on a 100x100 image
/// ImageThresholdInPercents = 1.0/65535 means that we allow one unit difference per channel on a 1x1 image
/// ImageThresholdInPercents = 1.0/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image
/// </summary>
public float ImageThreshold { get; }
/// <summary>
/// The threshold of the individual pixels before they acumulate towards the overall difference.
/// For an individual <see cref="Rgba32"/> pixel pair the value is the Manhattan distance of pixels:
/// For an individual <see cref="Rgba64"/> pixel pair the value is the Manhattan distance of pixels:
/// <see>
/// <cref>https://en.wikipedia.org/wiki/Taxicab_geometry</cref>
/// </see>
@ -60,12 +61,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
int width = actual.Width;
// TODO: Comparing through Rgba32 is not robust enough because of the existance of super high precision pixel types.
// TODO: Comparing through Rgba64 may not robust enough because of the existance of super high precision pixel types.
var aBuffer = new Rgba32[width];
var bBuffer = new Rgba32[width];
var aBuffer = new Rgba64[width];
var bBuffer = new Rgba64[width];
float totalDifference = 0.0f;
float totalDifference = 0F;
var differences = new List<PixelDifference>();
@ -74,8 +75,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
Span<TPixelA> aSpan = expected.GetPixelRowSpan(y);
Span<TPixelB> bSpan = actual.GetPixelRowSpan(y);
PixelOperations<TPixelA>.Instance.ToRgba32(aSpan, aBuffer, width);
PixelOperations<TPixelB>.Instance.ToRgba32(bSpan, bBuffer, width);
PixelOperations<TPixelA>.Instance.ToRgba64(aSpan, aBuffer, width);
PixelOperations<TPixelB>.Instance.ToRgba64(bSpan, bBuffer, width);
for (int x = 0; x < width; x++)
{
@ -91,8 +92,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
}
}
float normalizedDifference = totalDifference / ((float)actual.Width * (float)actual.Height);
normalizedDifference /= 4.0f * 255.0f;
float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height);
normalizedDifference /= 4F * 65535F;
if (normalizedDifference > this.ImageThreshold)
{
@ -105,12 +106,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetManhattanDistanceInRgbaSpace(ref Rgba32 a, ref Rgba32 b)
private static int GetManhattanDistanceInRgbaSpace(ref Rgba64 a, ref Rgba64 b)
{
return Diff(a.R, b.R) + Diff(a.G, b.G) + Diff(a.B, b.B) + Diff(a.A, b.A);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Diff(byte a, byte b) => Math.Abs(a - b);
private static int Diff(ushort a, ushort b) => Math.Abs(a - b);
}
}

19
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests
{
Directory.CreateDirectory(baseDir);
}
for (int i = 0; i < frameCount; i++)
{
string filePath = $"{baseDir}/{i:D2}.{extension}";
@ -258,7 +258,7 @@ namespace SixLabors.ImageSharp.Tests
this.TestName = methodName;
this.OutputSubfolderName = outputSubfolderName;
}
internal string GetTestOutputDir()
{
string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName);
@ -281,25 +281,26 @@ namespace SixLabors.ImageSharp.Tests
where TPixel : struct, IPixel<TPixel>
{
TPixel pixel = img[x, y];
var rgbaPixel = default(Rgba32);
pixel.ToRgba32(ref rgbaPixel);
Rgba64 rgbaPixel = default;
pixel.ToRgba64(ref rgbaPixel);
ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F);
if (rgbaPixel.R + perChannelChange <= 255)
{
rgbaPixel.R += perChannelChange;
rgbaPixel.R += change;
}
else
{
rgbaPixel.R -= perChannelChange;
rgbaPixel.R -= change;
}
if (rgbaPixel.G + perChannelChange <= 255)
{
rgbaPixel.G += perChannelChange;
rgbaPixel.G += change;
}
else
{
rgbaPixel.G -= perChannelChange;
rgbaPixel.G -= change;
}
if (rgbaPixel.B + perChannelChange <= 255)
@ -320,7 +321,7 @@ namespace SixLabors.ImageSharp.Tests
rgbaPixel.A -= perChannelChange;
}
pixel.PackFromRgba32(rgbaPixel);
pixel.PackFromRgba64(rgbaPixel);
img[x, y] = pixel;
}
}

18
tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs

@ -68,16 +68,14 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> clone = image.Clone())
{
byte perChannelChange = 2;
byte perChannelChange = 20;
ImagingTestCaseUtility.ModifyPixel(clone, 3, 1, perChannelChange);
var comparer = ImageComparer.Tolerant();
ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny<ImageDifferenceIsOverThresholdException>(
() =>
{
comparer.VerifySimilarity(image, clone);
});
() => comparer.VerifySimilarity(image, clone));
PixelDifference diff = ex.Reports.Single().Differences.Single();
Assert.Equal(new Point(3, 1), diff.Position);
}
@ -85,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests
}
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
[WithTestPatternImages(100, 100, PixelTypes.Rgba64)]
public void TolerantImageComparer_TestPerPixelThreshold<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
@ -93,11 +91,11 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> clone = image.Clone())
{
ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 10);
ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 10);
ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 10);
ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1);
ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 1);
ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 1);
var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 42);
var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 257 * 3);
comparer.VerifySimilarity(image, clone);
}
}

Loading…
Cancel
Save