//
// 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;
}
}
}
}
}