// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
public abstract class ImageComparer
{
public static ImageComparer Exact { get; } = Tolerant(0, 0);
///
/// Returns an instance of .
/// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'.
///
/// A ImageComparer instance.
public static ImageComparer Tolerant(
float imageThreshold = TolerantImageComparer.DefaultImageThreshold,
int perPixelManhattanThreshold = 0) =>
new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold);
///
/// Returns Tolerant(imageThresholdInPercents/100)
///
/// A ImageComparer instance.
public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0)
=> Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold);
public abstract ImageSimilarityReport CompareImagesOrFrames(
int index,
ImageFrame expected,
ImageFrame actual)
where TPixelA : unmanaged, IPixel
where TPixelB : unmanaged, IPixel;
}
public static class ImageComparerExtensions
{
public static ImageSimilarityReport CompareImagesOrFrames(
this ImageComparer comparer,
Image expected,
Image actual)
where TPixelA : unmanaged, IPixel
where TPixelB : unmanaged, IPixel => comparer.CompareImagesOrFrames(0, expected.Frames.RootFrame, actual.Frames.RootFrame);
public static IEnumerable> CompareImages(
this ImageComparer comparer,
Image expected,
Image actual)
where TPixelA : unmanaged, IPixel
where TPixelB : unmanaged, IPixel
{
var result = new List>();
if (expected.Frames.Count != actual.Frames.Count)
{
throw new Exception("Frame count does not match!");
}
for (int i = 0; i < expected.Frames.Count; i++)
{
ImageSimilarityReport report = comparer.CompareImagesOrFrames(i, expected.Frames[i], actual.Frames[i]);
if (!report.IsEmpty)
{
result.Add(report);
}
}
return result;
}
public static void VerifySimilarity(
this ImageComparer comparer,
Image expected,
Image actual)
where TPixelA : unmanaged, IPixel
where TPixelB : unmanaged, IPixel
{
if (expected.Size != actual.Size)
{
throw new ImageDimensionsMismatchException(expected.Size, actual.Size);
}
if (expected.Frames.Count != actual.Frames.Count)
{
throw new ImagesSimilarityException("Image frame count does not match!");
}
IEnumerable reports = comparer.CompareImages(expected, actual);
if (reports.Any())
{
throw new ImageDifferenceIsOverThresholdException(reports);
}
}
public static void VerifySimilarityIgnoreRegion(
this ImageComparer comparer,
Image expected,
Image actual,
Rectangle ignoredRegion)
where TPixelA : unmanaged, IPixel
where TPixelB : unmanaged, IPixel
{
if (expected.Size != actual.Size)
{
throw new ImageDimensionsMismatchException(expected.Size, actual.Size);
}
if (expected.Frames.Count != actual.Frames.Count)
{
throw new ImagesSimilarityException("Image frame count does not match!");
}
IEnumerable> reports = comparer.CompareImages(expected, actual);
if (reports.Any())
{
var cleanedReports = new List>(reports.Count());
foreach (ImageSimilarityReport r in reports)
{
IEnumerable outsideChanges = r.Differences.Where(
x =>
!(ignoredRegion.X <= x.Position.X
&& x.Position.X <= ignoredRegion.Right
&& ignoredRegion.Y <= x.Position.Y
&& x.Position.Y <= ignoredRegion.Bottom));
if (outsideChanges.Any())
{
cleanedReports.Add(new ImageSimilarityReport(r.Index, r.ExpectedImage, r.ActualImage, outsideChanges, null));
}
}
if (cleanedReports.Count > 0)
{
throw new ImageDifferenceIsOverThresholdException(cleanedReports);
}
}
}
}