diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
index e12b91eab..edddab181 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
@@ -5,13 +5,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
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
@@ -20,29 +16,42 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Provides the base methods to perform affine transforms on an image.
///
/// The pixel format.
- internal class AffineTransformProcessor : InterpolatedTransformProcessorBase
+ internal class AffineTransformProcessor : TransformProcessorBase
where TPixel : struct, IPixel
{
+ private readonly Rectangle transformedRectangle;
+
///
/// Initializes a new instance of the class.
///
/// The transform matrix
/// The sampler to perform the transform operation.
- /// The target dimensions to constrain the transformed image to.
- public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions)
- : base(sampler)
+ /// The source image size
+ public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size sourceSize)
{
+ Guard.NotNull(sampler, nameof(sampler));
+ this.Sampler = sampler;
this.TransformMatrix = matrix;
- this.TargetDimensions = targetDimensions;
+ this.transformedRectangle = TransformUtils.GetTransformedRectangle(
+ new Rectangle(Point.Empty, sourceSize),
+ matrix);
+
+ // We want to resize the canvas here taking into account any translations.
+ this.TargetDimensions = new Size(this.transformedRectangle.Right, this.transformedRectangle.Bottom);
}
///
- /// Gets the matrix used to supply the affine transform
+ /// Gets the sampler to perform interpolation of the transform operation.
+ ///
+ public IResampler Sampler { get; }
+
+ ///
+ /// Gets the matrix used to supply the affine transform.
///
public Matrix3x2 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; }
@@ -68,13 +77,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int width = this.TargetDimensions.Width;
Rectangle sourceBounds = source.Bounds();
- var targetBounds = new Rectangle(0, 0, width, height);
-
- // Since could potentially be resizing the canvas we might need to re-calculate the matrix
- Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds);
+ var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
// Convert from screen to world space.
- Matrix3x2.Invert(matrix, out matrix);
+ Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 matrix);
if (this.Sampler is NearestNeighborResampler)
{
@@ -82,158 +88,52 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
targetBounds,
configuration,
rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
{
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span destRow = destination.GetPixelRowSpan(y);
+ Span destRow = destination.GetPixelRowSpan(y);
- for (int x = 0; x < width; x++)
+ for (int x = 0; x < width; x++)
+ {
+ var point = Point.Transform(new Point(x, y), matrix);
+ if (sourceBounds.Contains(point.X, point.Y))
{
- var point = Point.Transform(new Point(x, y), matrix);
- if (sourceBounds.Contains(point.X, point.Y))
- {
- destRow[x] = source[point.X, point.Y];
- }
+ destRow[x] = source[point.X, point.Y];
}
}
- });
+ }
+ });
return;
}
- int maxSourceX = source.Width - 1;
- int maxSourceY = source.Height - 1;
- (float radius, float scale, float ratio) xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width);
- (float radius, float scale, float ratio) yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height);
- float xScale = xRadiusScale.scale;
- float yScale = yRadiusScale.scale;
- var radius = new Vector2(xRadiusScale.radius, yRadiusScale.radius);
- IResampler sampler = this.Sampler;
- var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY);
- int xLength = (int)MathF.Ceiling((radius.X * 2) + 2);
- int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2);
-
- MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
-
- using (Buffer2D yBuffer = memoryAllocator.Allocate2D(yLength, height))
- using (Buffer2D xBuffer = memoryAllocator.Allocate2D(xLength, height))
+ using (var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler))
{
- ParallelHelper.IterateRows(
+ ParallelHelper.IterateRowsWithTempBuffer(
targetBounds,
configuration,
- rows =>
+ (rows, vectorBuffer) =>
+ {
+ Span vectorSpan = vectorBuffer.Span;
+ for (int y = rows.Min; y < rows.Max; y++)
{
- for (int y = rows.Min; y < rows.Max; y++)
- {
- ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
- ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y));
- ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(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);
-
- // Clamp sampling pixel radial extents to the source image edges
- Vector2 maxXY = point + radius;
- Vector2 minXY = point - radius;
-
- // max, maxY, minX, minY
- var extents = new Vector4(
- MathF.Floor(maxXY.X + .5F),
- MathF.Floor(maxXY.Y + .5F),
- MathF.Ceiling(minXY.X - .5F),
- MathF.Ceiling(minXY.Y - .5F));
-
- int right = (int)extents.X;
- int bottom = (int)extents.Y;
- int left = (int)extents.Z;
- int top = (int)extents.W;
-
- extents = Vector4.Clamp(extents, Vector4.Zero, maxSource);
-
- int maxX = (int)extents.X;
- int maxY = (int)extents.Y;
- int minX = (int)extents.Z;
- int minY = (int)extents.W;
-
- if (minX == maxX || minY == maxY)
- {
- continue;
- }
-
- // It appears these have to be calculated on-the-fly.
- // Precalculating transformed weights would require prior knowledge of every transformed pixel location
- // since they can be at sub-pixel positions on both axis.
- // I've optimized where I can but am always open to suggestions.
- if (yScale > 1 && xScale > 1)
- {
- CalculateWeightsDown(
- top,
- bottom,
- minY,
- maxY,
- point.Y,
- sampler,
- yScale,
- ref ySpanRef,
- yLength);
-
- CalculateWeightsDown(
- left,
- right,
- minX,
- maxX,
- point.X,
- sampler,
- xScale,
- ref xSpanRef,
- xLength);
- }
- else
- {
- CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef);
- CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef);
- }
-
- // Now multiply the results against the offsets
- Vector4 sum = Vector4.Zero;
- for (int yy = 0, j = minY; j <= maxY; j++, yy++)
- {
- float yWeight = Unsafe.Add(ref ySpanRef, yy);
-
- for (int xx = 0, i = minX; i <= maxX; i++, xx++)
- {
- float xWeight = Unsafe.Add(ref xSpanRef, xx);
-
- // Values are first premultiplied to prevent darkening of edge pixels
- var current = source[i, j].ToVector4();
- Vector4Utils.Premultiply(ref current);
- sum += current * xWeight * yWeight;
- }
- }
-
- ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
-
- // Reverse the premultiplication
- Vector4Utils.UnPremultiply(ref sum);
- dest.FromVector4(sum);
- }
+ 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.FromVector4(configuration, vectorSpan, targetRowSpan);
+ }
+ });
}
}
-
- ///
- /// Gets a transform matrix adjusted for final processing based upon the target image bounds.
- ///
- /// The source image bounds.
- /// The destination image bounds.
- ///
- /// The .
- ///
- protected virtual Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
- => this.TransformMatrix;
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessorOld.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessorOld.cs
new file mode 100644
index 000000000..5891afd9a
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessorOld.cs
@@ -0,0 +1,239 @@
+// 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 System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+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 the base methods to perform affine transforms on an image.
+ ///
+ /// The pixel format.
+ internal class AffineTransformProcessorOld : InterpolatedTransformProcessorBase
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The transform matrix
+ /// The sampler to perform the transform operation.
+ /// The target dimensions to constrain the transformed image to.
+ public AffineTransformProcessorOld(Matrix3x2 matrix, IResampler sampler, Size targetDimensions)
+ : base(sampler)
+ {
+ this.TransformMatrix = matrix;
+ this.TargetDimensions = targetDimensions;
+ }
+
+ ///
+ /// Gets the matrix used to supply the affine transform
+ ///
+ public Matrix3x2 TransformMatrix { get; }
+
+ ///
+ /// Gets the target dimensions to constrain the transformed image to
+ ///
+ public Size TargetDimensions { 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)
+ {
+ int height = this.TargetDimensions.Height;
+ int width = this.TargetDimensions.Width;
+
+ Rectangle sourceBounds = source.Bounds();
+ var targetBounds = new Rectangle(0, 0, width, height);
+
+ // Since could potentially be resizing the canvas we might need to re-calculate the matrix
+ Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds);
+
+ // Convert from screen to world space.
+ Matrix3x2.Invert(matrix, out 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 (sourceBounds.Contains(point.X, point.Y))
+ {
+ destRow[x] = source[point.X, point.Y];
+ }
+ }
+ }
+ });
+
+ return;
+ }
+
+ int maxSourceX = source.Width - 1;
+ int maxSourceY = source.Height - 1;
+ (float radius, float scale, float ratio) xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width);
+ (float radius, float scale, float ratio) yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height);
+ float xScale = xRadiusScale.scale;
+ float yScale = yRadiusScale.scale;
+ var radius = new Vector2(xRadiusScale.radius, yRadiusScale.radius);
+ IResampler sampler = this.Sampler;
+ var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY);
+ int xLength = (int)MathF.Ceiling((radius.X * 2) + 2);
+ int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2);
+
+ MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
+
+ using (Buffer2D yBuffer = memoryAllocator.Allocate2D(yLength, height))
+ using (Buffer2D xBuffer = memoryAllocator.Allocate2D(xLength, height))
+ {
+ ParallelHelper.IterateRows(
+ targetBounds,
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
+ ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y));
+ ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(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);
+
+ // Clamp sampling pixel radial extents to the source image edges
+ Vector2 maxXY = point + radius;
+ Vector2 minXY = point - radius;
+
+ // max, maxY, minX, minY
+ var extents = new Vector4(
+ MathF.Floor(maxXY.X + .5F),
+ MathF.Floor(maxXY.Y + .5F),
+ MathF.Ceiling(minXY.X - .5F),
+ MathF.Ceiling(minXY.Y - .5F));
+
+ int right = (int)extents.X;
+ int bottom = (int)extents.Y;
+ int left = (int)extents.Z;
+ int top = (int)extents.W;
+
+ extents = Vector4.Clamp(extents, Vector4.Zero, maxSource);
+
+ int maxX = (int)extents.X;
+ int maxY = (int)extents.Y;
+ int minX = (int)extents.Z;
+ int minY = (int)extents.W;
+
+ if (minX == maxX || minY == maxY)
+ {
+ continue;
+ }
+
+ // It appears these have to be calculated on-the-fly.
+ // Precalculating transformed weights would require prior knowledge of every transformed pixel location
+ // since they can be at sub-pixel positions on both axis.
+ // I've optimized where I can but am always open to suggestions.
+ if (yScale > 1 && xScale > 1)
+ {
+ CalculateWeightsDown(
+ top,
+ bottom,
+ minY,
+ maxY,
+ point.Y,
+ sampler,
+ yScale,
+ ref ySpanRef,
+ yLength);
+
+ CalculateWeightsDown(
+ left,
+ right,
+ minX,
+ maxX,
+ point.X,
+ sampler,
+ xScale,
+ ref xSpanRef,
+ xLength);
+ }
+ else
+ {
+ CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef);
+ CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef);
+ }
+
+ // Now multiply the results against the offsets
+ Vector4 sum = Vector4.Zero;
+ for (int yy = 0, j = minY; j <= maxY; j++, yy++)
+ {
+ float yWeight = Unsafe.Add(ref ySpanRef, yy);
+
+ for (int xx = 0, i = minX; i <= maxX; i++, xx++)
+ {
+ float xWeight = Unsafe.Add(ref xSpanRef, xx);
+
+ // Values are first premultiplied to prevent darkening of edge pixels
+ var current = source[i, j].ToVector4();
+ Vector4Utils.Premultiply(ref current);
+ sum += current * xWeight * yWeight;
+ }
+ }
+
+ ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
+
+ // Reverse the premultiplication
+ Vector4Utils.UnPremultiply(ref sum);
+ dest.FromVector4(sum);
+ }
+ }
+ });
+ }
+ }
+
+ ///
+ /// Gets a transform matrix adjusted for final processing based upon the target image bounds.
+ ///
+ /// The source image bounds.
+ /// The destination image bounds.
+ ///
+ /// The .
+ ///
+ protected virtual Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
+ => this.TransformMatrix;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
index adaee1766..82614dc8c 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// A base class that provides methods to allow the automatic centering of affine transforms
///
/// The pixel format.
- internal abstract class CenteredAffineTransformProcessor : AffineTransformProcessor
+ internal abstract class CenteredAffineTransformProcessor : AffineTransformProcessorOld
where TPixel : struct, IPixel
{
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
index 2ad626755..cbf82cc9b 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
@@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@@ -16,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Provides methods that allow the rotating of images.
///
/// The pixel format.
- internal class RotateProcessor : CenteredAffineTransformProcessor
+ internal class RotateProcessor : AffineTransformProcessor
where TPixel : struct, IPixel
{
///
@@ -36,10 +34,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The sampler to perform the rotating operation.
/// The source image size
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
- : base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler, sourceSize)
- {
- this.Degrees = degrees;
- }
+ : base(
+ TransformUtils.CreateCenteredRotationMatrixDegrees(degrees, sourceSize),
+ sampler,
+ sourceSize)
+ => this.Degrees = degrees;
///
/// Gets the angle of rotation in degrees.
@@ -84,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The
private static float WrapDegrees(float degrees)
{
- degrees = degrees % 360;
+ degrees %= 360;
while (degrees < 0)
{
@@ -223,7 +222,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int newX = height - y - 1;
for (int x = 0; x < width; x++)
{
- // TODO: Optimize this:
if (destinationBounds.Contains(newX, x))
{
destination[newX, x] = sourceRow[x];
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
new file mode 100644
index 000000000..5acc7213c
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
@@ -0,0 +1,188 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+// TODO: It would be great if we could somehow optimize this to calculate the weights once.
+// currently we cannot do that as we are calulating the weight of the transformed point dimension
+// not the point in the original image.
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Contains the methods required to calculate kernel sampling weights on-the-fly.
+ ///
+ internal class TransformKernelMap : IDisposable
+ {
+ private readonly Buffer2D yBuffer;
+ private readonly Buffer2D xBuffer;
+ private readonly float yScale;
+ private readonly float xScale;
+ private readonly int yLength;
+ private readonly int xLength;
+ private readonly Vector2 extents;
+ private Vector4 maxSourceExtents;
+ private readonly IResampler sampler;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The source size.
+ /// The destination size.
+ /// The sampler.
+ public TransformKernelMap(Configuration configuration, Size source, Size destination, IResampler sampler)
+ {
+ this.sampler = sampler;
+ (float radius, float scale) yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height);
+ (float radius, float scale) xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width);
+
+ this.yScale = yRadiusScale.scale;
+ this.xScale = xRadiusScale.scale;
+ this.extents = new Vector2(xRadiusScale.radius, yRadiusScale.radius);
+ this.xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2);
+ this.yLength = (int)MathF.Ceiling((this.extents.Y * 2) + 2);
+
+ // We use 2D buffers so that we can access the weight spans in parallel.
+ this.yBuffer = configuration.MemoryAllocator.Allocate2D(this.yLength, destination.Height);
+ this.xBuffer = configuration.MemoryAllocator.Allocate2D(this.xLength, destination.Height);
+
+ int maxX = source.Width - 1;
+ int maxY = source.Height - 1;
+ this.maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY);
+ }
+
+ ///
+ /// Gets a reference to the first item of the y window.
+ ///
+ /// The reference to the first item of the window.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public ref float GetYStartReference(int y)
+ => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y));
+
+ ///
+ /// Gets a reference to the first item of the x window.
+ ///
+ /// The reference to the first item of the window.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public ref float GetXStartReference(int y)
+ => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y));
+
+ public void Convolve(
+ Vector2 transformedPoint,
+ int column,
+ ref float ySpanRef,
+ ref float xSpanRef,
+ Buffer2D sourcePixels,
+ Span targetRow)
+ where TPixel : struct, IPixel
+ {
+ // Clamp sampling pixel radial extents to the source image edges
+ Vector2 minXY = transformedPoint - this.extents;
+ Vector2 maxXY = transformedPoint + this.extents;
+
+ // minX, minY, maxX, maxY
+ var extents = new Vector4(
+ MathF.Ceiling(minXY.X - .5F),
+ MathF.Ceiling(minXY.Y - .5F),
+ MathF.Floor(maxXY.X + .5F),
+ MathF.Floor(maxXY.Y + .5F));
+
+ int left = (int)extents.X;
+ int top = (int)extents.Y;
+ int right = (int)extents.Z;
+ int bottom = (int)extents.W;
+
+ extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents);
+
+ int minX = (int)extents.X;
+ int minY = (int)extents.Y;
+ int maxX = (int)extents.Z;
+ int maxY = (int)extents.W;
+
+ if (minX == maxX || minY == maxY)
+ {
+ return;
+ }
+
+ // TODO: Get Anton to use his superior brain on this one.
+ // It looks to me like we're calculating the same weights over and over again
+ // since min(X+Y) and max(X+Y) are the same distance apart.
+ this.CalculateWeights(minY, maxY, maxY - minY, transformedPoint.Y, ref ySpanRef);
+ this.CalculateWeights(minX, maxX, maxX - minX, transformedPoint.X, ref xSpanRef);
+
+ Vector4 sum = Vector4.Zero;
+ for (int kernelY = 0, y = minY; y <= maxY; y++, kernelY++)
+ {
+ float yWeight = Unsafe.Add(ref ySpanRef, kernelY);
+
+ for (int kernelX = 0, x = minX; x <= maxX; x++, kernelX++)
+ {
+ float xWeight = Unsafe.Add(ref xSpanRef, kernelX);
+
+ // Values are first premultiplied to prevent darkening of edge pixels.
+ var current = sourcePixels[x, y].ToVector4();
+ Vector4Utils.Premultiply(ref current);
+ sum += current * xWeight * yWeight;
+ }
+ }
+
+ // Reverse the premultiplication
+ Vector4Utils.UnPremultiply(ref sum);
+ targetRow[column] = sum;
+ }
+
+ ///
+ /// Calculated the normalized weights for the given point.
+ ///
+ /// The minimum sampling offset
+ /// The maximum sampling offset
+ /// The length of the weights collection
+ /// The transformed point dimension
+ /// The reference to the collection of weights
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void CalculateWeights(int min, int max, int length, float point, ref float weightsRef)
+ {
+ float sum = 0;
+ for (int x = 0, i = min; i <= max; i++, x++)
+ {
+ float weight = this.sampler.GetValue(i - point);
+ sum += weight;
+ Unsafe.Add(ref weightsRef, x) = this.sampler.GetValue(i - point);
+ }
+
+ // TODO: Do we need this? Check what happens when we scale an image down.
+ // if (sum > 0)
+ // {
+ // for (int i = 0; i < length; i++)
+ // {
+ // ref float wRef = ref Unsafe.Add(ref weightsRef, i);
+ // wRef /= sum;
+ // }
+ // }
+ }
+
+ private (float radius, float scale) GetSamplingRadius(int sourceSize, int destinationSize)
+ {
+ float scale = (float)sourceSize / destinationSize;
+
+ if (scale < 1F)
+ {
+ scale = 1F;
+ }
+
+ return (MathF.Ceiling(scale * this.sampler.Radius), scale);
+ }
+
+ public void Dispose()
+ {
+ this.yBuffer?.Dispose();
+ this.xBuffer?.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
new file mode 100644
index 000000000..7d0350249
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Contains utility methods for working with transforms.
+ ///
+ public static class TransformUtils
+ {
+ ///
+ /// Creates a centered rotation matrix using the given rotation in degrees and the source size.
+ ///
+ /// The amount of rotation, in degrees.
+ /// The source image size.
+ /// The .
+ public static Matrix3x2 CreateCenteredRotationMatrixDegrees(float degrees, Size size)
+ => CreateCenteredTransformMatrix(
+ new Rectangle(Point.Empty, size),
+ Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty));
+
+ ///
+ /// Gets the centered transform matrix based upon the source and destination rectangles.
+ ///
+ /// The source image bounds.
+ /// The transformation matrix.
+ /// The
+ public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix)
+ {
+ Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix);
+
+ // We invert the matrix to handle the transformation from screen to world space.
+ // This ensures scaling matrices are correct.
+ Matrix3x2.Invert(matrix, out Matrix3x2 inverted);
+
+ var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F);
+ var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F);
+
+ // Translate back to world space.
+ Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
+
+ return centered;
+ }
+
+ ///
+ /// Gets the centered transform matrix based upon the source and destination rectangles.
+ ///
+ /// The source image bounds.
+ /// The destination image bounds.
+ /// The transformation matrix.
+ /// The
+ public static Matrix3x2 GetCenteredTransformMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle, Matrix3x2 matrix)
+ {
+ // We invert the matrix to handle the transformation from screen to world space.
+ // This ensures scaling matrices are correct.
+ Matrix3x2.Invert(matrix, out Matrix3x2 inverted);
+
+ var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F);
+ var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F);
+
+ // Translate back to world space.
+ Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
+
+ return centered;
+ }
+
+ ///
+ /// Returns the rectangle bounds relative to the source for the given transformation matrix.
+ ///
+ /// The source rectangle.
+ /// The transformation matrix.
+ ///
+ /// The .
+ ///
+ public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix)
+ {
+ Rectangle transformed = GetTransformedRectangle(rectangle, matrix);
+
+ // TODO: Check this.
+ return new Rectangle(0, 0, transformed.Width, transformed.Height);
+ }
+
+ ///
+ /// Returns the rectangle relative to the source for the given transformation matrix.
+ ///
+ /// The source rectangle.
+ /// The transformation matrix.
+ ///
+ /// The .
+ ///
+ public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix3x2 matrix)
+ {
+ if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix))
+ {
+ return rectangle;
+ }
+
+ var tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix);
+ var tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix);
+ var bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix);
+ var br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix);
+
+ return GetBoundingRectangle(tl, tr, bl, br);
+ }
+
+ private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br)
+ {
+ // Find the minimum and maximum "corners" based on the given vectors
+ float left = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X)));
+ float top = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y)));
+ float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X)));
+ float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y)));
+
+ return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom));
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/TransformExtensions.cs b/src/ImageSharp/Processing/TransformExtensions.cs
index 0ec1e295d..3f24969a3 100644
--- a/src/ImageSharp/Processing/TransformExtensions.cs
+++ b/src/ImageSharp/Processing/TransformExtensions.cs
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// The
public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix, IResampler sampler)
where TPixel : struct, IPixel
- => source.ApplyProcessor(new AffineTransformProcessor(matrix, sampler, source.GetCurrentSize()));
+ => source.ApplyProcessor(new AffineTransformProcessorOld(matrix, sampler, source.GetCurrentSize()));
///
/// Transforms an image by the given matrix using the specified sampling algorithm
@@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing
{
var t = Matrix3x2.CreateTranslation(-rectangle.Location);
Matrix3x2 combinedMatrix = t * matrix;
- return source.ApplyProcessor(new AffineTransformProcessor(combinedMatrix, sampler, rectangle.Size));
+ return source.ApplyProcessor(new AffineTransformProcessorOld(combinedMatrix, sampler, rectangle.Size));
}
///
@@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing
IResampler sampler,
Size destinationSize)
where TPixel : struct, IPixel
- => source.ApplyProcessor(new AffineTransformProcessor(matrix, sampler, destinationSize));
+ => source.ApplyProcessor(new AffineTransformProcessorOld(matrix, sampler, destinationSize));
///
/// Transforms an image by the given matrix.