// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { public abstract class ImageComparer { public static ImageComparer Exact { get; } = Tolerant(0, 0); public static ImageComparer Tolerant( float imageThreshold = TolerantImageComparer.DefaultImageThreshold, int pixelThresholdInPixelByteSum = 0) { return new TolerantImageComparer(imageThreshold, pixelThresholdInPixelByteSum); } public abstract ImageSimilarityReport CompareImagesOrFrames( ImageBase expected, ImageBase actual) where TPixelA : struct, IPixel where TPixelB : struct, IPixel; } public static class ImageComparerExtensions { public static IEnumerable CompareImages( this ImageComparer comparer, Image expected, Image actual) where TPixelA : struct, IPixel where TPixelB : struct, IPixel { var result = new List(); ImageSimilarityReport report = comparer.CompareImagesOrFrames(expected, actual); if (!report.IsEmpty) { result.Add(report); } if (expected.Frames.Count != actual.Frames.Count) { throw new Exception("Frame count does not match!"); } for (int i = 0; i < expected.Frames.Count; i++) { report = comparer.CompareImagesOrFrames(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 : struct, IPixel where TPixelB : struct, 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 ImagePixelsAreDifferentException(reports); } } /// /// 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 to use /// The expected image /// The actual image /// The bounds within the image has been altered public static void EnsureProcessorChangesAreConstrained( this ImageComparer comparer, Image expected, Image actual, Rectangle bounds) 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)); comparer.VerifySimilarity(expected, actual); } } }