Browse Source

Merge pull request #1011 from SixLabors/feature/processors

Make processors public, refactor cloning.
pull/1018/head
James Jackson-South 7 years ago
committed by GitHub
parent
commit
4a4c8dd68d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  2. 36
      src/ImageSharp/Advanced/AotCompilerTools.cs
  3. 22
      src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
  4. 22
      src/ImageSharp/Processing/Processors/CloningImageProcessor.cs
  5. 74
      src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
  6. 2
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
  7. 2
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  8. 2
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  9. 2
      src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs
  10. 4
      src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs
  11. 27
      src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs
  12. 17
      src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs
  13. 2
      src/ImageSharp/Processing/Processors/IImageProcessor.cs
  14. 11
      src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs
  15. 19
      src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs
  16. 9
      src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs
  17. 13
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  18. 55
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  19. 18
      src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs
  20. 11
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  21. 46
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  22. 6
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  23. 11
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  24. 56
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  25. 8
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
  26. 101
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  27. 7
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  28. 5
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  29. 4
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  30. 85
      tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs

16
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
// not a valid operation because rectangle does not overlap with this image. // Not a valid operation because rectangle does not overlap with this image.
if (workingRect.Width <= 0 || workingRect.Height <= 0) if (workingRect.Width <= 0 || workingRect.Height <= 0)
{ {
throw new ImageProcessingException( throw new ImageProcessingException(
@ -102,14 +102,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
workingRect, workingRect,
configuration, configuration,
rows => rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{ {
for (int y = rows.Min; y < rows.Max; y++) Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
{ Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width); blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); }
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity); });
}
});
} }
} }
} }

36
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Advanced namespace SixLabors.ImageSharp.Advanced
{ {
@ -81,9 +80,8 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileWuQuantizer<TPixel>(); AotCompileWuQuantizer<TPixel>();
AotCompileDithering<TPixel>(); AotCompileDithering<TPixel>();
AotCompilePixelOperations<TPixel>(); AotCompilePixelOperations<TPixel>();
AotCompileResizeOperations<TPixel>();
System.Runtime.CompilerServices.Unsafe.SizeOf<TPixel>(); Unsafe.SizeOf<TPixel>();
AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder()); AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder());
AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder()); AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder());
@ -107,8 +105,10 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileOctreeQuantizer<TPixel>() private static void AotCompileOctreeQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)); using (var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)))
test.AotGetPalette(); {
test.AotGetPalette();
}
} }
/// <summary> /// <summary>
@ -118,9 +118,11 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileWuQuantizer<TPixel>() private static void AotCompileWuQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)); using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)))
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1)); {
test.AotGetPalette(); test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
test.AotGetPalette();
}
} }
/// <summary> /// <summary>
@ -132,7 +134,10 @@ namespace SixLabors.ImageSharp.Advanced
{ {
var test = new FloydSteinbergDiffuser(); var test = new FloydSteinbergDiffuser();
TPixel pixel = default; TPixel pixel = default;
test.Dither(new ImageFrame<TPixel>(Configuration.Default, 1, 1), pixel, pixel, 0, 0, 0, 0, 0, 0); using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
{
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0, 0);
}
} }
/// <summary> /// <summary>
@ -171,16 +176,5 @@ namespace SixLabors.ImageSharp.Advanced
var pixelOp = new PixelOperations<TPixel>(); var pixelOp = new PixelOperations<TPixel>();
pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear); pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear);
} }
/// <summary>
/// This method pre-seeds the ResizeProcessor for the AoT compiler on iOS.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileResizeOperations<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var genericResizeProcessor = (ResizeProcessor<TPixel>)new ResizeProcessor(new ResizeOptions(), default).CreatePixelSpecificProcessor(new Image<TPixel>(0, 0), default);
genericResizeProcessor.AotCreateDestination();
}
} }
} }

22
src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs

@ -29,6 +29,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
this.mutate = mutate; this.mutate = mutate;
this.source = source; this.source = source;
// Mutate acts upon the source image only.
if (this.mutate) if (this.mutate)
{ {
this.destination = source; this.destination = source;
@ -43,7 +45,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
if (!this.mutate && this.destination is null) if (!this.mutate && this.destination is null)
{ {
// Ensure we have cloned it if we are not mutating as we might have failed to register any processors // Ensure we have cloned the source if we are not mutating as we might have failed
// to register any processors.
this.destination = this.source.Clone(); this.destination = this.source.Clone();
} }
@ -64,26 +67,25 @@ namespace SixLabors.ImageSharp.Processing
{ {
if (!this.mutate && this.destination is null) if (!this.mutate && this.destination is null)
{ {
// This will only work if the first processor applied is the cloning one thus // When cloning an image we can optimize the processing pipeline by avoiding an unnecessary
// realistically for this optimization to work the resize must the first processor // interim clone if the first processor in the pipeline is a cloning processor.
// applied any only up processors will take the double data path. if (processor is ICloningImageProcessor cloningImageProcessor)
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.source, rectangle))
{ {
// TODO: if 'specificProcessor' is not an ICloningImageProcessor<TPixel> we are unnecessarily disposing and recreating it. using (ICloningImageProcessor<TPixel> pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.source, rectangle))
// This should be solved in a future refactor.
if (specificProcessor is ICloningImageProcessor<TPixel> cloningImageProcessor)
{ {
this.destination = cloningImageProcessor.CloneAndApply(); this.destination = pixelProcessor.CloneAndExecute();
return this; return this;
} }
} }
// Not a cloning processor? We need to create a clone to operate on.
this.destination = this.source.Clone(); this.destination = this.source.Clone();
} }
// Standard processing pipeline.
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle)) using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle))
{ {
specificProcessor.Apply(); specificProcessor.Execute();
} }
return this; return this;

