From 3d86c583eeb4681f33847a9a7e7db8fde4994b56 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 21 Oct 2024 20:08:10 +1000 Subject: [PATCH] Refactor and fix for transform space --- .../Common/Helpers/QuadDistortionHelper.cs | 79 --------- .../Processing/AffineTransformBuilder.cs | 19 +-- .../AffineTransformProcessor{TPixel}.cs | 29 ++-- .../Linear}/GaussianEliminationSolver.cs | 2 +- .../ProjectiveTransformProcessor{TPixel}.cs | 25 +-- .../Processors/Transforms/TransformUtils.cs | 151 +++++++++++++++--- .../Processing/ProjectiveTransformBuilder.cs | 23 +-- .../Common/GaussianEliminationSolverTest.cs | 2 +- .../Transforms/ProjectiveTransformTests.cs | 10 +- ...X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png | 4 +- ...[ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png | 4 +- ...ntF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png | 4 +- ... X=140, Y=210 ]-PointF [ X=15, Y=125 ].png | 4 +- 13 files changed, 184 insertions(+), 172 deletions(-) delete mode 100644 src/ImageSharp/Common/Helpers/QuadDistortionHelper.cs rename src/ImageSharp/{Common/Helpers => Processing/Processors/Transforms/Linear}/GaussianEliminationSolver.cs (97%) diff --git a/src/ImageSharp/Common/Helpers/QuadDistortionHelper.cs b/src/ImageSharp/Common/Helpers/QuadDistortionHelper.cs deleted file mode 100644 index c1eee3daff..0000000000 --- a/src/ImageSharp/Common/Helpers/QuadDistortionHelper.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Common.Helpers; - -/// -/// Provides helper methods for performing quad distortion transformations. -/// -internal static class QuadDistortionHelper -{ - /// - /// Computes the projection matrix for a quad distortion transformation. - /// - /// The source rectangle. - /// The top-left point of the distorted quad. - /// The top-right point of the distorted quad. - /// The bottom-right point of the distorted quad. - /// The bottom-left point of the distorted quad. - /// The computed projection matrix for the quad distortion. - /// - /// This method is based on the algorithm described in the following article: - /// - /// - public static Matrix4x4 ComputeQuadDistortMatrix( - Rectangle rectangle, - PointF topLeft, - PointF topRight, - PointF bottomRight, - PointF bottomLeft) - { - PointF p1 = new(rectangle.X, rectangle.Y); - PointF p2 = new(rectangle.X + rectangle.Width, rectangle.Y); - PointF p3 = new(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height); - PointF p4 = new(rectangle.X, rectangle.Y + rectangle.Height); - - PointF q1 = topLeft; - PointF q2 = topRight; - PointF q3 = bottomRight; - PointF q4 = bottomLeft; - - double[][] matrixData = - [ - [p1.X, p1.Y, 1, 0, 0, 0, -p1.X * q1.X, -p1.Y * q1.X], - [0, 0, 0, p1.X, p1.Y, 1, -p1.X * q1.Y, -p1.Y * q1.Y], - [p2.X, p2.Y, 1, 0, 0, 0, -p2.X * q2.X, -p2.Y * q2.X], - [0, 0, 0, p2.X, p2.Y, 1, -p2.X * q2.Y, -p2.Y * q2.Y], - [p3.X, p3.Y, 1, 0, 0, 0, -p3.X * q3.X, -p3.Y * q3.X], - [0, 0, 0, p3.X, p3.Y, 1, -p3.X * q3.Y, -p3.Y * q3.Y], - [p4.X, p4.Y, 1, 0, 0, 0, -p4.X * q4.X, -p4.Y * q4.X], - [0, 0, 0, p4.X, p4.Y, 1, -p4.X * q4.Y, -p4.Y * q4.Y], - ]; - - double[] b = - [ - q1.X, - q1.Y, - q2.X, - q2.Y, - q3.X, - q3.Y, - q4.X, - q4.Y, - ]; - - GaussianEliminationSolver.Solve(matrixData, b); - -#pragma warning disable SA1117 - Matrix4x4 projectionMatrix = new( - (float)b[0], (float)b[3], 0, (float)b[6], - (float)b[1], (float)b[4], 0, (float)b[7], - 0, 0, 1, 0, - (float)b[2], (float)b[5], 0, 1); -#pragma warning restore SA1117 - - return projectionMatrix; - } -} diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 4ac9546f39..6d1e8aaa55 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing; /// public class AffineTransformBuilder { - private readonly List> transformMatrixFactories = new(); + private readonly List> transformMatrixFactories = []; /// /// Initializes a new instance of the class. @@ -301,7 +301,8 @@ public class AffineTransformBuilder /// /// The source image size. /// The . - public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + public Matrix3x2 BuildMatrix(Size sourceSize) + => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); /// /// Returns the combined transform matrix for a given source rectangle. @@ -345,18 +346,8 @@ public class AffineTransformBuilder /// The . public Size GetTransformedSize(Rectangle sourceRectangle) { - Size size = sourceRectangle.Size; - - // Translate the origin matrix to cater for source rectangle offsets. - Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location); - - foreach (Func factory in this.transformMatrixFactories) - { - matrix *= factory(size); - CheckDegenerate(matrix); - } - - return TransformUtils.GetTransformedSize(matrix, size, this.TransformSpace); + Matrix3x2 matrix = this.BuildMatrix(sourceRectangle); + return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); } private static void CheckDegenerate(Matrix3x2 matrix) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index c5c2a778eb..888d513206 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -61,12 +61,12 @@ internal class AffineTransformProcessor : TransformProcessor, IR if (matrix.Equals(Matrix3x2.Identity)) { // The clone will be blank here copy all the pixel data over - var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + Buffer2DRegion destinationBuffer = destination.PixelBuffer.GetRegion(interest); for (int y = 0; y < sourceBuffer.Height; y++) { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y)); } return; @@ -77,7 +77,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR if (sampler is NearestNeighborResampler) { - var nnOperation = new NNAffineOperation( + NNAffineOperation nnOperation = new( source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, @@ -91,7 +91,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR return; } - var operation = new AffineOperation( + AffineOperation operation = new( configuration, source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), @@ -128,17 +128,17 @@ internal class AffineTransformProcessor : TransformProcessor, IR [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span destRow = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - for (int x = 0; x < destRow.Length; x++) + for (int x = 0; x < destinationRowSpan.Length; x++) { - var point = Vector2.Transform(new Vector2(x, y), this.matrix); + Vector2 point = Vector2.Transform(new Vector2(x, y), this.matrix); int px = (int)MathF.Round(point.X); int py = (int)MathF.Round(point.Y); if (this.bounds.Contains(px, py)) { - destRow[x] = this.source.GetElementUnsafe(px, py); + destinationRowSpan[x] = this.source.GetElementUnsafe(px, py); } } } @@ -195,16 +195,16 @@ internal class AffineTransformProcessor : TransformProcessor, IR for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, - rowSpan, + destinationRowSpan, span, PixelConversionModifiers.Scale); for (int x = 0; x < span.Length; x++) { - var point = Vector2.Transform(new Vector2(x, y), matrix); + Vector2 point = Vector2.Transform(new Vector2(x, y), matrix); float pY = point.Y; float pX = point.X; @@ -221,13 +221,14 @@ internal class AffineTransformProcessor : TransformProcessor, IR Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { + Span sourceRowSpan = this.source.DangerousGetRowSpan(yK); float yWeight = sampler.GetValue(yK - pY); for (int xK = left; xK <= right; xK++) { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = sourceRowSpan[xK].ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -240,7 +241,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR PixelOperations.Instance.FromVector4Destructive( this.configuration, span, - rowSpan, + destinationRowSpan, PixelConversionModifiers.Scale); } } diff --git a/src/ImageSharp/Common/Helpers/GaussianEliminationSolver.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs similarity index 97% rename from src/ImageSharp/Common/Helpers/GaussianEliminationSolver.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs index ee87cdc864..1190de4352 100644 --- a/src/ImageSharp/Common/Helpers/GaussianEliminationSolver.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Common.Helpers; +namespace SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; /// /// Represents a solver for systems of linear equations using the Gaussian Elimination method. diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index b741dc4ee6..068f69cebc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -61,12 +61,12 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (matrix.Equals(Matrix4x4.Identity)) { // The clone will be blank here copy all the pixel data over - var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + Buffer2DRegion destinationBuffer = destination.PixelBuffer.GetRegion(interest); for (int y = 0; y < sourceBuffer.Height; y++) { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y)); } return; @@ -77,7 +77,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (sampler is NearestNeighborResampler) { - var nnOperation = new NNProjectiveOperation( + NNProjectiveOperation nnOperation = new( source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, @@ -91,7 +91,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor return; } - var operation = new ProjectiveOperation( + ProjectiveOperation operation = new( configuration, source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), @@ -128,9 +128,9 @@ internal class ProjectiveTransformProcessor : TransformProcessor [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span destRow = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - for (int x = 0; x < destRow.Length; x++) + for (int x = 0; x < destinationRowSpan.Length; x++) { Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); int px = (int)MathF.Round(point.X); @@ -138,7 +138,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (this.bounds.Contains(px, py)) { - destRow[x] = this.source.GetElementUnsafe(px, py); + destinationRowSpan[x] = this.source.GetElementUnsafe(px, py); } } } @@ -195,10 +195,10 @@ internal class ProjectiveTransformProcessor : TransformProcessor for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, - rowSpan, + destinationRowSpan, span, PixelConversionModifiers.Scale); @@ -221,13 +221,14 @@ internal class ProjectiveTransformProcessor : TransformProcessor Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { + Span sourceRowSpan = this.source.DangerousGetRowSpan(yK); float yWeight = sampler.GetValue(yK - pY); for (int xK = left; xK <= right; xK++) { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = sourceRowSpan[xK].ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -240,7 +241,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor PixelOperations.Instance.FromVector4Destructive( this.configuration, span, - rowSpan, + destinationRowSpan, PixelConversionModifiers.Scale); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs index fa6ab137b9..47b3250b87 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; namespace SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -278,6 +279,91 @@ internal static class TransformUtils return matrix; } + /// + /// Computes the projection matrix for a quad distortion transformation. + /// + /// The source rectangle. + /// The top-left point of the distorted quad. + /// The top-right point of the distorted quad. + /// The bottom-right point of the distorted quad. + /// The bottom-left point of the distorted quad. + /// The to use when creating the matrix. + /// The computed projection matrix for the quad distortion. + /// + /// This method is based on the algorithm described in the following article: + /// + /// + public static Matrix4x4 CreateQuadDistortionMatrix( + Rectangle rectangle, + PointF topLeft, + PointF topRight, + PointF bottomRight, + PointF bottomLeft, + TransformSpace transformSpace) + { + PointF p1 = new(rectangle.X, rectangle.Y); + PointF p2 = new(rectangle.X + rectangle.Width, rectangle.Y); + PointF p3 = new(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height); + PointF p4 = new(rectangle.X, rectangle.Y + rectangle.Height); + + PointF q1 = topLeft; + PointF q2 = topRight; + PointF q3 = bottomRight; + PointF q4 = bottomLeft; + + double[][] matrixData = + [ + [p1.X, p1.Y, 1, 0, 0, 0, -p1.X * q1.X, -p1.Y * q1.X], + [0, 0, 0, p1.X, p1.Y, 1, -p1.X * q1.Y, -p1.Y * q1.Y], + [p2.X, p2.Y, 1, 0, 0, 0, -p2.X * q2.X, -p2.Y * q2.X], + [0, 0, 0, p2.X, p2.Y, 1, -p2.X * q2.Y, -p2.Y * q2.Y], + [p3.X, p3.Y, 1, 0, 0, 0, -p3.X * q3.X, -p3.Y * q3.X], + [0, 0, 0, p3.X, p3.Y, 1, -p3.X * q3.Y, -p3.Y * q3.Y], + [p4.X, p4.Y, 1, 0, 0, 0, -p4.X * q4.X, -p4.Y * q4.X], + [0, 0, 0, p4.X, p4.Y, 1, -p4.X * q4.Y, -p4.Y * q4.Y], + ]; + + double[] b = + [ + q1.X, + q1.Y, + q2.X, + q2.Y, + q3.X, + q3.Y, + q4.X, + q4.Y, + ]; + + GaussianEliminationSolver.Solve(matrixData, b); + +#pragma warning disable SA1117 + Matrix4x4 projectionMatrix = new( + (float)b[0], (float)b[3], 0, (float)b[6], + (float)b[1], (float)b[4], 0, (float)b[7], + 0, 0, 1, 0, + (float)b[2], (float)b[5], 0, 1); +#pragma warning restore SA1117 + + // Check if the matrix involves only affine transformations by inspecting the relevant components. + // We want to use pixel space for calculations only if the transformation is purely 2D and does not include + // any perspective effects, non-standard scaling, or unusual translations that could distort the image. + if (transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(projectionMatrix)) + { + if (projectionMatrix.M41 != 0) + { + projectionMatrix.M41--; + } + + if (projectionMatrix.M42 != 0) + { + projectionMatrix.M42--; + } + } + + return projectionMatrix; + } + /// /// Returns the size relative to the source for the given transformation matrix. /// @@ -293,11 +379,12 @@ internal static class TransformUtils /// /// The transformation matrix. /// The source size. + /// The used when generating the matrix. /// /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size GetTransformedSize(Matrix4x4 matrix, Size size) + public static Size GetTransformedSize(Matrix4x4 matrix, Size size, TransformSpace transformSpace) { Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); @@ -309,27 +396,7 @@ internal static class TransformUtils // Check if the matrix involves only affine transformations by inspecting the relevant components. // We want to use pixel space for calculations only if the transformation is purely 2D and does not include // any perspective effects, non-standard scaling, or unusual translations that could distort the image. - // The conditions are as follows: - bool usePixelSpace = - - // 1. Ensure there's no perspective distortion: - // M34 corresponds to the perspective component. For a purely 2D affine transformation, this should be 0. - (matrix.M34 == 0) && - - // 2. Ensure standard affine transformation without any unusual depth or perspective scaling: - // M44 should be 1 for a standard affine transformation. If M44 is not 1, it indicates non-standard depth - // scaling or perspective, which suggests a more complex transformation. - (matrix.M44 == 1) && - - // 3. Ensure no unusual translation in the x-direction: - // M14 represents translation in the x-direction that might be part of a more complex transformation. - // For standard affine transformations, M14 should be 0. - (matrix.M14 == 0) && - - // 4. Ensure no unusual translation in the y-direction: - // M24 represents translation in the y-direction that might be part of a more complex transformation. - // For standard affine transformations, M24 should be 0. - (matrix.M24 == 0); + bool usePixelSpace = transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(matrix); // Define an offset size to translate between pixel space and coordinate space. // When using pixel space, apply a scaling sensitive offset to translate to discrete pixel coordinates. @@ -492,4 +559,44 @@ internal static class TransformUtils (int)Math.Ceiling(right), (int)Math.Ceiling(bottom)); } + + private static bool IsAffineRotationOrSkew(Matrix4x4 matrix) + { + const float epsilon = 1e-6f; + + // Check if the matrix is affine (last column should be [0, 0, 0, 1]) + if (Math.Abs(matrix.M14) > epsilon || + Math.Abs(matrix.M24) > epsilon || + Math.Abs(matrix.M34) > epsilon || + Math.Abs(matrix.M44 - 1f) > epsilon) + { + return false; + } + + // Translation component (M41, m42) are allowed, others are not. + if (Math.Abs(matrix.M43) > epsilon) + { + return false; + } + + // Extract the linear (rotation and skew) part of the matrix + // Upper-left 3x3 matrix + float m11 = matrix.M11, m12 = matrix.M12, m13 = matrix.M13; + float m21 = matrix.M21, m22 = matrix.M22, m23 = matrix.M23; + float m31 = matrix.M31, m32 = matrix.M32, m33 = matrix.M33; + + // Compute the determinant of the linear part + float determinant = (m11 * ((m22 * m33) - (m23 * m32))) - + (m12 * ((m21 * m33) - (m23 * m31))) + + (m13 * ((m21 * m32) - (m22 * m31))); + + // Check if the determinant is approximately ±1 (no scaling) + if (Math.Abs(Math.Abs(determinant) - 1f) > epsilon) + { + return false; + } + + // All checks passed; the matrix represents rotation and/or skew (with possible translation) + return true; + } } diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index d5dedb1a7c..82b897ea5d 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing; @@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing; /// public class ProjectiveTransformBuilder { - private readonly List> transformMatrixFactories = new(); + private readonly List> transformMatrixFactories = []; /// /// Initializes a new instance of the class. @@ -289,7 +288,8 @@ public class ProjectiveTransformBuilder /// The bottom-left corner point of the distorted quad. /// The . public ProjectiveTransformBuilder PrependQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) - => this.Prepend(size => QuadDistortionHelper.ComputeQuadDistortMatrix(new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft)); + => this.Prepend(size => TransformUtils.CreateQuadDistortionMatrix( + new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); /// /// Appends a quad distortion matrix using the specified corner points. @@ -300,7 +300,8 @@ public class ProjectiveTransformBuilder /// The bottom-left corner point of the distorted quad. /// The . public ProjectiveTransformBuilder AppendQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) - => this.Append(size => QuadDistortionHelper.ComputeQuadDistortMatrix(new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft)); + => this.Append(size => TransformUtils.CreateQuadDistortionMatrix( + new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); /// /// Prepends a raw matrix. @@ -384,18 +385,8 @@ public class ProjectiveTransformBuilder /// The . public Size GetTransformedSize(Rectangle sourceRectangle) { - Size size = sourceRectangle.Size; - - // Translate the origin matrix to cater for source rectangle offsets. - Matrix4x4 matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0)); - - foreach (Func factory in this.transformMatrixFactories) - { - matrix *= factory(size); - CheckDegenerate(matrix); - } - - return TransformUtils.GetTransformedSize(matrix, size); + Matrix4x4 matrix = this.BuildMatrix(sourceRectangle); + return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); } private static void CheckDegenerate(Matrix4x4 matrix) diff --git a/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs b/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs index 03bac732b0..95b8d2013f 100644 --- a/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs +++ b/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; namespace SixLabors.ImageSharp.Tests.Common; diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index a8e05a3327..6b6db69c11 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -154,11 +154,11 @@ public class ProjectiveTransformTests using (Image image = provider.GetImage()) { #pragma warning disable SA1117 // Parameters should be on same line or separate lines - Matrix4x4 matrix = new( - 0.260987f, -0.434909f, 0, -0.0022184f, - 0.373196f, 0.949882f, 0, -0.000312129f, - 0, 0, 1, 0, - 52, 165, 0, 1); + Matrix4x4 matrix = new( + 0.260987f, -0.434909f, 0, -0.0022184f, + 0.373196f, 0.949882f, 0, -0.000312129f, + 0, 0, 1, 0, + 52, 165, 0, 1); #pragma warning restore SA1117 // Parameters should be on same line or separate lines ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png index cac4e4ab24..38c603855c 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb2804f09a350d9889d57bec60972b6a60ac341f179926cbb8361e9809e47883 -size 33139 +oid sha256:abce6af307a81a8ebac8e502142b00b2615403b5570c8dbe7b6895cfdd1a6d60 +size 66879 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png index 2c4cb321b4..f7ea0d0060 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f468d2def55cda480d1adb077d8d087b4b15229d25bd39cd9e4fe7a203d6747 -size 1982 +oid sha256:d4cda265a50aa26711efafdbcd947c9a01eff872611df5298920583f9a3d4224 +size 26458 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png index cf48a0dcf8..78c37cc448 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5db33fb6cd26e7641370ece52710e3ec3ab2e23da1b4586616f2fa6ce0c4d46 -size 3030 +oid sha256:278a488a858b8eda141493fe00c617eb1f664196853da8341d7e5b7f231ddce4 +size 24645 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png index e0be7d88d5..b4740828d4 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a1ff90af73904b689c8ef1a63f0194c86ec46596a51c9c6cff7483d326b9968 -size 36739 +oid sha256:e03e79e6fab3a9e43041e54640a04c7cc3677709e7d879f9f410cf8afc7547a7 +size 42691