diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 2f631f66e..673fe5500 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -79,6 +79,17 @@ namespace ImageSharp this.PixelBase = (byte*)this.dataPointer.ToPointer(); } + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The height. + /// The component order. + public PixelArea(int width, int height, ComponentOrder componentOrder) + : this(width, height, componentOrder, 0) + { + } + /// /// Initializes a new instance of the class. /// diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 38a499a02..e0e84dbda 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -46,6 +46,9 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only }; + // TODO: Find a better place for this + internal const string TestOutputRoot = "TestOutput/"; + protected string CreateOutputDirectory(string path, params string[] pathParts) { var postFix = ""; @@ -54,7 +57,7 @@ namespace ImageSharp.Tests postFix = "/" + string.Join("/", pathParts); } - path = "TestOutput/" + path + postFix; + path = TestOutputRoot + path + postFix; if (!Directory.Exists(path)) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs index 08efe5c89..85bc18d11 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs @@ -6,7 +6,7 @@ using ImageSharp.Formats; using Xunit; using Xunit.Abstractions; -namespace ImageSharp.Tests.Formats.Jpg +namespace ImageSharp.Tests { public class JpegTests { @@ -17,62 +17,45 @@ namespace ImageSharp.Tests.Formats.Jpg public JpegTests(ITestOutputHelper output) { - Output = output; + this.Output = output; } + public static IEnumerable AllJpegFiles => TestImages.Jpeg.All; - protected string CreateTestOutputFile(string fileName) - { - if (!Directory.Exists(TestOutputDirectory)) - { - Directory.CreateDirectory(TestOutputDirectory); - } - - //string id = Guid.NewGuid().ToString().Substring(0, 4); - - string ext = Path.GetExtension(fileName); - fileName = Path.GetFileNameWithoutExtension(fileName); - - return $"{TestOutputDirectory}/{fileName}{ext}"; - } - - protected Stream CreateOutputStream(string fileName) - { - fileName = CreateTestOutputFile(fileName); - Output?.WriteLine("Opened for write: "+fileName); - return File.OpenWrite(fileName); - } - - public static IEnumerable AllJpegFiles - => TestImages.Jpeg.All.Select(file => new object[] {TestFile.Create(file)}); - + // TODO: Turned off PixelTypes.All to be CI-friendly, what should be the practice? [Theory] - [MemberData(nameof(AllJpegFiles))] - public void OpenJpeg_SaveBmp(TestFile file) + //[WithFileCollection(nameof(AllJpegFiles), PixelTypes.All)] + [WithFileCollection(nameof(AllJpegFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] + public void OpenJpeg_SaveBmp(TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable { - string bmpFileName = file.FileNameWithoutExtension + ".bmp"; + var image = provider.GetImage(); - var image = file.CreateImage(); - - using (var outputStream = CreateOutputStream(bmpFileName)) - { - image.Save(outputStream, new BmpFormat()); - } + provider.Utility.SaveTestOutputFile(image, "bmp"); } - public static IEnumerable AllBmpFiles - => TestImages.Bmp.All.Select(file => new object[] { TestFile.Create(file) }); + + public static IEnumerable AllBmpFiles => TestImages.Bmp.All; [Theory] - [MemberData(nameof(AllBmpFiles))] - public void OpenBmp_SaveJpeg(TestFile file) + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio420, 75)] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio444, 75)] + public void OpenBmp_SaveJpeg(TestImageProvider provider, JpegSubsample subSample, int quality) + where TColor : struct, IPackedPixel, IEquatable { - string jpegPath = file.FileNameWithoutExtension + ".jpeg"; + var image = provider.GetImage(); - var image = file.CreateImage(); + var utility = provider.Utility; + utility.TestName += "_" + subSample + "_Q" + quality; - using (var outputStream = CreateOutputStream(jpegPath)) + using (var outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) { - image.Save(outputStream, new JpegFormat()); + var encoder = new JpegEncoder() + { + Subsample = subSample, + Quality = quality + }; + + image.Save(outputStream, encoder); } } } diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs index e47678d0c..fbba55815 100644 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests { using System; + using System.Numerics; using Xunit; @@ -14,6 +15,114 @@ namespace ImageSharp.Tests /// public class PixelAccessorTests { + public static Image CreateTestImage(GenericFactory factory) + where TColor : struct, IPackedPixel, IEquatable + { + Image image = factory.CreateImage(10, 10); + + using (var pixels = image.Lock()) + { + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) + { + Vector4 v = new Vector4(i, j, 0, 1); + v /= 10; + + TColor color = default(TColor); + color.PackFromVector4(v); + + pixels[i, j] = color; + } + } + } + return image; + } + + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.XYZ)] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.ZYX)] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.XYZW)] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.ZYXW)] + public void CopyTo_Then_CopyFrom_OnFullImageRect(TestImageProvider provider, ComponentOrder order) + where TColor : struct, IPackedPixel, IEquatable + { + var src = provider.GetImage(); + + var dest = new Image(src.Width, src.Height); + + using (PixelArea area = new PixelArea(src.Width, src.Height, order)) + { + using (var srcPixels = src.Lock()) + { + srcPixels.CopyTo(area, 0, 0); + } + + using (var destPixels = dest.Lock()) + { + destPixels.CopyFrom(area, 0, 0); + } + } + + Assert.True(src.IsEquivalentTo(dest, false)); + } + + // TODO: Need a processor in the library with this signature + private static void Fill(Image image, Rectangle region, TColor color) + where TColor : struct, IPackedPixel, IEquatable + { + using (var pixels = image.Lock()) + { + for (int y = region.Top; y < region.Bottom; y++) + { + for (int x = region.Left; x < region.Right; x++) + { + pixels[x, y] = color; + } + } + } + } + + [Theory] + [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.XYZ)] + [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.ZYX)] + [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.XYZW)] + [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.ZYXW)] + public void CopyTo_Then_CopyFrom_WithOffset(TestImageProvider provider, ComponentOrder order) + where TColor : struct, IPackedPixel, IEquatable + + { + var srcImage = provider.GetImage(); + + var color = default(TColor); + color.PackFromBytes(255, 0, 0, 255); + + Fill(srcImage, new Rectangle(4, 4, 8, 8), color); + + var destImage = new Image(8, 8); + + using (var srcPixels = srcImage.Lock()) + { + using (var area = new PixelArea(8, 8, order)) + { + srcPixels.CopyTo(area, 4, 4); + + using (var destPixels = destImage.Lock()) + { + destPixels.CopyFrom(area, 0, 0); + } + } + } + + provider.Utility.SourceFileOrDescription = order.ToString(); + provider.Utility.SaveTestOutputFile(destImage, "bmp"); + + var expectedImage = new Image(8, 8).Fill(color); + + Assert.True(destImage.IsEquivalentTo(expectedImage)); + } + + [Fact] public void CopyFromZYX() { diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index d4d4a0a89..aab7c2620 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -4,6 +4,7 @@ // namespace ImageSharp.Tests { + using System; using System.Collections.Concurrent; using System.IO; @@ -33,7 +34,7 @@ namespace ImageSharp.Tests this.Bytes = File.ReadAllBytes(file); this.image = new Image(this.Bytes); } - + public static TestFile Create(string file) { return cache.GetOrAdd(file, (string fileName) => diff --git a/tests/ImageSharp.Tests/TestUtilities/FlagsHelper.cs b/tests/ImageSharp.Tests/TestUtilities/FlagsHelper.cs new file mode 100644 index 000000000..6c2589b82 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/FlagsHelper.cs @@ -0,0 +1,242 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Helper class for flag manipulation, based on + /// + /// http://www.codeproject.com/KB/dotnet/enum.aspx + /// + /// + /// Must be enum type (declared using enum keyword) + public class FlagsHelper + where T : struct, IConvertible + { + private static readonly EnumConverter Converter; + + static FlagsHelper() + { + Type type = typeof(T); + string[] names = Enum.GetNames(type); + var values = (T[])Enum.GetValues(type); + + Converter = new FlagsEnumConverter(names, values); + } + + public static T[] GetSortedValues() + { + T[] vals = (T[])Enum.GetValues(typeof(T)); + Array.Sort(vals); + return vals; + } + + public static T Parse(string value, bool ignoreCase = false, bool parseNumeric = true) + { + return (T)Enum.ToObject(typeof(T), Converter.ParseInternal(value, ignoreCase, parseNumeric)); + } + + /// + /// Converts enum value to string + /// + /// Enum value converted to int + /// If is defined, the enum member name; otherwise the string representation of the . + /// If is applied, can return comma-separated list of values + public static string ToString(int value) + { + return Converter.ToStringInternal(value); + } + + /// + /// Converts enum value to string + /// + /// Enum value + /// If is defined, the enum member name; otherwise the string representation of the . + /// If is applied, can return comma-separated list of values + public static string ToString(T value) + { + return Converter.ToStringInternal(value.ToInt32(null)); + } + + public static bool TryParse(string value, bool ignoreCase, bool parseNumeric, out T result) + { + int ir; + bool b = Converter.TryParseInternal(value, ignoreCase, parseNumeric, out ir); + result = (T)Enum.ToObject(typeof(T), ir); + return b; + } + + public static bool TryParse(string value, bool ignoreCase, out T result) + { + int ir; + bool b = Converter.TryParseInternal(value, ignoreCase, true, out ir); + result = (T)Enum.ToObject(typeof(T), ir); + return b; + } + + public static bool TryParse(string value, out T result) + { + int ir; + bool b = Converter.TryParseInternal(value, false, true, out ir); + result = (T)Enum.ToObject(typeof(T), ir); + return b; + } + + class DictionaryEnumConverter : EnumConverter + { + protected readonly Dictionary Dic; + + protected DictionaryEnumConverter(string[] names, T[] values) + { + this.Dic = new Dictionary(names.Length); + for (int j = 0; j < names.Length; j++) this.Dic.Add(Convert.ToInt32(values[j], null), names[j]); + } + + public override int ParseInternal(string value, bool ignoreCase, bool parseNumber) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (value.Length == 0) throw new ArgumentException("Value is empty", nameof(value)); + char f = value[0]; + if (parseNumber && (char.IsDigit(f) || f == '+' || f == '-')) return int.Parse(value); + StringComparison stringComparison = ignoreCase + ? StringComparison.OrdinalIgnoreCase + : StringComparison.Ordinal; + foreach (KeyValuePair pair in this.Dic) + { + if (pair.Value.Equals(value, stringComparison)) return pair.Key; + } + + throw new ArgumentException("Enum value wasn't found", nameof(value)); + } + + public override string ToStringInternal(int value) + { + string n; + return this.Dic.TryGetValue(value, out n) ? n : value.ToString(); + } + + public override bool TryParseInternal(string value, bool ignoreCase, bool parseNumber, out int result) + { + result = 0; + if (string.IsNullOrEmpty(value)) return false; + char f = value[0]; + if (parseNumber && (char.IsDigit(f) || f == '+' || f == '-')) + { + int i; + if (int.TryParse(value, out i)) + { + result = i; + return true; + } + + return false; + } + + StringComparison stringComparison = ignoreCase + ? StringComparison.OrdinalIgnoreCase + : StringComparison.Ordinal; + foreach (KeyValuePair pair in this.Dic) + { + if (pair.Value.Equals(value, stringComparison)) + { + result = pair.Key; + return true; + } + } + + return false; + } + } + + abstract class EnumConverter + { + public abstract int ParseInternal(string value, bool ignoreCase, bool parseNumber); + + public abstract string ToStringInternal(int value); + + public abstract bool TryParseInternal(string value, bool ignoreCase, bool parseNumber, out int result); + } + + class FlagsEnumConverter : DictionaryEnumConverter + { + private static readonly string[] Seps = new[] { "," }; + + private readonly uint[] values; + + public FlagsEnumConverter(string[] names, T[] values) + : base(names, values) + { + this.values = new uint[values.Length]; + for (int i = 0; i < values.Length; i++) this.values[i] = values[i].ToUInt32(null); + } + + public override int ParseInternal(string value, bool ignoreCase, bool parseNumber) + { + string[] parts = value.Split(Seps, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 1) return base.ParseInternal(value, ignoreCase, parseNumber); + int val = 0; + for (int i = 0; i < parts.Length; i++) + { + string part = parts[i]; + int t = base.ParseInternal(part.Trim(), ignoreCase, parseNumber); + val |= t; + } + + return val; + } + + public override string ToStringInternal(int value) + { + string n; + if (this.Dic.TryGetValue(value, out n)) return n; + var sb = new StringBuilder(); + const string sep = ", "; + uint uval; + unchecked + { + uval = (uint)value; + + for (int i = this.values.Length - 1; i >= 0; i--) + { + uint v = this.values[i]; + if (v == 0) continue; + if ((v & uval) == v) + { + uval &= ~v; + sb.Insert(0, sep).Insert(0, this.Dic[(int)v]); + } + } + } + + return uval == 0 && sb.Length > sep.Length ? sb.ToString(0, sb.Length - sep.Length) : value.ToString(); + } + + public override bool TryParseInternal(string value, bool ignoreCase, bool parseNumber, out int result) + { + string[] parts = value.Split(Seps, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 1) return base.TryParseInternal(value, ignoreCase, parseNumber, out result); + int val = 0; + for (int i = 0; i < parts.Length; i++) + { + string part = parts[i]; + int t; + if (!base.TryParseInternal(part.Trim(), ignoreCase, parseNumber, out t)) + { + result = 0; + return false; + } + + val |= t; + } + + result = val; + return true; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/GenericFactory.cs b/tests/ImageSharp.Tests/TestUtilities/GenericFactory.cs new file mode 100644 index 000000000..92fe8e16c --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/GenericFactory.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + + /// + /// Utility class to create specialized subclasses of generic classes (eg. ) + /// Used as parameter for -based factory methods + /// + public class GenericFactory + where TColor : struct, IPackedPixel, IEquatable + { + public virtual Image CreateImage(int width, int height) + { + return new Image(width, height); + } + + public virtual Image CreateImage(byte[] bytes) + { + return new Image(bytes); + } + } + + public class DefaultImageClassSpecificFactory : GenericFactory + { + public override Image CreateImage(byte[] bytes) => new Image(bytes); + + public override Image CreateImage(int width, int height) => new Image(width, height); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/ImageDataAttributeBase.cs new file mode 100644 index 000000000..18600085d --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageDataAttributeBase.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + + using Xunit.Sdk; + + /// + /// Base class for Theory Data attributes which pass an instance of to the test case. + /// + public abstract class ImageDataAttributeBase : DataAttribute + { + protected readonly object[] AdditionalParameters; + + protected readonly PixelTypes PixelTypes; + + protected ImageDataAttributeBase(PixelTypes pixelTypes, object[] additionalParameters) + { + this.PixelTypes = pixelTypes; + this.AdditionalParameters = additionalParameters; + } + + public override IEnumerable GetData(MethodInfo testMethod) + { + var type = testMethod.GetParameters().First().ParameterType; + if (!typeof(ITestImageFactory).IsAssignableFrom(type)) + { + yield return this.AdditionalParameters; + } + else + { + foreach (var kv in this.PixelTypes.ExpandAllTypes()) + { + var factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value); + + foreach (object[] originalFacoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType)) + { + var actualFactoryMethodArgs = new object[originalFacoryMethodArgs.Length + 2]; + Array.Copy(originalFacoryMethodArgs, actualFactoryMethodArgs, originalFacoryMethodArgs.Length); + actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; + actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; + + var factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) + .Invoke(null, actualFactoryMethodArgs); + + object[] result = new object[this.AdditionalParameters.Length + 1]; + result[0] = factory; + Array.Copy(this.AdditionalParameters, 0, result, 1, this.AdditionalParameters.Length); + yield return result; + } + } + } + } + + protected virtual IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + var args = this.GetFactoryMethodArgs(testMethod, factoryType); + return Enumerable.Repeat(args, 1); + } + + protected virtual object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + throw new InvalidOperationException("Semi-abstract method"); + } + + protected abstract string GetFactoryMethodName(MethodInfo testMethod); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs new file mode 100644 index 000000000..e2b885034 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -0,0 +1,115 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Tests +{ + using System; + using System.IO; + using System.Linq; + using System.Reflection; + + using ImageSharp.Formats; + + /// + /// Utility class to provide information about the test image & the test case for the test code, + /// and help managing IO. + /// + public class ImagingTestCaseUtility + { + /// + /// Name of the TColor in the owner + /// + public string PixelTypeName { get; set; } = string.Empty; + + /// + /// 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; + + /// + /// By default this is the name of the test class, but it's possible to change it + /// + public string TestGroupName { get; set; } = string.Empty; + + /// + /// The name of the test case (by default) + /// + public string TestName { get; set; } = string.Empty; + + /// + /// Root directory for output images + /// + public string TestOutputRoot { get; set; } = FileTestBase.TestOutputRoot; + + public string GetTestOutputDir() + { + string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); + + string dir = $@"{this.TestOutputRoot}{testGroupName}"; + Directory.CreateDirectory(dir); + return dir; + } + + /// + /// Gets the recommended file name for the output of the test + /// + /// + /// The required extension + public string GetTestOutputFileName(string extension = null) + { + string fn = string.Empty; + + fn = Path.GetFileNameWithoutExtension(this.SourceFileOrDescription); + extension = extension ?? Path.GetExtension(this.SourceFileOrDescription); + extension = extension ?? ".bmp"; + + if (extension[0] != '.') + { + extension = '.' + extension; + } + + if (fn != string.Empty) fn = '_' + fn; + + string pixName = this.PixelTypeName; + if (pixName != string.Empty) + { + pixName = '_' + pixName; + } + + return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{extension}"; + } + + private static IImageFormat GetImageFormatByExtension(string extension) + { + extension = extension.ToLower(); + return Bootstrapper.ImageFormats.First(f => f.SupportedExtensions.Contains(extension)); + } + + /// + /// Encodes image by the format matching the required extension, than saves it to the recommended output file. + /// + /// The pixel format of the image + /// The image instance + /// The requested extension + public void SaveTestOutputFile(Image image, string extension = null) + where TColor : struct, IPackedPixel, IEquatable + { + string path = this.GetTestOutputFileName(extension); + + var format = GetImageFormatByExtension(extension); + + using (var stream = File.OpenWrite(path)) + { + image.Save(stream, format); + } + } + + internal void Init(MethodInfo method) + { + this.TestGroupName = method.DeclaringType.Name; + this.TestName = method.Name; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs new file mode 100644 index 000000000..dd1460678 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Tests +{ + using System; + + /// + /// Flags that are mapped to PackedPixel types. + /// They trigger the desired parametrization for . + /// + [Flags] + public enum PixelTypes : uint + { + Undefined = 0, + + Alpha8 = 1 << 0, + + Argb = 1 << 1, + + Bgr565 = 1 << 2, + + Bgra4444 = 1 << 3, + + Byte4 = 1 << 4, + + Color = 1 << 5, + + HalfSingle = 1 << 6, + + HalfVector2 = 1 << 7, + + HalfVector4 = 1 << 8, + + NormalizedByte2 = 1 << 9, + + NormalizedByte4 = 1 << 10, + + NormalizedShort4 = 1 << 11, + + Rg32 = 1 << 12, + + Rgba1010102 = 1 << 13, + + Rgba64 = 1 << 14, + + Short2 = 1 << 15, + + Short4 = 1 << 16, + + /// + /// Triggers instantiating the subclass of + /// + StandardImageClass = 1 << 29, + + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper + + // "All" is handled as a separate, individual case instead of using bitwise OR + All = 30 + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageProvider.cs new file mode 100644 index 000000000..453e03af2 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageProvider.cs @@ -0,0 +1,192 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Concurrent; + using System.Reflection; + + /// + /// Provides instances for parametric unit tests. + /// + /// The pixel format of the image + public abstract class TestImageProvider : ITestImageFactory + where TColor : struct, IPackedPixel, IEquatable + { + public PixelTypes PixelType { get; private set; } = typeof(TColor).GetPixelType(); + + public virtual string SourceFileOrDescription => ""; + + /// + /// Utility instance to provide informations about the test image & manage input/output + /// + public ImagingTestCaseUtility Utility { get; private set; } + + public GenericFactory Factory { get; private set; } = new GenericFactory(); + + public static TestImageProvider Blank( + int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider File( + string filePath, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + { + return new FileProvider(filePath).Init(testMethod, pixelTypeOverride); + } + + public static TestImageProvider Lambda( + Func, Image> func, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new LambdaProvider(func).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider Solid( + int width, + int height, + byte r, + byte g, + byte b, + byte a = 255, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + { + return new SolidProvider(width, height, r, g, b, a).Init(testMethod, pixelTypeOverride); + } + + /// + /// Returns an instance to the test case with the necessary traits. + /// + public abstract Image GetImage(); + + protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) + { + if (pixelTypeOverride != PixelTypes.Undefined) + { + this.PixelType = pixelTypeOverride; + } + + if (pixelTypeOverride == PixelTypes.StandardImageClass) + { + this.Factory = new DefaultImageClassSpecificFactory() as GenericFactory; + } + + + this.Utility = new ImagingTestCaseUtility() + { + SourceFileOrDescription = this.SourceFileOrDescription, + PixelTypeName = typeof(TColor).Name + }; + + if (testMethod != null) + { + this.Utility.Init(testMethod); + } + + return this; + } + + private class BlankProvider : TestImageProvider + { + public BlankProvider(int width, int height) + { + this.Width = width; + this.Height = height; + } + + public override string SourceFileOrDescription => $"Blank{this.Width}x{this.Height}"; + + protected int Height { get; } + + protected int Width { get; } + + public override Image GetImage() => this.Factory.CreateImage(this.Width, this.Height); + } + + private class FileProvider : TestImageProvider + { + private static ConcurrentDictionary> cache = + new ConcurrentDictionary>(); + + private string filePath; + + public FileProvider(string filePath) + { + this.filePath = filePath; + } + + public override string SourceFileOrDescription => this.filePath; + + public override Image GetImage() + { + var cachedImage = cache.GetOrAdd( + this.filePath, + fn => + { + var testFile = TestFile.Create(this.filePath); + return this.Factory.CreateImage(testFile.Bytes); + }); + + return new Image(cachedImage); + } + } + + private class LambdaProvider : TestImageProvider + { + private readonly Func, Image> creator; + + public LambdaProvider(Func, Image> creator) + { + this.creator = creator; + } + + public override Image GetImage() => this.creator(this.Factory); + } + + private class SolidProvider : BlankProvider + { + private readonly byte a; + + private readonly byte b; + + private readonly byte g; + + private readonly byte r; + + public SolidProvider(int width, int height, byte r, byte g, byte b, byte a) + : base(width, height) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public override string SourceFileOrDescription + => $"Solid{this.Width}x{this.Height}_({this.r},{this.g},{this.b},{this.a})"; + + public override Image GetImage() + { + var image = base.GetImage(); + TColor color = default(TColor); + color.PackFromBytes(this.r, this.g, this.b, this.a); + + return image.Fill(color); + } + } + } + + /// + /// Marker + /// + public interface ITestImageFactory + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs new file mode 100644 index 000000000..a4e0a959a --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs @@ -0,0 +1,133 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; + + /// + /// Extension methods for TestUtilities + /// + public static class TestUtilityExtensions + { + private static readonly Dictionary ClrTypes2PixelTypes = new Dictionary(); + + private static readonly Assembly ImageSharpAssembly = typeof(Color).GetTypeInfo().Assembly; + + private static readonly Dictionary PixelTypes2ClrTypes = new Dictionary(); + + private static readonly PixelTypes[] AllConcretePixelTypes = FlagsHelper + .GetSortedValues() + .Except(new [] {PixelTypes.Undefined, PixelTypes.All }) + .ToArray(); + + static TestUtilityExtensions() + { + string nameSpace = typeof(Color).FullName; + nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(Color).Name.Length - 1); + foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.StandardImageClass)) + { + string typeName = $"{nameSpace}.{FlagsHelper.ToString(pt)}"; + var t = ImageSharpAssembly.GetType(typeName); + if (t != null) + { + PixelTypes2ClrTypes[pt] = t; + ClrTypes2PixelTypes[t] = pt; + } + } + PixelTypes2ClrTypes[PixelTypes.StandardImageClass] = typeof(Color); + } + + public static Type GetPackedType(Type pixelType) + { + var intrfcType = + pixelType.GetInterfaces() + .Single(i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == typeof(IPackedPixel<>)); + + return intrfcType.GetGenericArguments().Single(); + } + + public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; + + public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) + where TColor : struct, IPackedPixel, IEquatable + { + if (a.Width != b.Width || a.Height != b.Height) + { + return false; + } + + byte[] bytesA = new byte[3]; + byte[] bytesB = new byte[3]; + + using (var pixA = a.Lock()) + { + using (var pixB = b.Lock()) + { + for (int y = 0; y < a.Height; y++) + { + for (int x = 0; x < a.Width; x++) + { + var ca = pixA[x, y]; + var cb = pixB[x, y]; + + if (compareAlpha) + { + if (!ca.Equals(cb)) + { + return false; + } + } + else + { + ca.ToBytes(bytesA, 0, ComponentOrder.XYZ); + cb.ToBytes(bytesB, 0, ComponentOrder.XYZ); + + if (bytesA[0] != bytesB[0] || + bytesA[1] != bytesB[1] || + bytesA[2] != bytesB[2]) + { + return false; + } + } + } + } + } + } + + return true; + } + + public static string ToCsv(this IEnumerable items, string separator = ",") + { + return string.Join(separator, items.Select(o => string.Format(CultureInfo.InvariantCulture, "{0}", o))); + } + + public static Type ToType(this PixelTypes pixelType) => PixelTypes2ClrTypes[pixelType]; + + public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; + + public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) + { + if (pixelTypes == PixelTypes.Undefined) + { + return Enumerable.Empty>(); + } + else if (pixelTypes == PixelTypes.All) + { + // TODO: Need to return unknown types here without forcing CLR to load all types in ImageSharp assembly + return PixelTypes2ClrTypes; + } + + return AllConcretePixelTypes + .Where(pt => pixelTypes.HasFlag(pt)) + .Select(pt => new KeyValuePair(pt, pt.ToType())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs new file mode 100644 index 000000000..974fe35cc --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -0,0 +1,193 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming + +namespace ImageSharp.Tests +{ + using System; + + using Xunit; + using Xunit.Abstractions; + + public class TestImageProviderTests + { + public TestImageProviderTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + [Theory] + [WithBlankImages(42, 666, PixelTypes.Color | PixelTypes.Argb | PixelTypes.HalfSingle, "hello")] + public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) + where TColor : struct, IPackedPixel, IEquatable + { + var img = provider.GetImage(); + + Assert.Equal(42, img.Width); + Assert.Equal(666, img.Height); + Assert.Equal("hello", message); + } + + [Theory] + [WithBlankImages(42, 666, PixelTypes.All, "hello")] + public void Use_WithBlankImagesAttribute_WithAllPixelTypes( + TestImageProvider provider, + string message) + where TColor : struct, IPackedPixel, IEquatable + { + var img = provider.GetImage(); + + Assert.Equal(42, img.Width); + Assert.Equal(666, img.Height); + Assert.Equal("hello", message); + } + + [Theory] + [WithBlankImages(1, 1, PixelTypes.Color, PixelTypes.Color)] + [WithBlankImages(1, 1, PixelTypes.Alpha8, PixelTypes.Alpha8)] + [WithBlankImages(1, 1, PixelTypes.StandardImageClass, PixelTypes.StandardImageClass)] + public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) + where TColor : struct, IPackedPixel, IEquatable + { + Assert.Equal(expected, provider.PixelType); + } + + [Theory] + [WithBlankImages(1, 1, PixelTypes.StandardImageClass)] + public void PixelTypes_ColorWithDefaultImageClass_TriggersCreatingTheNonGenericDerivedImageClass( + TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + var img = provider.GetImage(); + + Assert.IsType(img); + } + + // TODO: @dlemstra this works only with constant strings! + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.All, 88)] + [WithFile(TestImages.Bmp.F, PixelTypes.All, 88)] + public void Use_WithFileAttribute(TestImageProvider provider, int yo) + where TColor : struct, IPackedPixel, IEquatable + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); + var img = provider.GetImage(); + Assert.True(img.Width * img.Height > 0); + + Assert.Equal(88, yo); + + string fn = provider.Utility.GetTestOutputFileName("jpg"); + this.Output.WriteLine(fn); + } + + public static string[] AllBmpFiles => TestImages.Bmp.All; + + [Theory] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.Argb)] + public void Use_WithFileCollection(TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); + var image = provider.GetImage(); + provider.Utility.SaveTestOutputFile(image, "png"); + } + + [Theory] + [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Color | PixelTypes.Argb)] + public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + var img = provider.GetImage(); + Assert.Equal(img.Width, 10); + Assert.Equal(img.Height, 20); + + byte[] colors = new byte[4]; + + using (var pixels = img.Lock()) + { + for (int y = 0; y < pixels.Height; y++) + { + for (int x = 0; x < pixels.Width; x++) + { + pixels[x, y].ToBytes(colors, 0, ComponentOrder.XYZW); + + Assert.Equal(colors[0], 255); + Assert.Equal(colors[1], 100); + Assert.Equal(colors[2], 50); + Assert.Equal(colors[3], 200); + } + } + } + } + + /// + /// Need to us to create instance of when pixelType is StandardImageClass + /// + /// + /// + /// + public static Image CreateTestImage(GenericFactory factory) + where TColor : struct, IPackedPixel, IEquatable + { + return factory.CreateImage(3, 3); + } + + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] + public void Use_WithMemberFactoryAttribute(TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + var img = provider.GetImage(); + Assert.Equal(img.Width, 3); + if (provider.PixelType == PixelTypes.StandardImageClass) + { + Assert.IsType(img); + } + + } + + public static readonly TheoryData BasicData = new TheoryData() + { + TestImageProvider.Blank(10, 20), + TestImageProvider.Blank( + 10, + 20) + }; + + [Theory] + [MemberData(nameof(BasicData))] + public void Blank_MemberData(TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + var img = provider.GetImage(); + + Assert.True(img.Width * img.Height > 0); + } + + public static readonly TheoryData FileData = new TheoryData() + { + TestImageProvider.File( + TestImages.Bmp.Car), + TestImageProvider.File( + TestImages.Bmp.F) + }; + + [Theory] + [MemberData(nameof(FileData))] + public void File_MemberData(TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); + this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); + + var img = provider.GetImage(); + + Assert.True(img.Width * img.Height > 0); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs new file mode 100644 index 000000000..371934127 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -0,0 +1,147 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Numerics; + using System.Reflection; + + using Xunit; + using Xunit.Abstractions; + + public class TestUtilityExtensionsTests + { + public TestUtilityExtensionsTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + public static Image CreateTestImage(GenericFactory factory) + where TColor : struct, IPackedPixel, IEquatable + { + Image image = factory.CreateImage(10, 10); + + using (var pixels = image.Lock()) + { + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) + { + Vector4 v = new Vector4(i, j, 0, 1); + v /= 10; + + TColor color = default(TColor); + color.PackFromVector4(v); + + pixels[i, j] = color; + } + } + } + + return image; + } + + [Fact] + public void Baz() + { + var type = typeof(Color).GetTypeInfo().Assembly.GetType("ImageSharp.Color"); + this.Output.WriteLine(type.ToString()); + + var fake = typeof(Color).GetTypeInfo().Assembly.GetType("ImageSharp.dsaada_DASqewrr"); + Assert.Null(fake); + } + + [Fact] + public void GetPackedType() + { + Type shouldBeUIint32 = TestUtilityExtensions.GetPackedType(typeof(Color)); + + Assert.Equal(shouldBeUIint32, typeof(uint)); + } + + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.Color, true)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Color, false)] + public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) + where TColor : struct, IPackedPixel, IEquatable + { + var a = provider.GetImage(); + var b = provider.GetImage(); + b = b.OilPaint(3, 2); + + Assert.False(a.IsEquivalentTo(b, compareAlpha)); + } + + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.Bgr565, true)] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.Bgr565, false)] + public void IsEquivalentTo_WhenTrue(TestImageProvider provider, bool compareAlpha) + where TColor : struct, IPackedPixel, IEquatable + { + var a = provider.GetImage(); + var b = provider.GetImage(); + + Assert.True(a.IsEquivalentTo(b, compareAlpha)); + } + + [Theory] + [InlineData(PixelTypes.Color, typeof(Color))] + [InlineData(PixelTypes.Argb, typeof(Argb))] + [InlineData(PixelTypes.HalfVector4, typeof(HalfVector4))] + [InlineData(PixelTypes.StandardImageClass, typeof(Color))] + public void ToType(PixelTypes pt, Type expectedType) + { + Assert.Equal(pt.ToType(), expectedType); + } + + [Theory] + [InlineData(typeof(Color), PixelTypes.Color)] + [InlineData(typeof(Argb), PixelTypes.Argb)] + public void GetPixelType(Type clrType, PixelTypes expectedPixelType) + { + Assert.Equal(expectedPixelType, clrType.GetPixelType()); + } + + private static void AssertContainsPixelType( + PixelTypes pt, + IEnumerable> pixelTypesExp) + { + Assert.Contains(new KeyValuePair(pt, typeof(T)), pixelTypesExp); + + } + + [Fact] + public void ToTypes() + { + PixelTypes pixelTypes = PixelTypes.Alpha8 | PixelTypes.Bgr565 | PixelTypes.Color | PixelTypes.HalfVector2 | PixelTypes.StandardImageClass; + + var expanded = pixelTypes.ExpandAllTypes(); + + Assert.Equal(expanded.Count(), 5); + + AssertContainsPixelType(PixelTypes.Alpha8, expanded); + AssertContainsPixelType(PixelTypes.Bgr565, expanded); + AssertContainsPixelType(PixelTypes.Color, expanded); + AssertContainsPixelType(PixelTypes.HalfVector2, expanded); + AssertContainsPixelType(PixelTypes.StandardImageClass, expanded); + } + + [Fact] + public void ToTypes_All() + { + var expanded = PixelTypes.All.ExpandAllTypes().ToArray(); + + Assert.True(expanded.Length >= FlagsHelper.GetSortedValues().Length - 2); + AssertContainsPixelType(PixelTypes.Color, expanded); + AssertContainsPixelType(PixelTypes.StandardImageClass, expanded); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/WithBlankImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/WithBlankImageAttribute.cs new file mode 100644 index 000000000..e1f8f4c55 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/WithBlankImageAttribute.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Reflection; + + /// + /// Triggers passing instances which produce a blank image of size width * height. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithBlankImagesAttribute : ImageDataAttributeBase + { + /// + /// Triggers passing an that produces a blank image of size width * height + /// + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithBlankImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + public int Width { get; } + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "Blank"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/WithFileAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/WithFileAttribute.cs new file mode 100644 index 000000000..617a9a237 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/WithFileAttribute.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Reflection; + + /// + /// Triggers passing instances which read an image from the given file + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithFileAttribute : ImageDataAttributeBase + { + private readonly string fileName; + + /// + /// Triggers passing instances which read an image from the given file + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the file + /// The requested pixel types + /// Additional theory parameter values + public WithFileAttribute(string fileName, PixelTypes pixelTypes, params object[] additionalParameters) + : base(pixelTypes, additionalParameters) + { + this.fileName = fileName; + } + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.fileName }; + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/WithFileCollectionAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/WithFileCollectionAttribute.cs new file mode 100644 index 000000000..bca69eea7 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/WithFileCollectionAttribute.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + /// + /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithFileCollectionAttribute : ImageDataAttributeBase + { + private readonly string enumeratorMemberName; + + /// + /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the static test class field/property enumerating the files + /// The requested pixel types + /// Additional theory parameter values + public WithFileCollectionAttribute( + string enumeratorMemberName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(pixelTypes, additionalParameters) + { + this.enumeratorMemberName = enumeratorMemberName; + } + + protected override IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + var accessor = this.GetPropertyAccessor(testMethod.DeclaringType); + + accessor = accessor ?? this.GetFieldAccessor(testMethod.DeclaringType); + + IEnumerable files = (IEnumerable)accessor(); + return files.Select(f => new object[] { f }); + } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; + + /// + /// Based on MemberData implementation + /// + private Func GetFieldAccessor(Type type) + { + FieldInfo fieldInfo = null; + for (var reflectionType = type; + reflectionType != null; + reflectionType = reflectionType.GetTypeInfo().BaseType) + { + fieldInfo = reflectionType.GetRuntimeField(this.enumeratorMemberName); + if (fieldInfo != null) break; + } + + if (fieldInfo == null || !fieldInfo.IsStatic) return null; + + return () => fieldInfo.GetValue(null); + } + + /// + /// Based on MemberData implementation + /// + private Func GetPropertyAccessor(Type type) + { + PropertyInfo propInfo = null; + for (var reflectionType = type; + reflectionType != null; + reflectionType = reflectionType.GetTypeInfo().BaseType) + { + propInfo = reflectionType.GetRuntimeProperty(this.enumeratorMemberName); + if (propInfo != null) break; + } + + if (propInfo == null || propInfo.GetMethod == null || !propInfo.GetMethod.IsStatic) return null; + + return () => propInfo.GetValue(null, null); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/WithMemberFactoryAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/WithMemberFactoryAttribute.cs new file mode 100644 index 000000000..e658e817d --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/WithMemberFactoryAttribute.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Linq; + using System.Reflection; + + /// + /// Triggers passing instances which return the image produced by the given test class member method + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// The parameter of the factory method must be a instance + /// + public class WithMemberFactoryAttribute : ImageDataAttributeBase + { + private readonly string memberMethodName; + + /// + /// Triggers passing instances which return the image produced by the given test class member method + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the static test class which returns the image + /// The requested pixel types + /// Additional theory parameter values + public WithMemberFactoryAttribute(string memberMethodName, PixelTypes pixelTypes, params object[] additionalParameters) + : base(pixelTypes, additionalParameters) + { + this.memberMethodName = memberMethodName; + } + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + var m = testMethod.DeclaringType.GetMethod(this.memberMethodName); + + var args = factoryType.GetGenericArguments(); + var colorType = args.Single(); + + var imgType = typeof(Image<>).MakeGenericType(colorType); + var genericFactoryType = (typeof(GenericFactory<>)).MakeGenericType(colorType); + + var funcType = typeof(Func<,>).MakeGenericType(genericFactoryType, imgType); + + var genericMethod = m.MakeGenericMethod(args); + + var d = genericMethod.CreateDelegate(funcType); + return new object[] { d }; + } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "Lambda"; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/WithSolidFilledImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/WithSolidFilledImagesAttribute.cs new file mode 100644 index 000000000..46f18e08c --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/WithSolidFilledImagesAttribute.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageSharp.Tests +{ + using System; + using System.Reflection; + + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithSolidFilledImagesAttribute : WithBlankImagesAttribute + { + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The width of the requested image + /// The height of the requested image + /// Red + /// Green + /// Blue + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + int width, + int height, + byte r, + byte g, + byte b, + PixelTypes pixelTypes, + params object[] additionalParameters) + : this(width, height, r, g, b, 255, pixelTypes, additionalParameters) + { + } + + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The width of the requested image + /// The height of the requested image + /// Red + /// Green + /// Blue + /// /// Alpha + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + int width, + int height, + byte r, + byte g, + byte b, + byte a, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(width, height, pixelTypes, additionalParameters) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Red + /// + public byte R { get; } + + /// + /// Green + /// + public byte G { get; } + + /// + /// Blue + /// + public byte B { get; } + + /// + /// Alpha + /// + public byte A { get; } + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + => new object[] { this.Width, this.Height, this.R, this.G, this.B, this.A }; + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "Solid"; + } +} \ No newline at end of file