22
src/ImageSharp/Processing/Processors/CloningImageProcessor.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// The base class for all cloning image processors.
/// </summary>
public abstract class CloningImageProcessor : ICloningImageProcessor
{
/// <inheritdoc/>
public abstract ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>;
/// <inheritdoc/>
IImageProcessor<TPixel> IImageProcessor.CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> this.CreatePixelSpecificCloningProcessor(source, sourceRectangle);
}
}

74
src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -9,10 +11,12 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
/// <summary> /// <summary>
/// Allows the application of processing algorithms to a clone of the original image. /// The base class for all pixel specific cloning image processors.
/// Allows the application of processing algorithms to the image.
/// The image is cloned before operating upon and the buffers swapped upon completion.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel> public abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
@ -38,21 +42,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected Rectangle SourceRectangle { get; } protected Rectangle SourceRectangle { get; }
/// <summary> /// <summary>
/// Gets the <see cref="ImageSharp.Configuration"/> instance to use when performing operations. /// Gets the <see cref="Configuration"/> instance to use when performing operations.
/// </summary> /// </summary>
protected Configuration Configuration { get; } protected Configuration Configuration { get; }
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> CloneAndApply() Image<TPixel> ICloningImageProcessor<TPixel>.CloneAndExecute()
{ {
try try
{ {
Image<TPixel> clone = this.CreateDestination(); Image<TPixel> clone = this.CreateTarget();
this.CheckFrameCount(this.Source, clone);
if (clone.Frames.Count != this.Source.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
Configuration configuration = this.Source.GetConfiguration(); Configuration configuration = this.Source.GetConfiguration();
this.BeforeImageApply(clone); this.BeforeImageApply(clone);
@ -84,17 +84,24 @@ namespace SixLabors.ImageSharp.Processing.Processors
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Apply() void IImageProcessor<TPixel>.Execute()
{ {
using (Image<TPixel> cloned = this.CloneAndApply()) // Create an interim clone of the source image to operate on.
// Doing this allows for the application of transforms that will alter
// the dimensions of the image.
Image<TPixel> clone = default;
try
{ {
// we now need to move the pixel data/size data from one image base to another clone = ((ICloningImageProcessor<TPixel>)this).CloneAndExecute();
if (cloned.Frames.Count != this.Source.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
this.Source.SwapOrCopyPixelsBuffersFrom(cloned); // We now need to move the pixel data/size data from the clone to the source.
this.CheckFrameCount(this.Source, clone);
this.Source.SwapOrCopyPixelsBuffersFrom(clone);
}
finally
{
// Dispose of the clone now that we have swapped the pixel/size data.
clone?.Dispose();
} }
} }
@ -106,10 +113,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
} }
/// <summary> /// <summary>
/// Generates a deep clone of the source image that operations should be applied to. /// Gets the size of the target image.
/// </summary> /// </summary>
/// <returns>The cloned image.</returns> /// <returns>The <see cref="Size"/>.</returns>
protected virtual Image<TPixel> CreateDestination() => this.Source.Clone(); protected abstract Size GetTargetSize();
/// <summary> /// <summary>
/// This method is called before the process is applied to prepare the processor. /// This method is called before the process is applied to prepare the processor.
@ -160,5 +167,30 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
} }
private Image<TPixel> CreateTarget()
{
Image<TPixel> source = this.Source;
Size targetSize = this.GetTargetSize();
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
source.GetConfiguration(),
targetSize.Width,
targetSize.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), frames);
}
private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b)
{
if (a.Frames.Count != b.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
}
} }
} }

2
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply(); base.BeforeImageApply();

2
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply(); base.BeforeImageApply();

2
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply(); base.BeforeImageApply();

2
src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <inheritdoc/> /// <inheritdoc/>
protected override void AfterImageApply() protected override void AfterImageApply()
{ {
new VignetteProcessor(VeryDarkGreen).Apply(this.Source, this.SourceRectangle); new VignetteProcessor(VeryDarkGreen).Execute(this.Source, this.SourceRectangle);
base.AfterImageApply(); base.AfterImageApply();
} }
} }

4
src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs

@ -31,8 +31,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <inheritdoc/> /// <inheritdoc/>
protected override void AfterImageApply() protected override void AfterImageApply()
{ {
new VignetteProcessor(VeryDarkOrange).Apply(this.Source, this.SourceRectangle); new VignetteProcessor(VeryDarkOrange).Execute(this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).Apply(this.Source, this.SourceRectangle); new GlowProcessor(LightOrange, this.Source.Width / 4F).Execute(this.Source, this.SourceRectangle);
base.AfterImageApply(); base.AfterImageApply();
} }
} }

27
src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs

@ -0,0 +1,27 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Defines an algorithm to alter the pixels of a cloned image.
/// </summary>
public interface ICloningImageProcessor : IImageProcessor
{
/// <summary>
/// Creates a pixel specific <see cref="ICloningImageProcessor{TPixel}"/> that is capable of executing
/// the processing algorithm on an <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <returns>The <see cref="ICloningImageProcessor{TPixel}"/></returns>
ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>;
}
}

17
src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs

@ -2,27 +2,20 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
/// <summary> /// <summary>
/// Encapsulates methods to alter the pixels of a new image, cloned from the original image. /// Implements an algorithm to alter the pixels of a cloned image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel> public interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/>. /// Clones the specified <see cref="Image{TPixel}"/> and executes the process against the clone.
/// </summary> /// </summary>
/// <exception cref="System.ArgumentNullException"> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// The target <see cref="Image{TPixel}"/> is null. Image<TPixel> CloneAndExecute();
/// </exception>
/// <exception cref="System.ArgumentException">
/// The target <see cref="Rectangle"/> doesn't fit the dimension of the image.
/// </exception>
/// <returns>Returns the cloned image after there processor has been applied to it.</returns>
Image<TPixel> CloneAndApply();
} }
} }

2
src/ImageSharp/Processing/Processors/IImageProcessor.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
public interface IImageProcessor public interface IImageProcessor
{ {
/// <summary> /// <summary>
/// Creates a pixel specific <see cref="IImageProcessor{TPixel}"/> that is capable for executing /// Creates a pixel specific <see cref="IImageProcessor{TPixel}"/> that is capable of executing
/// the processing algorithm on an <see cref="Image{TPixel}"/>. /// the processing algorithm on an <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam> /// <typeparam name="TPixel">The pixel type.</typeparam>

11
src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs

@ -3,7 +3,6 @@
using System; using System;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
@ -15,14 +14,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Applies the process to the specified portion of the specified <see cref="Image{TPixel}"/>. /// Executes the process against the specified <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
/// <exception cref="ArgumentNullException"> void Execute();
/// The target <see cref="Image{TPixel}"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// The target <see cref="Rectangle"/> doesn't fit the dimension of the image.
/// </exception>
void Apply();
} }
} }

19
src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs

@ -9,18 +9,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
internal static class ImageProcessorExtensions internal static class ImageProcessorExtensions
{ {
public static void Apply(this IImageProcessor processor, Image source, Rectangle sourceRectangle) /// <summary>
{ /// Executes the processor against the given source image and rectangle bounds.
source.AcceptVisitor(new ApplyVisitor(processor, sourceRectangle)); /// </summary>
} /// <param name="processor">The processor.</param>
/// <param name="source">The source image.</param>
/// <param name="sourceRectangle">The source bounds.</param>
public static void Execute(this IImageProcessor processor, Image source, Rectangle sourceRectangle)
=> source.AcceptVisitor(new ExecuteVisitor(processor, sourceRectangle));
private class ApplyVisitor : IImageVisitor private class ExecuteVisitor : IImageVisitor
{ {
private readonly IImageProcessor processor; private readonly IImageProcessor processor;
private readonly Rectangle sourceRectangle; private readonly Rectangle sourceRectangle;
public ApplyVisitor(IImageProcessor processor, Rectangle sourceRectangle) public ExecuteVisitor(IImageProcessor processor, Rectangle sourceRectangle)
{ {
this.processor = processor; this.processor = processor;
this.sourceRectangle = sourceRectangle; this.sourceRectangle = sourceRectangle;
@ -31,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
using (IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle)) using (IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle))
{ {
processorImpl.Apply(); processorImpl.Execute();
} }
} }
} }

9
src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs

@ -9,10 +9,11 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
/// <summary> /// <summary>
/// Allows the application of processors to images. /// The base class for all pixel specific image processors.
/// Allows the application of processing algorithms to the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class ImageProcessor<TPixel> : IImageProcessor<TPixel> public abstract class ImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
@ -43,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected Configuration Configuration { get; } protected Configuration Configuration { get; }
/// <inheritdoc/> /// <inheritdoc/>
public void Apply() void IImageProcessor<TPixel>.Execute()
{ {
try try
{ {
@ -69,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
} }
/// <summary> /// <summary>
/// Applies the processor to just a single ImageBase. /// Applies the processor to a single image frame.
/// </summary> /// </summary>
/// <param name="source">the source image.</param> /// <param name="source">the source image.</param>
public void Apply(ImageFrame<TPixel> source) public void Apply(ImageFrame<TPixel> source)

13
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Defines an affine transformation applicable on an <see cref="Image"/>. /// Defines an affine transformation applicable on an <see cref="Image"/>.
/// </summary> /// </summary>
public class AffineTransformProcessor : IImageProcessor public class AffineTransformProcessor : CloningImageProcessor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor"/> class. /// Initializes a new instance of the <see cref="AffineTransformProcessor"/> class.
@ -42,11 +40,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public Size TargetDimensions { get; } public Size TargetDimensions { get; }
/// <inheritdoc /> /// <inheritdoc/>
public virtual IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> => new AffineTransformProcessor<TPixel>(this, source, sourceRectangle);
{
return new AffineTransformProcessor<TPixel>(this, source, sourceRectangle);
}
} }
} }

55
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs

@ -2,10 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -20,6 +17,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel> internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private Size targetSize;
private Matrix3x2 transformMatrix;
private readonly IResampler resampler;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary> /// </summary>
@ -29,50 +30,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public AffineTransformProcessor(AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public AffineTransformProcessor(AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ {
this.Definition = definition; this.targetSize = definition.TargetDimensions;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
} }
protected AffineTransformProcessor Definition { get; } protected override Size GetTargetSize() => this.targetSize;
private Size TargetDimensions => this.Definition.TargetDimensions;
private Matrix3x2 TransformMatrix => this.Definition.TransformMatrix;
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination()
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = this.Source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(this.Configuration, this.TargetDimensions, x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Configuration, this.Source.Metadata.DeepClone(), frames);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{ {
// Handle transforms that result in output identical to the original. // Handle transforms that result in output identical to the original.
if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix3x2.Identity)) if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix3x2.Identity))
{ {
// The clone will be blank here copy all the pixel data over // The clone will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return; return;
} }
int width = this.TargetDimensions.Width; int width = this.targetSize.Width;
var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); Rectangle sourceBounds = this.SourceRectangle;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// Convert from screen to world space. // Convert from screen to world space.
Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 matrix); Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 matrix);
IResampler sampler = this.Definition.Sampler; if (this.resampler is NearestNeighborResampler)
if (sampler is NearestNeighborResampler)
{ {
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
targetBounds, targetBounds,
this.Configuration, configuration,
rows => rows =>
{ {
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
@ -82,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
var point = Point.Transform(new Point(x, y), matrix); var point = Point.Transform(new Point(x, y), matrix);
if (this.SourceRectangle.Contains(point.X, point.Y)) if (sourceBounds.Contains(point.X, point.Y))
{ {
destRow[x] = source[point.X, point.Y]; destRow[x] = source[point.X, point.Y];
} }
@ -93,19 +81,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return; return;
} }
var kernel = new TransformKernelMap(this.Configuration, source.Size(), destination.Size(), sampler); var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
try try
{ {
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
targetBounds, targetBounds,
this.Configuration, configuration,
(rows, vectorBuffer) => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span; Span<Vector4> vectorSpan = vectorBuffer.Span;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y); Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan, vectorSpan); PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan, vectorSpan);
ref float ySpanRef = ref kernel.GetYStartReference(y); ref float ySpanRef = ref kernel.GetYStartReference(y);
ref float xSpanRef = ref kernel.GetXStartReference(y); ref float xSpanRef = ref kernel.GetXStartReference(y);
@ -124,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
PixelOperations<TPixel>.Instance.FromVector4Destructive( PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.Configuration, configuration,
vectorSpan, vectorSpan,
targetRowSpan); targetRowSpan);
} }

18
src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs

@ -34,33 +34,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
switch (orientation) switch (orientation)
{ {
case OrientationMode.TopRight: case OrientationMode.TopRight:
new FlipProcessor(FlipMode.Horizontal).Apply(this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.BottomRight: case OrientationMode.BottomRight:
new RotateProcessor((int)RotateMode.Rotate180, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.BottomLeft: case OrientationMode.BottomLeft:
new FlipProcessor(FlipMode.Vertical).Apply(this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Vertical).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.LeftTop: case OrientationMode.LeftTop:
new RotateProcessor((int)RotateMode.Rotate90, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Source, this.SourceRectangle);
new FlipProcessor(FlipMode.Horizontal).Apply(this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.RightTop: case OrientationMode.RightTop:
new RotateProcessor((int)RotateMode.Rotate90, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.RightBottom: case OrientationMode.RightBottom:
new FlipProcessor(FlipMode.Vertical).Apply(this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Vertical).Execute(this.Source, this.SourceRectangle);
new RotateProcessor((int)RotateMode.Rotate270, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.LeftBottom: case OrientationMode.LeftBottom:
new RotateProcessor((int)RotateMode.Rotate270, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.Unknown: case OrientationMode.Unknown:

11
src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -9,7 +8,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Defines a crop operation on an image. /// Defines a crop operation on an image.
/// </summary> /// </summary>
public sealed class CropProcessor : IImageProcessor public sealed class CropProcessor : CloningImageProcessor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CropProcessor"/> class. /// Initializes a new instance of the <see cref="CropProcessor"/> class.
@ -23,6 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle),
nameof(cropRectangle), nameof(cropRectangle),
"Crop rectangle should be smaller than the source bounds."); "Crop rectangle should be smaller than the source bounds.");
this.CropRectangle = cropRectangle; this.CropRectangle = cropRectangle;
} }
@ -32,10 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Rectangle CropRectangle { get; } public Rectangle CropRectangle { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> => new CropProcessor<TPixel>(this, source, sourceRectangle);
{
return new CropProcessor<TPixel>(this, source, sourceRectangle);
}
} }
} }

46
src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs

