// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.IO; using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests { public static class TestImageExtensions { /// /// TODO: This should be a common processing method! The image.Opacity(val) multiplies the alpha channel! /// /// /// public static void MakeOpaque(this IImageProcessingContext ctx) where TPixel : struct, IPixel { MemoryManager memoryManager = ctx.MemoryManager; ctx.Apply(img => { using (Buffer2D temp = memoryManager.Allocate2D(img.Width, img.Height)) { Span tempSpan = temp.GetSpan(); foreach (ImageFrame frame in img.Frames) { Span pixelSpan = frame.GetPixelSpan(); PixelOperations.Instance.ToVector4(pixelSpan, tempSpan, pixelSpan.Length); for (int i = 0; i < tempSpan.Length; i++) { ref Vector4 v = ref tempSpan[i]; v.W = 1.0f; } PixelOperations.Instance.PackFromVector4(tempSpan, pixelSpan, pixelSpan.Length); } } }); } public static Image DebugSave( this Image image, ITestImageProvider provider, FormattableString testOutputDetails, string extension = "png", bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { return image.DebugSave( provider, (object)testOutputDetails, extension, appendPixelTypeToFileName, appendSourceFileOrDescription); } /// /// Saves the image only when not running in the CI server. /// /// The pixel format /// The image /// The image provider /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. public static Image DebugSave( this Image image, ITestImageProvider provider, object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { if (TestEnvironment.RunsOnCI) { return image; } // We are running locally then we want to save it out provider.Utility.SaveTestOutputFile( image, extension, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); return image; } public static Image DebugSave( this Image image, ITestImageProvider provider, IImageEncoder encoder, FormattableString testOutputDetails, bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { return image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); } /// /// Saves the image only when not running in the CI server. /// /// The pixel format /// The image /// The image provider /// The image encoder /// Details to be concatenated to the test output file, describing the parameters of the test. /// A boolean indicating whether to append the pixel type to the output file name. public static Image DebugSave( this Image image, ITestImageProvider provider, IImageEncoder encoder, object testOutputDetails = null, bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { if (TestEnvironment.RunsOnCI) { return image; } // We are running locally then we want to save it out provider.Utility.SaveTestOutputFile( image, encoder: encoder, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); return image; } public static Image DebugSaveMultiFrame( this Image image, ITestImageProvider provider, object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { if (TestEnvironment.RunsOnCI) { return image; } // We are running locally then we want to save it out provider.Utility.SaveTestOutputFileMultiFrame( image, extension, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); return image; } public static Image CompareToReferenceOutput( this Image image, ITestImageProvider provider, FormattableString testOutputDetails, string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { return image.CompareToReferenceOutput( provider, (object)testOutputDetails, extension, grayscale, appendPixelTypeToFileName, appendSourceFileOrDescription); } /// /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// The output file should be named identically to the output produced by . /// /// The pixel format /// The image /// The image provider /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. /// public static Image CompareToReferenceOutput( this Image image, ITestImageProvider provider, object testOutputDetails = null, string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { return CompareToReferenceOutput( image, ImageComparer.Tolerant(), provider, testOutputDetails, extension, grayscale, appendPixelTypeToFileName, appendSourceFileOrDescription); } public static Image CompareToReferenceOutput( this Image image, ImageComparer comparer, ITestImageProvider provider, FormattableString testOutputDetails, string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { return image.CompareToReferenceOutput( comparer, provider, (object)testOutputDetails, extension, grayscale, appendPixelTypeToFileName); } /// /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// The output file should be named identically to the output produced by . /// /// The pixel format /// The image /// The to use /// The image provider /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. /// public static Image CompareToReferenceOutput( this Image image, ImageComparer comparer, ITestImageProvider provider, object testOutputDetails = null, string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { using (Image referenceImage = GetReferenceOutputImage( provider, testOutputDetails, extension, appendPixelTypeToFileName, appendSourceFileOrDescription)) { comparer.VerifySimilarity(referenceImage, image); } return image; } public static Image CompareFirstFrameToReferenceOutput( this Image image, ImageComparer comparer, ITestImageProvider provider, FormattableString testOutputDetails, string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { return image.CompareFirstFrameToReferenceOutput( comparer, provider, (object)testOutputDetails, extension, grayscale, appendPixelTypeToFileName, appendSourceFileOrDescription); } public static Image CompareFirstFrameToReferenceOutput( this Image image, ImageComparer comparer, ITestImageProvider provider, object testOutputDetails = null, string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) using (Image referenceImage = GetReferenceOutputImage( provider, testOutputDetails, extension, appendPixelTypeToFileName, appendSourceFileOrDescription)) { firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); firstFrameOnlyImage.Frames.RemoveFrame(0); comparer.VerifySimilarity(referenceImage, firstFrameOnlyImage); } return image; } public static Image CompareToReferenceOutputMultiFrame( this Image image, ITestImageProvider provider, ImageComparer comparer, object testOutputDetails = null, string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { using (Image referenceImage = GetReferenceOutputImageMultiFrame( provider, image.Frames.Count, testOutputDetails, extension, appendPixelTypeToFileName)) { comparer.VerifySimilarity(referenceImage, image); } return image; } public static Image GetReferenceOutputImage(this ITestImageProvider provider, object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); if (!File.Exists(referenceOutputFile)) { throw new Exception("Reference output file missing: " + referenceOutputFile); } IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(referenceOutputFile); return Image.Load(referenceOutputFile, decoder); } public static Image GetReferenceOutputImageMultiFrame(this ITestImageProvider provider, int frameCount, object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( frameCount, extension, testOutputDetails, appendPixelTypeToFileName); var temporaryFrameImages = new List>(); IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]); foreach (string path in frameFiles) { if (!File.Exists(path)) { throw new Exception("Reference output file missing: " + path); } var tempImage = Image.Load(path, decoder); temporaryFrameImages.Add(tempImage); } Image firstTemp = temporaryFrameImages[0]; var result = new Image(firstTemp.Width, firstTemp.Height); foreach (Image fi in temporaryFrameImages) { result.Frames.AddFrame(fi.Frames.RootFrame); fi.Dispose(); } // remove the initial empty frame: result.Frames.RemoveFrame(0); return result; } public static IEnumerable GetReferenceOutputSimilarityReports( this Image image, ITestImageProvider provider, ImageComparer comparer, object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { using (Image referenceImage = provider.GetReferenceOutputImage( testOutputDetails, extension, appendPixelTypeToFileName)) { return comparer.CompareImages(referenceImage, image); } } public static Image ComparePixelBufferTo( this Image image, Span expectedPixels) where TPixel : struct, IPixel { Span actualPixels = image.GetPixelSpan(); Assert.True(expectedPixels.Length == actualPixels.Length, "Buffer sizes are not equal!"); for (int i = 0; i < expectedPixels.Length; i++) { Assert.True(expectedPixels[i].Equals(actualPixels[i]), $"Pixels are different on position {i}!"); } return image; } /// /// All pixels in all frames should be exactly equal to 'expectedPixel'. /// public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel) where TPixel : struct, IPixel { foreach (ImageFrame imageFrame in image.Frames) { imageFrame.ComparePixelBufferTo(expectedPixel); } return image; } /// /// All pixels in the frame should be exactly equal to 'expectedPixel'. /// public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) where TPixel : struct, IPixel { Span actualPixels = imageFrame.GetPixelSpan(); for (int i = 0; i < actualPixels.Length; i++) { Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!"); } return imageFrame; } public static ImageFrame ComparePixelBufferTo( this ImageFrame image, Span expectedPixels) where TPixel : struct, IPixel { Span actual = image.GetPixelSpan(); Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); for (int i = 0; i < expectedPixels.Length; i++) { Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!"); } return image; } public static Image CompareToOriginal( this Image image, ITestImageProvider provider) where TPixel : struct, IPixel { return CompareToOriginal(image, provider, ImageComparer.Tolerant()); } public static Image CompareToOriginal( this Image image, ITestImageProvider provider, ImageComparer comparer) where TPixel : struct, IPixel { string path = TestImageProvider.GetFilePathOrNull(provider); if (path == null) { throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); } var testFile = TestFile.Create(path); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(path); IImageFormat format = TestEnvironment.GetImageFormat(path); IImageDecoder defaultDecoder = Configuration.Default.ImageFormatsManager.FindDecoder(format); //if (referenceDecoder.GetType() == defaultDecoder.GetType()) //{ // throw new InvalidOperationException($"Can't use CompareToOriginal(): no actual reference decoder registered for {format.Name}"); //} using (var original = Image.Load(testFile.Bytes, referenceDecoder)) { comparer.VerifySimilarity(original, image); } return image; } /// /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) /// 2. Executing DebugSave() /// 3. Executing CopareToReferenceOutput() /// internal static void VerifyOperation( this TestImageProvider provider, ImageComparer comparer, Action> operation, FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { operation(image); image.DebugSave( provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); image.CompareToReferenceOutput(comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); } } /// /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) /// 2. Executing DebugSave() /// 3. Executing CopareToReferenceOutput() /// internal static void VerifyOperation( this TestImageProvider provider, Action> operation, FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { provider.VerifyOperation( ImageComparer.Tolerant(), operation, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); } /// /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) /// 2. Executing DebugSave() /// 3. Executing CopareToReferenceOutput() /// internal static void VerifyOperation( this TestImageProvider provider, ImageComparer comparer, Action> operation, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { provider.VerifyOperation( comparer, operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); } /// /// Utility method for doing the following in one step: /// 1. Executing an operation (taken as a delegate) /// 2. Executing DebugSave() /// 3. Executing CopareToReferenceOutput() /// internal static void VerifyOperation( this TestImageProvider provider, Action> operation, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); } /// /// Loads the expected image with a reference decoder + compares it to . /// Also performs a debug save using . /// internal static void VerifyEncoder( this Image image, ITestImageProvider provider, string extension, object testOutputDetails, IImageEncoder encoder, ImageComparer customComparer = null, bool appendPixelTypeToFileName = true, string referenceImageExtension = null) where TPixel : struct, IPixel { string actualOutputFile = provider.Utility.SaveTestOutputFile( image, extension, encoder, testOutputDetails, appendPixelTypeToFileName); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) { ImageComparer comparer = customComparer ?? ImageComparer.Exact; comparer.VerifySimilarity(actualImage, image); } } internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) { var image = new Image(buffer.Width, buffer.Height); Span pixels = image.Frames.RootFrame.GetPixelSpan(); Span bufferSpan = buffer.GetSpan(); for (int i = 0; i < bufferSpan.Length; i++) { float value = bufferSpan[i] * scale; var v = new Vector4(value, value, value, 1f); pixels[i].PackFromVector4(v); } return image; } } }