//
// 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}!");
}
}
}
}