@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -19,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class CropProcessor<TPixel> : TransformProcessor<TPixel> internal class CropProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly CropProcessor definition; private Rectangle cropRectangle;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class.
@ -29,53 +26,42 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public CropProcessor(CropProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public CropProcessor(CropProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ => this.cropRectangle = definition.CropRectangle;
this.definition = definition;
}
private Rectangle CropRectangle => this.definition.CropRectangle;
/// <inheritdoc/> /// <inheritdoc/>
protected override Image<TPixel> CreateDestination() protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height);
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = this.Source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
this.Source.GetConfiguration(),
this.CropRectangle.Width,
this.CropRectangle.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Source.GetConfiguration(), this.Source.Metadata.DeepClone(), frames);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{ {
// Handle resize dimensions identical to the original // Handle crop dimensions identical to the original
if (source.Width == destination.Width && source.Height == destination.Height && this.SourceRectangle == this.CropRectangle) if (source.Width == destination.Width
&& source.Height == destination.Height
&& this.SourceRectangle == this.cropRectangle)
{ {
// the cloned will be blank here copy all the pixel data over // the cloned will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return; return;
} }
Rectangle rect = this.CropRectangle; Rectangle bounds = this.cropRectangle;
// Copying is cheap, we should process more pixels per task: // Copying is cheap, we should process more pixels per task:
ParallelExecutionSettings parallelSettings = this.Configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); ParallelExecutionSettings parallelSettings
= this.Configuration
.GetParallelSettings()
.MultiplyMinimumPixelsPerTask(4);
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
rect, bounds,
parallelSettings, parallelSettings,
rows => rows =>
{ {
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left); Span<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left);
Span<TPixel> targetRow = destination.GetPixelRowSpan(y - rect.Top); Span<TPixel> targetRow = destination.GetPixelRowSpan(y - bounds.Top);
sourceRow.Slice(0, rect.Width).CopyTo(targetRow); sourceRow.Slice(0, bounds.Width).CopyTo(targetRow);
} }
}); });
} }

6
src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs

@ -42,16 +42,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration = this.Source.GetConfiguration(); Configuration configuration = this.Source.GetConfiguration();
// Detect the edges. // Detect the edges.
new SobelProcessor(false).Apply(temp, this.SourceRectangle); new SobelProcessor(false).Execute(temp, this.SourceRectangle);
// Apply threshold binarization filter. // Apply threshold binarization filter.
new BinaryThresholdProcessor(this.definition.Threshold).Apply(temp, this.SourceRectangle); new BinaryThresholdProcessor(this.definition.Threshold).Execute(temp, this.SourceRectangle);
// Search for the first white pixels // Search for the first white pixels
rectangle = ImageMaths.GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0); rectangle = ImageMaths.GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0);
} }
new CropProcessor(rectangle, this.Source.Size()).Apply(this.Source, this.SourceRectangle); new CropProcessor(rectangle, this.Source.Size()).Execute(this.Source, this.SourceRectangle);
base.BeforeImageApply(); base.BeforeImageApply();
} }

11
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Defines a projective transformation applicable to an <see cref="Image"/>. /// Defines a projective transformation applicable to an <see cref="Image"/>.
/// </summary> /// </summary>
public sealed class ProjectiveTransformProcessor : IImageProcessor public sealed class ProjectiveTransformProcessor : CloningImageProcessor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor"/> class. /// Initializes a new instance of the <see cref="ProjectiveTransformProcessor"/> class.
@ -43,10 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Size TargetDimensions { get; } public Size TargetDimensions { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> => new ProjectiveTransformProcessor<TPixel>(this, source, sourceRectangle);
{
return new ProjectiveTransformProcessor<TPixel>(this, source, sourceRectangle);
}
} }
} }

56
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -20,7 +18,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel> internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly ProjectiveTransformProcessor definition; private Size targetSize;
private readonly IResampler resampler;
private Matrix4x4 transformMatrix;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class.
@ -31,52 +31,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ {
this.definition = definition; this.targetSize = definition.TargetDimensions;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
} }
private Size TargetDimensions => this.definition.TargetDimensions; protected override Size GetTargetSize() => this.targetSize;
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination()
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = this.Source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
this.Source.GetConfiguration(),
this.TargetDimensions.Width,
this.TargetDimensions.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Source.GetConfiguration(), this.Source.Metadata.DeepClone(), frames);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{ {
Matrix4x4 transformMatrix = this.definition.TransformMatrix;
// Handle transforms that result in output identical to the original. // Handle transforms that result in output identical to the original.
if (transformMatrix.Equals(default) || transformMatrix.Equals(Matrix4x4.Identity)) if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix4x4.Identity))
{ {
// The clone will be blank here copy all the pixel data over // The clone will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return; return;
} }
int width = this.TargetDimensions.Width; int width = this.targetSize.Width;
var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); Rectangle sourceBounds = this.SourceRectangle;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// Convert from screen to world space. // Convert from screen to world space.
Matrix4x4.Invert(transformMatrix, out Matrix4x4 matrix); Matrix4x4.Invert(this.transformMatrix, out Matrix4x4 matrix);
IResampler sampler = this.definition.Sampler;
if (sampler is NearestNeighborResampler) if (this.resampler is NearestNeighborResampler)
{ {
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
targetBounds, targetBounds,
this.Configuration, configuration,
rows => rows =>
{ {
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
@ -89,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int px = (int)MathF.Round(point.X); int px = (int)MathF.Round(point.X);
int py = (int)MathF.Round(point.Y); int py = (int)MathF.Round(point.Y);
if (this.SourceRectangle.Contains(px, py)) if (sourceBounds.Contains(px, py))
{ {
destRow[x] = source[px, py]; destRow[x] = source[px, py];
} }
@ -100,19 +85,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return; return;
} }
var kernel = new TransformKernelMap(this.Configuration, source.Size(), destination.Size(), sampler); var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
try try
{ {
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
targetBounds, targetBounds,
this.Configuration, configuration,
(rows, vectorBuffer) => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span; Span<Vector4> vectorSpan = vectorBuffer.Span;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y); Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan, vectorSpan); PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan, vectorSpan);
ref float ySpanRef = ref kernel.GetYStartReference(y); ref float ySpanRef = ref kernel.GetYStartReference(y);
ref float xSpanRef = ref kernel.GetXStartReference(y); ref float xSpanRef = ref kernel.GetXStartReference(y);
@ -131,7 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
PixelOperations<TPixel>.Instance.FromVector4Destructive( PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.Configuration, configuration,
vectorSpan, vectorSpan,
targetRowSpan); targetRowSpan);
} }

8
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs

@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,7 +8,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Defines an image resizing operation with the given <see cref="IResampler"/> and dimensional parameters. /// Defines an image resizing operation with the given <see cref="IResampler"/> and dimensional parameters.
/// </summary> /// </summary>
public class ResizeProcessor : IImageProcessor public class ResizeProcessor : CloningImageProcessor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class. /// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
@ -58,8 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public bool Compand { get; } public bool Compand { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
=> new ResizeProcessor<TPixel>(this, source, sourceRectangle); => new ResizeProcessor<TPixel>(this, source, sourceRectangle);
} }
} }

101
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -24,74 +22,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel> internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly ResizeProcessor parameterSource;
private bool isDisposed; private bool isDisposed;
private readonly int targetWidth;
private readonly int targetHeight;
private readonly IResampler resampler;
private Rectangle targetRectangle;
private readonly bool compand;
// The following fields are not immutable but are optionally created on demand. // The following fields are not immutable but are optionally created on demand.
private ResizeKernelMap horizontalKernelMap; private ResizeKernelMap horizontalKernelMap;
private ResizeKernelMap verticalKernelMap; private ResizeKernelMap verticalKernelMap;
public ResizeProcessor(ResizeProcessor parameterSource, Image<TPixel> source, Rectangle sourceRectangle) public ResizeProcessor(ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ {
this.parameterSource = parameterSource; this.targetWidth = definition.TargetWidth;
this.targetHeight = definition.TargetHeight;
this.targetRectangle = definition.TargetRectangle;
this.resampler = definition.Sampler;
this.compand = definition.Compand;
} }
/// <summary>
/// Gets the sampler to perform the resize operation.
/// </summary>
public IResampler Sampler => this.parameterSource.Sampler;
/// <summary>
/// Gets the target width.
/// </summary>
public int TargetWidth => this.parameterSource.TargetWidth;
/// <summary>
/// Gets the target height.
/// </summary>
public int TargetHeight => this.parameterSource.TargetHeight;
/// <summary>
/// Gets the target resize rectangle.
/// </summary>
public Rectangle TargetRectangle => this.parameterSource.TargetRectangle;
/// <summary>
/// Gets a value indicating whether to compress or expand individual pixel color values on processing.
/// </summary>
public bool Compand => this.parameterSource.Compand;
/// <summary>
/// This is a shim for tagging the CreateDestination virtual generic method for the AoT iOS compiler.
/// This method should never be referenced outside of the AotCompiler code.
/// </summary>
/// <returns>The result returned from <see cref="M:CreateDestination"/>.</returns>
internal Image<TPixel> AotCreateDestination()
=> this.CreateDestination();
/// <inheritdoc/> /// <inheritdoc/>
protected override Image<TPixel> CreateDestination() protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight);
{
Image<TPixel> source = this.Source;
Configuration configuration = this.Configuration;
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
configuration,
this.TargetWidth,
this.TargetHeight,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(configuration, source.Metadata.DeepClone(), frames);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply(Image<TPixel> destination) protected override void BeforeImageApply(Image<TPixel> destination)
{ {
if (!(this.Sampler is NearestNeighborResampler)) if (!(this.resampler is NearestNeighborResampler))
{ {
Image<TPixel> source = this.Source; Image<TPixel> source = this.Source;
Rectangle sourceRectangle = this.SourceRectangle; Rectangle sourceRectangle = this.SourceRectangle;
@ -99,14 +57,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Since all image frame dimensions have to be the same we can calculate this for all frames. // Since all image frame dimensions have to be the same we can calculate this for all frames.
MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); MemoryAllocator memoryAllocator = source.GetMemoryAllocator();
this.horizontalKernelMap = ResizeKernelMap.Calculate( this.horizontalKernelMap = ResizeKernelMap.Calculate(
this.Sampler, this.resampler,
this.TargetRectangle.Width, this.targetRectangle.Width,
sourceRectangle.Width, sourceRectangle.Width,
memoryAllocator); memoryAllocator);
this.verticalKernelMap = ResizeKernelMap.Calculate( this.verticalKernelMap = ResizeKernelMap.Calculate(
this.Sampler, this.resampler,
this.TargetRectangle.Height, this.targetRectangle.Height,
sourceRectangle.Height, sourceRectangle.Height,
memoryAllocator); memoryAllocator);
} }
@ -121,29 +79,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
// Handle resize dimensions identical to the original // Handle resize dimensions identical to the original
if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.TargetRectangle) if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.targetRectangle)
{ {
// The cloned will be blank here copy all the pixel data over // The cloned will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return; return;
} }
int width = this.TargetWidth; int width = this.targetWidth;
int height = this.TargetHeight; int height = this.targetHeight;
int sourceX = sourceRectangle.X; int sourceX = sourceRectangle.X;
int sourceY = sourceRectangle.Y; int sourceY = sourceRectangle.Y;
int startY = this.TargetRectangle.Y; int startY = this.targetRectangle.Y;
int startX = this.TargetRectangle.X; int startX = this.targetRectangle.X;
var targetWorkingRect = Rectangle.Intersect( var targetWorkingRect = Rectangle.Intersect(
this.TargetRectangle, this.targetRectangle,
new Rectangle(0, 0, width, height)); new Rectangle(0, 0, width, height));
if (this.Sampler is NearestNeighborResampler) if (this.resampler is NearestNeighborResampler)
{ {
// Scaling factors // Scaling factors
float widthFactor = sourceRectangle.Width / (float)this.TargetRectangle.Width; float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.TargetRectangle.Height; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height;
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
targetWorkingRect, targetWorkingRect,
@ -153,8 +111,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
// Y coordinates of source points // Y coordinates of source points
Span<TPixel> sourceRow = Span<TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span<TPixel> targetRow = destination.GetPixelRowSpan(y); Span<TPixel> targetRow = destination.GetPixelRowSpan(y);
for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++) for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++)
@ -169,7 +126,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
PixelConversionModifiers conversionModifiers = PixelConversionModifiers conversionModifiers =
PixelConversionModifiers.Premultiply.ApplyCompanding(this.Compand); PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand);
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle); BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle);
@ -183,7 +140,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.verticalKernelMap, this.verticalKernelMap,
width, width,
targetWorkingRect, targetWorkingRect,
this.TargetRectangle.Location)) this.targetRectangle.Location))
{ {
worker.Initialize(); worker.Initialize();

7
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -47,9 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public float Degrees { get; } public float Degrees { get; }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ => new RotateProcessor<TPixel>(this, source, sourceRectangle);
return new RotateProcessor<TPixel>(this, source, sourceRectangle);
}
} }
} }

