diff --git a/src/ImageSharp/Processing/Transforms/Processors/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Transforms/Processors/ProjectiveTransformProcessor.cs index eb40c3f87..0a857edd2 100644 --- a/src/ImageSharp/Processing/Transforms/Processors/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Transforms/Processors/ProjectiveTransformProcessor.cs @@ -70,6 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors // Convert from screen to world space. Matrix4x4.Invert(matrix, out matrix); + const float Epsilon = 0.0000001F; if (this.Sampler is NearestNeighborResampler) { @@ -83,7 +84,8 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors for (int x = 0; x < width; x++) { - var point = Point.Round(Vector2.Transform(new Vector2(x, y), matrix)); + var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); + var point = Point.Round(new Vector2(v3.X, v3.Y) / MathF.Max(v3.Z, Epsilon)); if (sourceBounds.Contains(point.X, point.Y)) { destRow[x] = source[point.X, point.Y]; @@ -125,7 +127,8 @@ namespace SixLabors.ImageSharp.Processing.Transforms.Processors { // 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); + var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); + Vector2 point = new Vector2(v3.X, v3.Y) / MathF.Max(v3.Z, Epsilon); // Clamp sampling pixel radial extents to the source image edges Vector2 maxXY = point + radius; diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TaperTransform.cs b/tests/ImageSharp.Tests/Processing/Transforms/TaperTransform.cs new file mode 100644 index 000000000..74d1d42c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/TaperTransform.cs @@ -0,0 +1,103 @@ +using System.Numerics; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Tests.Processing.Transforms +{ + public enum TaperSide { Left, Top, Right, Bottom } + + public enum TaperCorner { LeftOrTop, RightOrBottom, Both } + + public static class TaperTransform + { + public static Matrix4x4 Make(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 / 2) * 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 / 2) * 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 / 2) * 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 / 2) * matrix.M23; + break; + } + break; + } + return matrix; + } + } +}