From a00aec7d13b92c698be2d01514952799895d4265 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 15 Aug 2017 16:40:22 +0200 Subject: [PATCH] ImageComparer refactors, ExactComparer --- .../Formats/Png/PngSmokeTests.cs | 4 +- tests/ImageSharp.Tests/ImageComparer.cs | 173 +------------- .../ImagesSimilarityException.cs | 59 +++++ .../PercentageImageComparer.cs | 213 ++++++++++++++++++ .../Binarization/BinaryThresholdTest.cs | 2 +- .../Processors/Binarization/DitherTest.cs | 4 +- .../Processors/ColorMatrix/BlackWhiteTest.cs | 2 +- .../ColorMatrix/ColorBlindnessTest.cs | 2 +- .../Processors/ColorMatrix/GrayscaleTest.cs | 2 +- .../Processors/ColorMatrix/HueTest.cs | 2 +- .../Processors/ColorMatrix/KodachromeTest.cs | 2 +- .../Processors/ColorMatrix/LomographTest.cs | 2 +- .../Processors/ColorMatrix/PolaroidTest.cs | 2 +- .../Processors/ColorMatrix/SaturationTest.cs | 2 +- .../Processors/ColorMatrix/SepiaTest.cs | 2 +- .../Processors/Convolution/BoxBlurTest.cs | 2 +- .../Processors/Convolution/DetectEdgesTest.cs | 2 +- .../Convolution/GaussianBlurTest.cs | 2 +- .../Convolution/GaussianSharpenTest.cs | 2 +- .../Processors/Effects/AlphaTest.cs | 2 +- .../Processors/Effects/BackgroundColorTest.cs | 2 +- .../Processors/Effects/BrightnessTest.cs | 2 +- .../Processors/Effects/ContrastTest.cs | 2 +- .../Processors/Effects/InvertTest.cs | 2 +- .../Processors/Effects/OilPaintTest.cs | 2 +- .../Processors/Effects/PixelateTest.cs | 2 +- .../Processors/Overlays/GlowTest.cs | 2 +- .../Processors/Overlays/VignetteTest.cs | 2 +- .../TestUtilities/PixelTypes.cs | 1 + .../TestUtilities/TestImageExtensions.cs | 14 +- .../TestUtilities/Tests/ImageComparerTests.cs | 175 ++++++++++++++ ...stUtilsTests.cs => ReferenceCodecTests.cs} | 0 32 files changed, 493 insertions(+), 196 deletions(-) create mode 100644 tests/ImageSharp.Tests/ImagesSimilarityException.cs create mode 100644 tests/ImageSharp.Tests/PercentageImageComparer.cs create mode 100644 tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs rename tests/ImageSharp.Tests/TestUtilities/Tests/{IntegrationTestUtilsTests.cs => ReferenceCodecTests.cs} (100%) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 19324ac88..6baaf1a54 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -35,7 +35,7 @@ namespace ImageSharp.Tests.Formats.Png using (Image img2 = Image.Load(ms, new PngDecoder())) { // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - ImageComparer.VerifySimilarity(image, img2); + PercentageImageComparer.VerifySimilarity(image, img2); } } } @@ -121,7 +121,7 @@ namespace ImageSharp.Tests.Formats.Png ms.Position = 0; using (Image img2 = Image.Load(ms, new PngDecoder())) { - ImageComparer.VerifySimilarity(image, img2); + PercentageImageComparer.VerifySimilarity(image, img2); } } } diff --git a/tests/ImageSharp.Tests/ImageComparer.cs b/tests/ImageSharp.Tests/ImageComparer.cs index 53e5f609c..6994d99ed 100644 --- a/tests/ImageSharp.Tests/ImageComparer.cs +++ b/tests/ImageSharp.Tests/ImageComparer.cs @@ -1,173 +1,22 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - namespace ImageSharp.Tests { using System; - using ImageSharp; - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - using SixLabors.Primitives; - using Xunit; - - /// - /// Class to perform simple image comparisons. - /// - 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 - - /// - /// 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. - /// - /// The color of the expected image - /// The color type fo the the actual image - /// The expected image - /// The actual image - /// The bounds within the image has been altered - /// - /// The threshold for the percentage difference where the images are asumed to be the same. - /// The default/undefined value is - /// - /// - /// The threshold of the individual segments before it acumulates towards the overall difference. - /// The default undefined value is - /// - /// - /// This is a sampling factor we sample a grid of average pixels width by high - /// The default undefined value is - /// - public static void EnsureProcessorChangesAreConstrained( - Image expected, - Image actual, - Rectangle bounds, - float imageTheshold = DefaultImageThreshold, - byte segmentThreshold = DefaultSegmentThreshold, - int scalingFactor = DefaultScalingFactor) - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel - { - // Draw identical shapes over the bounded and compare to ensure changes are constrained. - expected.Mutate(x => x.Fill(NamedColors.HotPink, bounds)); - actual.Mutate(x => x.Fill(NamedColors.HotPink, bounds)); - - VerifySimilarity(expected, actual, imageTheshold, segmentThreshold, scalingFactor); - } - /// - /// Does a visual comparison between 2 images and then asserts the difference is less then a configurable threshold - /// - /// The color of the expected image - /// The color type fo the the actual image - /// The expected image - /// The actual image - /// - /// The threshold for the percentage difference where the images are asumed to be the same. - /// The default/undefined value is - /// - /// - /// The threshold of the individual segments before it acumulates towards the overall difference. - /// The default undefined value is - /// - /// - /// This is a sampling factor we sample a grid of average pixels width by high - /// The default undefined value is - /// - public static void VerifySimilarity( - Image expected, - Image actual, - float imageTheshold = DefaultImageThreshold, - byte segmentThreshold = DefaultSegmentThreshold, - int scalingFactor = DefaultScalingFactor) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel - { - Assert.Equal(expected.Width, actual.Width); - Assert.Equal(expected.Height, actual.Height); - - float percentage = expected.PercentageDifference(actual, segmentThreshold, scalingFactor); - - Assert.InRange(percentage, 0, imageTheshold); - } - - /// - /// Does a visual comparison between 2 images and then and returns the percentage diffence between the 2 - /// - /// The color of the source image - /// The color type for the target image - /// The source image - /// The target image - /// - /// The threshold of the individual segments before it acumulates towards the overall difference. - /// The default undefined value is - /// - /// - /// This is a sampling factor we sample a grid of average pixels width by high - /// The default undefined value is - /// - /// Returns a number from 0 - 1 which represents the difference focter between the images. - public static float PercentageDifference(this Image source, Image target, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel - { - // code adapted from https://www.codeproject.com/Articles/374386/Simple-image-comparison-in-NET - Fast2DArray 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 GetDifferences(Image source, Image target, int scalingFactor) - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel - { - var differences = new Fast2DArray(scalingFactor, scalingFactor); - Fast2DArray firstGray = source.GetGrayScaleValues(scalingFactor); - Fast2DArray secondGray = target.GetGrayScaleValues(scalingFactor); + using ImageSharp.PixelFormats; - 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); - } - } + public abstract class ImageComparer + { + public abstract void Verify(Image expected, Image actual) + where TPixelA : struct, IPixel where TPixelB : struct, IPixel; + } - return differences; - } + public class ExactComparer : ImageComparer + { + public static ExactComparer Instance { get; } = new ExactComparer(); - private static Fast2DArray GetGrayScaleValues(this Image source, int scalingFactor) - where TPixelA : struct, IPixel + public override void Verify(Image expected, Image actual) { - byte[] buffer = new byte[3]; - using (Image img = source.Clone(x => x.Resize(scalingFactor, scalingFactor).Grayscale())) - { - using (PixelAccessor pixels = img.Lock()) - { - var grayScale = new Fast2DArray(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; - } - } + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ImagesSimilarityException.cs b/tests/ImageSharp.Tests/ImagesSimilarityException.cs new file mode 100644 index 000000000..92e5f518d --- /dev/null +++ b/tests/ImageSharp.Tests/ImagesSimilarityException.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PercentageImageComparer.cs b/tests/ImageSharp.Tests/PercentageImageComparer.cs new file mode 100644 index 000000000..e94ce8d68 --- /dev/null +++ b/tests/ImageSharp.Tests/PercentageImageComparer.cs @@ -0,0 +1,213 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using ImageSharp; + using ImageSharp.Memory; + using ImageSharp.PixelFormats; + using SixLabors.Primitives; + using Xunit; + + /// + /// Class to perform simple image comparisons. + /// + 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; + } + + /// + /// This is means the images get scaled into a 32x32 image to sample pixels + /// + public const int DefaultScaleIntoSize = 32; + + /// + /// The greyscale difference between 2 segements my be > 3 before it influences the overall difference + /// + public const int DefaultSegmentThreshold = 3; + + /// + /// After segment thresholds the images must have no differences + /// + public const float DefaultImageThreshold = 0.000F; + + /// + /// 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. + /// + /// The color of the expected image + /// The color type fo the the actual image + /// The expected image + /// The actual image + /// The bounds within the image has been altered + /// + /// The threshold for the percentage difference where the images are asumed to be the same. + /// The default/undefined value is + /// + /// + /// The threshold of the individual segments before it acumulates towards the overall difference. + /// The default undefined value is + /// + /// + /// This is a sampling factor we sample a grid of average pixels width by high + /// The default undefined value is + /// + public static void EnsureProcessorChangesAreConstrained( + Image expected, + Image actual, + Rectangle bounds, + float imageTheshold = DefaultImageThreshold, + byte segmentThreshold = DefaultSegmentThreshold, + int scaleIntoSize = DefaultScaleIntoSize) + where TPixelA : struct, IPixel + where TPixelB : struct, IPixel + { + // Draw identical shapes over the bounded and compare to ensure changes are constrained. + expected.Mutate(x => x.Fill(NamedColors.HotPink, bounds)); + actual.Mutate(x => x.Fill(NamedColors.HotPink, bounds)); + + VerifySimilarity(expected, actual, imageTheshold, segmentThreshold, scaleIntoSize); + } + + /// + /// Does a visual comparison between 2 images and then asserts the difference is less then a configurable threshold + /// + /// The color of the expected image + /// The color type fo the the actual image + /// The expected image + /// The actual image + /// + /// The threshold for the percentage difference where the images are asumed to be the same. + /// The default/undefined value is + /// + /// + /// The threshold of the individual segments before it acumulates towards the overall difference. + /// The default undefined value is + /// + /// + /// This is a sampling factor we sample a grid of average pixels width by high + /// The default undefined value is + /// + public static void VerifySimilarity( + Image expected, + Image actual, + float imageTheshold = DefaultImageThreshold, + byte segmentThreshold = DefaultSegmentThreshold, + int scaleIntoSize = DefaultScaleIntoSize) + where TPixelA : struct, IPixel where TPixelB : struct, IPixel + { + var comparer = new PercentageImageComparer(imageTheshold, segmentThreshold, scaleIntoSize); + comparer.Verify(expected, actual); + } + + /// + /// Does a visual comparison between 2 images and then and returns the percentage diffence between the 2 + /// + /// The color of the source image + /// The color type for the target image + /// The source image + /// The target image + /// + /// The threshold of the individual segments before it acumulates towards the overall difference. + /// The default undefined value is + /// + /// + /// This is a sampling factor we sample a grid of average pixels width by high + /// The default undefined value is + /// + /// Returns a number from 0 - 1 which represents the difference focter between the images. + public static float PercentageDifference(Image source, Image target, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScaleIntoSize) + where TPixelA : struct, IPixel + where TPixelB : struct, IPixel + { + // code adapted from https://www.codeproject.com/Articles/374386/Simple-image-comparison-in-NET + Fast2DArray 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 GetDifferences(Image source, Image target, int scalingFactor) + where TPixelA : struct, IPixel + where TPixelB : struct, IPixel + { + var differences = new Fast2DArray(scalingFactor, scalingFactor); + Fast2DArray firstGray = GetGrayScaleValues(source, scalingFactor); + Fast2DArray 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 GetGrayScaleValues(Image source, int scalingFactor) + where TPixelA : struct, IPixel + { + byte[] buffer = new byte[3]; + using (Image img = source.Clone(x => x.Resize(scalingFactor, scalingFactor).Grayscale())) + { + using (PixelAccessor pixels = img.Lock()) + { + var grayScale = new Fast2DArray(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(Image expected, Image 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}!"); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs index e0d6ed484..6825a0cda 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs @@ -44,7 +44,7 @@ namespace ImageSharp.Tests.Processing.Processors.Binarization image.Mutate(x => x.BinaryThreshold(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTest.cs index 6570bc721..149655ea6 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTest.cs @@ -56,7 +56,7 @@ namespace ImageSharp.Tests.Processing.Processors.Binarization image.Mutate(x => x.Dither(ditherer, bounds)); image.DebugSave(provider, name); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } @@ -85,7 +85,7 @@ namespace ImageSharp.Tests.Processing.Processors.Binarization image.Mutate(x => x.Dither(diffuser, .5F, bounds)); image.DebugSave(provider, name); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/BlackWhiteTest.cs index e572847ad..10c6c2690 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/BlackWhiteTest.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.BlackWhite(bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/ColorBlindnessTest.cs index 6a3c341a6..bf923c373 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/ColorBlindnessTest.cs @@ -50,7 +50,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.ColorBlindness(colorBlindness, bounds)); image.DebugSave(provider, colorBlindness.ToString()); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/GrayscaleTest.cs index 7fdc07005..2567c41af 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/GrayscaleTest.cs @@ -54,7 +54,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.Grayscale(value, bounds)); image.DebugSave(provider, value.ToString()); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/HueTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/HueTest.cs index 41d0aa8f2..d96c908e2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/HueTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.Hue(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/KodachromeTest.cs index bed4954c8..17d969a68 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/KodachromeTest.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.Kodachrome(bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/LomographTest.cs index 8e6d984d8..fe5017392 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/LomographTest.cs @@ -38,7 +38,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.Lomograph(bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/PolaroidTest.cs index 322f6d424..b09288fd3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/PolaroidTest.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.Polaroid(bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/SaturationTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/SaturationTest.cs index ced6fceb5..20d0b8d9f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/SaturationTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/SaturationTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.Saturation(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/SepiaTest.cs index 322925c60..00a59f9c6 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/ColorMatrix/SepiaTest.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests.Processing.Processors.ColorMatrix image.Mutate(x => x.Sepia(bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index cbd32bebd..69c65e7c7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.Convolution image.Mutate(x => x.BoxBlur(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index 43d3d0d02..3d6467716 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -56,7 +56,7 @@ namespace ImageSharp.Tests.Processing.Processors.Convolution image.DebugSave(provider, grayscale: true); // TODO: We don't need this any longer after switching to ReferenceImages - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 6c4efe4d4..93b57e7dc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.Convolution image.Mutate(x => x.GaussianBlur(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs index 9b555c216..4cfbd022e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.Convolution image.Mutate(x => x.GaussianSharpen(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/AlphaTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/AlphaTest.cs index 3b6189d17..616e5f7ab 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/AlphaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/AlphaTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.Effects image.Mutate(x => x.Alpha(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs index 286248e96..b87069b7b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests.Processing.Processors.Effects image.Mutate(x => x.BackgroundColor(NamedColors.HotPink, bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/BrightnessTest.cs index 08a46a9c4..aa7d0bd9f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BrightnessTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.Effects image.Mutate(x => x.Brightness(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); ; + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); ; } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/ContrastTest.cs index 3339414d4..bc8fc7f79 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/ContrastTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.Effects image.Mutate(x => x.Contrast(value, bounds)); image.DebugSave(provider, value); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/InvertTest.cs index f146c5ba3..aa1bcb638 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/InvertTest.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests.Processing.Processors.Effects image.Mutate(x => x.Invert(bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs index b2b274831..7f6459573 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Processing.Processors.Effects image.Mutate(x => x.OilPaint(levels, brushSize, bounds)); image.DebugSave(provider, string.Join("-", levels, brushSize)); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds, 0.001F); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds, 0.001F); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs index 34f26a1e1..7d6931747 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs @@ -77,7 +77,7 @@ namespace ImageSharp.Tests.Processing.Processors.Effects } } - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs index 5c0316392..cc9fa1095 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs @@ -60,7 +60,7 @@ namespace ImageSharp.Tests.Processing.Processors.Overlays image.Mutate(x => x.Glow(bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs index 5d0f593e1..bd8e72e43 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs @@ -60,7 +60,7 @@ namespace ImageSharp.Tests.Processing.Processors.Overlays image.Mutate(x => x.Vignette(bounds)); image.DebugSave(provider); - ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + PercentageImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index 645a4dc59..c4e0a0384 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -60,6 +60,7 @@ namespace ImageSharp.Tests // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper + Default = Rgba32, // "All" is handled as a separate, individual case instead of using bitwise OR All = 30 } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index fbeb1d4d4..bd7d7f0c6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -59,15 +59,15 @@ namespace ImageSharp.Tests /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// /// The threshold for the percentage difference where the images are asumed to be the same. - /// The default/undefined value is + /// The default/undefined value is /// /// /// The threshold of the individual segments before it acumulates towards the overall difference. - /// The default undefined value is + /// The default undefined value is /// /// /// This is a sampling factor we sample a grid of average pixels width by high - /// The default undefined value is + /// The default undefined value is /// /// public static Image CompareToReferenceOutput( @@ -76,9 +76,9 @@ namespace ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool grayscale = false, - float imageTheshold = ImageComparer.DefaultImageThreshold, - byte segmentThreshold = ImageComparer.DefaultSegmentThreshold, - int scalingFactor = ImageComparer.DefaultScalingFactor) + float imageTheshold = PercentageImageComparer.DefaultImageThreshold, + byte segmentThreshold = PercentageImageComparer.DefaultSegmentThreshold, + int scalingFactor = PercentageImageComparer.DefaultScaleIntoSize) where TPixel : struct, IPixel { string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(extension, testOutputDetails); @@ -99,7 +99,7 @@ namespace ImageSharp.Tests using (Image referenceImage = Image.Load(referenceOutputFile, ReferenceDecoder.Instance)) { - ImageComparer.VerifySimilarity(referenceImage, image, imageTheshold, segmentThreshold, scalingFactor); + PercentageImageComparer.VerifySimilarity(referenceImage, image, imageTheshold, segmentThreshold, scalingFactor); } return image; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs new file mode 100644 index 000000000..e95a084df --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -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( + TestImageProvider provider, + float imageTheshold, + byte segmentThreshold, + int scaleIntoSize) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + using (Image clone = image.Clone()) + { + PercentageImageComparer.VerifySimilarity( + image, + clone, + imageTheshold, + segmentThreshold, + scaleIntoSize); + } + } + } + + private static void ModifyPixel(Image img, int x, int y, byte value) + where TPixel : struct, IPixel + { + 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(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + using (Image 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(TestImageProvider provider, int w, int h) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + using (Image clone = image.Clone(ctx => ctx.Resize(w, h))) + { + ImageDimensionsMismatchException ex = Assert.ThrowsAny( + () => + { + PercentageImageComparer.VerifySimilarity(image, clone); + }); + this.Output.WriteLine(ex.Message); + } + } + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void PercentageComparer_WhenDifferenceIsTooLarge_Throws(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + using (Image clone = image.Clone()) + { + ModifyPixel(clone, 0, 0, 42); + ModifyPixel(clone, 1, 0, 42); + ModifyPixel(clone, 2, 0, 42); + + Assert.ThrowsAny( + () => { PercentageImageComparer.VerifySimilarity(image, clone, scaleIntoSize: 100); }); + } + } + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void ExactComparer_ApprovesExactEquality(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + using (Image 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(TestImageProvider provider, int w, int h) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + using (Image clone = image.Clone(ctx => ctx.Resize(w, h))) + { + ImageDimensionsMismatchException ex = Assert.ThrowsAny( + () => + { + ExactComparer.Instance.Verify(image, clone); + }); + this.Output.WriteLine(ex.Message); + } + } + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void ExactComparer_ThrowsOnSmallestPixelDifference(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + using (Image clone = image.Clone()) + { + ModifyPixel(clone, 42, 42, 1); + ModifyPixel(clone, 7, 93, 1); + + ImagesAreNotEqualException ex = Assert.ThrowsAny( + () => + { + 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); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/IntegrationTestUtilsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs similarity index 100% rename from tests/ImageSharp.Tests/TestUtilities/Tests/IntegrationTestUtilsTests.cs rename to tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs