diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings
index 1839bf2f8..435aad73b 100644
--- a/ImageSharp.sln.DotSettings
+++ b/ImageSharp.sln.DotSettings
@@ -38,10 +38,15 @@
NEXT_LINE_SHIFTED_2
1
1
+ False
+ False
False
+ NEVER
False
False
+ NEVER
False
+ ALWAYS
False
True
ON_SINGLE_LINE
@@ -370,8 +375,13 @@
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ True
+ True
+ True
+ True
True
True
+ True
True
True
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
similarity index 67%
rename from src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
rename to src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
index 59b844263..ce4fbdd71 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
@@ -5,12 +5,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
-using System.Runtime.CompilerServices;
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;
@@ -20,35 +18,42 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Provides the base methods to perform affine transforms on an image.
///
/// The pixel format.
- internal abstract class AffineProcessor : CloningImageProcessor
+ internal class AffineTransformProcessor : InterpolatedTransformProcessorBase
where TPixel : struct, IPixel
{
private Rectangle targetRectangle;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
+ ///
+ /// The transform matrix
+ public AffineTransformProcessor(Matrix3x2 matrix)
+ : this(matrix, KnownResamplers.Bicubic)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
///
/// The transform matrix
/// The sampler to perform the transform operation.
- protected AffineProcessor(Matrix3x2 matrix, IResampler sampler)
+ public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler)
: this(matrix, sampler, Rectangle.Empty)
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The transform matrix
/// The sampler to perform the transform operation.
/// The rectangle to constrain the transformed image to.
- protected AffineProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle)
+ public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle)
+ : base(sampler)
{
// Tansforms are inverted else the output is the opposite of the expected.
Matrix3x2.Invert(matrix, out matrix);
this.TransformMatrix = matrix;
-
- this.Sampler = sampler;
-
this.targetRectangle = rectangle;
}
@@ -57,11 +62,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
///
public Matrix3x2 TransformMatrix { get; }
- ///
- /// Gets the sampler to perform interpolation of the transform operation.
- ///
- public IResampler Sampler { get; }
-
///
protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
{
@@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
int width = this.targetRectangle.Width;
Rectangle sourceBounds = source.Bounds();
- // Since could potentially be resizing the canvas we need to re-center the matrix
+ // Since could potentially be resizing the canvas we might need to re-calculate the matrix
Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle);
if (this.Sampler is NearestNeighborResampler)
@@ -211,26 +211,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
}
- ///
- protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle)
- {
- ExifProfile profile = destination.MetaData.ExifProfile;
- if (profile == null)
- {
- return;
- }
-
- if (profile.GetValue(ExifTag.PixelXDimension) != null)
- {
- profile.SetValue(ExifTag.PixelXDimension, destination.Width);
- }
-
- if (profile.GetValue(ExifTag.PixelYDimension) != null)
- {
- profile.SetValue(ExifTag.PixelYDimension, destination.Height);
- }
- }
-
///
/// Gets a transform matrix adjusted for final processing based upon the target image bounds.
///
@@ -254,91 +234,5 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
return sourceRectangle;
}
-
- ///
- /// Calculated the weights for the given point.
- /// This method uses more samples than the upscaled version to ensure edge pixels are correctly rendered.
- /// Additionally the weights are nomalized.
- ///
- /// The minimum sampling offset
- /// The maximum sampling offset
- /// The minimum source bounds
- /// The maximum source bounds
- /// The transformed point dimension
- /// The sampler
- /// The transformed image scale relative to the source
- /// The collection of weights
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, Span weights)
- {
- float sum = 0;
- ref float weightsBaseRef = ref weights[0];
-
- // Downsampling weights requires more edge sampling plus normalization of the weights
- for (int x = 0, i = min; i <= max; i++, x++)
- {
- int index = i;
- if (index < sourceMin)
- {
- index = sourceMin;
- }
-
- if (index > sourceMax)
- {
- index = sourceMax;
- }
-
- float weight = sampler.GetValue((index - point) / scale);
- sum += weight;
- Unsafe.Add(ref weightsBaseRef, x) = weight;
- }
-
- if (sum > 0)
- {
- for (int i = 0; i < weights.Length; i++)
- {
- ref float wRef = ref Unsafe.Add(ref weightsBaseRef, i);
- wRef = wRef / sum;
- }
- }
- }
-
- ///
- /// Calculated the weights for the given point.
- ///
- /// The minimum source bounds
- /// The maximum source bounds
- /// The transformed point dimension
- /// The sampler
- /// The collection of weights
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, Span weights)
- {
- ref float weightsBaseRef = ref weights[0];
- for (int x = 0, i = sourceMin; i <= sourceMax; i++, x++)
- {
- float weight = sampler.GetValue(i - point);
- Unsafe.Add(ref weightsBaseRef, x) = weight;
- }
- }
-
- ///
- /// Calculates the sampling radius for the current sampler
- ///
- /// The source dimension size
- /// The destination dimension size
- /// The radius, and scaling factor
- private (float radius, float scale, float ratio) GetSamplingRadius(int sourceSize, int destinationSize)
- {
- float ratio = (float)sourceSize / destinationSize;
- float scale = ratio;
-
- if (scale < 1F)
- {
- scale = 1F;
- }
-
- return (MathF.Ceiling(scale * this.Sampler.Radius), scale, ratio);
- }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
similarity index 77%
rename from src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs
rename to src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
index 5631af3aa..34eabba9b 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
@@ -7,15 +7,19 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
- internal abstract class CenteredAffineProcessor : AffineProcessor
+ ///
+ /// A base class that provides methods to allow the automatic centering of affine transforms
+ ///
+ /// The pixel format.
+ internal abstract class CenteredAffineTransformProcessor : AffineTransformProcessor
where TPixel : struct, IPixel
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The transform matrix
/// The sampler to perform the transform operation.
- protected CenteredAffineProcessor(Matrix3x2 matrix, IResampler sampler)
+ protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler)
: base(matrix, sampler)
{
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredNonAffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredNonAffineTransformProcessor.cs
new file mode 100644
index 000000000..bb1505c80
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredNonAffineTransformProcessor.cs
@@ -0,0 +1,43 @@
+// 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
+{
+ ///
+ /// A base class that provides methods to allow the automatic centering of non-affine transforms
+ ///
+ /// The pixel format.
+ internal abstract class CenteredNonAffineTransformProcessor : NonAffineTransformProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The transform matrix
+ /// The sampler to perform the transform operation.
+ protected CenteredNonAffineTransformProcessor(Matrix4x4 matrix, IResampler sampler)
+ : base(matrix, sampler)
+ {
+ }
+
+ ///
+ protected override Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
+ {
+ var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0);
+ var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0);
+ return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter;
+ }
+
+ ///
+ protected override Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix)
+ {
+ return Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 sizeMatrix)
+ ? TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix)
+ : sourceRectangle;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs
new file mode 100644
index 000000000..5c32f044a
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs
@@ -0,0 +1,139 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.MetaData.Profiles.Exif;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors
+{
+ ///
+ /// The base class for performing interpolated affine and non-affine transforms.
+ ///
+ /// The pixel format.
+ internal abstract class InterpolatedTransformProcessorBase : CloningImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The sampler to perform the transform operation.
+ protected InterpolatedTransformProcessorBase(IResampler sampler)
+ {
+ this.Sampler = sampler;
+ }
+
+ ///
+ /// Gets the sampler to perform interpolation of the transform operation.
+ ///
+ public IResampler Sampler { get; }
+
+ ///
+ /// Calculated the weights for the given point.
+ /// This method uses more samples than the upscaled version to ensure edge pixels are correctly rendered.
+ /// Additionally the weights are nomalized.
+ ///
+ /// The minimum sampling offset
+ /// The maximum sampling offset
+ /// The minimum source bounds
+ /// The maximum source bounds
+ /// The transformed point dimension
+ /// The sampler
+ /// The transformed image scale relative to the source
+ /// The collection of weights
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, Span weights)
+ {
+ float sum = 0;
+ ref float weightsBaseRef = ref weights[0];
+
+ // Downsampling weights requires more edge sampling plus normalization of the weights
+ for (int x = 0, i = min; i <= max; i++, x++)
+ {
+ int index = i;
+ if (index < sourceMin)
+ {
+ index = sourceMin;
+ }
+
+ if (index > sourceMax)
+ {
+ index = sourceMax;
+ }
+
+ float weight = sampler.GetValue((index - point) / scale);
+ sum += weight;
+ Unsafe.Add(ref weightsBaseRef, x) = weight;
+ }
+
+ if (sum > 0)
+ {
+ for (int i = 0; i < weights.Length; i++)
+ {
+ ref float wRef = ref Unsafe.Add(ref weightsBaseRef, i);
+ wRef = wRef / sum;
+ }
+ }
+ }
+
+ ///
+ /// Calculated the weights for the given point.
+ ///
+ /// The minimum source bounds
+ /// The maximum source bounds
+ /// The transformed point dimension
+ /// The sampler
+ /// The collection of weights
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, Span weights)
+ {
+ ref float weightsBaseRef = ref weights[0];
+ for (int x = 0, i = sourceMin; i <= sourceMax; i++, x++)
+ {
+ float weight = sampler.GetValue(i - point);
+ Unsafe.Add(ref weightsBaseRef, x) = weight;
+ }
+ }
+
+ ///
+ /// Calculates the sampling radius for the current sampler
+ ///
+ /// The source dimension size
+ /// The destination dimension size
+ /// The radius, and scaling factor
+ protected (float radius, float scale, float ratio) GetSamplingRadius(int sourceSize, int destinationSize)
+ {
+ float ratio = (float)sourceSize / destinationSize;
+ float scale = ratio;
+
+ if (scale < 1F)
+ {
+ scale = 1F;
+ }
+
+ return (MathF.Ceiling(scale * this.Sampler.Radius), scale, ratio);
+ }
+
+ ///
+ protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle)
+ {
+ ExifProfile profile = destination.MetaData.ExifProfile;
+ if (profile == null)
+ {
+ return;
+ }
+
+ if (profile.GetValue(ExifTag.PixelXDimension) != null)
+ {
+ profile.SetValue(ExifTag.PixelXDimension, destination.Width);
+ }
+
+ if (profile.GetValue(ExifTag.PixelYDimension) != null)
+ {
+ profile.SetValue(ExifTag.PixelYDimension, destination.Height);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/NonAffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/NonAffineTransformProcessor.cs
new file mode 100644
index 000000000..caaf812b0
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/NonAffineTransformProcessor.cs
@@ -0,0 +1,238 @@
+// 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.Threading.Tasks;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Helpers;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors
+{
+ ///
+ /// Provides the base methods to perform non-affine transforms on an image.
+ ///
+ /// The pixel format.
+ internal class NonAffineTransformProcessor : InterpolatedTransformProcessorBase
+ where TPixel : struct, IPixel
+ {
+ private Rectangle targetRectangle;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The transform matrix
+ public NonAffineTransformProcessor(Matrix4x4 matrix)
+ : this(matrix, KnownResamplers.Bicubic)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The transform matrix
+ /// The sampler to perform the transform operation.
+ public NonAffineTransformProcessor(Matrix4x4 matrix, IResampler sampler)
+ : this(matrix, sampler, Rectangle.Empty)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The transform matrix
+ /// The sampler to perform the transform operation.
+ /// The rectangle to constrain the transformed image to.
+ public NonAffineTransformProcessor(Matrix4x4 matrix, IResampler sampler, Rectangle rectangle)
+ : base(sampler)
+ {
+ // Tansforms are inverted else the output is the opposite of the expected.
+ Matrix4x4.Invert(matrix, out matrix);
+ this.TransformMatrix = matrix;
+ this.targetRectangle = rectangle;
+ }
+
+ ///
+ /// Gets the matrix used to supply the non-affine transform
+ ///
+ public Matrix4x4 TransformMatrix { get; }
+
+ ///
+ protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
+ {
+ if (this.targetRectangle == Rectangle.Empty)
+ {
+ this.targetRectangle = this.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix);
+ }
+
+ // 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.targetRectangle.Width, this.targetRectangle.Height, x.MetaData.Clone()));
+
+ // Use the overload to prevent an extra frame being added
+ return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames);
+ }
+
+ ///
+ protected override void OnApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
+ {
+ int height = this.targetRectangle.Height;
+ int width = this.targetRectangle.Width;
+ Rectangle sourceBounds = source.Bounds();
+
+ // Since could potentially be resizing the canvas we might need to re-calculate the matrix
+ Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle);
+
+ if (this.Sampler is NearestNeighborResampler)
+ {
+ Parallel.For(
+ 0,
+ height,
+ configuration.ParallelOptions,
+ y =>
+ {
+ Span destRow = destination.GetPixelRowSpan(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ var point = Point.Round(Vector2.Transform(new Vector2(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);
+
+ using (var yBuffer = new Buffer2D(yLength, height))
+ using (var xBuffer = new Buffer2D(xLength, height))
+ {
+ Parallel.For(
+ 0,
+ height,
+ configuration.ParallelOptions,
+ y =>
+ {
+ Span destRow = destination.GetPixelRowSpan(y);
+ Span ySpan = yBuffer.GetRowSpan(y);
+ Span xSpan = 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.
+ // Precalulating 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, ySpan);
+ CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, xSpan);
+ }
+ else
+ {
+ CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ySpan);
+ CalculateWeightsScaleUp(minX, maxX, point.X, sampler, xSpan);
+ }
+
+ // Now multiply the results against the offsets
+ Vector4 sum = Vector4.Zero;
+ for (int yy = 0, j = minY; j <= maxY; j++, yy++)
+ {
+ float yWeight = ySpan[yy];
+
+ for (int xx = 0, i = minX; i <= maxX; i++, xx++)
+ {
+ float xWeight = xSpan[xx];
+ var vector = source[i, j].ToVector4();
+
+ // Values are first premultiplied to prevent darkening of edge pixels
+ var mupltiplied = new Vector4(new Vector3(vector.X, vector.Y, vector.Z) * vector.W, vector.W);
+ sum += mupltiplied * xWeight * yWeight;
+ }
+ }
+
+ ref TPixel dest = ref destRow[x];
+
+ // Reverse the premultiplication
+ dest.PackFromVector4(new Vector4(new Vector3(sum.X, sum.Y, sum.Z) / sum.W, sum.W));
+ }
+ });
+ }
+ }
+
+ ///
+ /// 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 Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
+ {
+ return this.TransformMatrix;
+ }
+
+ ///
+ /// Gets the bounding relative to the source for the given transformation matrix.
+ ///
+ /// The source rectangle.
+ /// The transformation matrix.
+ /// The
+ protected virtual Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix)
+ {
+ return sourceRectangle;
+ }
+ }
+}
\ 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 fa47dadaa..b93c86915 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Provides methods that allow the rotating of images.
///
/// The pixel format.
- internal class RotateProcessor : CenteredAffineProcessor
+ internal class RotateProcessor : CenteredAffineTransformProcessor
where TPixel : struct, IPixel
{
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
index b123a309b..8c47b3527 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
@@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Provides methods that allow the skewing of images.
///
/// The pixel format.
- internal class SkewProcessor : CenteredAffineProcessor
+ internal class SkewProcessor : CenteredAffineTransformProcessor
where TPixel : struct, IPixel
{
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
deleted file mode 100644
index 140fd7ec6..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
+++ /dev/null
@@ -1,47 +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 that allow the tranformation of images using various algorithms.
- ///
- /// The pixel format.
- internal class TransformProcessor : AffineProcessor
- where TPixel : struct, IPixel
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The transformation matrix
- public TransformProcessor(Matrix3x2 matrix)
- : this(matrix, KnownResamplers.Bicubic)
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The transformation matrix
- /// The sampler to perform the transform operation.
- public TransformProcessor(Matrix3x2 matrix, IResampler sampler)
- : base(matrix, sampler)
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The transform matrix
- /// The sampler to perform the transform operation.
- /// The rectangle to constrain the transformed image to.
- public TransformProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle)
- : base(matrix, sampler, rectangle)
- {
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Transforms/Transform.cs b/src/ImageSharp/Processing/Transforms/Transform.cs
index 74f91fa70..cbfe6da18 100644
--- a/src/ImageSharp/Processing/Transforms/Transform.cs
+++ b/src/ImageSharp/Processing/Transforms/Transform.cs
@@ -18,18 +18,18 @@ namespace SixLabors.ImageSharp
/// Transforms an image by the given matrix.
///
/// The pixel format.
- /// The image to skew.
+ /// The image to transform.
/// The transformation matrix.
/// The
public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix)
where TPixel : struct, IPixel
- => Transform(source, matrix, KnownResamplers.NearestNeighbor);
+ => Transform(source, matrix, KnownResamplers.NearestNeighbor);
///
/// Transforms an image by the given matrix using the specified sampling algorithm.
///
/// The pixel format.
- /// The image to skew.
+ /// The image to transform.
/// The transformation matrix.
/// The to perform the resampling.
/// The
@@ -41,13 +41,49 @@ namespace SixLabors.ImageSharp
/// Transforms an image by the given matrix using the specified sampling algorithm.
///
/// The pixel format.
- /// The image to skew.
+ /// The image to transform.
/// The transformation matrix.
/// The to perform the resampling.
/// The rectangle to constrain the transformed image to.
/// The
public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix, IResampler sampler, Rectangle rectangle)
where TPixel : struct, IPixel
- => source.ApplyProcessor(new TransformProcessor(matrix, sampler, rectangle));
+ => source.ApplyProcessor(new AffineTransformProcessor(matrix, sampler, rectangle));
+
+ ///
+ /// Transforms an image by the given matrix.
+ ///
+ /// The pixel format.
+ /// The image to transform.
+ /// The transformation matrix.
+ /// The
+ internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix)
+ where TPixel : struct, IPixel
+ => Transform(source, matrix, KnownResamplers.NearestNeighbor);
+
+ ///
+ /// Transforms an image by the given matrix using the specified sampling algorithm.
+ ///
+ /// The pixel format.
+ /// The image to transform.
+ /// The transformation matrix.
+ /// The to perform the resampling.
+ /// The
+ internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler)
+ where TPixel : struct, IPixel
+ => Transform(source, matrix, sampler, Rectangle.Empty);
+
+ ///
+ /// Transforms an image by the given matrix using the specified sampling algorithm.
+ ///
+ /// The pixel format.
+ /// The image to transform.
+ /// The transformation matrix.
+ /// The to perform the resampling.
+ /// The rectangle to constrain the transformed image to.
+ /// The
+ internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ => source.ApplyProcessor(new NonAffineTransformProcessor(matrix, sampler, rectangle));
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Transforms/TransformHelpers.cs b/src/ImageSharp/Processing/Transforms/TransformHelpers.cs
index 419c1c13d..119fc9eed 100644
--- a/src/ImageSharp/Processing/Transforms/TransformHelpers.cs
+++ b/src/ImageSharp/Processing/Transforms/TransformHelpers.cs
@@ -8,7 +8,7 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
///
- /// Contains helper methods for working with affine transforms
+ /// Contains helper methods for working with affine and non-affine transforms
///
internal class TransformHelpers
{
@@ -29,7 +29,32 @@ namespace SixLabors.ImageSharp
var bl = Vector2.Transform(new Vector2(0, rectangle.Height), matrix);
var br = Vector2.Transform(new Vector2(rectangle.Width, rectangle.Height), matrix);
- // Find the minimum and maximum "corners" based on the ones above
+ return GetBoundingRectangle(tl, tr, bl, br);
+ }
+
+ ///
+ /// Returns the bounding relative to the source for the given transformation matrix.
+ ///
+ /// The source rectangle.
+ /// The transformation matrix.
+ ///
+ /// The .
+ ///
+ public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix4x4 matrix)
+ {
+ // Calculate the position of the four corners in world space by applying
+ // The world matrix to the four corners in object space (0, 0, width, height)
+ var tl = Vector2.Transform(Vector2.Zero, matrix);
+ var tr = Vector2.Transform(new Vector2(rectangle.Width, 0), matrix);
+ var bl = Vector2.Transform(new Vector2(0, rectangle.Height), matrix);
+ var br = Vector2.Transform(new Vector2(rectangle.Width, rectangle.Height), 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 minX = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X)));
float maxX = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X)));
float minY = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y)));