diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs
new file mode 100644
index 000000000..e5ce1450f
--- /dev/null
+++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs
@@ -0,0 +1,177 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+using System.Numerics;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// A helper class for constructing instances for use in affine transforms.
+ ///
+ public class AffineTransformBuilder
+ {
+ private readonly List matrices = new List();
+ private Rectangle rectangle;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The source image size.
+ public AffineTransformBuilder(Size sourceSize) => this.Size = sourceSize;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The source rectangle.
+ public AffineTransformBuilder(Rectangle sourceRectangle)
+ : this(sourceRectangle.Size)
+ => this.rectangle = sourceRectangle;
+
+ ///
+ /// Prepends a centered rotation matrix using the given rotation in degrees.
+ ///
+ /// The amount of rotation, in degrees.
+ /// The .
+ public AffineTransformBuilder PrependRotateMatrixDegrees(float degrees)
+ {
+ this.matrices.Insert(0, TransformUtils.CreateRotationMatrixDegrees(degrees, this.Size));
+ return this;
+ }
+
+ ///
+ /// Gets the source image size.
+ ///
+ internal Size Size { get; }
+
+ ///
+ /// Appends a centered rotation matrix using the given rotation in degrees.
+ ///
+ /// The amount of rotation, in degrees.
+ /// The .
+ public AffineTransformBuilder AppendRotateMatrixDegrees(float degrees)
+ {
+ this.matrices.Add(TransformUtils.CreateRotationMatrixDegrees(degrees, this.Size));
+ return this;
+ }
+
+ ///
+ /// Prepends a scale matrix from the given vector scale.
+ ///
+ /// The horizontal and vertical scale.
+ /// The .
+ public AffineTransformBuilder PrependScaleMatrix(SizeF scales)
+ {
+ this.matrices.Insert(0, Matrix3x2Extensions.CreateScale(scales));
+ return this;
+ }
+
+ ///
+ /// Appends a scale matrix from the given vector scale.
+ ///
+ /// The horizontal and vertical scale.
+ /// The .
+ public AffineTransformBuilder AppendScaleMatrix(SizeF scales)
+ {
+ this.matrices.Add(Matrix3x2Extensions.CreateScale(scales));
+ return this;
+ }
+
+ ///
+ /// Prepends a centered skew matrix from the give angles in degrees.
+ ///
+ /// The X angle, in degrees.
+ /// The Y angle, in degrees.
+ /// The .
+ public AffineTransformBuilder PrependSkewMatrixDegrees(float degreesX, float degreesY)
+ {
+ this.matrices.Insert(0, TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, this.Size));
+ return this;
+ }
+
+ ///
+ /// Appends a centered skew matrix from the give angles in degrees.
+ ///
+ /// The X angle, in degrees.
+ /// The Y angle, in degrees.
+ /// The .
+ public AffineTransformBuilder AppendSkewMatrixDegrees(float degreesX, float degreesY)
+ {
+ this.matrices.Add(TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, this.Size));
+ return this;
+ }
+
+ ///
+ /// Prepends a translation matrix from the given vector.
+ ///
+ /// The translation position.
+ /// The .
+ public AffineTransformBuilder PrependTranslationMatrix(PointF position)
+ {
+ this.matrices.Insert(0, Matrix3x2Extensions.CreateTranslation(position));
+ return this;
+ }
+
+ ///
+ /// Appends a translation matrix from the given vector.
+ ///
+ /// The translation position.
+ /// The .
+ public AffineTransformBuilder AppendTranslationMatrix(PointF position)
+ {
+ this.matrices.Add(Matrix3x2Extensions.CreateTranslation(position));
+ return this;
+ }
+
+ ///
+ /// Prepends a raw matrix.
+ ///
+ /// The matrix to prepend.
+ /// The .
+ public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix)
+ {
+ this.matrices.Insert(0, matrix);
+ return this;
+ }
+
+ ///
+ /// Appends a raw matrix.
+ ///
+ /// The matrix to append.
+ /// The .
+ public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix)
+ {
+ this.matrices.Add(matrix);
+ return this;
+ }
+
+ ///
+ /// Returns the combined matrix.
+ ///
+ /// The .
+ public Matrix3x2 BuildMatrix()
+ {
+ Matrix3x2 matrix = Matrix3x2.Identity;
+
+ // Translate the origin matrix to cater for source rectangle offsets.
+ if (!this.rectangle.Equals(default))
+ {
+ matrix *= Matrix3x2.CreateTranslation(-this.rectangle.Location);
+ }
+
+ foreach (Matrix3x2 m in this.matrices)
+ {
+ matrix *= m;
+ }
+
+ return matrix;
+ }
+
+ ///
+ /// Removes all matrices from the builder.
+ ///
+ public void Clear() => this.matrices.Clear();
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
index f63baa95c..fb42b8334 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
@@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class AffineTransformProcessor : TransformProcessorBase
where TPixel : struct, IPixel
{
- private readonly Rectangle transformedRectangle;
-
///
/// Initializes a new instance of the class.
///
@@ -32,18 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Guard.NotNull(sampler, nameof(sampler));
this.Sampler = sampler;
this.TransformMatrix = matrix;
- 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);
-
- // Handle a negative translation that exceeds the original with of the image.
- if (this.TargetDimensions.Width <= 0 || this.TargetDimensions.Height <= 0)
- {
- this.TargetDimensions = sourceSize;
- }
+ this.TargetDimensions = TransformUtils.GetTransformedSize(sourceSize, matrix);
}
///
@@ -79,10 +66,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle sourceRectangle,
Configuration configuration)
{
+ // Handle tranforms that result in output identical to the original.
+ if (this.TransformMatrix.Equals(Matrix3x2.Identity))
+ {
+ // The cloned will be blank here copy all the pixel data over
+ source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
+ return;
+ }
+
int height = this.TargetDimensions.Height;
int width = this.TargetDimensions.Width;
- Rectangle sourceBounds = source.Bounds();
var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
// Convert from screen to world space.
@@ -102,7 +96,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = 0; x < width; x++)
{
var point = Point.Transform(new Point(x, y), matrix);
- if (sourceBounds.Contains(point.X, point.Y))
+ if (sourceRectangle.Contains(point.X, point.Y))
{
destRow[x] = source[point.X, point.Y];
}
@@ -113,7 +107,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return;
}
- using (var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler))
+ var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler);
+ try
{
ParallelHelper.IterateRowsWithTempBuffer(
targetBounds,
@@ -140,6 +135,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
});
}
+ finally
+ {
+ kernel.Dispose();
+ }
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessorOld.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessorOld.cs
deleted file mode 100644
index 5891afd9a..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessorOld.cs
+++ /dev/null
@@ -1,239 +0,0 @@
-// 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
deleted file mode 100644
index 82614dc8c..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
+++ /dev/null
@@ -1,38 +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.Transforms
-{
- ///
- /// A base class that provides methods to allow the automatic centering of affine transforms
- ///
- /// The pixel format.
- internal abstract class CenteredAffineTransformProcessor : AffineTransformProcessorOld
- where TPixel : struct, IPixel
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The transform matrix
- /// The sampler to perform the transform operation.
- /// The source image size
- protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size sourceSize)
- : base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix))
- {
- }
-
- ///
- protected override Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
- => TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix);
-
- private static Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix)
- {
- var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height);
- return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix).Size;
- }
- }
-}
\ 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 cbf82cc9b..57cca4bf9 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
@@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The source image size
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
: base(
- TransformUtils.CreateCenteredRotationMatrixDegrees(degrees, sourceSize),
+ TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize),
sampler,
sourceSize)
=> this.Degrees = degrees;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
index a0cfa6379..4a006a9df 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
@@ -2,7 +2,6 @@
// 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.Processors.Transforms
@@ -11,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Provides methods that allow the skewing of images.
///
/// The pixel format.
- internal class SkewProcessor : CenteredAffineTransformProcessor
+ internal class SkewProcessor : AffineTransformProcessor
where TPixel : struct, IPixel
{
///
@@ -33,7 +32,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The sampler to perform the skew operation.
/// The source image size
public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize)
- : base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler, sourceSize)
+ : base(
+ TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize),
+ sampler,
+ sourceSize)
{
this.DegreesX = degreesX;
this.DegreesY = degreesY;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformHelpers.cs
index b22fa64cf..2e85f6c2c 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformHelpers.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformHelpers.cs
@@ -102,26 +102,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return centered;
}
- ///
- /// Returns the bounding rectangle relative to the source for the given transformation matrix.
- ///
- /// The source rectangle.
- /// The transformation matrix.
- ///
- /// The .
- ///
- public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 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);
- }
-
///
/// Returns the bounding rectangle relative to the source for the given transformation matrix.
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
index 531edbc45..573120888 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
@@ -9,20 +9,15 @@ 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.
+ /// Contains the methods required to calculate transform kernel convolution.
///
internal class TransformKernelMap : IDisposable
{
private readonly Buffer2D yBuffer;
private readonly Buffer2D xBuffer;
- private readonly int yLength;
- private readonly int xLength;
private readonly Vector2 extents;
private Vector4 maxSourceExtents;
private readonly IResampler sampler;
@@ -41,12 +36,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
float xRadius = this.GetSamplingRadius(source.Width, destination.Width);
this.extents = new Vector2(xRadius, yRadius);
- this.xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2);
- this.yLength = (int)MathF.Ceiling((this.extents.Y * 2) + 2);
+ int xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2);
+ int 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);
+ // We use 2D buffers so that we can access the weight spans per row in parallel.
+ this.yBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height);
+ this.xBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height);
int maxX = source.Width - 1;
int maxY = source.Height - 1;
@@ -82,42 +77,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Vector2 minXY = transformedPoint - this.extents;
Vector2 maxXY = transformedPoint + this.extents;
- // minX, minY, maxX, maxY
+ // left, top, right, bottom
var extents = new Vector4(
MathF.Ceiling(minXY.X - .5F),
MathF.Ceiling(minXY.Y - .5F),
MathF.Floor(maxXY.X + .5F),
MathF.Floor(maxXY.Y + .5F));
+ extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents);
+
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)
+ if (left == right || top == bottom)
{
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);
+ this.CalculateWeights(top, bottom, transformedPoint.Y, ref ySpanRef);
+ this.CalculateWeights(left, right, transformedPoint.X, ref xSpanRef);
Vector4 sum = Vector4.Zero;
- for (int kernelY = 0, y = minY; y <= maxY; y++, kernelY++)
+ for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++)
{
float yWeight = Unsafe.Add(ref ySpanRef, kernelY);
- for (int kernelX = 0, x = minX; x <= maxX; x++, kernelX++)
+ for (int kernelX = 0, x = left; x <= right; x++, kernelX++)
{
float xWeight = Unsafe.Add(ref xSpanRef, kernelX);
@@ -138,29 +125,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// 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)
+ private void CalculateWeights(int min, int max, 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);
+ Unsafe.Add(ref weightsRef, x) = weight;
}
-
- // 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;
- // }
- // }
}
[MethodImpl(InliningOptions.ShortMethod)]
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
index 7d0350249..10cf49c34 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
@@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// Contains utility methods for working with transforms.
///
- public static class TransformUtils
+ internal static class TransformUtils
{
///
/// Creates a centered rotation matrix using the given rotation in degrees and the source size.
@@ -18,43 +18,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The amount of rotation, in degrees.
/// The source image size.
/// The .
- public static Matrix3x2 CreateCenteredRotationMatrixDegrees(float degrees, Size size)
+ public static Matrix3x2 CreateRotationMatrixDegrees(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.
+ /// Creates a centered skew matrix from the give angles in degrees and the source size.
///
- /// 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;
- }
+ /// The X angle, in degrees.
+ /// The Y angle, in degrees.
+ /// The source image size.
+ /// The .
+ public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size)
+ => CreateCenteredTransformMatrix(
+ new Rectangle(Point.Empty, size),
+ Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty));
///
/// 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)
+ 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);
@@ -79,8 +69,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
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);
}
@@ -107,6 +95,44 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return GetBoundingRectangle(tl, tr, bl, br);
}
+ ///
+ /// Returns the size relative to the source for the given transformation matrix.
+ ///
+ /// The source size.
+ /// The transformation matrix.
+ ///
+ /// The .
+ ///
+ public static Size GetTransformedSize(Size size, Matrix3x2 matrix)
+ {
+ Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!");
+
+ if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity))
+ {
+ return size;
+ }
+
+ Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix);
+
+ // We want to resize the canvas here taking into account any translations.
+ int height = rectangle.Top < 0 ? rectangle.Bottom : Math.Max(rectangle.Height, rectangle.Bottom);
+ int width = rectangle.Left < 0 ? rectangle.Right : Math.Max(rectangle.Width, rectangle.Right);
+
+ // If location in either direction is translated to a negative value equal to or exceeding the
+ // dimensions in eith direction we need to reassign the dimension.
+ if (height <= 0)
+ {
+ height = rectangle.Height;
+ }
+
+ if (width <= 0)
+ {
+ width = rectangle.Width;
+ }
+
+ return new Size(width, height);
+ }
+
private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br)
{
// Find the minimum and maximum "corners" based on the given vectors
diff --git a/src/ImageSharp/Processing/TransformExtensions.cs b/src/ImageSharp/Processing/TransformExtensions.cs
index 3f24969a3..4cbd1b041 100644
--- a/src/ImageSharp/Processing/TransformExtensions.cs
+++ b/src/ImageSharp/Processing/TransformExtensions.cs
@@ -18,65 +18,23 @@ namespace SixLabors.ImageSharp.Processing
///
/// The pixel format.
/// The image to transform.
- /// The transformation matrix.
+ /// The affine transform builder.
/// The
- public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix)
+ public static IImageProcessingContext Transform(this IImageProcessingContext source, AffineTransformBuilder builder)
where TPixel : struct, IPixel
- => Transform(source, matrix, KnownResamplers.Bicubic);
+ => Transform(source, builder, KnownResamplers.Bicubic);
///
/// 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
- public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix, IResampler sampler)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new AffineTransformProcessorOld(matrix, sampler, source.GetCurrentSize()));
-
- ///
- /// Transforms an image by the given matrix using the specified sampling algorithm
- /// and a rectangle defining the transform origin in the source image and the size of the result image.
- ///
- /// The pixel format.
- /// The image to transform.
- /// The transformation matrix.
- /// The to perform the resampling.
- ///
- /// The rectangle defining the transform origin in the source image, and the size of the result image.
- ///
- /// The
- public static IImageProcessingContext Transform(
- this IImageProcessingContext source,
- Matrix3x2 matrix,
- IResampler sampler,
- Rectangle rectangle)
- where TPixel : struct, IPixel
- {
- var t = Matrix3x2.CreateTranslation(-rectangle.Location);
- Matrix3x2 combinedMatrix = t * matrix;
- return source.ApplyProcessor(new AffineTransformProcessorOld(combinedMatrix, sampler, rectangle.Size));
- }
-
- ///
- /// Transforms an image by the given matrix using the specified sampling algorithm,
- /// cropping or extending the image according to .
- ///
- /// The pixel format.
- /// The image to transform.
- /// The transformation matrix.
+ /// The affine transform builder.
/// The to perform the resampling.
- /// The size of the destination image.
/// The
- public static IImageProcessingContext Transform(
- this IImageProcessingContext source,
- Matrix3x2 matrix,
- IResampler sampler,
- Size destinationSize)
+ public static IImageProcessingContext Transform(this IImageProcessingContext source, AffineTransformBuilder builder, IResampler sampler)
where TPixel : struct, IPixel
- => source.ApplyProcessor(new AffineTransformProcessorOld(matrix, sampler, destinationSize));
+ => source.ApplyProcessor(new AffineTransformProcessor(builder.BuildMatrix(), sampler, builder.Size));
///
/// Transforms an image by the given matrix.
diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs
index c1456f9d7..f898576af 100644
--- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs
+++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs
@@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
}
}
-// Nov 4 2018
+// Nov 7 2018
//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763
//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores
//.NET Core SDK = 2.1.403
@@ -36,4 +36,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
// Method | Runtime | Mean | Error | StdDev | Allocated |
//--------- |-------- |---------:|----------:|----------:|----------:|
// DoRotate | Clr | 85.19 ms | 13.379 ms | 0.7560 ms | 6 KB |
-// DoRotate | Core | 53.51 ms | 9.512 ms | 0.5375 ms | 4.29 KB |
\ No newline at end of file
+// DoRotate | Core | 53.51 ms | 9.512 ms | 0.5375 ms | 4.29 KB |
+
+// #### AFTER ####:
+//Method | Runtime | Mean | Error | StdDev | Allocated |
+//--------- |-------- |---------:|---------:|---------:|----------:|
+// DoRotate | Clr | 77.08 ms | 23.97 ms | 1.354 ms | 6 KB |
+// DoRotate | Core | 40.36 ms | 47.43 ms | 2.680 ms | 4.36 KB |
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs
new file mode 100644
index 000000000..84819750a
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs
@@ -0,0 +1,45 @@
+using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Benchmarks.Samplers
+{
+ [Config(typeof(Config.ShortClr))]
+ public class Skew
+ {
+ [Benchmark]
+ public Size DoSkew()
+ {
+ using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond))
+ {
+ image.Mutate(x => x.Skew(20, 10));
+
+ return image.Size();
+ }
+ }
+ }
+}
+
+// Nov 7 2018
+//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763
+//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores
+//.NET Core SDK = 2.1.403
+
+// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
+// Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0
+// Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
+
+//LaunchCount=1 TargetCount=3 WarmupCount=3
+
+// #### BEFORE ####:
+//Method | Runtime | Mean | Error | StdDev | Allocated |
+//------- |-------- |---------:|---------:|----------:|----------:|
+// DoSkew | Clr | 78.14 ms | 8.383 ms | 0.4736 ms | 6 KB |
+// DoSkew | Core | 44.22 ms | 4.109 ms | 0.2322 ms | 4.28 KB |
+
+// #### AFTER ####:
+//Method | Runtime | Mean | Error | StdDev | Allocated |
+//------- |-------- |---------:|----------:|----------:|----------:|
+// DoSkew | Clr | 71.63 ms | 25.589 ms | 1.4458 ms | 6 KB |
+// DoSkew | Core | 38.99 ms | 8.640 ms | 0.4882 ms | 4.36 KB |
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
index 496692d96..564318e5e 100644
--- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
+++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
@@ -2,10 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
-using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
@@ -75,21 +73,15 @@ namespace SixLabors.ImageSharp.Tests
using (Image image = provider.GetImage())
using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes))
{
- Matrix3x2 rotate = Matrix3x2Extensions.CreateRotationDegrees(45F);
- Matrix3x2 scale = Matrix3x2Extensions.CreateScale(new SizeF(.25F, .25F));
- Matrix3x2 matrix = rotate * scale;
+ AffineTransformBuilder builder = new AffineTransformBuilder(blend.Size())
+ .AppendRotateMatrixDegrees(45F)
+ .AppendScaleMatrix(new SizeF(.25F, .25F))
+ .AppendTranslationMatrix(new PointF(10, 10));
- // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor
- Rectangle srcBounds = blend.Bounds();
- Rectangle destBounds = TransformHelpers.GetTransformedBoundingRectangle(srcBounds, matrix);
- Matrix3x2 centeredMatrix = TransformHelpers.GetCenteredTransformMatrix(srcBounds, destBounds, matrix);
-
- // We pass a new rectangle here based on the dest bounds since we've offset the matrix
- blend.Mutate(x => x.Transform(
- centeredMatrix,
- KnownResamplers.Bicubic,
- new Rectangle(0, 0, destBounds.Width, destBounds.Height)));
+ // Apply a background color so we can see the translation.
+ blend.Mutate(x => x.Transform(builder).BackgroundColor(NamedColors.HotPink));
+ // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor
var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2);
image.Mutate(x => x.DrawImage(blend, position, mode, .75F));
image.DebugSave(provider, new[] { "Transformed" });
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
index ae572498a..32280d48c 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
@@ -78,15 +78,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
IResampler resampler = GetResampler(resamplerName);
using (Image image = provider.GetImage())
{
- var rotate = Matrix3x2.CreateRotation((float)Math.PI / 4F, new Vector2(5 / 2F, 5 / 2F));
- var translate = Matrix3x2.CreateTranslation((7 - 5) / 2F, (7 - 5) / 2F);
+ AffineTransformBuilder builder = new AffineTransformBuilder(image.Size())
+ .AppendRotateMatrixDegrees((float)Math.PI / 4F);
- Rectangle sourceRectangle = image.Bounds();
- Matrix3x2 matrix = rotate * translate;
-
- Rectangle destRectangle = TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix);
-
- image.Mutate(c => c.Transform(matrix, resampler, destRectangle));
+ image.Mutate(c => c.Transform(builder, resampler));
image.DebugSave(provider, resamplerName);
VerifyAllPixelsAreWhiteOrTransparent(image);
@@ -104,14 +99,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
using (Image image = provider.GetImage())
{
- Matrix3x2 rotate = Matrix3x2Extensions.CreateRotationDegrees(angleDeg);
- var translate = Matrix3x2.CreateTranslation(tx, ty);
- var scale = Matrix3x2.CreateScale(sx, sy);
- Matrix3x2 m = rotate * scale * translate;
+ image.DebugSave(provider, $"_original");
+ AffineTransformBuilder builder = new AffineTransformBuilder(image.Size())
+ .AppendRotateMatrixDegrees(angleDeg)
+ .AppendScaleMatrix(new SizeF(sx, sy))
+ .AppendTranslationMatrix(new PointF(tx, ty));
- this.PrintMatrix(m);
+ this.PrintMatrix(builder.BuildMatrix());
- image.Mutate(i => i.Transform(m, KnownResamplers.Bicubic));
+ image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic));
FormattableString testOutputDetails = $"R({angleDeg})_S({sx},{sy})_T({tx},{ty})";
image.DebugSave(provider, testOutputDetails);
@@ -126,9 +122,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
using (Image image = provider.GetImage())
{
- Matrix3x2 m = this.MakeManuallyCenteredMatrix(angleDeg, s, image);
+ AffineTransformBuilder builder = new AffineTransformBuilder(image.Size())
+ .AppendRotateMatrixDegrees(angleDeg)
+ .AppendScaleMatrix(new SizeF(s, s));
- image.Mutate(i => i.Transform(m, KnownResamplers.Bicubic));
+ image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic));
FormattableString testOutputDetails = $"R({angleDeg})_S({s})";
image.DebugSave(provider, testOutputDetails);
@@ -155,13 +153,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void Transform_FromSourceRectangle1(TestImageProvider provider)
where TPixel : struct, IPixel
{
- var rectangle = new Rectangle(48, 0, 96, 36);
+ var rectangle = new Rectangle(48, 0, 48, 24);
using (Image image = provider.GetImage())
{
- var m = Matrix3x2.CreateScale(2.0F, 1.5F);
+ image.DebugSave(provider, $"_original");
+ AffineTransformBuilder builder = new AffineTransformBuilder(rectangle)
+ .AppendScaleMatrix(new SizeF(2, 1.5F));
- image.Mutate(i => i.Transform(m, KnownResamplers.Spline, rectangle));
+ image.Mutate(i => i.Transform(builder, KnownResamplers.Spline));
image.DebugSave(provider);
image.CompareToReferenceOutput(ValidatorComparer, provider);
@@ -173,13 +173,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void Transform_FromSourceRectangle2(TestImageProvider provider)
where TPixel : struct, IPixel
{
- var rectangle = new Rectangle(0, 24, 48, 48);
+ var rectangle = new Rectangle(0, 24, 48, 24);
using (Image image = provider.GetImage())
{
- var m = Matrix3x2.CreateScale(1.0F, 2.0F);
+ AffineTransformBuilder builder = new AffineTransformBuilder(rectangle)
+ .AppendScaleMatrix(new SizeF(1F, 2F));
- image.Mutate(i => i.Transform(m, KnownResamplers.Spline, rectangle));
+ image.Mutate(i => i.Transform(builder, KnownResamplers.Spline));
image.DebugSave(provider);
image.CompareToReferenceOutput(ValidatorComparer, provider);
@@ -194,12 +195,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
IResampler sampler = GetResampler(resamplerName);
using (Image image = provider.GetImage())
{
- Matrix3x2 m = this.MakeManuallyCenteredMatrix(50, 0.6f, image);
+ AffineTransformBuilder builder = new AffineTransformBuilder(image.Size())
+ .AppendRotateMatrixDegrees(50)
+ .AppendScaleMatrix(new SizeF(.6F, .6F));
- image.Mutate(i =>
- {
- i.Transform(m, sampler);
- });
+ image.Mutate(i => i.Transform(builder, sampler));
image.DebugSave(provider, resamplerName);
image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName);