// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Concurrent; using System.IO; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public class TestImageProviderTests { public static readonly TheoryData BasicData = new TheoryData { TestImageProvider.Blank(10, 20), TestImageProvider.Blank(10, 20), }; public static readonly TheoryData FileData = new TheoryData { TestImageProvider.File(TestImages.Bmp.Car), TestImageProvider.File(TestImages.Bmp.F) }; public static string[] AllBmpFiles = { TestImages.Bmp.F, TestImages.Bmp.Bit8 }; public TestImageProviderTests(ITestOutputHelper output) => this.Output = output; private ITestOutputHelper Output { get; } /// /// Need to us to create instance of when pixelType is StandardImageClass /// /// /// /// public static Image CreateTestImage() where TPixel : struct, IPixel => new Image(3, 3); [Theory] [MemberData(nameof(BasicData))] public void Blank_MemberData(TestImageProvider provider) where TPixel : struct, IPixel { Image img = provider.GetImage(); Assert.True(img.Width * img.Height > 0); } [Theory] [MemberData(nameof(FileData))] public void File_MemberData(TestImageProvider provider) where TPixel : struct, IPixel { this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); Image img = provider.GetImage(); Assert.True(img.Width * img.Height > 0); } [Theory] [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( TestImageProvider provider) where TPixel : struct, IPixel { if (!TestEnvironment.Is64BitProcess) { // We don't cache with the 32 bit build. return; } Assert.NotNull(provider.Utility.SourceFileOrDescription); TestDecoder.DoTestThreadSafe( () => { string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache); var decoder = new TestDecoder(); decoder.InitCaller(testName); provider.GetImage(decoder); Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); provider.GetImage(decoder); Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); }); } [Theory] [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual( TestImageProvider provider) where TPixel : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); TestDecoderWithParameters.DoTestThreadSafe( () => { string testName = nameof(this .GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); var decoder1 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 42 }; decoder1.InitCaller(testName); var decoder2 = new TestDecoderWithParameters { Param1 = "LoL", Param2 = 42 }; decoder2.InitCaller(testName); provider.GetImage(decoder1); Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); provider.GetImage(decoder2); Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); }); } [Theory] [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( TestImageProvider provider) where TPixel : struct, IPixel { if (!TestEnvironment.Is64BitProcess) { // We don't cache with the 32 bit build. return; } Assert.NotNull(provider.Utility.SourceFileOrDescription); TestDecoderWithParameters.DoTestThreadSafe( () => { string testName = nameof(this .GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); var decoder1 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 666 }; decoder1.InitCaller(testName); var decoder2 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 666 }; decoder2.InitCaller(testName); provider.GetImage(decoder1); Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); provider.GetImage(decoder2); Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); }); } [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) where TPixel : struct, IPixel => Assert.Empty(provider.Utility.OutputSubfolderName); [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32, PixelTypes.Rgba32)] [WithBlankImages(1, 1, PixelTypes.A8, PixelTypes.A8)] [WithBlankImages(1, 1, PixelTypes.Argb32, PixelTypes.Argb32)] public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) where TPixel : struct, IPixel => Assert.Equal(expected, provider.PixelType); [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void SaveTestOutputFileMultiFrame(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); Assert.True(files.Length > 2); foreach (string path in files) { this.Output.WriteLine(path); Assert.True(File.Exists(path)); } } } [Theory] [WithBasicTestPatternImages(50, 100, PixelTypes.Rgba32)] [WithBasicTestPatternImages(49, 17, PixelTypes.Rgba32)] [WithBasicTestPatternImages(20, 10, PixelTypes.Rgba32)] public void Use_WithBasicTestPatternImages(TestImageProvider provider) where TPixel : struct, IPixel { using (Image img = provider.GetImage()) { img.DebugSave(provider); } } [Theory] [WithBlankImages(42, 666, PixelTypes.All, "hello")] public void Use_WithBlankImagesAttribute_WithAllPixelTypes( TestImageProvider provider, string message) where TPixel : struct, IPixel { Image img = provider.GetImage(); Assert.Equal(42, img.Width); Assert.Equal(666, img.Height); Assert.Equal("hello", message); } [Theory] [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) where TPixel : struct, IPixel { Image img = provider.GetImage(); Assert.Equal(42, img.Width); Assert.Equal(666, img.Height); Assert.Equal("hello", message); } [Theory] [WithFile(TestImages.Bmp.Car, PixelTypes.All, 123)] [WithFile(TestImages.Bmp.F, PixelTypes.All, 123)] public void Use_WithFileAttribute(TestImageProvider provider, int yo) where TPixel : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); using (Image img = provider.GetImage()) { Assert.True(img.Width * img.Height > 0); Assert.Equal(123, yo); string fn = provider.Utility.GetTestOutputFileName("jpg"); this.Output.WriteLine(fn); } } [Theory] [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) where TPixel : struct, IPixel { EnsureCustomConfigurationIsApplied(provider); } [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] public void Use_WithFileCollection(TestImageProvider provider) where TPixel : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); using (Image image = provider.GetImage()) { provider.Utility.SaveTestOutputFile(image, "png"); } } [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] public void Use_WithMemberFactoryAttribute(TestImageProvider provider) where TPixel : struct, IPixel { Image img = provider.GetImage(); Assert.Equal(3, img.Width); if (provider.PixelType == PixelTypes.Rgba32) { Assert.IsType>(img); } } [Theory] [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Rgba32 | PixelTypes.Argb32)] public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) where TPixel : struct, IPixel { Image img = provider.GetImage(); Assert.Equal(10, img.Width); Assert.Equal(20, img.Height); Buffer2D pixels = img.GetRootFramePixelBuffer(); Rgba32 rgba = default; for (int y = 0; y < pixels.Height; y++) { for (int x = 0; x < pixels.Width; x++) { pixels[x, y].ToRgba32(ref rgba); Assert.Equal(255, rgba.R); Assert.Equal(100, rgba.G); Assert.Equal(50, rgba.B); Assert.Equal(200, rgba.A); } } } [Theory] [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] public void Use_WithTestPatternImages(TestImageProvider provider) where TPixel : struct, IPixel { using (Image img = provider.GetImage()) { img.DebugSave(provider); } } [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) where TPixel : struct, IPixel { EnsureCustomConfigurationIsApplied(provider); } private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) where TPixel : struct, IPixel { using (provider.GetImage()) { var customConfiguration = Configuration.CreateDefaultInstance(); provider.Configuration = customConfiguration; using (Image image2 = provider.GetImage()) using (Image image3 = provider.GetImage()) { Assert.Same(customConfiguration, image2.GetConfiguration()); Assert.Same(customConfiguration, image3.GetConfiguration()); } } } private class TestDecoder : IImageDecoder { // Couldn't make xUnit happy without this hackery: private static readonly ConcurrentDictionary invocationCounts = new ConcurrentDictionary(); private static readonly object Monitor = new object(); private string callerName; public static void DoTestThreadSafe(Action action) { lock (Monitor) { action(); } } public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { invocationCounts[this.callerName]++; return new Image(42, 42); } internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; internal void InitCaller(string name) { this.callerName = name; invocationCounts[name] = 0; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } private class TestDecoderWithParameters : IImageDecoder { private static readonly ConcurrentDictionary invocationCounts = new ConcurrentDictionary(); private static readonly object Monitor = new object(); private string callerName; public string Param1 { get; set; } public int Param2 { get; set; } public static void DoTestThreadSafe(Action action) { lock (Monitor) { action(); } } public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { invocationCounts[this.callerName]++; return new Image(42, 42); } internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; internal void InitCaller(string name) { this.callerName = name; invocationCounts[name] = 0; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } } }