diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
index 98d488a42..0b5627e19 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
@@ -75,7 +75,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Convert from screen to world space.
Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 matrix);
- const float Epsilon = 0.0000001F;
if (this.Sampler is NearestNeighborResampler)
{
@@ -90,11 +89,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = 0; x < width; x++)
{
- var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix);
-
- float z = MathF.Max(v3.Z, Epsilon);
- int px = (int)MathF.Round(v3.X / z);
- int py = (int)MathF.Round(v3.Y / z);
+ Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
+ int px = (int)MathF.Round(point.X);
+ int py = (int)MathF.Round(point.Y);
if (sourceRectangle.Contains(px, py))
{
@@ -127,9 +124,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
// 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, 1F), matrix);
- Vector2 point = new Vector2(v3.X, v3.Y) / MathF.Max(v3.Z, Epsilon);
-
+ Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan);
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
index 24b15d309..0ec8c8393 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
@@ -3,6 +3,7 @@
using System;
using System.Numerics;
+using System.Runtime.CompilerServices;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@@ -12,6 +13,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
internal static class TransformUtils
{
+ ///
+ /// Applies the projective transform against the given coordinates flattened into the 2D space.
+ ///
+ /// The "x" vector coordinate.
+ /// The "y" vector coordinate.
+ /// The transform matrix.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix)
+ {
+ const float Epsilon = 0.0000001F;
+ var v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix);
+ return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, Epsilon);
+ }
+
///
/// Creates a centered rotation matrix using the given rotation in degrees and the source size.
///
@@ -94,12 +110,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
Matrix4x4 matrix = Matrix4x4.Identity;
+ /*
+ * SkMatrix is layed out in the following manner:
+ *
+ * [ ScaleX SkewY Persp0 ]
+ * [ SkewX ScaleY Persp1 ]
+ * [ TransX TransY Persp2 ]
+ *
+ * When converting from Matrix4x4 to SkMatrix, the third row and
+ * column is dropped. When converting from SkMatrix to Matrix4x4
+ * the third row and column remain as identity:
+ *
+ * [ a b c ] [ a b 0 c ]
+ * [ d e f ] -> [ d e 0 f ]
+ * [ g h i ] [ 0 0 1 0 ]
+ * [ g h 0 i ]
+ */
switch (side)
{
case TaperSide.Left:
matrix.M11 = fraction;
matrix.M22 = fraction;
- matrix.M13 = (fraction - 1) / size.Width;
+ matrix.M14 = (fraction - 1) / size.Width;
switch (corner)
{
@@ -107,13 +139,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
break;
case TaperCorner.LeftOrTop:
- matrix.M12 = size.Height * matrix.M13;
- matrix.M32 = size.Height * (1 - fraction);
+ matrix.M12 = size.Height * matrix.M14;
+ matrix.M42 = size.Height * (1 - fraction);
break;
case TaperCorner.Both:
- matrix.M12 = size.Height * .5F * matrix.M13;
- matrix.M32 = size.Height * (1 - fraction) / 2;
+ matrix.M12 = size.Height * .5F * matrix.M14;
+ matrix.M42 = size.Height * (1 - fraction) / 2;
break;
}
@@ -122,7 +154,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case TaperSide.Top:
matrix.M11 = fraction;
matrix.M22 = fraction;
- matrix.M23 = (fraction - 1) / size.Height;
+ matrix.M24 = (fraction - 1) / size.Height;
switch (corner)
{
@@ -130,13 +162,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
break;
case TaperCorner.LeftOrTop:
- matrix.M21 = size.Width * matrix.M23;
- matrix.M31 = size.Width * (1 - fraction);
+ matrix.M21 = size.Width * matrix.M24;
+ matrix.M41 = size.Width * (1 - fraction);
break;
case TaperCorner.Both:
- matrix.M21 = size.Width * .5F * matrix.M23;
- matrix.M31 = size.Width * (1 - fraction) / 2;
+ matrix.M21 = size.Width * .5F * matrix.M24;
+ matrix.M41 = size.Width * (1 - fraction) * .5F;
break;
}
@@ -144,7 +176,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case TaperSide.Right:
matrix.M11 = 1 / fraction;
- matrix.M13 = (1 - fraction) / (size.Width * fraction);
+ matrix.M14 = (1 - fraction) / (size.Width * fraction);
switch (corner)
{
@@ -152,11 +184,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
break;
case TaperCorner.LeftOrTop:
- matrix.M12 = size.Height * matrix.M13;
+ matrix.M12 = size.Height * matrix.M14;
break;
case TaperCorner.Both:
- matrix.M12 = size.Height * .5F * matrix.M13;
+ matrix.M12 = size.Height * .5F * matrix.M14;
break;
}
@@ -164,7 +196,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case TaperSide.Bottom:
matrix.M22 = 1 / fraction;
- matrix.M23 = (1 - fraction) / (size.Height * fraction);
+ matrix.M24 = (1 - fraction) / (size.Height * fraction);
switch (corner)
{
@@ -172,11 +204,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
break;
case TaperCorner.LeftOrTop:
- matrix.M21 = size.Width * matrix.M23;
+ matrix.M21 = size.Width * matrix.M24;
break;
case TaperCorner.Both:
- matrix.M21 = size.Width * .5F * matrix.M23;
+ matrix.M21 = size.Width * .5F * matrix.M24;
break;
}
@@ -260,17 +292,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
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);
+ Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix);
+ Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix);
+ Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix);
+ Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix);
return GetBoundingRectangle(tl, tr, bl, br);
}
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
index 1da660d22..056f66af7 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
@@ -111,7 +111,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
using (Image image = provider.GetImage())
{
Matrix4x4 matrix = Matrix4x4.Identity;
- matrix.M13 = 0.01F;
+ matrix.M14 = 0.01F;
+
+ ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
+ .AppendMatrix(matrix);
+
+ image.Mutate(i => i.Transform(builder));
+
+ image.DebugSave(provider);
+ image.CompareToReferenceOutput(TolerantComparer, provider);
+ }
+ }
+
+ [Theory]
+ [WithSolidFilledImages(290, 154, 0, 0, 255, PixelTypes.Rgba32)]
+ public void PerspectiveTransformMatchesCSS(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ // https://jsfiddle.net/dFrHS/545/
+ // https://github.com/SixLabors/ImageSharp/issues/787
+ using (Image image = provider.GetImage())
+ {
+ var matrix = new Matrix4x4(
+ 0.260987f, -0.434909f, 0, -0.0022184f,
+ 0.373196f, 0.949882f, 0, -0.000312129f,
+ 0, 0, 1, 0,
+ 52, 165, 0, 1);
ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
.AppendMatrix(matrix);
diff --git a/tests/Images/External b/tests/Images/External
index 1edb0f3e0..69603ee5b 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit 1edb0f3e04c18974821a3012a87f7c2e073c8019
+Subproject commit 69603ee5b6f7dd64114fc44d321e50d9b2d439be