// 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, Func predicate = null) where TPixelA : unmanaged, IPixel where TPixelB : unmanaged, IPixel { List> result = []; int expectedFrameCount = actual.Frames.Count; if (predicate != null) { expectedFrameCount = 0; for (int i = 0; i < actual.Frames.Count; i++) { if (predicate(i, actual.Frames.Count)) { expectedFrameCount++; } } } if (expected.Frames.Count != expectedFrameCount) { throw new ImagesSimilarityException("Frame count does not match!"); } for (int i = 0; i < expected.Frames.Count; i++) { if (predicate != null && !predicate(i, expected.Frames.Count)) { continue; } 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, Func predicate = null) where TPixelA : unmanaged, IPixel where TPixelB : unmanaged, IPixel { if (expected.Size != actual.Size) { throw new ImageDimensionsMismatchException(expected.Size, actual.Size); } int expectedFrameCount = actual.Frames.Count; if (predicate != null) { expectedFrameCount = 0; for (int i = 0; i < actual.Frames.Count; i++) { if (predicate(i, actual.Frames.Count)) { expectedFrameCount++; } } } if (expected.Frames.Count != expectedFrameCount) { throw new ImagesSimilarityException("Image frame count does not match!"); } IEnumerable reports = comparer.CompareImages(expected, actual, predicate); 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()) { List> cleanedReports = new(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); } } } }