// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests { /// /// Utility class to provide information about the test image & the test case for the test code, /// and help managing IO. /// public class ImagingTestCaseUtility { /// /// Gets or sets the name of the TPixel in the owner /// public string PixelTypeName { get; set; } = string.Empty; /// /// Gets or sets the name of the file which is provided by /// Or a short string describing the image in the case of a non-file based image provider. /// public string SourceFileOrDescription { get; set; } = string.Empty; /// /// Gets or sets the test group name. /// By default this is the name of the test class, but it's possible to change it. /// public string TestGroupName { get; set; } = string.Empty; public string OutputSubfolderName { get; set; } = string.Empty; /// /// Gets or sets the name of the test case (by default). /// public string TestName { get; set; } = string.Empty; private string GetTestOutputFileNameImpl( string extension, string details, bool appendPixelTypeToFileName, bool appendSourceFileOrDescription) { if (string.IsNullOrWhiteSpace(extension)) { extension = null; } string fn = appendSourceFileOrDescription ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) : string.Empty; if (string.IsNullOrWhiteSpace(extension)) { extension = Path.GetExtension(this.SourceFileOrDescription); } if (string.IsNullOrWhiteSpace(extension)) { extension = ".bmp"; } extension = extension.ToLower(); if (extension[0] != '.') { extension = '.' + extension; } if (fn != string.Empty) { fn = '_' + fn; } string pixName = string.Empty; if (appendPixelTypeToFileName) { pixName = this.PixelTypeName; if (pixName != string.Empty) { pixName = '_' + pixName; } } details = details ?? string.Empty; if (details != string.Empty) { details = '_' + details; } return TestUtils.AsInvariantString($"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}"); } /// /// Gets the recommended file name for the output of the test /// /// The required extension /// The settings modifying the output path /// A boolean indicating whether to append the pixel type to output file name. /// A boolean indicating whether to append to the test output file name. /// The file test name public string GetTestOutputFileName( string extension = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) { string detailsString = null; if (testOutputDetails is FormattableString fs) { detailsString = fs.AsInvariantString(); } else if (testOutputDetails is string s) { detailsString = s; } else if (testOutputDetails != null) { Type type = testOutputDetails.GetType(); TypeInfo info = type.GetTypeInfo(); if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) { detailsString = TestUtils.AsInvariantString($"{testOutputDetails}"); } else { IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); detailsString = string.Join( "_", properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}"))); } } return this.GetTestOutputFileNameImpl( extension, detailsString, appendPixelTypeToFileName, appendSourceFileOrDescription); } /// /// Encodes image by the format matching the required extension, than saves it to the recommended output file. /// /// The image instance. /// The requested extension. /// Optional encoder. /// Additional information to append to the test output file name. /// A value indicating whether to append the pixel type to the test output file name. /// A boolean indicating whether to append to the test output file name. /// The path to the saved image file. public string SaveTestOutputFile( Image image, string extension = null, IImageEncoder encoder = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) { string path = this.GetTestOutputFileName( extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); encoder = encoder ?? TestEnvironment.GetReferenceEncoder(path); using (FileStream stream = File.OpenWrite(path)) { image.Save(stream, encoder); } return path; } public IEnumerable GetTestOutputFileNamesMultiFrame( int frameCount, string extension = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) { string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); if (!Directory.Exists(baseDir)) { Directory.CreateDirectory(baseDir); } for (int i = 0; i < frameCount; i++) { string filePath = $"{baseDir}/{i:D2}.{extension}"; yield return filePath; } } public string[] SaveTestOutputFileMultiFrame( Image image, string extension = "png", IImageEncoder encoder = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true) where TPixel : unmanaged, IPixel { encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); string[] files = this.GetTestOutputFileNamesMultiFrame( image.Frames.Count, extension, testOutputDetails, appendPixelTypeToFileName).ToArray(); for (int i = 0; i < image.Frames.Count; i++) { using (Image frameImage = image.Frames.CloneFrame(i)) { string filePath = files[i]; using (FileStream stream = File.OpenWrite(filePath)) { frameImage.Save(stream, encoder); } } } return files; } internal string GetReferenceOutputFileName( string extension, object testOutputDetails, bool appendPixelTypeToFileName, bool appendSourceFileOrDescription) { return TestEnvironment.GetReferenceOutputFileName( this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); } public string[] GetReferenceOutputFileNamesMultiFrame( int frameCount, string extension, object testOutputDetails, bool appendPixelTypeToFileName = true) { return this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails) .Select(TestEnvironment.GetReferenceOutputFileName).ToArray(); } internal void Init(string typeName, string methodName, string outputSubfolderName) { this.TestGroupName = typeName; this.TestName = methodName; this.OutputSubfolderName = outputSubfolderName; } internal string GetTestOutputDir() { string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); if (!string.IsNullOrEmpty(this.OutputSubfolderName)) { testGroupName = Path.Combine(this.OutputSubfolderName, testGroupName); } return TestEnvironment.CreateOutputDirectory(testGroupName); } public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) where TPixel : unmanaged, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) where TPixel : unmanaged, IPixel { TPixel pixel = img[x, y]; Rgba64 rgbaPixel = default; rgbaPixel.FromScaledVector4(pixel.ToScaledVector4()); ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F); if (rgbaPixel.R + perChannelChange <= 255) { rgbaPixel.R += change; } else { rgbaPixel.R -= change; } if (rgbaPixel.G + perChannelChange <= 255) { rgbaPixel.G += change; } else { rgbaPixel.G -= change; } if (rgbaPixel.B + perChannelChange <= 255) { rgbaPixel.B += perChannelChange; } else { rgbaPixel.B -= perChannelChange; } if (rgbaPixel.A + perChannelChange <= 255) { rgbaPixel.A += perChannelChange; } else { rgbaPixel.A -= perChannelChange; } pixel.FromRgba64(rgbaPixel); img[x, y] = pixel; } } }