diff --git a/src/ImageSharp/ApplyProcessors.cs b/src/ImageSharp/ApplyProcessors.cs index a092b789b..d625b1c24 100644 --- a/src/ImageSharp/ApplyProcessors.cs +++ b/src/ImageSharp/ApplyProcessors.cs @@ -28,7 +28,7 @@ namespace ImageSharp Guard.NotNull(operations, nameof(operations)); // TODO: add parameter to Configuration to configure how this is created, create an IImageOperationsFactory that cna be used to switch this out with a fake for testing - var operationsRunner = new ImageOperations(source); + IImageOperations operationsRunner = source.Configuration.ImageOperationsProvider.CreateMutator(source); operations(operationsRunner); } @@ -44,7 +44,7 @@ namespace ImageSharp Guard.NotNull(operations, nameof(operations)); // TODO: add parameter to Configuration to configure how this is created, create an IImageOperationsFactory that cna be used to switch this out with a fake for testing - var operationsRunner = new ImageOperations(source); + IImageOperations operationsRunner = source.Configuration.ImageOperationsProvider.CreateMutator(source); operationsRunner.ApplyProcessors(operations); } @@ -62,7 +62,7 @@ namespace ImageSharp var generated = new Image(source); // TODO: add parameter to Configuration to configure how this is created, create an IImageOperationsFactory that cna be used to switch this out with a fake for testing - var operationsRunner = new ImageOperations(generated); + IImageOperations operationsRunner = source.Configuration.ImageOperationsProvider.CreateMutator(generated); operations(operationsRunner); return generated; } @@ -81,7 +81,7 @@ namespace ImageSharp var generated = new Image(source); // TODO: add parameter to Configuration to configure how this is created, create an IImageOperationsFactory that cna be used to switch this out with a fake for testing - var operationsRunner = new ImageOperations(generated); + IImageOperations operationsRunner = source.Configuration.ImageOperationsProvider.CreateMutator(generated); operationsRunner.ApplyProcessors(operations); return generated; } @@ -96,5 +96,23 @@ namespace ImageSharp public static IImageOperations Run(this IImageOperations source, Action> operation) where TPixel : struct, IPixel => source.ApplyProcessor(new DelegateImageProcessor(operation)); + + /// + /// Queues up a simple operation that provides access to the mutatable image. + /// + /// The pixel format. + /// The image to rotate, flip, or both. + /// The operations to perform on the source. + /// returns the current optinoatins class to allow chaining of oprations. + internal static IImageOperations ApplyProcessors(this IImageOperations source, params IImageProcessor[] operations) + where TPixel : struct, IPixel + { + foreach (IImageProcessor op in operations) + { + source = source.ApplyProcessor(op); + } + + return source; + } } } diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 226d45132..834019633 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -103,11 +103,16 @@ namespace ImageSharp #if !NETSTANDARD1_1 /// - /// Gets or sets the fielsystem helper for accessing the local file system. + /// Gets or sets the filesystem helper for accessing the local file system. /// internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); #endif + /// + /// Gets or sets the image operations providers. + /// + internal IImageOperationsProvider ImageOperationsProvider { get; set; } = new DefaultImageOperationsProvider(); + /// /// Registers a new format provider. /// diff --git a/src/ImageSharp/IImageOperationsProvider.cs b/src/ImageSharp/IImageOperationsProvider.cs new file mode 100644 index 000000000..475c63265 --- /dev/null +++ b/src/ImageSharp/IImageOperationsProvider.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using ImageSharp.PixelFormats; + + /// + /// Represents an interface that will create IImageOperations + /// + internal interface IImageOperationsProvider + { + /// + /// Called during Mutate operations to generate the imageoperations provider. + /// + /// The pixel format + /// The source image. + /// A new IImageOPeration + IImageOperations CreateMutator(Image source) + where TPixel : struct, IPixel; + } + + /// + /// The default implmentation of IImageOperationsProvider + /// + internal class DefaultImageOperationsProvider : IImageOperationsProvider + { + /// + public IImageOperations CreateMutator(Image source) + where TPixel : struct, IPixel + { + return new ImageOperations(source); + } + } +} diff --git a/src/ImageSharp/IImageOperations{TPixel}.cs b/src/ImageSharp/IImageOperations{TPixel}.cs index a176c0e28..349cef7f3 100644 --- a/src/ImageSharp/IImageOperations{TPixel}.cs +++ b/src/ImageSharp/IImageOperations{TPixel}.cs @@ -12,7 +12,7 @@ namespace ImageSharp using SixLabors.Primitives; /// - /// The static collection of all the default image formats + /// An interface to queue up image operations. /// /// The pixel format public interface IImageOperations diff --git a/src/ImageSharp/Processing/DelegateImageProcessor.cs b/src/ImageSharp/Processing/DelegateImageProcessor.cs index e1d1060c6..5fbb84114 100644 --- a/src/ImageSharp/Processing/DelegateImageProcessor.cs +++ b/src/ImageSharp/Processing/DelegateImageProcessor.cs @@ -29,6 +29,11 @@ namespace ImageSharp.Processing this.action = action; } + /// + /// Gets the action that will be applied to the image. + /// + internal Action> Action => this.action; + /// protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) { diff --git a/src/Shared/AssemblyInfo.Common.cs b/src/Shared/AssemblyInfo.Common.cs index 252ef3eae..c2aa13de2 100644 --- a/src/Shared/AssemblyInfo.Common.cs +++ b/src/Shared/AssemblyInfo.Common.cs @@ -37,4 +37,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("ImageSharp.Drawing")] [assembly: InternalsVisibleTo("ImageSharp.Benchmarks")] [assembly: InternalsVisibleTo("ImageSharp.Tests")] -[assembly: InternalsVisibleTo("ImageSharp.Sandbox46")] \ No newline at end of file +[assembly: InternalsVisibleTo("ImageSharp.Sandbox46")] + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs new file mode 100644 index 000000000..d6bea7108 --- /dev/null +++ b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ImageSharp.Processing; +using Xunit; + +namespace ImageSharp.Tests +{ + public abstract class BaseImageOperationsExtensionTest + { + protected readonly FakeImageOperationsProvider.FakeImageOperations operations; + + public BaseImageOperationsExtensionTest() + { + this.operations = new FakeImageOperationsProvider.FakeImageOperations(null); + } + + public T Verify(int index = 0) + { + var operation = this.operations.applied[index]; + + return Assert.IsType(operation.Processor); + } + } +} diff --git a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs new file mode 100644 index 000000000..b9a1d80a2 --- /dev/null +++ b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs @@ -0,0 +1,77 @@ +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + using SixLabors.Primitives; + + public class FakeImageOperationsProvider : IImageOperationsProvider + { + private List ImageOperators = new List(); + + public bool HasCreated(Image source) + where TPixel : struct, IPixel + { + return Created(source).Any(); + } + public IEnumerable> Created(Image source) where TPixel : struct, IPixel + { + return this.ImageOperators.OfType>() + .Where(x => x.source == source); + } + + public IEnumerable.AppliedOpperation> AppliedOperations(Image source) where TPixel : struct, IPixel + { + return Created(source) + .SelectMany(x => x.applied); + } + + public IImageOperations CreateMutator(Image source) where TPixel : struct, IPixel + { + var op = new FakeImageOperations(source); + this.ImageOperators.Add(op); + return op; + } + + + public class FakeImageOperations : IImageOperations + where TPixel : struct, IPixel + { + public Image source; + + public List applied = new List(); + + public FakeImageOperations(Image source) + { + this.source = source; + } + + public IImageOperations ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + applied.Add(new AppliedOpperation + { + Processor = processor, + Rectangle = rectangle + }); + return this; + } + + public IImageOperations ApplyProcessor(IImageProcessor processor) + { + applied.Add(new AppliedOpperation + { + Processor = processor + }); + return this; + } + public struct AppliedOpperation + { + public Rectangle? Rectangle { get; set; } + public IImageProcessor Processor { get; set; } + } + } + } +} diff --git a/tests/ImageSharp.Tests/ImageOperationTests.cs b/tests/ImageSharp.Tests/ImageOperationTests.cs new file mode 100644 index 000000000..61bb18e76 --- /dev/null +++ b/tests/ImageSharp.Tests/ImageOperationTests.cs @@ -0,0 +1,120 @@ +// +// 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.IO; + using System.Linq; + + using ImageSharp.Formats; + using ImageSharp.IO; + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + using Moq; + using SixLabors.Primitives; + using Xunit; + + /// + /// Tests the configuration class. + /// + public class ImageOperationTests : IDisposable + { + private readonly Image image; + private readonly FakeImageOperationsProvider provider; + private readonly IImageProcessor processor; + + public Configuration Configuration { get; private set; } + + public ImageOperationTests() + { + this.provider = new FakeImageOperationsProvider(); + this.processor = new Mock>().Object; + this.image = new Image(new Configuration() + { + ImageOperationsProvider = this.provider + }, 1, 1); + } + + [Fact] + public void MutateCallsImageOperationsProvider_Func_OriginalImage() + { + this.image.Mutate(x => x.ApplyProcessor(this.processor)); + + Assert.True(this.provider.HasCreated(this.image)); + Assert.Contains(this.processor, this.provider.AppliedOperations(this.image).Select(x=>x.Processor)); + } + + [Fact] + public void MutateCallsImageOperationsProvider_ListOfProcessors_OriginalImage() + { + this.image.Mutate(this.processor); + + Assert.True(this.provider.HasCreated(this.image)); + Assert.Contains(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + } + + [Fact] + public void CloneCallsImageOperationsProvider_Func_WithDuplicateImage() + { + var returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); + + Assert.True(this.provider.HasCreated(returned)); + Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor)); + } + + [Fact] + public void CloneCallsImageOperationsProvider_ListOfProcessors_WithDuplicateImage() + { + var returned = this.image.Clone(this.processor); + + Assert.True(this.provider.HasCreated(returned)); + Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor)); + } + + [Fact] + public void CloneCallsImageOperationsProvider_Func_NotOnOrigional() + { + var returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); + Assert.False(this.provider.HasCreated(this.image)); + Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + } + + [Fact] + public void CloneCallsImageOperationsProvider_ListOfProcessors_NotOnOrigional() + { + var returned = this.image.Clone(this.processor); + Assert.False(this.provider.HasCreated(this.image)); + Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); + } + + [Fact] + public void ApplyProcessors_ListOfProcessors_AppliesALlProcessorsToOperation() + { + var operations = new FakeImageOperationsProvider.FakeImageOperations(null); + operations.ApplyProcessors(this.processor); + Assert.Contains(this.processor, operations.applied.Select(x => x.Processor)); + } + + public void Dispose() + { + this.image.Dispose(); + } + } + + public class RunImageOperation : BaseImageOperationsExtensionTest + { + [Fact] + public void Run_CreatedDelegateProcessor() + { + Action> action = (i) => { }; + this.operations.Run(action); + + DelegateImageProcessor processor = this.Verify>(); + Assert.Equal(action, processor.Action); + } + } +} \ No newline at end of file