diff --git a/src/ImageSharp/Processing/CropExtensions.cs b/src/ImageSharp/Processing/CropExtensions.cs
index 1c0d80afc..6aaff5656 100644
--- a/src/ImageSharp/Processing/CropExtensions.cs
+++ b/src/ImageSharp/Processing/CropExtensions.cs
@@ -1,40 +1,35 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
///
- /// Adds extensions that allow the application of cropping operations to the type.
+ /// Adds extensions that allow the application of cropping operations to the type.
///
public static class CropExtensions
{
///
/// Crops an image to the given width and height.
///
- /// The pixel format.
/// The image to resize.
/// The target image width.
/// The target image height.
/// The
- public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height)
- where TPixel : struct, IPixel
- => Crop(source, new Rectangle(0, 0, width, height));
+ public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) =>
+ Crop(source, new Rectangle(0, 0, width, height));
///
/// Crops an image to the given rectangle.
///
- /// The pixel format.
/// The image to crop.
///
/// The structure that specifies the portion of the image object to retain.
///
/// The
- public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize()));
+ public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) =>
+ source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize()));
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/EntropyCropExtensions.cs b/src/ImageSharp/Processing/EntropyCropExtensions.cs
index 157e69ef2..34bc5daeb 100644
--- a/src/ImageSharp/Processing/EntropyCropExtensions.cs
+++ b/src/ImageSharp/Processing/EntropyCropExtensions.cs
@@ -1,35 +1,30 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Processing
{
///
- /// Adds extensions that allow the application of entropy cropping operations to the type.
+ /// Adds extensions that allow the application of entropy cropping operations to the type.
///
public static class EntropyCropExtensions
{
///
/// Crops an image to the area of greatest entropy using a threshold for entropic density of .5F.
///
- /// The pixel format.
/// The image to crop.
- /// The
- public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new EntropyCropProcessor());
+ /// The .
+ public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) =>
+ source.ApplyProcessor(new EntropyCropProcessor());
///
/// Crops an image to the area of greatest entropy.
///
- /// The pixel format.
/// The image to crop.
/// The threshold for entropic density.
- /// The
- public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new EntropyCropProcessor(threshold));
+ /// The .
+ public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) =>
+ source.ApplyProcessor(new EntropyCropProcessor(threshold));
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/FlipExtensions.cs b/src/ImageSharp/Processing/FlipExtensions.cs
index dfbff7e4d..bc862972e 100644
--- a/src/ImageSharp/Processing/FlipExtensions.cs
+++ b/src/ImageSharp/Processing/FlipExtensions.cs
@@ -1,25 +1,22 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Processing
{
///
- /// Adds extensions that allow the application of flipping operations to the type.
+ /// Adds extensions that allow the application of flipping operations to the type.
///
public static class FlipExtensions
{
///
/// Flips an image by the given instructions.
///
- /// The pixel format.
/// The image to rotate, flip, or both.
/// The to perform the flip.
/// The
- public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new FlipProcessor(flipMode));
+ public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode)
+ => source.ApplyProcessor(new FlipProcessor(flipMode));
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
index 9775e5ca9..7efb71fa1 100644
--- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
@@ -1,5 +1,5 @@
// 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 System.Numerics;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
index 7633ed441..713f04265 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
@@ -1,26 +1,20 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Numerics;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.ParallelUtils;
+
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Provides the base methods to perform affine transforms on an image.
+ /// Defines an affine transformation applicable on an .
///
- /// The pixel format.
- internal class AffineTransformProcessor : TransformProcessorBase
- where TPixel : struct, IPixel
+ public class AffineTransformProcessor : IImageProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The transform matrix.
/// The sampler to perform the transform operation.
@@ -48,95 +42,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
public Size TargetDimensions { get; }
- ///
- protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
+ ///
+ public virtual IImageProcessor CreatePixelSpecificProcessor()
+ where TPixel : struct, IPixel
{
- // We will always be creating the clone even for mutate because we may need to resize the canvas
- IEnumerable> frames =
- source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.Metadata.DeepClone()));
-
- // Use the overload to prevent an extra frame being added
- return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames);
- }
-
- ///
- protected override void OnFrameApply(
- ImageFrame source,
- ImageFrame destination,
- Rectangle sourceRectangle,
- Configuration configuration)
- {
- // Handle tranforms that result in output identical to the original.
- if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix3x2.Identity))
- {
- // The clone will be blank here copy all the pixel data over
- source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
- return;
- }
-
- int width = this.TargetDimensions.Width;
- var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
-
- // Convert from screen to world space.
- Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 matrix);
-
- if (this.Sampler is NearestNeighborResampler)
- {
- ParallelHelper.IterateRows(
- targetBounds,
- configuration,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span destRow = destination.GetPixelRowSpan(y);
-
- for (int x = 0; x < width; x++)
- {
- var point = Point.Transform(new Point(x, y), matrix);
- if (sourceRectangle.Contains(point.X, point.Y))
- {
- destRow[x] = source[point.X, point.Y];
- }
- }
- }
- });
-
- return;
- }
-
- var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler);
- try
- {
- ParallelHelper.IterateRowsWithTempBuffer(
- targetBounds,
- configuration,
- (rows, vectorBuffer) =>
- {
- Span vectorSpan = vectorBuffer.Span;
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetRowSpan = destination.GetPixelRowSpan(y);
- PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan);
- ref float ySpanRef = ref kernel.GetYStartReference(y);
- ref float xSpanRef = ref kernel.GetXStartReference(y);
-
- for (int x = 0; x < width; x++)
- {
- // Use the single precision position to calculate correct bounding pixels
- // otherwise we get rogue pixels outside of the bounds.
- var point = Vector2.Transform(new Vector2(x, y), matrix);
- kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan);
- }
-
- PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
- }
- });
- }
- finally
- {
- kernel.Dispose();
- }
+ return new AffineTransformProcessor(this);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
new file mode 100644
index 000000000..e57ce826b
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
@@ -0,0 +1,127 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Provides the base methods to perform affine transforms on an image.
+ ///
+ /// The pixel format.
+ internal class AffineTransformProcessor : TransformProcessorBase
+ where TPixel : struct, IPixel
+ {
+ public AffineTransformProcessor(AffineTransformProcessor definition)
+ {
+ this.Definition = definition;
+ }
+
+ private Size TargetDimensions => this.Definition.TargetDimensions;
+
+ private Matrix3x2 TransformMatrix => this.Definition.TransformMatrix;
+
+ protected AffineTransformProcessor Definition { get; }
+
+ ///
+ protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
+ {
+ // We will always be creating the clone even for mutate because we may need to resize the canvas
+ IEnumerable> frames =
+ source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.Metadata.DeepClone()));
+
+ // Use the overload to prevent an extra frame being added
+ return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames);
+ }
+
+ ///
+ protected override void OnFrameApply(
+ ImageFrame source,
+ ImageFrame destination,
+ Rectangle sourceRectangle,
+ Configuration configuration)
+ {
+ // Handle tranforms that result in output identical to the original.
+ if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix3x2.Identity))
+ {
+ // The clone will be blank here copy all the pixel data over
+ source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
+ return;
+ }
+
+ int width = this.TargetDimensions.Width;
+ var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
+
+ // Convert from screen to world space.
+ Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 matrix);
+
+ var sampler = this.Definition.Sampler;
+
+ if (sampler is NearestNeighborResampler)
+ {
+ ParallelHelper.IterateRows(
+ targetBounds,
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span destRow = destination.GetPixelRowSpan(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ var point = Point.Transform(new Point(x, y), matrix);
+ if (sourceRectangle.Contains(point.X, point.Y))
+ {
+ destRow[x] = source[point.X, point.Y];
+ }
+ }
+ }
+ });
+
+ return;
+ }
+
+ var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), sampler);
+ try
+ {
+ ParallelHelper.IterateRowsWithTempBuffer(
+ targetBounds,
+ configuration,
+ (rows, vectorBuffer) =>
+ {
+ Span vectorSpan = vectorBuffer.Span;
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = destination.GetPixelRowSpan(y);
+ PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan);
+ ref float ySpanRef = ref kernel.GetYStartReference(y);
+ ref float xSpanRef = ref kernel.GetXStartReference(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ // Use the single precision position to calculate correct bounding pixels
+ // otherwise we get rogue pixels outside of the bounds.
+ var point = Vector2.Transform(new Vector2(x, y), matrix);
+ kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan);
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
+ }
+ });
+ }
+ finally
+ {
+ kernel.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
index 5b9e3dde2..9bbbba843 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
@@ -1,103 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image.
///
- /// The pixel format.
- internal class AutoOrientProcessor : ImageProcessor
- where TPixel : struct, IPixel
+ public sealed class AutoOrientProcessor : IImageProcessor
{
- ///
- protected override void BeforeImageApply(Image source, Rectangle sourceRectangle)
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor()
+ where TPixel : struct, IPixel
{
- OrientationMode orientation = GetExifOrientation(source);
- Size size = sourceRectangle.Size;
- switch (orientation)
- {
- case OrientationMode.TopRight:
- new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle);
- break;
-
- case OrientationMode.BottomRight:
- new RotateProcessor((int)RotateMode.Rotate180, size).Apply(source, sourceRectangle);
- break;
-
- case OrientationMode.BottomLeft:
- new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle);
- break;
-
- case OrientationMode.LeftTop:
- new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle);
- new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle);
- break;
-
- case OrientationMode.RightTop:
- new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle);
- break;
-
- case OrientationMode.RightBottom:
- new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle);
- new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle);
- break;
-
- case OrientationMode.LeftBottom:
- new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle);
- break;
-
- case OrientationMode.Unknown:
- case OrientationMode.TopLeft:
- default:
- break;
- }
- }
-
- ///
- protected override void OnFrameApply(ImageFrame sourceBase, Rectangle sourceRectangle, Configuration config)
- {
- // All processing happens at the image level within BeforeImageApply();
- }
-
- ///
- /// Returns the current EXIF orientation
- ///
- /// The image to auto rotate.
- /// The
- private static OrientationMode GetExifOrientation(Image source)
- {
- if (source.Metadata.ExifProfile is null)
- {
- return OrientationMode.Unknown;
- }
-
- ExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation);
- if (value is null)
- {
- return OrientationMode.Unknown;
- }
-
- OrientationMode orientation;
- if (value.DataType == ExifDataType.Short)
- {
- orientation = (OrientationMode)value.Value;
- }
- else
- {
- orientation = (OrientationMode)Convert.ToUInt16(value.Value);
- source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation);
- }
-
- source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft);
-
- return orientation;
+ return new AutoOrientProcessor();
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs
new file mode 100644
index 000000000..257c223dc
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image.
+ ///
+ /// The pixel format.
+ internal class AutoOrientProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ protected override void BeforeImageApply(Image source, Rectangle sourceRectangle)
+ {
+ OrientationMode orientation = GetExifOrientation(source);
+ Size size = sourceRectangle.Size;
+ switch (orientation)
+ {
+ case OrientationMode.TopRight:
+ new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle);
+ break;
+
+ case OrientationMode.BottomRight:
+ new RotateProcessor((int)RotateMode.Rotate180, size).Apply(source, sourceRectangle);
+ break;
+
+ case OrientationMode.BottomLeft:
+ new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle);
+ break;
+
+ case OrientationMode.LeftTop:
+ new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle);
+ new FlipProcessor(FlipMode.Horizontal).Apply(source, sourceRectangle);
+ break;
+
+ case OrientationMode.RightTop:
+ new RotateProcessor((int)RotateMode.Rotate90, size).Apply(source, sourceRectangle);
+ break;
+
+ case OrientationMode.RightBottom:
+ new FlipProcessor(FlipMode.Vertical).Apply(source, sourceRectangle);
+ new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle);
+ break;
+
+ case OrientationMode.LeftBottom:
+ new RotateProcessor((int)RotateMode.Rotate270, size).Apply(source, sourceRectangle);
+ break;
+
+ case OrientationMode.Unknown:
+ case OrientationMode.TopLeft:
+ default:
+ break;
+ }
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame sourceBase, Rectangle sourceRectangle, Configuration config)
+ {
+ // All processing happens at the image level within BeforeImageApply();
+ }
+
+ ///
+ /// Returns the current EXIF orientation
+ ///
+ /// The image to auto rotate.
+ /// The
+ private static OrientationMode GetExifOrientation(Image source)
+ {
+ if (source.Metadata.ExifProfile is null)
+ {
+ return OrientationMode.Unknown;
+ }
+
+ ExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation);
+ if (value is null)
+ {
+ return OrientationMode.Unknown;
+ }
+
+ OrientationMode orientation;
+ if (value.DataType == ExifDataType.Short)
+ {
+ orientation = (OrientationMode)value.Value;
+ }
+ else
+ {
+ orientation = (OrientationMode)Convert.ToUInt16(value.Value);
+ source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation);
+ }
+
+ source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft);
+
+ return orientation;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
index 5baa196a0..76f223e03 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
@@ -1,32 +1,28 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Provides methods to allow the cropping of an image.
+ /// Defines a crop operation on an image.
///
- /// The pixel format.
- internal class CropProcessor : TransformProcessorBase
- where TPixel : struct, IPixel
+ public sealed class CropProcessor : IImageProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The target cropped rectangle.
/// The source image size.
public CropProcessor(Rectangle cropRectangle, Size sourceSize)
{
// Check bounds here and throw if we are passed a rectangle exceeding our source bounds.
- Guard.IsTrue(new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), nameof(cropRectangle), "Crop rectangle should be smaller than the source bounds.");
+ Guard.IsTrue(
+ new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle),
+ nameof(cropRectangle),
+ "Crop rectangle should be smaller than the source bounds.");
this.CropRectangle = cropRectangle;
}
@@ -35,44 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
public Rectangle CropRectangle { get; }
- ///
- protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor()
+ where TPixel : struct, IPixel
{
- // We will always be creating the clone even for mutate because we may need to resize the canvas
- IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.Metadata.DeepClone()));
-
- // Use the overload to prevent an extra frame being added
- return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames);
- }
-
- ///
- protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
- {
- // Handle resize dimensions identical to the original
- if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.CropRectangle)
- {
- // the cloned will be blank here copy all the pixel data over
- source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
- return;
- }
-
- Rectangle rect = this.CropRectangle;
-
- // Copying is cheap, we should process more pixels per task:
- ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4);
-
- ParallelHelper.IterateRows(
- rect,
- parallelSettings,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left);
- Span targetRow = destination.GetPixelRowSpan(y - rect.Top);
- sourceRow.Slice(0, rect.Width).CopyTo(targetRow);
- }
- });
+ return new CropProcessor(this);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
new file mode 100644
index 000000000..9bddda382
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Provides methods to allow the cropping of an image.
+ ///
+ /// The pixel format.
+ internal class CropProcessor : TransformProcessorBase
+ where TPixel : struct, IPixel
+ {
+ private readonly CropProcessor definition;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ public CropProcessor(CropProcessor definition)
+ {
+ this.definition = definition;
+ }
+
+ private Rectangle CropRectangle => this.definition.CropRectangle;
+
+ ///
+ protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
+ {
+ // We will always be creating the clone even for mutate because we may need to resize the canvas
+ IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.Metadata.DeepClone()));
+
+ // Use the overload to prevent an extra frame being added
+ return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames);
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
+ {
+ // Handle resize dimensions identical to the original
+ if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.CropRectangle)
+ {
+ // the cloned will be blank here copy all the pixel data over
+ source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
+ return;
+ }
+
+ Rectangle rect = this.CropRectangle;
+
+ // Copying is cheap, we should process more pixels per task:
+ ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4);
+
+ ParallelHelper.IterateRows(
+ rect,
+ parallelSettings,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left);
+ Span targetRow = destination.GetPixelRowSpan(y - rect.Top);
+ sourceRow.Slice(0, rect.Width).CopyTo(targetRow);
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs
index 0e744dd96..dee5e7fb3 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs
@@ -1,31 +1,25 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors.Binarization;
-using SixLabors.ImageSharp.Processing.Processors.Convolution;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Provides methods to allow the cropping of an image to preserve areas of highest entropy.
+ /// Defines cropping operation that preserves areas of highest entropy.
///
- /// The pixel format.
- internal class EntropyCropProcessor : ImageProcessor
- where TPixel : struct, IPixel
+ public sealed class EntropyCropProcessor : IImageProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
public EntropyCropProcessor()
- : this(.5F)
+ : this(.5F)
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The threshold to split the image. Must be between 0 and 1.
///
@@ -42,33 +36,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
public float Threshold { get; }
- ///
- protected override void BeforeImageApply(Image source, Rectangle sourceRectangle)
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor()
+ where TPixel : struct, IPixel
{
- Rectangle rectangle;
-
- // All frames have be the same size so we only need to calculate the correct dimensions for the first frame
- using (ImageFrame temp = source.Frames.RootFrame.Clone())
- {
- Configuration configuration = source.GetConfiguration();
-
- // Detect the edges.
- new SobelProcessor(false).ApplyToFrame(temp, sourceRectangle, configuration);
-
- // Apply threshold binarization filter.
- new BinaryThresholdProcessor(this.Threshold).Apply(temp, sourceRectangle, configuration);
-
- // Search for the first white pixels
- rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
- }
-
- new CropProcessor(rectangle, source.Size()).Apply(source, sourceRectangle);
- }
-
- ///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- // All processing happens at the image level within BeforeImageApply();
+ return new EntropyCropProcessor(this);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
new file mode 100644
index 000000000..eaeb6939e
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Binarization;
+using SixLabors.ImageSharp.Processing.Processors.Convolution;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Provides methods to allow the cropping of an image to preserve areas of highest entropy.
+ ///
+ /// The pixel format.
+ internal class EntropyCropProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ private readonly EntropyCropProcessor definition;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ public EntropyCropProcessor(EntropyCropProcessor definition)
+ {
+ this.definition = definition;
+ }
+
+ ///
+ protected override void BeforeImageApply(Image source, Rectangle sourceRectangle)
+ {
+ Rectangle rectangle;
+
+ // All frames have be the same size so we only need to calculate the correct dimensions for the first frame
+ using (ImageFrame temp = source.Frames.RootFrame.Clone())
+ {
+ Configuration configuration = source.GetConfiguration();
+
+ // Detect the edges.
+ new SobelProcessor(false).ApplyToFrame(temp, sourceRectangle, configuration);
+
+ // Apply threshold binarization filter.
+ new BinaryThresholdProcessor(this.definition.Threshold).Apply(temp, sourceRectangle, configuration);
+
+ // Search for the first white pixels
+ rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
+ }
+
+ new CropProcessor(rectangle, source.Size()).Apply(source, sourceRectangle);
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ // All processing happens at the image level within BeforeImageApply();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
index c6f5e9d7b..9a3eab6cc 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
@@ -1,27 +1,17 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-using System.Buffers;
-using System.Threading.Tasks;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Memory;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Provides methods that allow the flipping of an image around its center point.
+ /// Defines a flipping around the center point of the image.
///
- /// The pixel format.
- internal class FlipProcessor : ImageProcessor
- where TPixel : struct, IPixel
+ public class FlipProcessor : IImageProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The used to perform flipping.
public FlipProcessor(FlipMode flipMode)
@@ -34,63 +24,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
public FlipMode FlipMode { get; }
- ///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor()
+ where TPixel : struct, IPixel
{
- switch (this.FlipMode)
- {
- // No default needed as we have already set the pixels.
- case FlipMode.Vertical:
- this.FlipX(source, configuration);
- break;
- case FlipMode.Horizontal:
- this.FlipY(source, configuration);
- break;
- }
- }
-
- ///
- /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image.
- ///
- /// The source image to apply the process to.
- /// The configuration.
- private void FlipX(ImageFrame source, Configuration configuration)
- {
- int height = source.Height;
-
- using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width))
- {
- Span temp = tempBuffer.Memory.Span;
-
- for (int yTop = 0; yTop < height / 2; yTop++)
- {
- int yBottom = height - yTop - 1;
- Span topRow = source.GetPixelRowSpan(yBottom);
- Span bottomRow = source.GetPixelRowSpan(yTop);
- topRow.CopyTo(temp);
- bottomRow.CopyTo(topRow);
- temp.CopyTo(bottomRow);
- }
- }
- }
-
- ///
- /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image.
- ///
- /// The source image to apply the process to.
- /// The configuration.
- private void FlipY(ImageFrame source, Configuration configuration)
- {
- ParallelHelper.IterateRows(
- source.Bounds(),
- configuration,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- source.GetPixelRowSpan(y).Reverse();
- }
- });
+ return new FlipProcessor(this);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs
new file mode 100644
index 000000000..024786209
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Provides methods that allow the flipping of an image around its center point.
+ ///
+ /// The pixel format.
+ internal class FlipProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ private readonly FlipProcessor definition;
+
+ public FlipProcessor(FlipProcessor definition)
+ {
+ this.definition = definition;
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ switch (this.definition.FlipMode)
+ {
+ // No default needed as we have already set the pixels.
+ case FlipMode.Vertical:
+ this.FlipX(source, configuration);
+ break;
+ case FlipMode.Horizontal:
+ this.FlipY(source, configuration);
+ break;
+ }
+ }
+
+ ///
+ /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image.
+ ///
+ /// The source image to apply the process to.
+ /// The configuration.
+ private void FlipX(ImageFrame source, Configuration configuration)
+ {
+ int height = source.Height;
+
+ using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width))
+ {
+ Span temp = tempBuffer.Memory.Span;
+
+ for (int yTop = 0; yTop < height / 2; yTop++)
+ {
+ int yBottom = height - yTop - 1;
+ Span topRow = source.GetPixelRowSpan(yBottom);
+ Span bottomRow = source.GetPixelRowSpan(yTop);
+ topRow.CopyTo(temp);
+ bottomRow.CopyTo(topRow);
+ temp.CopyTo(bottomRow);
+ }
+ }
+ }
+
+ ///
+ /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image.
+ ///
+ /// The source image to apply the process to.
+ /// The configuration.
+ private void FlipY(ImageFrame source, Configuration configuration)
+ {
+ ParallelHelper.IterateRows(
+ source.Bounds(),
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ source.GetPixelRowSpan(y).Reverse();
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
index 6c7271c5e..3a86b3fe4 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
@@ -1,26 +1,20 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Numerics;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.ParallelUtils;
+
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Provides the base methods to perform non-affine transforms on an image.
+ /// Defines a projective transformation applicable to an .
///
- /// The pixel format.
- internal class ProjectiveTransformProcessor : TransformProcessorBase
- where TPixel : struct, IPixel
+ public sealed class ProjectiveTransformProcessor : IImageProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The transform matrix.
/// The sampler to perform the transform operation.
@@ -39,103 +33,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public IResampler Sampler { get; }
///
- /// Gets the matrix used to supply the projective transform
+ /// Gets the matrix used to supply the projective transform.
///
public Matrix4x4 TransformMatrix { get; }
///
- /// Gets the target dimensions to constrain the transformed image to
+ /// Gets the target dimensions to constrain the transformed image to.
///
public Size TargetDimensions { get; }
- ///
- protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor()
+ where TPixel : struct, IPixel
{
- // We will always be creating the clone even for mutate because we may need to resize the canvas
- IEnumerable> frames =
- source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.Metadata.DeepClone()));
-
- // Use the overload to prevent an extra frame being added
- return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames);
- }
-
- ///
- protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
- {
- // Handle tranforms that result in output identical to the original.
- if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix4x4.Identity))
- {
- // The clone will be blank here copy all the pixel data over
- source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
- return;
- }
-
- int width = this.TargetDimensions.Width;
- var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
-
- // Convert from screen to world space.
- Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 matrix);
-
- if (this.Sampler is NearestNeighborResampler)
- {
- ParallelHelper.IterateRows(
- targetBounds,
- configuration,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span destRow = destination.GetPixelRowSpan(y);
-
- for (int x = 0; x < width; x++)
- {
- Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
- int px = (int)MathF.Round(point.X);
- int py = (int)MathF.Round(point.Y);
-
- if (sourceRectangle.Contains(px, py))
- {
- destRow[x] = source[px, py];
- }
- }
- }
- });
-
- return;
- }
-
- var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler);
- try
- {
- ParallelHelper.IterateRowsWithTempBuffer(
- targetBounds,
- configuration,
- (rows, vectorBuffer) =>
- {
- Span vectorSpan = vectorBuffer.Span;
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetRowSpan = destination.GetPixelRowSpan(y);
- PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan);
- ref float ySpanRef = ref kernel.GetYStartReference(y);
- ref float xSpanRef = ref kernel.GetXStartReference(y);
-
- for (int x = 0; x < width; x++)
- {
- // Use the single precision position to calculate correct bounding pixels
- // otherwise we get rogue pixels outside of the bounds.
- Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
- kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan);
- }
-
- PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
- }
- });
- }
- finally
- {
- kernel.Dispose();
- }
+ return new ProjectiveTransformProcessor(this);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
new file mode 100644
index 000000000..e6d885803
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
@@ -0,0 +1,126 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Provides the base methods to perform non-affine transforms on an image.
+ ///
+ /// The pixel format.
+ internal class ProjectiveTransformProcessor : TransformProcessorBase
+ where TPixel : struct, IPixel
+ {
+ private readonly ProjectiveTransformProcessor definition;
+
+ public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition)
+ {
+ this.definition = definition;
+ }
+
+ private Size TargetDimensions => this.definition.TargetDimensions;
+
+ ///
+ protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
+ {
+ // We will always be creating the clone even for mutate because we may need to resize the canvas
+ IEnumerable> frames =
+ source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.Metadata.DeepClone()));
+
+ // Use the overload to prevent an extra frame being added
+ return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames);
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
+ {
+ Matrix4x4 transformMatrix = this.definition.TransformMatrix;
+
+ // Handle tranforms that result in output identical to the original.
+ if (transformMatrix.Equals(default) || transformMatrix.Equals(Matrix4x4.Identity))
+ {
+ // The clone will be blank here copy all the pixel data over
+ source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
+ return;
+ }
+
+ int width = this.TargetDimensions.Width;
+ var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
+
+ // Convert from screen to world space.
+ Matrix4x4.Invert(transformMatrix, out Matrix4x4 matrix);
+
+ IResampler sampler = this.definition.Sampler;
+
+ if (sampler is NearestNeighborResampler)
+ {
+ ParallelHelper.IterateRows(
+ targetBounds,
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span destRow = destination.GetPixelRowSpan(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
+ int px = (int)MathF.Round(point.X);
+ int py = (int)MathF.Round(point.Y);
+
+ if (sourceRectangle.Contains(px, py))
+ {
+ destRow[x] = source[px, py];
+ }
+ }
+ }
+ });
+
+ return;
+ }
+
+ var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), sampler);
+ try
+ {
+ ParallelHelper.IterateRowsWithTempBuffer(
+ targetBounds,
+ configuration,
+ (rows, vectorBuffer) =>
+ {
+ Span vectorSpan = vectorBuffer.Span;
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = destination.GetPixelRowSpan(y);
+ PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan);
+ ref float ySpanRef = ref kernel.GetYStartReference(y);
+ ref float xSpanRef = ref kernel.GetXStartReference(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ // Use the single precision position to calculate correct bounding pixels
+ // otherwise we get rogue pixels outside of the bounds.
+ Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
+ kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan);
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
+ }
+ });
+ }
+ finally
+ {
+ kernel.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
index 57902a5e6..ef0671d20 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
@@ -1,26 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
using System.Numerics;
-using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Metadata.Profiles.Exif;
-using SixLabors.ImageSharp.ParallelUtils;
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Provides methods that allow the rotating of images.
+ /// Defines a rotation applicable to an .
///
- /// The pixel format.
- internal class RotateProcessor : AffineTransformProcessor
- where TPixel : struct, IPixel
+ public sealed class RotateProcessor : AffineTransformProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The angle of rotation in degrees.
/// The source image size
@@ -30,16 +23,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The angle of rotation in degrees.
/// The sampler to perform the rotating operation.
/// The source image size
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
: this(
- TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize),
- sampler,
- sourceSize)
+ TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize),
+ sampler,
+ sourceSize)
=> this.Degrees = degrees;
// Helper constructor
@@ -53,190 +46,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
public float Degrees { get; }
- ///
- protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
+ ///
+ public override IImageProcessor CreatePixelSpecificProcessor()
{
- if (this.OptimizedApply(source, destination, configuration))
- {
- return;
- }
-
- base.OnFrameApply(source, destination, sourceRectangle, configuration);
- }
-
- ///
- protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle)
- {
- ExifProfile profile = destination.Metadata.ExifProfile;
- if (profile is null)
- {
- return;
- }
-
- if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon)
- {
- // No need to do anything so return.
- return;
- }
-
- profile.RemoveValue(ExifTag.Orientation);
-
- base.AfterImageApply(source, destination, sourceRectangle);
- }
-
- ///
- /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range
- ///
- /// The angle of rotation in degrees.
- /// The
- private static float WrapDegrees(float degrees)
- {
- degrees %= 360;
-
- while (degrees < 0)
- {
- degrees += 360;
- }
-
- return degrees;
- }
-
- ///
- /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
- ///
- /// The source image.
- /// The destination image.
- /// The configuration.
- ///
- /// The
- ///
- private bool OptimizedApply(ImageFrame source, ImageFrame destination, Configuration configuration)
- {
- // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible.
- float degrees = WrapDegrees(this.Degrees);
-
- if (MathF.Abs(degrees) < Constants.Epsilon)
- {
- // The destination will be blank here so copy all the pixel data over
- source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
- return true;
- }
-
- if (MathF.Abs(degrees - 90) < Constants.Epsilon)
- {
- this.Rotate90(source, destination, configuration);
- return true;
- }
-
- if (MathF.Abs(degrees - 180) < Constants.Epsilon)
- {
- this.Rotate180(source, destination, configuration);
- return true;
- }
-
- if (MathF.Abs(degrees - 270) < Constants.Epsilon)
- {
- this.Rotate270(source, destination, configuration);
- return true;
- }
-
- return false;
- }
-
- ///
- /// Rotates the image 270 degrees clockwise at the centre point.
- ///
- /// The source image.
- /// The destination image.
- /// The configuration.
- private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration)
- {
- int width = source.Width;
- int height = source.Height;
- Rectangle destinationBounds = destination.Bounds();
-
- ParallelHelper.IterateRows(
- source.Bounds(),
- configuration,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- for (int x = 0; x < width; x++)
- {
- int newX = height - y - 1;
- newX = height - newX - 1;
- int newY = width - x - 1;
-
- if (destinationBounds.Contains(newX, newY))
- {
- destination[newX, newY] = sourceRow[x];
- }
- }
- }
- });
- }
-
- ///
- /// Rotates the image 180 degrees clockwise at the centre point.
- ///
- /// The source image.
- /// The destination image.
- /// The configuration.
- private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration)
- {
- int width = source.Width;
- int height = source.Height;
-
- ParallelHelper.IterateRows(
- source.Bounds(),
- configuration,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- Span targetRow = destination.GetPixelRowSpan(height - y - 1);
-
- for (int x = 0; x < width; x++)
- {
- targetRow[width - x - 1] = sourceRow[x];
- }
- }
- });
- }
-
- ///
- /// Rotates the image 90 degrees clockwise at the centre point.
- ///
- /// The source image.
- /// The destination image.
- /// The configuration.
- private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration)
- {
- int width = source.Width;
- int height = source.Height;
- Rectangle destinationBounds = destination.Bounds();
-
- ParallelHelper.IterateRows(
- source.Bounds(),
- configuration,
- rows =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- int newX = height - y - 1;
- for (int x = 0; x < width; x++)
- {
- if (destinationBounds.Contains(newX, x))
- {
- destination[newX, x] = sourceRow[x];
- }
- }
- }
- });
+ return new RotateProcessor(this);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs
new file mode 100644
index 000000000..aac6f6514
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs
@@ -0,0 +1,215 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Provides methods that allow the rotating of images.
+ ///
+ /// The pixel format.
+ internal class RotateProcessor : AffineTransformProcessor
+ where TPixel : struct, IPixel
+ {
+ public RotateProcessor(RotateProcessor definition)
+ : base(definition)
+ {
+ this.Degrees = definition.Degrees;
+ }
+
+ private float Degrees { get; }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
+ {
+ if (this.OptimizedApply(source, destination, configuration))
+ {
+ return;
+ }
+
+ base.OnFrameApply(source, destination, sourceRectangle, configuration);
+ }
+
+ ///
+ protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle)
+ {
+ ExifProfile profile = destination.Metadata.ExifProfile;
+ if (profile is null)
+ {
+ return;
+ }
+
+ if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon)
+ {
+ // No need to do anything so return.
+ return;
+ }
+
+ profile.RemoveValue(ExifTag.Orientation);
+
+ base.AfterImageApply(source, destination, sourceRectangle);
+ }
+
+ ///
+ /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range
+ ///
+ /// The angle of rotation in degrees.
+ /// The .
+ private static float WrapDegrees(float degrees)
+ {
+ degrees %= 360;
+
+ while (degrees < 0)
+ {
+ degrees += 360;
+ }
+
+ return degrees;
+ }
+
+ ///
+ /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees.
+ ///
+ /// The source image.
+ /// The destination image.
+ /// The configuration.
+ ///
+ /// The
+ ///
+ private bool OptimizedApply(ImageFrame source, ImageFrame destination, Configuration configuration)
+ {
+ // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible.
+ float degrees = WrapDegrees(this.Degrees);
+
+ if (MathF.Abs(degrees) < Constants.Epsilon)
+ {
+ // The destination will be blank here so copy all the pixel data over
+ source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
+ return true;
+ }
+
+ if (MathF.Abs(degrees - 90) < Constants.Epsilon)
+ {
+ this.Rotate90(source, destination, configuration);
+ return true;
+ }
+
+ if (MathF.Abs(degrees - 180) < Constants.Epsilon)
+ {
+ this.Rotate180(source, destination, configuration);
+ return true;
+ }
+
+ if (MathF.Abs(degrees - 270) < Constants.Epsilon)
+ {
+ this.Rotate270(source, destination, configuration);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Rotates the image 270 degrees clockwise at the centre point.
+ ///
+ /// The source image.
+ /// The destination image.
+ /// The configuration.
+ private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration)
+ {
+ int width = source.Width;
+ int height = source.Height;
+ Rectangle destinationBounds = destination.Bounds();
+
+ ParallelHelper.IterateRows(
+ source.Bounds(),
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = source.GetPixelRowSpan(y);
+ for (int x = 0; x < width; x++)
+ {
+ int newX = height - y - 1;
+ newX = height - newX - 1;
+ int newY = width - x - 1;
+
+ if (destinationBounds.Contains(newX, newY))
+ {
+ destination[newX, newY] = sourceRow[x];
+ }
+ }
+ }
+ });
+ }
+
+ ///
+ /// Rotates the image 180 degrees clockwise at the centre point.
+ ///
+ /// The source image.
+ /// The destination image.
+ /// The configuration.
+ private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration)
+ {
+ int width = source.Width;
+ int height = source.Height;
+
+ ParallelHelper.IterateRows(
+ source.Bounds(),
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = source.GetPixelRowSpan(y);
+ Span targetRow = destination.GetPixelRowSpan(height - y - 1);
+
+ for (int x = 0; x < width; x++)
+ {
+ targetRow[width - x - 1] = sourceRow[x];
+ }
+ }
+ });
+ }
+
+ ///
+ /// Rotates the image 90 degrees clockwise at the centre point.
+ ///
+ /// The source image.
+ /// The destination image.
+ /// The configuration.
+ private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration)
+ {
+ int width = source.Width;
+ int height = source.Height;
+ Rectangle destinationBounds = destination.Bounds();
+
+ ParallelHelper.IterateRows(
+ source.Bounds(),
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span sourceRow = source.GetPixelRowSpan(y);
+ int newX = height - y - 1;
+ for (int x = 0; x < width; x++)
+ {
+ if (destinationBounds.Contains(newX, x))
+ {
+ destination[newX, x] = sourceRow[x];
+ }
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
index c7b1d7410..35c3717ee 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
@@ -9,14 +9,12 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Provides methods that allow the skewing of images.
+ /// Defines a skew transformation applicable to an .
///
- /// The pixel format.
- internal class SkewProcessor : AffineTransformProcessor
- where TPixel : struct, IPixel
+ internal class SkewProcessor : AffineTransformProcessor
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The angle in degrees to perform the skew along the x-axis.
/// The angle in degrees to perform the skew along the y-axis.
@@ -27,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The angle in degrees to perform the skew along the x-axis.
/// The angle in degrees to perform the skew along the y-axis.
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs
index 4973b90f4..286ada2e5 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs
@@ -16,6 +16,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle)
- => TransformProcessorHelpers.UpdateDimensionalMetData(destination);
+ => TransformProcessorHelpers.UpdateDimensionalMetadata(destination);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
index 7bb666bce..00c1227a6 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
@@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// The pixel format.
/// The image to update
- public static void UpdateDimensionalMetData(Image image)
+ public static void UpdateDimensionalMetadata(Image image)
where TPixel : struct, IPixel
{
ExifProfile profile = image.Metadata.ExifProfile;
diff --git a/src/ImageSharp/Processing/RotateExtensions.cs b/src/ImageSharp/Processing/RotateExtensions.cs
index 398a634d1..cb637a1b8 100644
--- a/src/ImageSharp/Processing/RotateExtensions.cs
+++ b/src/ImageSharp/Processing/RotateExtensions.cs
@@ -1,48 +1,44 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Processing
{
///
- /// Adds extensions that allow the application of rotate operations to the type.
+ /// Adds extensions that allow the application of rotate operations to the type.
///
public static class RotateExtensions
{
///
/// Rotates and flips an image by the given instructions.
///
- /// The pixel format.
/// The image to rotate.
/// The to perform the rotation.
/// The
- public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode)
- where TPixel : struct, IPixel
- => Rotate(source, (float)rotateMode);
+ public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) =>
+ Rotate(source, (float)rotateMode);
///
/// Rotates an image by the given angle in degrees.
///
- /// The pixel format.
/// The image to rotate.
/// The angle in degrees to perform the rotation.
/// The
- public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees)
- where TPixel : struct, IPixel
- => Rotate(source, degrees, KnownResamplers.Bicubic);
+ public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) =>
+ Rotate(source, degrees, KnownResamplers.Bicubic);
///