mirror of https://github.com/SixLabors/ImageSharp
32 changed files with 493 additions and 196 deletions
@ -1,173 +1,22 @@ |
|||||
// <copyright file="ImageComparer.cs" company="James Jackson-South">
|
|
||||
// Copyright (c) James Jackson-South and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
// </copyright>
|
|
||||
|
|
||||
namespace ImageSharp.Tests |
namespace ImageSharp.Tests |
||||
{ |
{ |
||||
using System; |
using System; |
||||
using ImageSharp; |
|
||||
using ImageSharp.Memory; |
|
||||
using ImageSharp.PixelFormats; |
|
||||
using SixLabors.Primitives; |
|
||||
using Xunit; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Class to perform simple image comparisons.
|
|
||||
/// </summary>
|
|
||||
public static class ImageComparer |
|
||||
{ |
|
||||
internal const int DefaultScalingFactor = 32; // This is means the images get scaled into a 32x32 image to sample pixels
|
|
||||
internal const int DefaultSegmentThreshold = 3; // The greyscale difference between 2 segements my be > 3 before it influences the overall difference
|
|
||||
internal const float DefaultImageThreshold = 0.000F; // After segment thresholds the images must have no differences
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Fills the bounded area with a solid color and does a visual comparison between 2 images asserting the difference outwith
|
|
||||
/// that area is less then a configurable threshold.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixelA">The color of the expected image</typeparam>
|
|
||||
/// <typeparam name="TPixelB">The color type fo the the actual image</typeparam>
|
|
||||
/// <param name="expected">The expected image</param>
|
|
||||
/// <param name="actual">The actual image</param>
|
|
||||
/// <param name="bounds">The bounds within the image has been altered</param>
|
|
||||
/// <param name="imageTheshold">
|
|
||||
/// The threshold for the percentage difference where the images are asumed to be the same.
|
|
||||
/// The default/undefined value is <see cref="DefaultImageThreshold"/>
|
|
||||
/// </param>
|
|
||||
/// <param name="segmentThreshold">
|
|
||||
/// The threshold of the individual segments before it acumulates towards the overall difference.
|
|
||||
/// The default undefined value is <see cref="DefaultSegmentThreshold"/>
|
|
||||
/// </param>
|
|
||||
/// <param name="scalingFactor">
|
|
||||
/// This is a sampling factor we sample a grid of average pixels <paramref name="scalingFactor"/> width by <paramref name="scalingFactor"/> high
|
|
||||
/// The default undefined value is <see cref="DefaultScalingFactor"/>
|
|
||||
/// </param>
|
|
||||
public static void EnsureProcessorChangesAreConstrained<TPixelA, TPixelB>( |
|
||||
Image<TPixelA> expected, |
|
||||
Image<TPixelB> actual, |
|
||||
Rectangle bounds, |
|
||||
float imageTheshold = DefaultImageThreshold, |
|
||||
byte segmentThreshold = DefaultSegmentThreshold, |
|
||||
int scalingFactor = DefaultScalingFactor) |
|
||||
where TPixelA : struct, IPixel<TPixelA> |
|
||||
where TPixelB : struct, IPixel<TPixelB> |
|
||||
{ |
|
||||
// Draw identical shapes over the bounded and compare to ensure changes are constrained.
|
|
||||
expected.Mutate(x => x.Fill(NamedColors<TPixelA>.HotPink, bounds)); |
|
||||
actual.Mutate(x => x.Fill(NamedColors<TPixelB>.HotPink, bounds)); |
|
||||
|
|
||||
VerifySimilarity(expected, actual, imageTheshold, segmentThreshold, scalingFactor); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
using ImageSharp.PixelFormats; |
||||
/// Does a visual comparison between 2 images and then asserts the difference is less then a configurable threshold
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixelA">The color of the expected image</typeparam>
|
|
||||
/// <typeparam name="TPixelB">The color type fo the the actual image</typeparam>
|
|
||||
/// <param name="expected">The expected image</param>
|
|
||||
/// <param name="actual">The actual image</param>
|
|
||||
/// <param name="imageTheshold">
|
|
||||
/// The threshold for the percentage difference where the images are asumed to be the same.
|
|
||||
/// The default/undefined value is <see cref="DefaultImageThreshold"/>
|
|
||||
/// </param>
|
|
||||
/// <param name="segmentThreshold">
|
|
||||
/// The threshold of the individual segments before it acumulates towards the overall difference.
|
|
||||
/// The default undefined value is <see cref="DefaultSegmentThreshold"/>
|
|
||||
/// </param>
|
|
||||
/// <param name="scalingFactor">
|
|
||||
/// This is a sampling factor we sample a grid of average pixels <paramref name="scalingFactor"/> width by <paramref name="scalingFactor"/> high
|
|
||||
/// The default undefined value is <see cref="DefaultScalingFactor"/>
|
|
||||
/// </param>
|
|
||||
public static void VerifySimilarity<TPixelA, TPixelB>( |
|
||||
Image<TPixelA> expected, |
|
||||
Image<TPixelB> actual, |
|
||||
float imageTheshold = DefaultImageThreshold, |
|
||||
byte segmentThreshold = DefaultSegmentThreshold, |
|
||||
int scalingFactor = DefaultScalingFactor) |
|
||||
where TPixelA : struct, IPixel<TPixelA> where TPixelB : struct, IPixel<TPixelB> |
|
||||
{ |
|
||||
Assert.Equal(expected.Width, actual.Width); |
|
||||
Assert.Equal(expected.Height, actual.Height); |
|
||||
|
|
||||
float percentage = expected.PercentageDifference(actual, segmentThreshold, scalingFactor); |
|
||||
|
|
||||
Assert.InRange(percentage, 0, imageTheshold); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Does a visual comparison between 2 images and then and returns the percentage diffence between the 2
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixelA">The color of the source image</typeparam>
|
|
||||
/// <typeparam name="TPixelB">The color type for the target image</typeparam>
|
|
||||
/// <param name="source">The source image</param>
|
|
||||
/// <param name="target">The target image</param>
|
|
||||
/// <param name="segmentThreshold">
|
|
||||
/// The threshold of the individual segments before it acumulates towards the overall difference.
|
|
||||
/// The default undefined value is <see cref="DefaultSegmentThreshold"/>
|
|
||||
/// </param>
|
|
||||
/// <param name="scalingFactor">
|
|
||||
/// This is a sampling factor we sample a grid of average pixels <paramref name="scalingFactor"/> width by <paramref name="scalingFactor"/> high
|
|
||||
/// The default undefined value is <see cref="ImageComparer.DefaultScalingFactor"/>
|
|
||||
/// </param>
|
|
||||
/// <returns>Returns a number from 0 - 1 which represents the difference focter between the images.</returns>
|
|
||||
public static float PercentageDifference<TPixelA, TPixelB>(this Image<TPixelA> source, Image<TPixelB> target, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) |
|
||||
where TPixelA : struct, IPixel<TPixelA> |
|
||||
where TPixelB : struct, IPixel<TPixelB> |
|
||||
{ |
|
||||
// code adapted from https://www.codeproject.com/Articles/374386/Simple-image-comparison-in-NET
|
|
||||
Fast2DArray<byte> differences = GetDifferences(source, target, scalingFactor); |
|
||||
|
|
||||
int diffPixels = 0; |
|
||||
|
|
||||
foreach (byte b in differences.Data) |
|
||||
{ |
|
||||
if (b > segmentThreshold) { diffPixels++; } |
|
||||
} |
|
||||
|
|
||||
return diffPixels / (float)(scalingFactor * scalingFactor); |
|
||||
} |
|
||||
|
|
||||
private static Fast2DArray<byte> GetDifferences<TPixelA, TPixelB>(Image<TPixelA> source, Image<TPixelB> target, int scalingFactor) |
|
||||
where TPixelA : struct, IPixel<TPixelA> |
|
||||
where TPixelB : struct, IPixel<TPixelB> |
|
||||
{ |
|
||||
var differences = new Fast2DArray<byte>(scalingFactor, scalingFactor); |
|
||||
Fast2DArray<byte> firstGray = source.GetGrayScaleValues(scalingFactor); |
|
||||
Fast2DArray<byte> secondGray = target.GetGrayScaleValues(scalingFactor); |
|
||||
|
|
||||
for (int y = 0; y < scalingFactor; y++) |
public abstract class ImageComparer |
||||
{ |
{ |
||||
for (int x = 0; x < scalingFactor; x++) |
public abstract void Verify<TPixelA, TPixelB>(Image<TPixelA> expected, Image<TPixelB> actual) |
||||
{ |
where TPixelA : struct, IPixel<TPixelA> where TPixelB : struct, IPixel<TPixelB>; |
||||
int diff = firstGray[x, y] - secondGray[x, y]; |
} |
||||
differences[x, y] = (byte)Math.Abs(diff); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return differences; |
public class ExactComparer : ImageComparer |
||||
} |
{ |
||||
|
public static ExactComparer Instance { get; } = new ExactComparer(); |
||||
|
|
||||
private static Fast2DArray<byte> GetGrayScaleValues<TPixelA>(this Image<TPixelA> source, int scalingFactor) |
public override void Verify<TPixelA, TPixelB>(Image<TPixelA> expected, Image<TPixelB> actual) |
||||
where TPixelA : struct, IPixel<TPixelA> |
|
||||
{ |
{ |
||||
byte[] buffer = new byte[3]; |
throw new NotImplementedException(); |
||||
using (Image<TPixelA> img = source.Clone(x => x.Resize(scalingFactor, scalingFactor).Grayscale())) |
|
||||
{ |
|
||||
using (PixelAccessor<TPixelA> pixels = img.Lock()) |
|
||||
{ |
|
||||
var grayScale = new Fast2DArray<byte>(scalingFactor, scalingFactor); |
|
||||
for (int y = 0; y < scalingFactor; y++) |
|
||||
{ |
|
||||
for (int x = 0; x < scalingFactor; x++) |
|
||||
{ |
|
||||
pixels[x, y].ToXyzBytes(buffer, 0); |
|
||||
grayScale[x, y] = buffer[0]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return grayScale; |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
namespace ImageSharp.Tests |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Text; |
||||
|
|
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
public class ImagesSimilarityException : Exception |
||||
|
{ |
||||
|
public ImagesSimilarityException(string message) |
||||
|
: base(message) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class ImageDimensionsMismatchException : ImagesSimilarityException |
||||
|
{ |
||||
|
public ImageDimensionsMismatchException(Size expectedSize, Size actualSize) |
||||
|
: base($"The image dimensions {actualSize} do not match the expected {expectedSize}!") |
||||
|
{ |
||||
|
this.ExpectedSize = expectedSize; |
||||
|
this.ActualSize = actualSize; |
||||
|
} |
||||
|
|
||||
|
public Size ExpectedSize { get; } |
||||
|
public Size ActualSize { get; } |
||||
|
} |
||||
|
|
||||
|
public class ImagesAreNotEqualException : ImagesSimilarityException |
||||
|
{ |
||||
|
public ImagesAreNotEqualException(Point[] differences) |
||||
|
: base("Images are not equal! Differences: " + StringifyDifferences(differences)) |
||||
|
{ |
||||
|
this.Differences = differences; |
||||
|
} |
||||
|
|
||||
|
public Point[] Differences { get; } |
||||
|
|
||||
|
private static string StringifyDifferences(Point[] differences) |
||||
|
{ |
||||
|
var sb = new StringBuilder(); |
||||
|
int max = Math.Min(5, differences.Length); |
||||
|
|
||||
|
for (int i = 0; i < max; i++) |
||||
|
{ |
||||
|
sb.Append(differences[i]); |
||||
|
if (i < max - 1) |
||||
|
{ |
||||
|
sb.Append(';'); |
||||
|
} |
||||
|
} |
||||
|
if (differences.Length >= 5) |
||||
|
{ |
||||
|
sb.Append("..."); |
||||
|
} |
||||
|
return sb.ToString(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,213 @@ |
|||||
|
// <copyright file="ImageComparer.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Tests |
||||
|
{ |
||||
|
using System; |
||||
|
using ImageSharp; |
||||
|
using ImageSharp.Memory; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
using Xunit; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Class to perform simple image comparisons.
|
||||
|
/// </summary>
|
||||
|
public class PercentageImageComparer : ImageComparer |
||||
|
{ |
||||
|
public float ImageThreshold { get; } |
||||
|
|
||||
|
public byte SegmentThreshold { get; } |
||||
|
|
||||
|
public int ScaleIntoSize { get; } |
||||
|
|
||||
|
|
||||
|
public PercentageImageComparer( |
||||
|
float imageThreshold = DefaultImageThreshold, |
||||
|
byte segmentThreshold = DefaultSegmentThreshold, |
||||
|
int scaleIntoSize = DefaultScaleIntoSize) |
||||
|
{ |
||||
|
this.ImageThreshold = imageThreshold; |
||||
|
this.SegmentThreshold = segmentThreshold; |
||||
|
this.ScaleIntoSize = scaleIntoSize; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This is means the images get scaled into a 32x32 image to sample pixels
|
||||
|
/// </summary>
|
||||
|
public const int DefaultScaleIntoSize = 32; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The greyscale difference between 2 segements my be > 3 before it influences the overall difference
|
||||
|
/// </summary>
|
||||
|
public const int DefaultSegmentThreshold = 3; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// After segment thresholds the images must have no differences
|
||||
|
/// </summary>
|
||||
|
public const float DefaultImageThreshold = 0.000F; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Fills the bounded area with a solid color and does a visual comparison between 2 images asserting the difference outwith
|
||||
|
/// that area is less then a configurable threshold.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixelA">The color of the expected image</typeparam>
|
||||
|
/// <typeparam name="TPixelB">The color type fo the the actual image</typeparam>
|
||||
|
/// <param name="expected">The expected image</param>
|
||||
|
/// <param name="actual">The actual image</param>
|
||||
|
/// <param name="bounds">The bounds within the image has been altered</param>
|
||||
|
/// <param name="imageTheshold">
|
||||
|
/// The threshold for the percentage difference where the images are asumed to be the same.
|
||||
|
/// The default/undefined value is <see cref="DefaultImageThreshold"/>
|
||||
|
/// </param>
|
||||
|
/// <param name="segmentThreshold">
|
||||
|
/// The threshold of the individual segments before it acumulates towards the overall difference.
|
||||
|
/// The default undefined value is <see cref="DefaultSegmentThreshold"/>
|
||||
|
/// </param>
|
||||
|
/// <param name="scaleIntoSize">
|
||||
|
/// This is a sampling factor we sample a grid of average pixels <paramref name="scaleIntoSize"/> width by <paramref name="scaleIntoSize"/> high
|
||||
|
/// The default undefined value is <see cref="DefaultScaleIntoSize"/>
|
||||
|
/// </param>
|
||||
|
public static void EnsureProcessorChangesAreConstrained<TPixelA, TPixelB>( |
||||
|
Image<TPixelA> expected, |
||||
|
Image<TPixelB> actual, |
||||
|
Rectangle bounds, |
||||
|
float imageTheshold = DefaultImageThreshold, |
||||
|
byte segmentThreshold = DefaultSegmentThreshold, |
||||
|
int scaleIntoSize = DefaultScaleIntoSize) |
||||
|
where TPixelA : struct, IPixel<TPixelA> |
||||
|
where TPixelB : struct, IPixel<TPixelB> |
||||
|
{ |
||||
|
// Draw identical shapes over the bounded and compare to ensure changes are constrained.
|
||||
|
expected.Mutate(x => x.Fill(NamedColors<TPixelA>.HotPink, bounds)); |
||||
|
actual.Mutate(x => x.Fill(NamedColors<TPixelB>.HotPink, bounds)); |
||||
|
|
||||
|
VerifySimilarity(expected, actual, imageTheshold, segmentThreshold, scaleIntoSize); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Does a visual comparison between 2 images and then asserts the difference is less then a configurable threshold
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixelA">The color of the expected image</typeparam>
|
||||
|
/// <typeparam name="TPixelB">The color type fo the the actual image</typeparam>
|
||||
|
/// <param name="expected">The expected image</param>
|
||||
|
/// <param name="actual">The actual image</param>
|
||||
|
/// <param name="imageTheshold">
|
||||
|
/// The threshold for the percentage difference where the images are asumed to be the same.
|
||||
|
/// The default/undefined value is <see cref="DefaultImageThreshold"/>
|
||||
|
/// </param>
|
||||
|
/// <param name="segmentThreshold">
|
||||
|
/// The threshold of the individual segments before it acumulates towards the overall difference.
|
||||
|
/// The default undefined value is <see cref="DefaultSegmentThreshold"/>
|
||||
|
/// </param>
|
||||
|
/// <param name="scaleIntoSize">
|
||||
|
/// This is a sampling factor we sample a grid of average pixels <paramref name="scaleIntoSize"/> width by <paramref name="scaleIntoSize"/> high
|
||||
|
/// The default undefined value is <see cref="DefaultScaleIntoSize"/>
|
||||
|
/// </param>
|
||||
|
public static void VerifySimilarity<TPixelA, TPixelB>( |
||||
|
Image<TPixelA> expected, |
||||
|
Image<TPixelB> actual, |
||||
|
float imageTheshold = DefaultImageThreshold, |
||||
|
byte segmentThreshold = DefaultSegmentThreshold, |
||||
|
int scaleIntoSize = DefaultScaleIntoSize) |
||||
|
where TPixelA : struct, IPixel<TPixelA> where TPixelB : struct, IPixel<TPixelB> |
||||
|
{ |
||||
|
var comparer = new PercentageImageComparer(imageTheshold, segmentThreshold, scaleIntoSize); |
||||
|
comparer.Verify(expected, actual); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Does a visual comparison between 2 images and then and returns the percentage diffence between the 2
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixelA">The color of the source image</typeparam>
|
||||
|
/// <typeparam name="TPixelB">The color type for the target image</typeparam>
|
||||
|
/// <param name="source">The source image</param>
|
||||
|
/// <param name="target">The target image</param>
|
||||
|
/// <param name="segmentThreshold">
|
||||
|
/// The threshold of the individual segments before it acumulates towards the overall difference.
|
||||
|
/// The default undefined value is <see cref="DefaultSegmentThreshold"/>
|
||||
|
/// </param>
|
||||
|
/// <param name="scalingFactor">
|
||||
|
/// This is a sampling factor we sample a grid of average pixels <paramref name="scalingFactor"/> width by <paramref name="scalingFactor"/> high
|
||||
|
/// The default undefined value is <see cref="DefaultScaleIntoSize"/>
|
||||
|
/// </param>
|
||||
|
/// <returns>Returns a number from 0 - 1 which represents the difference focter between the images.</returns>
|
||||
|
public static float PercentageDifference<TPixelA, TPixelB>(Image<TPixelA> source, Image<TPixelB> target, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScaleIntoSize) |
||||
|
where TPixelA : struct, IPixel<TPixelA> |
||||
|
where TPixelB : struct, IPixel<TPixelB> |
||||
|
{ |
||||
|
// code adapted from https://www.codeproject.com/Articles/374386/Simple-image-comparison-in-NET
|
||||
|
Fast2DArray<byte> differences = GetDifferences(source, target, scalingFactor); |
||||
|
|
||||
|
int diffPixels = 0; |
||||
|
|
||||
|
foreach (byte b in differences.Data) |
||||
|
{ |
||||
|
if (b > segmentThreshold) { diffPixels++; } |
||||
|
} |
||||
|
|
||||
|
return diffPixels / (float)(scalingFactor * scalingFactor); |
||||
|
} |
||||
|
|
||||
|
private static Fast2DArray<byte> GetDifferences<TPixelA, TPixelB>(Image<TPixelA> source, Image<TPixelB> target, int scalingFactor) |
||||
|
where TPixelA : struct, IPixel<TPixelA> |
||||
|
where TPixelB : struct, IPixel<TPixelB> |
||||
|
{ |
||||
|
var differences = new Fast2DArray<byte>(scalingFactor, scalingFactor); |
||||
|
Fast2DArray<byte> firstGray = GetGrayScaleValues(source, scalingFactor); |
||||
|
Fast2DArray<byte> secondGray = GetGrayScaleValues(target, scalingFactor); |
||||
|
|
||||
|
for (int y = 0; y < scalingFactor; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < scalingFactor; x++) |
||||
|
{ |
||||
|
int diff = firstGray[x, y] - secondGray[x, y]; |
||||
|
differences[x, y] = (byte)Math.Abs(diff); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return differences; |
||||
|
} |
||||
|
|
||||
|
private static Fast2DArray<byte> GetGrayScaleValues<TPixelA>(Image<TPixelA> source, int scalingFactor) |
||||
|
where TPixelA : struct, IPixel<TPixelA> |
||||
|
{ |
||||
|
byte[] buffer = new byte[3]; |
||||
|
using (Image<TPixelA> img = source.Clone(x => x.Resize(scalingFactor, scalingFactor).Grayscale())) |
||||
|
{ |
||||
|
using (PixelAccessor<TPixelA> pixels = img.Lock()) |
||||
|
{ |
||||
|
var grayScale = new Fast2DArray<byte>(scalingFactor, scalingFactor); |
||||
|
for (int y = 0; y < scalingFactor; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < scalingFactor; x++) |
||||
|
{ |
||||
|
pixels[x, y].ToXyzBytes(buffer, 0); |
||||
|
grayScale[x, y] = buffer[0]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return grayScale; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void Verify<TPixelA, TPixelB>(Image<TPixelA> expected, Image<TPixelB> actual) |
||||
|
{ |
||||
|
if (expected.Size() != actual.Size()) |
||||
|
{ |
||||
|
throw new ImageDimensionsMismatchException(expected.Size(), actual.Size()); |
||||
|
} |
||||
|
|
||||
|
float percentage = PercentageDifference(expected, actual, this.SegmentThreshold, this.ScaleIntoSize); |
||||
|
|
||||
|
if (percentage > this.ImageThreshold) |
||||
|
{ |
||||
|
throw new ImagesSimilarityException( |
||||
|
$"The percentage difference of images {percentage} is larger than expected maximum {this.ImageThreshold}!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,175 @@ |
|||||
|
// ReSharper disable InconsistentNaming
|
||||
|
namespace ImageSharp.Tests |
||||
|
{ |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
using Xunit; |
||||
|
using Xunit.Abstractions; |
||||
|
|
||||
|
public class ImageComparerTests |
||||
|
{ |
||||
|
public ImageComparerTests(ITestOutputHelper output) |
||||
|
{ |
||||
|
this.Output = output; |
||||
|
} |
||||
|
|
||||
|
private ITestOutputHelper Output { get; } |
||||
|
|
||||
|
[Theory] |
||||
|
[WithTestPatternImages( |
||||
|
100, |
||||
|
100, |
||||
|
PixelTypes.Rgba32, |
||||
|
PercentageImageComparer.DefaultImageThreshold, |
||||
|
PercentageImageComparer.DefaultSegmentThreshold, |
||||
|
PercentageImageComparer.DefaultScaleIntoSize)] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0, 0, 100)] |
||||
|
public void PercentageComparer_ApprovesPerfectSimilarity<TPixel>( |
||||
|
TestImageProvider<TPixel> provider, |
||||
|
float imageTheshold, |
||||
|
byte segmentThreshold, |
||||
|
int scaleIntoSize) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = provider.GetImage()) |
||||
|
{ |
||||
|
using (Image<TPixel> clone = image.Clone()) |
||||
|
{ |
||||
|
PercentageImageComparer.VerifySimilarity( |
||||
|
image, |
||||
|
clone, |
||||
|
imageTheshold, |
||||
|
segmentThreshold, |
||||
|
scaleIntoSize); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void ModifyPixel<TPixel>(Image<TPixel> img, int x, int y, byte value) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = img[x, y]; |
||||
|
var rgbaPixel = default(Rgba32); |
||||
|
pixel.ToRgba32(ref rgbaPixel); |
||||
|
rgbaPixel.R += value; |
||||
|
pixel.PackFromRgba32(rgbaPixel); |
||||
|
img[x, y] = pixel; |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] |
||||
|
public void PercentageComparer_ApprovesImperfectSimilarity<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = provider.GetImage()) |
||||
|
{ |
||||
|
using (Image<TPixel> clone = image.Clone()) |
||||
|
{ |
||||
|
ModifyPixel(clone, 0, 0, 2); |
||||
|
|
||||
|
PercentageImageComparer.VerifySimilarity(image, clone, scaleIntoSize: 100); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, 99, 100)] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 99)] |
||||
|
public void PercentageComparer_ThrowsOnSizeMismatch<TPixel>(TestImageProvider<TPixel> provider, int w, int h) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = provider.GetImage()) |
||||
|
{ |
||||
|
using (Image<TPixel> clone = image.Clone(ctx => ctx.Resize(w, h))) |
||||
|
{ |
||||
|
ImageDimensionsMismatchException ex = Assert.ThrowsAny<ImageDimensionsMismatchException>( |
||||
|
() => |
||||
|
{ |
||||
|
PercentageImageComparer.VerifySimilarity(image, clone); |
||||
|
}); |
||||
|
this.Output.WriteLine(ex.Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] |
||||
|
public void PercentageComparer_WhenDifferenceIsTooLarge_Throws<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = provider.GetImage()) |
||||
|
{ |
||||
|
using (Image<TPixel> clone = image.Clone()) |
||||
|
{ |
||||
|
ModifyPixel(clone, 0, 0, 42); |
||||
|
ModifyPixel(clone, 1, 0, 42); |
||||
|
ModifyPixel(clone, 2, 0, 42); |
||||
|
|
||||
|
Assert.ThrowsAny<ImagesSimilarityException>( |
||||
|
() => { PercentageImageComparer.VerifySimilarity(image, clone, scaleIntoSize: 100); }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] |
||||
|
public void ExactComparer_ApprovesExactEquality<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = provider.GetImage()) |
||||
|
{ |
||||
|
using (Image<TPixel> clone = image.Clone()) |
||||
|
{ |
||||
|
ExactComparer.Instance.Verify(image, clone); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, 99, 100)] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 99)] |
||||
|
public void ExactComparer_ThrowsOnSizeMismatch<TPixel>(TestImageProvider<TPixel> provider, int w, int h) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = provider.GetImage()) |
||||
|
{ |
||||
|
using (Image<TPixel> clone = image.Clone(ctx => ctx.Resize(w, h))) |
||||
|
{ |
||||
|
ImageDimensionsMismatchException ex = Assert.ThrowsAny<ImageDimensionsMismatchException>( |
||||
|
() => |
||||
|
{ |
||||
|
ExactComparer.Instance.Verify(image, clone); |
||||
|
}); |
||||
|
this.Output.WriteLine(ex.Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] |
||||
|
public void ExactComparer_ThrowsOnSmallestPixelDifference<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> image = provider.GetImage()) |
||||
|
{ |
||||
|
using (Image<TPixel> clone = image.Clone()) |
||||
|
{ |
||||
|
ModifyPixel(clone, 42, 42, 1); |
||||
|
ModifyPixel(clone, 7, 93, 1); |
||||
|
|
||||
|
ImagesAreNotEqualException ex = Assert.ThrowsAny<ImagesAreNotEqualException>( |
||||
|
() => |
||||
|
{ |
||||
|
ExactComparer.Instance.Verify(image, clone); |
||||
|
}); |
||||
|
this.Output.WriteLine(ex.Message); |
||||
|
Assert.Equal(2, ex.Differences.Length); |
||||
|
Assert.Contains(new Point(42, 24), ex.Differences); |
||||
|
Assert.Contains(new Point(7, 93), ex.Differences); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue