diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
new file mode 100644
index 0000000000..4d70fba84f
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors
+{
+ ///
+ /// Provides the base methods to perform affine transforms on an image.
+ ///
+ /// The pixel format.
+ internal abstract class AffineProcessor : ResamplingWeightedProcessor
+ where TPixel : struct, IPixel
+ {
+ // TODO: Move to constants somewhere else to prevent generic type duplication.
+ private static readonly Rectangle DefaultRectangle = new Rectangle(0, 0, 1, 1);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The sampler to perform the resize operation.
+ protected AffineProcessor(IResampler sampler)
+ : base(sampler, 1, 1, DefaultRectangle) // Hack to prevent Guard throwing in base, we always set the canvas
+ {
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
+ ///
+ public bool Expand { get; set; } = true;
+
+ ///
+ /// Returns the processing matrix used for transforming the image.
+ ///
+ /// The
+ protected abstract Matrix3x2 CreateProcessingMatrix();
+
+ ///
+ /// Creates a new target canvas to contain the results of the matrix transform.
+ ///
+ /// The source rectangle.
+ protected virtual void CreateNewCanvas(Rectangle sourceRectangle)
+ {
+ if (this.ResizeRectangle == DefaultRectangle)
+ {
+ if (this.Expand)
+ {
+ this.ResizeRectangle = Matrix3x2.Invert(this.CreateProcessingMatrix(), out Matrix3x2 sizeMatrix)
+ ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix)
+ : sourceRectangle;
+ }
+ else
+ {
+ this.ResizeRectangle = sourceRectangle;
+ }
+ }
+
+ this.Width = this.ResizeRectangle.Width;
+ this.Height = this.ResizeRectangle.Height;
+ }
+
+ ///
+ protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
+ {
+ this.CreateNewCanvas(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(this.Width, this.Height, x.MetaData.Clone()));
+
+ // Use the overload to prevent an extra frame being added
+ return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames);
+ }
+
+ ///
+ /// Gets a transform matrix adjusted to center upon the target image bounds.
+ ///
+ /// The source image.
+ /// The transform matrix.
+ ///
+ /// The .
+ ///
+ protected Matrix3x2 GetCenteredMatrix(ImageFrame source, Matrix3x2 matrix)
+ {
+ var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.ResizeRectangle.Width * .5F, -this.ResizeRectangle.Height * .5F);
+ var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F);
+ return (translationToTargetCenter * matrix) * translateToSourceCenter;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs
deleted file mode 100644
index 4a15254ab2..0000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System.Numerics;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Primitives;
-
-namespace SixLabors.ImageSharp.Processing.Processors
-{
- ///
- /// Provides methods to transform an image using a .
- ///
- /// The pixel format.
- internal abstract class Matrix3x2Processor : ImageProcessor
- where TPixel : struct, IPixel
- {
- ///
- /// Gets the rectangle designating the target canvas.
- ///
- protected Rectangle CanvasRectangle { get; private set; }
-
- ///
- /// Creates a new target canvas to contain the results of the matrix transform.
- ///
- /// The source rectangle.
- /// The processing matrix.
- protected void CreateNewCanvas(Rectangle sourceRectangle, Matrix3x2 processMatrix)
- {
- Matrix3x2 sizeMatrix;
- this.CanvasRectangle = Matrix3x2.Invert(processMatrix, out sizeMatrix)
- ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix)
- : sourceRectangle;
- }
-
- ///
- /// Gets a transform matrix adjusted to center upon the target image bounds.
- ///
- /// The source image.
- /// The transform matrix.
- ///
- /// The .
- ///
- protected Matrix3x2 GetCenteredMatrix(ImageFrame source, Matrix3x2 matrix)
- {
- var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F);
- var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F);
- return (translationToTargetCenter * matrix) * translateToSourceCenter;
- }
- }
-}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
index 86a0c73603..f47d483596 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
@@ -6,7 +6,6 @@ using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Helpers;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@@ -17,46 +16,84 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Provides methods that allow the rotating of images.
///
/// The pixel format.
- internal class RotateProcessor : Matrix3x2Processor
+ internal class RotateProcessor : AffineProcessor
where TPixel : struct, IPixel
{
+ private Matrix3x2 transformMatrix;
+
///
- /// The transform matrix to apply.
+ /// Initializes a new instance of the class.
///
- private Matrix3x2 processMatrix;
+ public RotateProcessor()
+ : base(new BicubicResampler())
+ {
+ }
///
- /// Gets or sets the angle of processMatrix in degrees.
+ /// Initializes a new instance of the class.
///
- public float Angle { get; set; }
+ /// The sampler to perform the resize operation.
+ public RotateProcessor(IResampler sampler)
+ : base(sampler)
+ {
+ }
///
- /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
+ /// Gets or sets the angle of processMatrix in degrees.
///
- public bool Expand { get; set; } = true;
+ public float Angle { get; set; }
///
- protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ protected override Matrix3x2 CreateProcessingMatrix()
{
- if (this.OptimizedApply(source, configuration))
+ if (this.transformMatrix == default(Matrix3x2))
+ {
+ this.transformMatrix = Matrix3x2Extensions.CreateRotationDegrees(-this.Angle, new Point(0, 0));
+ }
+
+ return this.transformMatrix;
+ }
+
+ ///
+ protected override void CreateNewCanvas(Rectangle sourceRectangle)
+ {
+ if (MathF.Abs(this.Angle) < Constants.Epsilon ||
+ MathF.Abs(this.Angle - 180) < Constants.Epsilon)
+ {
+ this.ResizeRectangle = sourceRectangle;
+ }
+
+ if (MathF.Abs(this.Angle - 90) < Constants.Epsilon ||
+ MathF.Abs(this.Angle - 270) < Constants.Epsilon)
+ {
+ // We always expand enumerated rectangle values
+ this.ResizeRectangle = new Rectangle(0, 0, sourceRectangle.Height, sourceRectangle.Width);
+ }
+
+ base.CreateNewCanvas(sourceRectangle);
+ }
+
+ ///
+ protected override void OnApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
+ {
+ if (this.OptimizedApply(source, destination, configuration))
{
return;
}
- int height = this.CanvasRectangle.Height;
- int width = this.CanvasRectangle.Width;
- Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix);
+ int height = this.ResizeRectangle.Height;
+ int width = this.ResizeRectangle.Width;
+ Matrix3x2 matrix = this.GetCenteredMatrix(source, this.CreateProcessingMatrix());
Rectangle sourceBounds = source.Bounds();
- using (var targetPixels = new PixelAccessor(width, height))
- {
- Parallel.For(
- 0,
- height,
- configuration.ParallelOptions,
- y =>
+ // TODO: Use our new weights functionality to resample on transform
+ Parallel.For(
+ 0,
+ height,
+ configuration.ParallelOptions,
+ y =>
{
- Span targetRow = targetPixels.GetRowSpan(y);
+ Span destRow = destination.GetPixelRowSpan(y);
for (int x = 0; x < width; x++)
{
@@ -64,34 +101,16 @@ namespace SixLabors.ImageSharp.Processing.Processors
if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y))
{
- targetRow[x] = source[transformedPoint.X, transformedPoint.Y];
+ destRow[x] = source[transformedPoint.X, transformedPoint.Y];
}
}
});
-
- source.SwapPixelsBuffers(targetPixels);
- }
- }
-
- ///
- protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- if (MathF.Abs(this.Angle) < Constants.Epsilon || MathF.Abs(this.Angle - 90) < Constants.Epsilon || MathF.Abs(this.Angle - 180) < Constants.Epsilon || MathF.Abs(this.Angle - 270) < Constants.Epsilon)
- {
- return;
- }
-
- this.processMatrix = Matrix3x2Extensions.CreateRotationDegrees(-this.Angle, new Point(0, 0));
- if (this.Expand)
- {
- this.CreateNewCanvas(sourceRectangle, this.processMatrix);
- }
}
///
- protected override void AfterImageApply(Image source, Rectangle sourceRectangle)
+ protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle)
{
- ExifProfile profile = source.MetaData.ExifProfile;
+ ExifProfile profile = destination.MetaData.ExifProfile;
if (profile == null)
{
return;
@@ -116,33 +135,35 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// 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, Configuration configuration)
+ private bool OptimizedApply(ImageFrame source, ImageFrame destination, Configuration configuration)
{
if (MathF.Abs(this.Angle) < Constants.Epsilon)
{
- // No need to do anything so return.
+ // The destination will be blank here so copy all the pixel data over
+ source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return true;
}
if (MathF.Abs(this.Angle - 90) < Constants.Epsilon)
{
- this.Rotate90(source, configuration);
+ this.Rotate90(source, destination, configuration);
return true;
}
if (MathF.Abs(this.Angle - 180) < Constants.Epsilon)
{
- this.Rotate180(source, configuration);
+ this.Rotate180(source, destination, configuration);
return true;
}
if (MathF.Abs(this.Angle - 270) < Constants.Epsilon)
{
- this.Rotate270(source, configuration);
+ this.Rotate270(source, destination, configuration);
return true;
}
@@ -153,95 +174,82 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Rotates the image 270 degrees clockwise at the centre point.
///
/// The source image.
+ /// The destination image.
/// The configuration.
- private void Rotate270(ImageFrame source, Configuration configuration)
+ private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration)
{
int width = source.Width;
int height = source.Height;
- using (var targetPixels = new PixelAccessor(height, width))
- {
- using (PixelAccessor sourcePixels = source.Lock())
+ Parallel.For(
+ 0,
+ height,
+ configuration.ParallelOptions,
+ y =>
{
- Parallel.For(
- 0,
- height,
- configuration.ParallelOptions,
- y =>
- {
- for (int x = 0; x < width; x++)
- {
- int newX = height - y - 1;
- newX = height - newX - 1;
- int newY = width - x - 1;
- targetPixels[newX, newY] = sourcePixels[x, 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;
- source.SwapPixelsBuffers(targetPixels);
- }
+ 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, Configuration configuration)
+ private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration)
{
int width = source.Width;
int height = source.Height;
- using (var targetPixels = new PixelAccessor(width, height))
- {
- Parallel.For(
- 0,
- height,
- configuration.ParallelOptions,
- y =>
- {
- Span sourceRow = source.GetPixelRowSpan(y);
- Span targetRow = targetPixels.GetRowSpan(height - y - 1);
-
- for (int x = 0; x < width; x++)
- {
- targetRow[width - x - 1] = sourceRow[x];
- }
- });
+ Parallel.For(
+ 0,
+ height,
+ configuration.ParallelOptions,
+ y =>
+ {
+ Span sourceRow = source.GetPixelRowSpan(y);
+ Span targetRow = destination.GetPixelRowSpan(height - y - 1);
- source.SwapPixelsBuffers(targetPixels);
- }
+ 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, Configuration configuration)
+ private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration)
{
int width = source.Width;
int height = source.Height;
- using (var targetPixels = new PixelAccessor(height, width))
- {
- Parallel.For(
- 0,
- height,
- configuration.ParallelOptions,
- y =>
+ Parallel.For(
+ 0,
+ height,
+ configuration.ParallelOptions,
+ y =>
+ {
+ Span sourceRow = source.GetPixelRowSpan(y);
+ int newX = height - y - 1;
+ for (int x = 0; x < width; x++)
{
- Span sourceRow = source.GetPixelRowSpan(y);
- int newX = height - y - 1;
- for (int x = 0; x < width; x++)
- {
- targetPixels[newX, x] = sourceRow[x];
- }
- });
-
- source.SwapPixelsBuffers(targetPixels);
- }
+ 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 316e2a2af3..ba84eab9e8 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
@@ -6,7 +6,6 @@ using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Helpers;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@@ -16,13 +15,19 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Provides methods that allow the skewing of images.
///
/// The pixel format.
- internal class SkewProcessor : Matrix3x2Processor
+ internal class SkewProcessor : AffineProcessor
where TPixel : struct, IPixel
{
+ private Matrix3x2 transformMatrix;
+
///
- /// The transform matrix to apply.
+ /// Initializes a new instance of the class.
///
- private Matrix3x2 processMatrix;
+ /// The sampler to perform the skew operation.
+ public SkewProcessor(IResampler sampler)
+ : base(sampler)
+ {
+ }
///
/// Gets or sets the angle of rotation along the x-axis in degrees.
@@ -34,52 +39,44 @@ namespace SixLabors.ImageSharp.Processing.Processors
///
public float AngleY { get; set; }
- ///
- /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
- ///
- public bool Expand { get; set; } = true;
+ ///
+ protected override Matrix3x2 CreateProcessingMatrix()
+ {
+ if (this.transformMatrix == default(Matrix3x2))
+ {
+ this.transformMatrix = Matrix3x2Extensions.CreateSkewDegrees(-this.AngleX, -this.AngleY, new Point(0, 0));
+ }
+
+ return this.transformMatrix;
+ }
///
- protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ protected override void OnApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
{
- int height = this.CanvasRectangle.Height;
- int width = this.CanvasRectangle.Width;
- Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix);
+ int height = this.ResizeRectangle.Height;
+ int width = this.ResizeRectangle.Width;
+ Matrix3x2 matrix = this.GetCenteredMatrix(source, this.CreateProcessingMatrix());
Rectangle sourceBounds = source.Bounds();
- using (var targetPixels = new PixelAccessor(width, height))
- {
- Parallel.For(
- 0,
- height,
- configuration.ParallelOptions,
- y =>
+ // TODO: Use our new weights functionality to resample on transform
+ Parallel.For(
+ 0,
+ height,
+ configuration.ParallelOptions,
+ y =>
+ {
+ Span destRow = destination.GetPixelRowSpan(y);
+
+ for (int x = 0; x < width; x++)
{
- Span targetRow = targetPixels.GetRowSpan(y);
+ var transformedPoint = Point.Skew(new Point(x, y), matrix);
- for (int x = 0; x < width; x++)
+ if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y))
{
- var transformedPoint = Point.Skew(new Point(x, y), matrix);
-
- if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y))
- {
- targetRow[x] = source[transformedPoint.X, transformedPoint.Y];
- }
+ destRow[x] = source[transformedPoint.X, transformedPoint.Y];
}
- });
-
- source.SwapPixelsBuffers(targetPixels);
- }
- }
-
- ///
- protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- this.processMatrix = Matrix3x2Extensions.CreateSkewDegrees(-this.AngleX, -this.AngleY, new Point(0, 0));
- if (this.Expand)
- {
- this.CreateNewCanvas(sourceRectangle, this.processMatrix);
- }
+ }
+ });
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Transforms/Rotate.cs b/src/ImageSharp/Processing/Transforms/Rotate.cs
index 7b35a879bc..436aa3cd98 100644
--- a/src/ImageSharp/Processing/Transforms/Rotate.cs
+++ b/src/ImageSharp/Processing/Transforms/Rotate.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
@@ -47,6 +46,6 @@ namespace SixLabors.ImageSharp
/// The
public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees, bool expand)
where TPixel : struct, IPixel
- => source.ApplyProcessor(new RotateProcessor { Angle = degrees, Expand = expand });
+ => source.ApplyProcessor(new RotateProcessor(new BicubicResampler()) { Angle = degrees, Expand = expand });
}
}
diff --git a/src/ImageSharp/Processing/Transforms/Skew.cs b/src/ImageSharp/Processing/Transforms/Skew.cs
index 00411946d9..873dbe5100 100644
--- a/src/ImageSharp/Processing/Transforms/Skew.cs
+++ b/src/ImageSharp/Processing/Transforms/Skew.cs
@@ -1,8 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
namespace SixLabors.ImageSharp
@@ -37,6 +37,6 @@ namespace SixLabors.ImageSharp
/// The
public static IImageProcessingContext Skew(this IImageProcessingContext source, float degreesX, float degreesY, bool expand)
where TPixel : struct, IPixel
- => source.ApplyProcessor(new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand });
+ => source.ApplyProcessor(new SkewProcessor(new BicubicResampler()) { AngleX = degreesX, AngleY = degreesY, Expand = expand });
}
}