// // 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 Xunit; /// /// Class to perform simple image comparisons. /// public static class ImageComparer { const int DefaultScalingFactor = 32; // This is means the images get scaled into a 32x32 image to sample pixels const int DefaultSegmentThreshold = 3; // The greyscale difference between 2 segements my be > 3 before it influences the overall difference 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.Fill(NamedColors.HotPink, bounds); actual.Fill(NamedColors.HotPink, bounds); CheckSimilarity(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 CheckSimilarity(Image expected, Image actual, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) where TPixelA : struct, IPixel where TPixelB : struct, IPixel { 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); 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(this Image source, int scalingFactor) where TPixelA : struct, IPixel { byte[] buffer = new byte[3]; using (Image img = new Image(source).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; } } } } }