5
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

@ -1,8 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -56,4 +55,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public float DegreesY { get; } public float DegreesY { get; }
} }
} }

4
tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
}; };
var processor = new FillRegionProcessor(brush.Object, region, options); var processor = new FillRegionProcessor(brush.Object, region, options);
var img = new Image<Rgba32>(1, 1); var img = new Image<Rgba32>(1, 1);
processor.Apply(img, bounds); processor.Execute(img, bounds);
Assert.Equal(4, region.ScanInvocationCounter); Assert.Equal(4, region.ScanInvocationCounter);
} }
@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
var options = new GraphicsOptions(true); var options = new GraphicsOptions(true);
var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options); var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options);
var img = new Image<Rgba32>(10, 10); var img = new Image<Rgba32>(10, 10);
processor.Apply(img, bounds); processor.Execute(img, bounds);
} }
[Fact] [Fact]

85
tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using Moq; using Moq;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives; using SixLabors.Primitives;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing namespace SixLabors.ImageSharp.Tests.Processing
@ -21,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Processing
private readonly Mock<IImageProcessor> processorDefinition; private readonly Mock<IImageProcessor> processorDefinition;
private readonly Mock<ICloningImageProcessor> cloningProcessorDefinition;
private readonly Mock<IImageProcessor<Rgba32>> regularProcessorImpl; private readonly Mock<IImageProcessor<Rgba32>> regularProcessorImpl;
private readonly Mock<ICloningImageProcessor<Rgba32>> cloningProcessorImpl; private readonly Mock<ICloningImageProcessor<Rgba32>> cloningProcessorImpl;
@ -30,18 +30,20 @@ namespace SixLabors.ImageSharp.Tests.Processing
public ImageProcessingContextTests() public ImageProcessingContextTests()
{ {
this.processorDefinition = new Mock<IImageProcessor>(); this.processorDefinition = new Mock<IImageProcessor>();
this.cloningProcessorDefinition = new Mock<ICloningImageProcessor>();
this.regularProcessorImpl = new Mock<IImageProcessor<Rgba32>>(); this.regularProcessorImpl = new Mock<IImageProcessor<Rgba32>>();
this.cloningProcessorImpl = new Mock<ICloningImageProcessor<Rgba32>>(); this.cloningProcessorImpl = new Mock<ICloningImageProcessor<Rgba32>>();
} }
// bool throwException, bool useBounds // bool throwException, bool useBounds
public static readonly TheoryData<bool, bool> ProcessorTestData = new TheoryData<bool, bool>() public static readonly TheoryData<bool, bool> ProcessorTestData = new TheoryData<bool, bool>()
{ {
{ false, false }, { false, false },
{ false, true }, { false, true },
{ true, false }, { true, false },
{ true, true } { true, true }
}; };
[Theory] [Theory]
[MemberData(nameof(ProcessorTestData))] [MemberData(nameof(ProcessorTestData))]
public void Mutate_RegularProcessor(bool throwException, bool useBounds) public void Mutate_RegularProcessor(bool throwException, bool useBounds)
@ -50,14 +52,14 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException) if (throwException)
{ {
Assert.Throws<ImageProcessingException>(() => this.MutateApply(useBounds)); Assert.Throws<ImageProcessingException>(() => this.MutateRegularApply(useBounds));
} }
else else
{ {
this.MutateApply(useBounds); this.MutateRegularApply(useBounds);
} }
this.regularProcessorImpl.Verify(p => p.Apply(), Times.Once()); this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once());
this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once()); this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once());
} }
@ -69,16 +71,15 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException) if (throwException)
{ {
Assert.Throws<ImageProcessingException>(() => this.CloneApply(useBounds)); Assert.Throws<ImageProcessingException>(() => this.CloneRegularApply(useBounds));
} }
else else
{ {
this.CloneApply(useBounds); this.CloneRegularApply(useBounds);
} }
// TODO: This should be Times.Once(). See comments in DefaultImageProcessingContext<T>.ApplyProcessor() this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once);
this.regularProcessorImpl.Verify(p => p.Apply(), Times.AtLeast(1)); this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once);
this.regularProcessorImpl.Verify(p => p.Dispose(), Times.AtLeast(1));
} }
[Theory] [Theory]
@ -89,14 +90,14 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException) if (throwException)
{ {
Assert.Throws<ImageProcessingException>(() => this.MutateApply(useBounds)); Assert.Throws<ImageProcessingException>(() => this.MutateCloneApply(useBounds));
} }
else else
{ {
this.MutateApply(useBounds); this.MutateCloneApply(useBounds);
} }
this.cloningProcessorImpl.Verify(p => p.Apply(), Times.Once()); this.cloningProcessorImpl.Verify(p => p.Execute(), Times.Once());
this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once());
} }
@ -108,18 +109,18 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException) if (throwException)
{ {
Assert.Throws<ImageProcessingException>(() => this.CloneApply(useBounds)); Assert.Throws<ImageProcessingException>(() => this.CloneCloneApply(useBounds));
} }
else else
{ {
this.CloneApply(useBounds); this.CloneCloneApply(useBounds);
} }
this.cloningProcessorImpl.Verify(p => p.CloneAndApply(), Times.Once()); this.cloningProcessorImpl.Verify(p => p.CloneAndExecute(), Times.Once());
this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once());
} }
private void MutateApply(bool useBounds) private void MutateRegularApply(bool useBounds)
{ {
if (useBounds) if (useBounds)
{ {
@ -131,7 +132,19 @@ namespace SixLabors.ImageSharp.Tests.Processing
} }
} }
private void CloneApply(bool useBounds) private void MutateCloneApply(bool useBounds)
{
if (useBounds)
{
this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds));
}
else
{
this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object));
}
}
private void CloneRegularApply(bool useBounds)
{ {
if (useBounds) if (useBounds)
{ {
@ -143,11 +156,23 @@ namespace SixLabors.ImageSharp.Tests.Processing
} }
} }
private void CloneCloneApply(bool useBounds)
{
if (useBounds)
{
this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds)).Dispose();
}
else
{
this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object)).Dispose();
}
}
private void SetupRegularProcessor(bool throwsException) private void SetupRegularProcessor(bool throwsException)
{ {
if (throwsException) if (throwsException)
{ {
this.regularProcessorImpl.Setup(p => p.Apply()).Throws(new ImageProcessingException("Test")); this.regularProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test"));
} }
this.processorDefinition this.processorDefinition
@ -159,11 +184,15 @@ namespace SixLabors.ImageSharp.Tests.Processing
{ {
if (throwsException) if (throwsException)
{ {
this.cloningProcessorImpl.Setup(p => p.Apply()).Throws(new ImageProcessingException("Test")); this.cloningProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test"));
this.cloningProcessorImpl.Setup(p => p.CloneAndApply()).Throws(new ImageProcessingException("Test")); this.cloningProcessorImpl.Setup(p => p.CloneAndExecute()).Throws(new ImageProcessingException("Test"));
} }
this.processorDefinition this.cloningProcessorDefinition
.Setup(p => p.CreatePixelSpecificCloningProcessor(It.IsAny<Image<Rgba32>>(), It.IsAny<Rectangle>()))
.Returns(this.cloningProcessorImpl.Object);
this.cloningProcessorDefinition
.Setup(p => p.CreatePixelSpecificProcessor(It.IsAny<Image<Rgba32>>(), It.IsAny<Rectangle>())) .Setup(p => p.CreatePixelSpecificProcessor(It.IsAny<Image<Rgba32>>(), It.IsAny<Rectangle>()))
.Returns(this.cloningProcessorImpl.Object); .Returns(this.cloningProcessorImpl.Object);
} }

Loading…
Cancel
Save