Browse Source

Merge branch 'master' into cq10

af/merge-core
James Jackson-South 7 years ago
committed by GitHub
parent
commit
e26fe87bc4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  2. 79
      src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
  3. 27
      tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
  4. 2
      tests/Images/External

13
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs

@ -75,7 +75,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Convert from screen to world space. // Convert from screen to world space.
Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 matrix); Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 matrix);
const float Epsilon = 0.0000001F;
if (this.Sampler is NearestNeighborResampler) if (this.Sampler is NearestNeighborResampler)
{ {
@ -90,11 +89,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
int px = (int)MathF.Round(point.X);
float z = MathF.Max(v3.Z, Epsilon); int py = (int)MathF.Round(point.Y);
int px = (int)MathF.Round(v3.X / z);
int py = (int)MathF.Round(v3.Y / z);
if (sourceRectangle.Contains(px, py)) 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 // Use the single precision position to calculate correct bounding pixels
// otherwise we get rogue pixels outside of the bounds. // otherwise we get rogue pixels outside of the bounds.
var v3 = Vector3.Transform(new Vector3(x, y, 1F), matrix); Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
Vector2 point = new Vector2(v3.X, v3.Y) / MathF.Max(v3.Z, Epsilon);
kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan); kernel.Convolve(point, x, ref ySpanRef, ref xSpanRef, source.PixelBuffer, vectorSpan);
} }

79
src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -12,6 +13,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
internal static class TransformUtils internal static class TransformUtils
{ {
/// <summary>
/// Applies the projective transform against the given coordinates flattened into the 2D space.
/// </summary>
/// <param name="x">The "x" vector coordinate.</param>
/// <param name="y">The "y" vector coordinate.</param>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="Vector2"/>.</returns>
[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);
}
/// <summary> /// <summary>
/// Creates a centered rotation matrix using the given rotation in degrees and the source size. /// Creates a centered rotation matrix using the given rotation in degrees and the source size.
/// </summary> /// </summary>
@ -94,12 +110,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
Matrix4x4 matrix = Matrix4x4.Identity; 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) switch (side)
{ {
case TaperSide.Left: case TaperSide.Left:
matrix.M11 = fraction; matrix.M11 = fraction;
matrix.M22 = fraction; matrix.M22 = fraction;
matrix.M13 = (fraction - 1) / size.Width; matrix.M14 = (fraction - 1) / size.Width;
switch (corner) switch (corner)
{ {
@ -107,13 +139,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
break; break;
case TaperCorner.LeftOrTop: case TaperCorner.LeftOrTop:
matrix.M12 = size.Height * matrix.M13; matrix.M12 = size.Height * matrix.M14;
matrix.M32 = size.Height * (1 - fraction); matrix.M42 = size.Height * (1 - fraction);
break; break;
case TaperCorner.Both: case TaperCorner.Both:
matrix.M12 = size.Height * .5F * matrix.M13; matrix.M12 = size.Height * .5F * matrix.M14;
matrix.M32 = size.Height * (1 - fraction) / 2; matrix.M42 = size.Height * (1 - fraction) / 2;
break; break;
} }
@ -122,7 +154,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case TaperSide.Top: case TaperSide.Top:
matrix.M11 = fraction; matrix.M11 = fraction;
matrix.M22 = fraction; matrix.M22 = fraction;
matrix.M23 = (fraction - 1) / size.Height; matrix.M24 = (fraction - 1) / size.Height;
switch (corner) switch (corner)
{ {
@ -130,13 +162,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
break; break;
case TaperCorner.LeftOrTop: case TaperCorner.LeftOrTop:
matrix.M21 = size.Width * matrix.M23; matrix.M21 = size.Width * matrix.M24;
matrix.M31 = size.Width * (1 - fraction); matrix.M41 = size.Width * (1 - fraction);
break; break;
case TaperCorner.Both: case TaperCorner.Both:
matrix.M21 = size.Width * .5F * matrix.M23; matrix.M21 = size.Width * .5F * matrix.M24;
matrix.M31 = size.Width * (1 - fraction) / 2; matrix.M41 = size.Width * (1 - fraction) * .5F;
break; break;
} }
@ -144,7 +176,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case TaperSide.Right: case TaperSide.Right:
matrix.M11 = 1 / fraction; matrix.M11 = 1 / fraction;
matrix.M13 = (1 - fraction) / (size.Width * fraction); matrix.M14 = (1 - fraction) / (size.Width * fraction);
switch (corner) switch (corner)
{ {
@ -152,11 +184,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
break; break;
case TaperCorner.LeftOrTop: case TaperCorner.LeftOrTop:
matrix.M12 = size.Height * matrix.M13; matrix.M12 = size.Height * matrix.M14;
break; break;
case TaperCorner.Both: case TaperCorner.Both:
matrix.M12 = size.Height * .5F * matrix.M13; matrix.M12 = size.Height * .5F * matrix.M14;
break; break;
} }
@ -164,7 +196,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case TaperSide.Bottom: case TaperSide.Bottom:
matrix.M22 = 1 / fraction; matrix.M22 = 1 / fraction;
matrix.M23 = (1 - fraction) / (size.Height * fraction); matrix.M24 = (1 - fraction) / (size.Height * fraction);
switch (corner) switch (corner)
{ {
@ -172,11 +204,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
break; break;
case TaperCorner.LeftOrTop: case TaperCorner.LeftOrTop:
matrix.M21 = size.Width * matrix.M23; matrix.M21 = size.Width * matrix.M24;
break; break;
case TaperCorner.Both: case TaperCorner.Both:
matrix.M21 = size.Width * .5F * matrix.M23; matrix.M21 = size.Width * .5F * matrix.M24;
break; break;
} }
@ -260,17 +292,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return rectangle; return rectangle;
} }
Vector2 GetVector(float x, float y) Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix);
{ Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix);
const float Epsilon = 0.0000001F; Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix);
var v3 = Vector3.Transform(new Vector3(x, y, 1F), matrix); Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, 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); return GetBoundingRectangle(tl, tr, bl, br);
} }

27
tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs

@ -111,7 +111,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
Matrix4x4 matrix = Matrix4x4.Identity; 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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
// https://jsfiddle.net/dFrHS/545/
// https://github.com/SixLabors/ImageSharp/issues/787
using (Image<TPixel> 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() ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
.AppendMatrix(matrix); .AppendMatrix(matrix);

2
tests/Images/External

@ -1 +1 @@
Subproject commit 1edb0f3e04c18974821a3012a87f7c2e073c8019 Subproject commit 69603ee5b6f7dd64114fc44d321e50d9b2d439be
Loading…
Cancel
Save