diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs
index e5ce1450f..ff44915b1 100644
--- a/src/ImageSharp/Processing/AffineTransformBuilder.cs
+++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs
@@ -30,6 +30,11 @@ namespace SixLabors.ImageSharp.Processing
: this(sourceRectangle.Size)
=> this.rectangle = sourceRectangle;
+ ///
+ /// Gets the source image size.
+ ///
+ internal Size Size { get; }
+
///
/// Prepends a centered rotation matrix using the given rotation in degrees.
///
@@ -41,11 +46,6 @@ namespace SixLabors.ImageSharp.Processing
return this;
}
- ///
- /// Gets the source image size.
- ///
- internal Size Size { get; }
-
///
/// Appends a centered rotation matrix using the given rotation in degrees.
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
index fb42b8334..2370adb86 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
@@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// Initializes a new instance of the class.
///
- /// The transform matrix
+ /// The transform matrix.
/// The sampler to perform the transform operation.
- /// The source image size
+ /// The source image size.
public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size sourceSize)
{
Guard.NotNull(sampler, nameof(sampler));
@@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration)
{
// Handle tranforms that result in output identical to the original.
- if (this.TransformMatrix.Equals(Matrix3x2.Identity))
+ if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix3x2.Identity))
{
// The cloned will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs
deleted file mode 100644
index 962b9e4c9..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs
+++ /dev/null
@@ -1,40 +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 non-affine transforms
- ///
- /// The pixel format.
- internal abstract class CenteredProjectiveTransformProcessor : ProjectiveTransformProcessor
- 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 CenteredProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size sourceSize)
- : base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix))
- {
- }
-
- ///
- protected override Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
- {
- return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix);
- }
-
- private static Size GetTransformedDimensions(Size sourceDimensions, Matrix4x4 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/InterpolatedTransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs
deleted file mode 100644
index 4737a4102..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.PixelFormats;
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The base class for performing interpolated affine and non-affine transforms.
- ///
- /// The pixel format.
- internal abstract class InterpolatedTransformProcessorBase : TransformProcessorBase
- where TPixel : struct, IPixel
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The sampler to perform the transform operation.
- protected InterpolatedTransformProcessorBase(IResampler sampler)
- {
- Guard.NotNull(sampler, nameof(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 normalized.
- ///
- /// 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 reference to the collection of weights
- /// The length of the weights collection
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, ref float weightsRef, int length)
- {
- float sum = 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 weightsRef, x) = weight;
- }
-
- if (sum > 0)
- {
- for (int i = 0; i < length; i++)
- {
- ref float wRef = ref Unsafe.Add(ref weightsRef, i);
- wRef /= sum;
- }
- }
- }
-
- ///
- /// Calculated the weights for the given point.
- ///
- /// The minimum source bounds
- /// The maximum source bounds
- /// The transformed point dimension
- /// The sampler
- /// The reference to the collection of weights
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, ref float weightsRef)
- {
- for (int x = 0, i = sourceMin; i <= sourceMax; i++, x++)
- {
- Unsafe.Add(ref weightsRef, x) = sampler.GetValue(i - point);
- }
- }
-
- ///
- /// 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);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
index 50af26aeb..bfde1769c 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.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,22 +16,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Provides the base methods to perform non-affine transforms on an image.
///
/// The pixel format.
- internal class ProjectiveTransformProcessor : InterpolatedTransformProcessorBase
+ internal class ProjectiveTransformProcessor : TransformProcessorBase
where TPixel : struct, IPixel
{
///
/// Initializes a new instance of the class.
///
- /// The transform matrix
+ /// The transform matrix.
/// The sampler to perform the transform operation.
- /// The target dimensions to constrain the transformed image to.
- public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions)
- : base(sampler)
+ /// The source image size.
+ public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size sourceSize)
{
+ Guard.NotNull(sampler, nameof(sampler));
+ this.Sampler = sampler;
this.TransformMatrix = matrix;
- this.TargetDimensions = targetDimensions;
+ this.TargetDimensions = TransformUtils.GetTransformedSize(sourceSize, matrix);
}
+ ///
+ /// Gets the sampler to perform interpolation of the transform operation.
+ ///
+ public IResampler Sampler { get; }
+
///
/// Gets the matrix used to supply the projective transform
///
@@ -60,17 +62,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
{
+ // Handle tranforms that result in output identical to the original.
+ if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix4x4.Identity))
+ {
+ // The 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(0, 0, width, height);
-
- // Since could potentially be resizing the canvas we might need to re-calculate the matrix
- Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds);
+ var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
// Convert from screen to world space.
- Matrix4x4.Invert(matrix, out matrix);
+ Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 matrix);
const float Epsilon = 0.0000001F;
if (this.Sampler is NearestNeighborResampler)
@@ -92,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int px = (int)MathF.Round(v3.X / z);
int py = (int)MathF.Round(v3.Y / z);
- if (sourceBounds.Contains(px, py))
+ if (sourceRectangle.Contains(px, py))
{
destRow[x] = source[px, py];
}
@@ -103,145 +109,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
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;
-
- // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable:
- var radius = new Vector4(xRadiusScale.radius, yRadiusScale.radius, 0, 0);
-
- 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))
+ var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.Sampler);
+ try
{
- 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));
-
- 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 v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
- float z = MathF.Max(v3.Z, Epsilon);
-
- // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable:
- Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z;
-
- // Clamp sampling pixel radial extents to the source image edges
- Vector4 maxXY = point + radius;
- Vector4 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;
+ 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);
- 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,
- 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);
+ 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 v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
+ Vector2 point = new Vector2(v3.X, v3.Y) / MathF.Max(v3.Z, Epsilon);
- // Reverse the premultiplication
- Vector4Utils.UnPremultiply(ref sum);
- dest.FromVector4(sum);
- }
+ kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan);
}
- });
+
+ PixelOperations.Instance.FromVector4(configuration, vectorSpan, targetRowSpan);
+ }
+ });
+ }
+ finally
+ {
+ kernel.Dispose();
}
}
-
- ///
- /// 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) => this.TransformMatrix;
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformHelpers.cs
deleted file mode 100644
index 2e85f6c2c..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformHelpers.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Numerics;
-using SixLabors.ImageSharp.MetaData.Profiles.Exif;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Primitives;
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// Contains helper methods for working with affine and non-affine transforms
- ///
- internal static class TransformHelpers
- {
- ///
- /// Updates the dimensional metadata of a transformed image
- ///
- /// The pixel format.
- /// The image to update
- public static void UpdateDimensionalMetData(Image image)
- where TPixel : struct, IPixel
- {
- ExifProfile profile = image.MetaData.ExifProfile;
- if (profile is null)
- {
- return;
- }
-
- // Removing the previously stored value allows us to set a value with our own data tag if required.
- if (profile.GetValue(ExifTag.PixelXDimension) != null)
- {
- profile.RemoveValue(ExifTag.PixelXDimension);
-
- if (image.Width <= ushort.MaxValue)
- {
- profile.SetValue(ExifTag.PixelXDimension, (ushort)image.Width);
- }
- else
- {
- profile.SetValue(ExifTag.PixelXDimension, (uint)image.Width);
- }
- }
-
- if (profile.GetValue(ExifTag.PixelYDimension) != null)
- {
- profile.RemoveValue(ExifTag.PixelYDimension);
-
- if (image.Height <= ushort.MaxValue)
- {
- profile.SetValue(ExifTag.PixelYDimension, (ushort)image.Height);
- }
- else
- {
- profile.SetValue(ExifTag.PixelYDimension, (uint)image.Height);
- }
- }
- }
-
- ///
- /// 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(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F);
- var translateToSourceCenter = Matrix3x2.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F);
-
- Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
-
- // Translate back to world to pass to the Transform method.
- 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 Matrix4x4 GetCenteredTransformMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle, Matrix4x4 matrix)
- {
- // We invert the matrix to handle the transformation from screen to world space.
- // This ensures scaling matrices are correct.
- Matrix4x4.Invert(matrix, out Matrix4x4 inverted);
-
- var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0);
- var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0);
-
- Matrix4x4.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix4x4 centered);
-
- // Translate back to world to pass to the Transform method.
- 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, 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)));
- float maxY = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y)));
- float sizeX = maxX - minX + .5F;
- float sizeY = maxY - minY + .5F;
-
- return new Rectangle((int)(MathF.Ceiling(minX) - .5F), (int)(MathF.Ceiling(minY) - .5F), (int)MathF.Floor(sizeX), (int)MathF.Floor(sizeY));
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs
index 13ee90a06..4973b90f4 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorBase.cs
@@ -16,6 +16,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle)
- => TransformHelpers.UpdateDimensionalMetData(destination);
+ => TransformProcessorHelpers.UpdateDimensionalMetData(destination);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
new file mode 100644
index 000000000..f5536d046
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.MetaData.Profiles.Exif;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Contains helper methods for working with transforms.
+ ///
+ internal static class TransformProcessorHelpers
+ {
+ ///
+ /// Updates the dimensional metadata of a transformed image
+ ///
+ /// The pixel format.
+ /// The image to update
+ public static void UpdateDimensionalMetData(Image image)
+ where TPixel : struct, IPixel
+ {
+ ExifProfile profile = image.MetaData.ExifProfile;
+ if (profile is null)
+ {
+ return;
+ }
+
+ // Removing the previously stored value allows us to set a value with our own data tag if required.
+ if (profile.GetValue(ExifTag.PixelXDimension) != null)
+ {
+ profile.RemoveValue(ExifTag.PixelXDimension);
+
+ if (image.Width <= ushort.MaxValue)
+ {
+ profile.SetValue(ExifTag.PixelXDimension, (ushort)image.Width);
+ }
+ else
+ {
+ profile.SetValue(ExifTag.PixelXDimension, (uint)image.Width);
+ }
+ }
+
+ if (profile.GetValue(ExifTag.PixelYDimension) != null)
+ {
+ profile.RemoveValue(ExifTag.PixelYDimension);
+
+ if (image.Height <= ushort.MaxValue)
+ {
+ profile.SetValue(ExifTag.PixelYDimension, (ushort)image.Height);
+ }
+ else
+ {
+ profile.SetValue(ExifTag.PixelYDimension, (uint)image.Height);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
index 10cf49c34..f561d3513 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
@@ -58,6 +58,111 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return centered;
}
+ ///
+ /// Creates a matrix that performs a tapering projective transform.
+ ///
+ ///
+ /// The rectangular size of the image being transformed.
+ /// An enumeration that indicates the side of the rectangle that tapers.
+ /// An enumeration that indicates on which corners to taper the rectangle.
+ /// The amount to taper.
+ /// The
+ public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction)
+ {
+ Matrix4x4 matrix = Matrix4x4.Identity;
+
+ switch (side)
+ {
+ case TaperSide.Left:
+ matrix.M11 = fraction;
+ matrix.M22 = fraction;
+ matrix.M13 = (fraction - 1) / size.Width;
+
+ switch (corner)
+ {
+ case TaperCorner.RightOrBottom:
+ break;
+
+ case TaperCorner.LeftOrTop:
+ matrix.M12 = size.Height * matrix.M13;
+ matrix.M32 = size.Height * (1 - fraction);
+ break;
+
+ case TaperCorner.Both:
+ matrix.M12 = size.Height * .5F * matrix.M13;
+ matrix.M32 = size.Height * (1 - fraction) / 2;
+ break;
+ }
+
+ break;
+
+ case TaperSide.Top:
+ matrix.M11 = fraction;
+ matrix.M22 = fraction;
+ matrix.M23 = (fraction - 1) / size.Height;
+
+ switch (corner)
+ {
+ case TaperCorner.RightOrBottom:
+ break;
+
+ case TaperCorner.LeftOrTop:
+ matrix.M21 = size.Width * matrix.M23;
+ matrix.M31 = size.Width * (1 - fraction);
+ break;
+
+ case TaperCorner.Both:
+ matrix.M21 = size.Width * .5F * matrix.M23;
+ matrix.M31 = size.Width * (1 - fraction) / 2;
+ break;
+ }
+
+ break;
+
+ case TaperSide.Right:
+ matrix.M11 = 1 / fraction;
+ matrix.M13 = (1 - fraction) / (size.Width * fraction);
+
+ switch (corner)
+ {
+ case TaperCorner.RightOrBottom:
+ break;
+
+ case TaperCorner.LeftOrTop:
+ matrix.M12 = size.Height * matrix.M13;
+ break;
+
+ case TaperCorner.Both:
+ matrix.M12 = size.Height * .5F * matrix.M13;
+ break;
+ }
+
+ break;
+
+ case TaperSide.Bottom:
+ matrix.M22 = 1 / fraction;
+ matrix.M23 = (1 - fraction) / (size.Height * fraction);
+
+ switch (corner)
+ {
+ case TaperCorner.RightOrBottom:
+ break;
+
+ case TaperCorner.LeftOrTop:
+ matrix.M21 = size.Width * matrix.M23;
+ break;
+
+ case TaperCorner.Both:
+ matrix.M21 = size.Width * .5F * matrix.M23;
+ break;
+ }
+
+ break;
+ }
+
+ return matrix;
+ }
+
///
/// Returns the rectangle bounds relative to the source for the given transformation matrix.
///
@@ -114,6 +219,63 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix);
+ return ConstrainSize(rectangle);
+ }
+
+ ///
+ /// 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, Matrix4x4 matrix)
+ {
+ if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix))
+ {
+ return rectangle;
+ }
+
+ Vector2 GetVector(float x, float y)
+ {
+ const float Epsilon = 0.0000001F;
+ var v3 = Vector3.Transform(new Vector3(x, y, 1F), matrix);
+ return new Vector2(v3.X, v3.Y) / MathF.Max(v3.Z, Epsilon);
+ }
+
+ Vector2 tl = GetVector(rectangle.Left, rectangle.Top);
+ Vector2 tr = GetVector(rectangle.Right, rectangle.Top);
+ Vector2 bl = GetVector(rectangle.Left, rectangle.Bottom);
+ Vector2 br = GetVector(rectangle.Right, rectangle.Bottom);
+
+ 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, Matrix4x4 matrix)
+ {
+ Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!");
+
+ if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity))
+ {
+ return size;
+ }
+
+ Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix);
+
+ return ConstrainSize(rectangle);
+ }
+
+ private static Size ConstrainSize(Rectangle rectangle)
+ {
// 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);
diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
new file mode 100644
index 000000000..3edc993c4
--- /dev/null
+++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
@@ -0,0 +1,113 @@
+// 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 projective transforms.
+ ///
+ public class ProjectiveTransformBuilder
+ {
+ private readonly List matrices = new List();
+ private Rectangle rectangle;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The source image size.
+ public ProjectiveTransformBuilder(Size sourceSize) => this.Size = sourceSize;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The source rectangle.
+ public ProjectiveTransformBuilder(Rectangle sourceRectangle)
+ : this(sourceRectangle.Size)
+ => this.rectangle = sourceRectangle;
+
+ ///
+ /// Gets the source image size.
+ ///
+ internal Size Size { get; }
+
+ ///
+ /// Prepends a matrix that performs a tapering projective transform.
+ ///
+ /// An enumeration that indicates the side of the rectangle that tapers.
+ /// An enumeration that indicates on which corners to taper the rectangle.
+ /// The amount to taper.
+ /// The .
+ public ProjectiveTransformBuilder PrependTaperMatrix(TaperSide side, TaperCorner corner, float fraction)
+ {
+ this.matrices.Insert(0, TransformUtils.CreateTaperMatrix(this.Size, side, corner, fraction));
+ return this;
+ }
+
+ ///
+ /// Appends a matrix that performs a tapering projective transform.
+ ///
+ /// An enumeration that indicates the side of the rectangle that tapers.
+ /// An enumeration that indicates on which corners to taper the rectangle.
+ /// The amount to taper.
+ /// The .
+ public ProjectiveTransformBuilder AppendTaperMatrix(TaperSide side, TaperCorner corner, float fraction)
+ {
+ this.matrices.Add(TransformUtils.CreateTaperMatrix(this.Size, side, corner, fraction));
+ return this;
+ }
+
+ ///
+ /// Prepends a raw matrix.
+ ///
+ /// The matrix to prepend.
+ /// The .
+ public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix)
+ {
+ this.matrices.Insert(0, matrix);
+ return this;
+ }
+
+ ///
+ /// Appends a raw matrix.
+ ///
+ /// The matrix to append.
+ /// The .
+ public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix)
+ {
+ this.matrices.Add(matrix);
+ return this;
+ }
+
+ ///
+ /// Returns the combined matrix.
+ ///
+ /// The .
+ public Matrix4x4 BuildMatrix()
+ {
+ Matrix4x4 matrix = Matrix4x4.Identity;
+
+ // Translate the origin matrix to cater for source rectangle offsets.
+ if (!this.rectangle.Equals(default))
+ {
+ matrix *= Matrix4x4.CreateTranslation(new Vector3(-this.rectangle.Location, 0));
+ }
+
+ foreach (Matrix4x4 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/ProjectiveTransformHelper.cs b/src/ImageSharp/Processing/ProjectiveTransformHelper.cs
deleted file mode 100644
index 4057ec586..000000000
--- a/src/ImageSharp/Processing/ProjectiveTransformHelper.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System.Numerics;
-using SixLabors.Primitives;
-
-namespace SixLabors.ImageSharp.Processing
-{
- ///
- /// Enumerates the various options which determine which side to taper
- ///
- public enum TaperSide
- {
- ///
- /// Taper the left side
- ///
- Left,
-
- ///
- /// Taper the top side
- ///
- Top,
-
- ///
- /// Taper the right side
- ///
- Right,
-
- ///
- /// Taper the bottom side
- ///
- Bottom
- }
-
- ///
- /// Enumerates the various options which determine how to taper corners
- ///
- public enum TaperCorner
- {
- ///
- /// Taper the left or top corner
- ///
- LeftOrTop,
-
- ///
- /// Taper the right or bottom corner
- ///
- RightOrBottom,
-
- ///
- /// Taper the both sets of corners
- ///
- Both
- }
-
- ///
- /// Provides helper methods for working with generalized projective transforms.
- ///
- public static class ProjectiveTransformHelper
- {
- ///
- /// Creates a matrix that performs a tapering projective transform.
- ///
- ///
- /// The rectangular size of the image being transformed.
- /// An enumeration that indicates the side of the rectangle that tapers.
- /// An enumeration that indicates on which corners to taper the rectangle.
- /// The amount to taper.
- /// The
- public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide taperSide, TaperCorner taperCorner, float taperFraction)
- {
- Matrix4x4 matrix = Matrix4x4.Identity;
-
- switch (taperSide)
- {
- case TaperSide.Left:
- matrix.M11 = taperFraction;
- matrix.M22 = taperFraction;
- matrix.M13 = (taperFraction - 1) / size.Width;
-
- switch (taperCorner)
- {
- case TaperCorner.RightOrBottom:
- break;
-
- case TaperCorner.LeftOrTop:
- matrix.M12 = size.Height * matrix.M13;
- matrix.M32 = size.Height * (1 - taperFraction);
- break;
-
- case TaperCorner.Both:
- matrix.M12 = (size.Height * 0.5f) * matrix.M13;
- matrix.M32 = size.Height * (1 - taperFraction) / 2;
- break;
- }
-
- break;
-
- case TaperSide.Top:
- matrix.M11 = taperFraction;
- matrix.M22 = taperFraction;
- matrix.M23 = (taperFraction - 1) / size.Height;
-
- switch (taperCorner)
- {
- case TaperCorner.RightOrBottom:
- break;
-
- case TaperCorner.LeftOrTop:
- matrix.M21 = size.Width * matrix.M23;
- matrix.M31 = size.Width * (1 - taperFraction);
- break;
-
- case TaperCorner.Both:
- matrix.M21 = (size.Width * 0.5f) * matrix.M23;
- matrix.M31 = size.Width * (1 - taperFraction) / 2;
- break;
- }
-
- break;
-
- case TaperSide.Right:
- matrix.M11 = 1 / taperFraction;
- matrix.M13 = (1 - taperFraction) / (size.Width * taperFraction);
-
- switch (taperCorner)
- {
- case TaperCorner.RightOrBottom:
- break;
-
- case TaperCorner.LeftOrTop:
- matrix.M12 = size.Height * matrix.M13;
- break;
-
- case TaperCorner.Both:
- matrix.M12 = (size.Height * 0.5f) * matrix.M13;
- break;
- }
-
- break;
-
- case TaperSide.Bottom:
- matrix.M22 = 1 / taperFraction;
- matrix.M23 = (1 - taperFraction) / (size.Height * taperFraction);
-
- switch (taperCorner)
- {
- case TaperCorner.RightOrBottom:
- break;
-
- case TaperCorner.LeftOrTop:
- matrix.M21 = size.Width * matrix.M23;
- break;
-
- case TaperCorner.Both:
- matrix.M21 = (size.Width * 0.5f) * matrix.M23;
- break;
- }
-
- break;
- }
-
- return matrix;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/TaperCorner.cs b/src/ImageSharp/Processing/TaperCorner.cs
new file mode 100644
index 000000000..395b17142
--- /dev/null
+++ b/src/ImageSharp/Processing/TaperCorner.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// Enumerates the various options which determine how to taper corners
+ ///
+ public enum TaperCorner
+ {
+ ///
+ /// Taper the left or top corner
+ ///
+ LeftOrTop,
+
+ ///
+ /// Taper the right or bottom corner
+ ///
+ RightOrBottom,
+
+ ///
+ /// Taper the both sets of corners
+ ///
+ Both
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/TaperSide.cs b/src/ImageSharp/Processing/TaperSide.cs
new file mode 100644
index 000000000..226d11aed
--- /dev/null
+++ b/src/ImageSharp/Processing/TaperSide.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// Enumerates the various options which determine which side to taper
+ ///
+ public enum TaperSide
+ {
+ ///
+ /// Taper the left side
+ ///
+ Left,
+
+ ///
+ /// Taper the top side
+ ///
+ Top,
+
+ ///
+ /// Taper the right side
+ ///
+ Right,
+
+ ///
+ /// Taper the bottom side
+ ///
+ Bottom
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/TransformExtensions.cs b/src/ImageSharp/Processing/TransformExtensions.cs
index 4cbd1b041..ea789eb3d 100644
--- a/src/ImageSharp/Processing/TransformExtensions.cs
+++ b/src/ImageSharp/Processing/TransformExtensions.cs
@@ -1,10 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
-using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
@@ -14,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing
public static class TransformExtensions
{
///
- /// Transforms an image by the given matrix.
+ /// Performs an affine transform of an image.
///
/// The pixel format.
/// The image to transform.
@@ -25,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing
=> Transform(source, builder, KnownResamplers.Bicubic);
///
- /// Transforms an image by the given matrix using the specified sampling algorithm.
+ /// Performs an affine transform of an image using the specified sampling algorithm.
///
/// The pixel format.
/// The image to transform.
@@ -37,44 +35,26 @@ namespace SixLabors.ImageSharp.Processing
=> source.ApplyProcessor(new AffineTransformProcessor(builder.BuildMatrix(), sampler, builder.Size));
///
- /// Transforms an image by the given matrix.
+ /// Performs a projective transform of an image.
///
/// The pixel format.
/// The image to transform.
- /// The transformation matrix.
- /// The
- public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix)
- where TPixel : struct, IPixel
- => Transform(source, matrix, KnownResamplers.Bicubic);
-
- ///
- /// Applies a projective transform to the 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 affine transform builder.
/// The
- public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler)
+ public static IImageProcessingContext Transform(this IImageProcessingContext source, ProjectiveTransformBuilder builder)
where TPixel : struct, IPixel
- => source.ApplyProcessor(new ProjectiveTransformProcessor(matrix, sampler, source.GetCurrentSize()));
+ => Transform(source, builder, KnownResamplers.Bicubic);
///
- /// Applies a projective transform to the image by the given matrix using the specified sampling algorithm.
- /// TODO: Should we be offsetting the matrix here?
+ /// Performs a projective transform of an image using the specified sampling algorithm.
///
/// The pixel format.
/// The image to transform.
- /// The transformation matrix.
+ /// The projective transform builder.
/// 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)
+ public static IImageProcessingContext Transform(this IImageProcessingContext source, ProjectiveTransformBuilder builder, IResampler sampler)
where TPixel : struct, IPixel
- {
- var t = Matrix4x4.CreateTranslation(new Vector3(-rectangle.Location, 0));
- Matrix4x4 combinedMatrix = t * matrix;
- return source.ApplyProcessor(new ProjectiveTransformProcessor(combinedMatrix, sampler, rectangle.Size));
- }
+ => source.ApplyProcessor(new ProjectiveTransformProcessor(builder.BuildMatrix(), sampler, builder.Size));
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
index 32280d48c..e05a8e381 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
@@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
using (Image image = provider.GetImage())
{
AffineTransformBuilder builder = new AffineTransformBuilder(image.Size())
- .AppendRotateMatrixDegrees((float)Math.PI / 4F);
+ .AppendRotateMatrixDegrees(30);
image.Mutate(c => c.Transform(builder, resampler));
image.DebugSave(provider, resamplerName);
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
index 5190a71e7..0362d75a0 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
@@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.5f, 3);
private ITestOutputHelper Output { get; }
-
+
public static readonly TheoryData ResamplerNames = new TheoryData
{
nameof(KnownResamplers.Bicubic),
@@ -60,10 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
};
- public ProjectiveTransformTests(ITestOutputHelper output)
- {
- this.Output = output;
- }
+ public ProjectiveTransformTests(ITestOutputHelper output) => this.Output = output;
[Theory]
[WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)]
@@ -73,9 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
IResampler sampler = GetResampler(resamplerName);
using (Image image = provider.GetImage())
{
- Matrix4x4 m = ProjectiveTransformHelper.CreateTaperMatrix(image.Size(), TaperSide.Right, TaperCorner.Both, .5F);
+ ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder(image.Size())
+ .AppendTaperMatrix(TaperSide.Right, TaperCorner.Both, .5F);
- image.Mutate(i => { i.Transform(m, sampler); });
+ image.Mutate(i => i.Transform(builder, sampler));
image.DebugSave(provider, resamplerName);
image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName);
@@ -89,8 +87,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
using (Image image = provider.GetImage())
{
- Matrix4x4 m = ProjectiveTransformHelper.CreateTaperMatrix(image.Size(), taperSide, taperCorner, .5F);
- image.Mutate(i => { i.Transform(m); });
+ ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder(image.Size())
+ .AppendTaperMatrix(taperSide, taperCorner, .5F);
+
+ image.Mutate(i => i.Transform(builder));
FormattableString testOutputDetails = $"{taperSide}-{taperCorner}";
image.DebugSave(provider, testOutputDetails);
@@ -110,10 +110,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
// https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/non-affine
using (Image image = provider.GetImage())
{
- Matrix4x4 m = Matrix4x4.Identity;
- m.M13 = 0.01F;
+ Matrix4x4 matrix = Matrix4x4.Identity;
+ matrix.M13 = 0.01F;
+
+ ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder(image.Size())
+ .AppendMatrix(matrix);
- image.Mutate(i => { i.Transform(m); });
+ image.Mutate(i => i.Transform(builder));
image.DebugSave(provider);
image.CompareToReferenceOutput(TolerantComparer, provider);
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs
index 146ed6230..909e50535 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs
@@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType);
Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType);
- TransformHelpers.UpdateDimensionalMetData(img);
+ TransformProcessorHelpers.UpdateDimensionalMetData(img);
Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType);
Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType);