Browse Source

Merge branch 'release/3.1.x' into js/affine-fix-v4

pull/2761/head
James Jackson-South 2 years ago
parent
commit
d27c03ad61
  1. 73
      src/ImageSharp/Processing/AffineTransformBuilder.cs
  2. 4
      src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs
  3. 8
      src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs
  4. 7
      src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs
  5. 7
      src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs
  6. 159
      src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
  7. 69
      src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
  8. 28
      tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs
  9. 28
      tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
  10. 4
      tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs
  11. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(-20,-10).png
  12. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(0,0).png
  13. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(20,10).png
  14. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png
  15. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png
  16. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png
  17. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png
  18. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png
  19. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png
  20. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png
  21. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png
  22. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png
  23. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png
  24. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png
  25. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png
  26. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png
  27. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png
  28. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png
  29. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png
  30. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png
  31. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png
  32. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png
  33. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png
  34. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png
  35. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png
  36. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png
  37. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png
  38. 4
      tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png
  39. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png
  40. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png
  41. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png
  42. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png
  43. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png
  44. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png
  45. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png
  46. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png
  47. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png
  48. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png
  49. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png
  50. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png
  51. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png
  52. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png
  53. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png
  54. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png
  55. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png
  56. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png
  57. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png
  58. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png
  59. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png
  60. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png
  61. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png
  62. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png
  63. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png
  64. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png
  65. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png

73
src/ImageSharp/Processing/AffineTransformBuilder.cs

@ -11,7 +11,8 @@ namespace SixLabors.ImageSharp.Processing;
/// </summary>
public class AffineTransformBuilder
{
private readonly List<Func<Size, Matrix3x2>> matrixFactories = new List<Func<Size, Matrix3x2>>();
private readonly List<Func<Size, Matrix3x2>> transformMatrixFactories = new();
private readonly List<Func<Size, Matrix3x2>> boundsMatrixFactories = new();
/// <summary>
/// Prepends a rotation matrix using the given rotation angle in degrees
@ -29,7 +30,9 @@ public class AffineTransformBuilder
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependRotationRadians(float radians)
=> this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
=> this.Prepend(
size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size),
size => TransformUtils.CreateRotationBoundsMatrixRadians(radians, size));
/// <summary>
/// Prepends a rotation matrix using the given rotation in degrees at the given origin.
@ -65,7 +68,9 @@ public class AffineTransformBuilder
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendRotationRadians(float radians)
=> this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
=> this.Append(
size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size),
size => TransformUtils.CreateRotationBoundsMatrixRadians(radians, size));
/// <summary>
/// Appends a rotation matrix using the given rotation in degrees at the given origin.
@ -140,7 +145,9 @@ public class AffineTransformBuilder
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
=> this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
=> this.Prepend(
size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size),
size => TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, size));
/// <summary>
/// Prepends a centered skew matrix from the give angles in radians.
@ -149,7 +156,9 @@ public class AffineTransformBuilder
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
=> this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
=> this.Prepend(
size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size),
size => TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size));
/// <summary>
/// Prepends a skew matrix using the given angles in degrees at the given origin.
@ -178,7 +187,9 @@ public class AffineTransformBuilder
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
=> this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
=> this.Append(
size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size),
size => TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, size));
/// <summary>
/// Appends a centered skew matrix from the give angles in radians.
@ -187,7 +198,9 @@ public class AffineTransformBuilder
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
=> this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
=> this.Append(
size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size),
size => TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size));
/// <summary>
/// Appends a skew matrix using the given angles in degrees at the given origin.
@ -254,7 +267,7 @@ public class AffineTransformBuilder
public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix)
{
CheckDegenerate(matrix);
return this.Prepend(_ => matrix);
return this.Prepend(_ => matrix, _ => matrix);
}
/// <summary>
@ -270,7 +283,7 @@ public class AffineTransformBuilder
public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix)
{
CheckDegenerate(matrix);
return this.Append(_ => matrix);
return this.Append(_ => matrix, _ => matrix);
}
/// <summary>
@ -281,7 +294,7 @@ public class AffineTransformBuilder
public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));
/// <summary>
/// Returns the combined matrix for a given source rectangle.
/// Returns the combined transform matrix for a given source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
@ -296,11 +309,11 @@ public class AffineTransformBuilder
Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle));
// Translate the origin matrix to cater for source rectangle offsets.
var matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
Size size = sourceRectangle.Size;
foreach (Func<Size, Matrix3x2> factory in this.matrixFactories)
foreach (Func<Size, Matrix3x2> factory in this.transformMatrixFactories)
{
matrix *= factory(size);
}
@ -310,6 +323,32 @@ public class AffineTransformBuilder
return matrix;
}
/// <summary>
/// Returns the size of a rectangle large enough to contain the transformed source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="Size"/>.</returns>
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<Size, Matrix3x2> factory in this.boundsMatrixFactories)
{
matrix *= factory(size);
CheckDegenerate(matrix);
}
return TransformUtils.GetTransformedSize(size, matrix);
}
private static void CheckDegenerate(Matrix3x2 matrix)
{
if (TransformUtils.IsDegenerate(matrix))
@ -318,15 +357,17 @@ public class AffineTransformBuilder
}
}
private AffineTransformBuilder Prepend(Func<Size, Matrix3x2> factory)
private AffineTransformBuilder Prepend(Func<Size, Matrix3x2> transformFactory, Func<Size, Matrix3x2> boundsFactory)
{
this.matrixFactories.Insert(0, factory);
this.transformMatrixFactories.Insert(0, transformFactory);
this.boundsMatrixFactories.Insert(0, boundsFactory);
return this;
}
private AffineTransformBuilder Append(Func<Size, Matrix3x2> factory)
private AffineTransformBuilder Append(Func<Size, Matrix3x2> transformFactory, Func<Size, Matrix3x2> boundsFactory)
{
this.matrixFactories.Add(factory);
this.transformMatrixFactories.Add(transformFactory);
this.boundsMatrixFactories.Add(boundsFactory);
return this;
}
}

4
src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs

@ -51,7 +51,7 @@ public static class TransformExtensions
IResampler sampler)
{
Matrix3x2 transform = builder.BuildMatrix(sourceRectangle);
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform);
Size targetDimensions = builder.GetTransformedSize(sourceRectangle);
return source.Transform(sourceRectangle, transform, targetDimensions, sampler);
}
@ -113,7 +113,7 @@ public static class TransformExtensions
IResampler sampler)
{
Matrix4x4 transform = builder.BuildMatrix(sourceRectangle);
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform);
Size targetDimensions = builder.GetTransformedSize(sourceRectangle);
return source.Transform(sourceRectangle, transform, targetDimensions, sampler);
}

8
src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs

@ -38,8 +38,8 @@ internal static class LinearTransformUtility
/// </summary>
/// <param name="radius">The radius.</param>
/// <param name="center">The center position.</param>
/// <param name="min">The min allowed amouunt.</param>
/// <param name="max">The max allowed amouunt.</param>
/// <param name="min">The min allowed amount.</param>
/// <param name="max">The max allowed amount.</param>
/// <returns>The <see cref="int"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetRangeStart(float radius, float center, int min, int max)
@ -51,8 +51,8 @@ internal static class LinearTransformUtility
/// </summary>
/// <param name="radius">The radius.</param>
/// <param name="center">The center position.</param>
/// <param name="min">The min allowed amouunt.</param>
/// <param name="max">The max allowed amouunt.</param>
/// <param name="min">The min allowed amount.</param>
/// <param name="max">The max allowed amount.</param>
/// <returns>The <see cref="int"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetRangeEnd(float radius, float center, int min, int max)

7
src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs

@ -28,14 +28,15 @@ public sealed class RotateProcessor : AffineTransformProcessor
/// <param name="sourceSize">The source image size</param>
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
: this(
TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize),
TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize),
TransformUtils.CreateRotationBoundsMatrixDegrees(degrees, sourceSize),
sampler,
sourceSize)
=> this.Degrees = degrees;
// Helper constructor
private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize)
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, rotationMatrix))
private RotateProcessor(Matrix3x2 rotationMatrix, Matrix3x2 boundsMatrix, IResampler sampler, Size sourceSize)
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, boundsMatrix))
{
}

7
src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs

@ -30,7 +30,8 @@ public sealed class SkewProcessor : AffineTransformProcessor
/// <param name="sourceSize">The source image size</param>
public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize)
: this(
TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize),
TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize),
TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, sourceSize),
sampler,
sourceSize)
{
@ -39,8 +40,8 @@ public sealed class SkewProcessor : AffineTransformProcessor
}
// Helper constructor:
private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize)
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix))
private SkewProcessor(Matrix3x2 skewMatrix, Matrix3x2 boundsMatrix, IResampler sampler, Size sourceSize)
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, boundsMatrix))
{
}

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

@ -29,7 +29,7 @@ internal static class TransformUtils
public static bool IsDegenerate(Matrix4x4 matrix)
=> IsNaN(matrix) || IsZero(matrix.GetDeterminant());
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsZero(float a)
=> a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared;
@ -39,13 +39,11 @@ internal static class TransformUtils
/// </summary>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNaN(Matrix3x2 matrix)
{
return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32);
}
=> float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32);
/// <summary>
/// Returns a value that indicates whether the specified matrix contains any values
@ -53,14 +51,12 @@ internal static class TransformUtils
/// </summary>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNaN(Matrix4x4 matrix)
{
return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34)
|| float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44);
}
=> float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34)
|| float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44);
/// <summary>
/// Applies the projective transform against the given coordinates flattened into the 2D space.
@ -69,71 +65,121 @@ internal static class TransformUtils
/// <param name="y">The "y" vector coordinate.</param>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="Vector2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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);
const float epsilon = 0.0000001F;
Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix);
return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon);
}
/// <summary>
/// Creates a centered rotation matrix using the given rotation in degrees and the source size.
/// Creates a centered rotation transform matrix using the given rotation in degrees and the source size.
/// </summary>
/// <param name="degrees">The amount of rotation, in degrees.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty));
/// <summary>
/// Creates a centered rotation matrix using the given rotation in radians and the source size.
/// Creates a centered rotation transform matrix using the given rotation in radians and the source size.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotation(radians, PointF.Empty));
/// <summary>
/// Creates a centered skew matrix from the give angles in degrees and the source size.
/// Creates a centered rotation bounds matrix using the given rotation in degrees and the source size.
/// </summary>
/// <param name="degrees">The amount of rotation, in degrees.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationBoundsMatrixDegrees(float degrees, Size size)
=> CreateCenteredBoundsMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty));
/// <summary>
/// Creates a centered rotation bounds matrix using the given rotation in radians and the source size.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationBoundsMatrixRadians(float radians, Size size)
=> CreateCenteredBoundsMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotation(radians, PointF.Empty));
/// <summary>
/// Creates a centered skew transform matrix from the give angles in degrees and the source size.
/// </summary>
/// <param name="degreesX">The X angle, in degrees.</param>
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty));
/// <summary>
/// Creates a centered skew matrix from the give angles in radians and the source size.
/// Creates a centered skew transform matrix from the give angles in radians and the source size.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty));
/// <summary>
/// Gets the centered transform matrix based upon the source and destination rectangles.
/// Creates a centered skew bounds matrix from the give angles in degrees and the source size.
/// </summary>
/// <param name="degreesX">The X angle, in degrees.</param>
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewBoundsMatrixDegrees(float degreesX, float degreesY, Size size)
=> CreateCenteredBoundsMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty));
/// <summary>
/// Creates a centered skew bounds matrix from the give angles in radians and the source size.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewBoundsMatrixRadians(float radiansX, float radiansY, Size size)
=> CreateCenteredBoundsMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty));
/// <summary>
/// Gets the centered transform matrix based upon the source rectangle.
/// </summary>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Matrix3x2"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix)
{
Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix);
@ -142,8 +188,33 @@ internal static class TransformUtils
// This ensures scaling matrices are correct.
Matrix3x2.Invert(matrix, out Matrix3x2 inverted);
var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F);
var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F);
// Centered transforms must be 0 based so we offset the bounds width and height.
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(destinationRectangle.Width - 1), -(destinationRectangle.Height - 1)) * .5F);
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width - 1, sourceRectangle.Height - 1) * .5F);
// Translate back to world space.
Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
return centered;
}
/// <summary>
/// Gets the centered bounds matrix based upon the source rectangle.
/// </summary>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Matrix3x2"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateCenteredBoundsMatrix(Rectangle sourceRectangle, Matrix3x2 matrix)
{
Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, 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);
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F);
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F);
// Translate back to world space.
Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
@ -160,7 +231,7 @@ internal static class TransformUtils
/// <param name="corner">An enumeration that indicates on which corners to taper the rectangle.</param>
/// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction)
{
Matrix4x4 matrix = Matrix4x4.Identity;
@ -281,7 +352,7 @@ internal static class TransformUtils
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix)
{
Rectangle transformed = GetTransformedRectangle(rectangle, matrix);
@ -303,10 +374,10 @@ internal static class TransformUtils
return rectangle;
}
var tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix);
var tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix);
var bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix);
var br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix);
Vector2 tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix);
Vector2 tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix);
Vector2 bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix);
Vector2 br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix);
return GetBoundingRectangle(tl, tr, bl, br);
}
@ -341,7 +412,7 @@ internal static class TransformUtils
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix)
{
if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix))
@ -365,7 +436,7 @@ internal static class TransformUtils
/// <returns>
/// The <see cref="Size"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size GetTransformedSize(Size size, Matrix4x4 matrix)
{
Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!");
@ -380,7 +451,7 @@ internal static class TransformUtils
return ConstrainSize(rectangle);
}
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Size ConstrainSize(Rectangle rectangle)
{
// We want to resize the canvas here taking into account any translations.
@ -402,7 +473,7 @@ internal static class TransformUtils
return new Size(width, height);
}
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br)
{
// Find the minimum and maximum "corners" based on the given vectors

69
src/ImageSharp/Processing/ProjectiveTransformBuilder.cs

@ -11,7 +11,8 @@ namespace SixLabors.ImageSharp.Processing;
/// </summary>
public class ProjectiveTransformBuilder
{
private readonly List<Func<Size, Matrix4x4>> matrixFactories = new();
private readonly List<Func<Size, Matrix4x4>> transformMatrixFactories = new();
private readonly List<Func<Size, Matrix4x4>> boundsMatrixFactories = new();
/// <summary>
/// Prepends a matrix that performs a tapering projective transform.
@ -21,7 +22,9 @@ public class ProjectiveTransformBuilder
/// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction)
=> this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction));
=> this.Prepend(
size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction),
size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction));
/// <summary>
/// Appends a matrix that performs a tapering projective transform.
@ -31,7 +34,9 @@ public class ProjectiveTransformBuilder
/// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction)
=> this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction));
=> this.Append(
size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction),
size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction));
/// <summary>
/// Prepends a centered rotation matrix using the given rotation in degrees.
@ -47,7 +52,9 @@ public class ProjectiveTransformBuilder
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependRotationRadians(float radians)
=> this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size)));
=> this.Prepend(
size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size)),
size => new Matrix4x4(TransformUtils.CreateRotationBoundsMatrixRadians(radians, size)));
/// <summary>
/// Prepends a centered rotation matrix using the given rotation in degrees at the given origin.
@ -81,7 +88,9 @@ public class ProjectiveTransformBuilder
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendRotationRadians(float radians)
=> this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size)));
=> this.Append(
size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size)),
size => new Matrix4x4(TransformUtils.CreateRotationBoundsMatrixRadians(radians, size)));
/// <summary>
/// Appends a centered rotation matrix using the given rotation in degrees at the given origin.
@ -165,7 +174,9 @@ public class ProjectiveTransformBuilder
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
=> this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)));
=> this.Prepend(
size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size)),
size => new Matrix4x4(TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size)));
/// <summary>
/// Prepends a skew matrix using the given angles in degrees at the given origin.
@ -203,7 +214,9 @@ public class ProjectiveTransformBuilder
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
=> this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)));
=> this.Append(
size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size)),
size => new Matrix4x4(TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size)));
/// <summary>
/// Appends a skew matrix using the given angles in degrees at the given origin.
@ -270,7 +283,7 @@ public class ProjectiveTransformBuilder
public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix)
{
CheckDegenerate(matrix);
return this.Prepend(_ => matrix);
return this.Prepend(_ => matrix, _ => matrix);
}
/// <summary>
@ -286,7 +299,7 @@ public class ProjectiveTransformBuilder
public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix)
{
CheckDegenerate(matrix);
return this.Append(_ => matrix);
return this.Append(_ => matrix, _ => matrix);
}
/// <summary>
@ -317,7 +330,7 @@ public class ProjectiveTransformBuilder
Size size = sourceRectangle.Size;
foreach (Func<Size, Matrix4x4> factory in this.matrixFactories)
foreach (Func<Size, Matrix4x4> factory in this.transformMatrixFactories)
{
matrix *= factory(size);
}
@ -327,6 +340,32 @@ public class ProjectiveTransformBuilder
return matrix;
}
/// <summary>
/// Returns the size of a rectangle large enough to contain the transformed source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="Size"/>.</returns>
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<Size, Matrix4x4> factory in this.boundsMatrixFactories)
{
matrix *= factory(size);
CheckDegenerate(matrix);
}
return TransformUtils.GetTransformedSize(size, matrix);
}
private static void CheckDegenerate(Matrix4x4 matrix)
{
if (TransformUtils.IsDegenerate(matrix))
@ -335,15 +374,17 @@ public class ProjectiveTransformBuilder
}
}
private ProjectiveTransformBuilder Prepend(Func<Size, Matrix4x4> factory)
private ProjectiveTransformBuilder Prepend(Func<Size, Matrix4x4> transformFactory, Func<Size, Matrix4x4> boundsFactory)
{
this.matrixFactories.Insert(0, factory);
this.transformMatrixFactories.Insert(0, transformFactory);
this.boundsMatrixFactories.Insert(0, boundsFactory);
return this;
}
private ProjectiveTransformBuilder Append(Func<Size, Matrix4x4> factory)
private ProjectiveTransformBuilder Append(Func<Size, Matrix4x4> transformFactory, Func<Size, Matrix4x4> boundsFactory)
{
this.matrixFactories.Add(factory);
this.transformMatrixFactories.Add(transformFactory);
this.boundsMatrixFactories.Add(boundsFactory);
return this;
}
}

28
tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs

@ -249,6 +249,34 @@ public class AffineTransformTests
image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians);
}
[Fact]
public void TransformRotationDoesNotOffset()
{
Rgba32 marker = Color.Aqua;
using Image<Rgba32> img = new(100, 100, Color.DimGray);
img[0, 0] = marker;
img.Mutate(c => c.Rotate(180));
Assert.Equal(marker, img[99, 99]);
using Image<Rgba32> img2 = new(100, 100, Color.DimGray);
img2[0, 0] = marker;
img2.Mutate(
c =>
c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180), KnownResamplers.NearestNeighbor));
using Image<Rgba32> img3 = new(100, 100, Color.DimGray);
img3[0, 0] = marker;
img3.Mutate(c => c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180)));
ImageComparer.Exact.VerifySimilarity(img, img2);
ImageComparer.Exact.VerifySimilarity(img, img3);
}
private static IResampler GetResampler(string name)
{
PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name)

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

@ -185,6 +185,34 @@ public class ProjectiveTransformTests
image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians);
}
[Fact]
public void TransformRotationDoesNotOffset()
{
Rgba32 marker = Color.Aqua;
using Image<Rgba32> img = new(100, 100, Color.DimGray);
img[0, 0] = marker;
img.Mutate(c => c.Rotate(180));
Assert.Equal(marker, img[99, 99]);
using Image<Rgba32> img2 = new(100, 100, Color.DimGray);
img2[0, 0] = marker;
img2.Mutate(
c =>
c.Transform(new ProjectiveTransformBuilder().AppendRotationDegrees(180), KnownResamplers.NearestNeighbor));
using Image<Rgba32> img3 = new(100, 100, Color.DimGray);
img3[0, 0] = marker;
img3.Mutate(c => c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180)));
ImageComparer.Exact.VerifySimilarity(img, img2);
ImageComparer.Exact.VerifySimilarity(img, img3);
}
private static IResampler GetResampler(string name)
{
PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name);

4
tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs

@ -97,7 +97,7 @@ public abstract class TransformBuilderTestBase<TBuilder>
this.AppendRotationDegrees(builder, degrees);
// TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness
Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size);
Matrix3x2 matrix = TransformUtils.CreateRotationTransformMatrixDegrees(degrees, size);
var position = new Vector2(x, y);
var expected = Vector2.Transform(position, matrix);
@ -151,7 +151,7 @@ public abstract class TransformBuilderTestBase<TBuilder>
this.AppendSkewDegrees(builder, degreesX, degreesY);
Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size);
Matrix3x2 matrix = TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size);
var position = new Vector2(x, y);
var expected = Vector2.Transform(position, matrix);

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(-20,-10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6b13fd15f767271628930510b9a00cc71c8dbe37158cdee7459e8184b2c254b
size 689
oid sha256:8dc4da0a0c727f2c95bbfdcc7710ca612b008dca97dfc5101175c384c010641b
size 770

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5c186653c2431d706175ac59b06637852942d18a9177b21772d3ab511c563202
size 723
oid sha256:5ec2dd66678dc1a0ae3bd4bf6667fbb8ceb8a8af63ca878c490545e303c9d1ad
size 851

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(20,10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:291e1044a062943cc91840cd252e7cc4722d8f5bc59386597c6c2aac5565b5f4
size 757
oid sha256:febfdb0554e69f7fe363bca5aaff4b0a5347d216b29a0ba8cbebe92aa8678015
size 892

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d8f2167445c7ca069b0f33b56f4e758e9929ff5753d076c987cf53c5d7cc3bc2
size 3318
oid sha256:b66a5f9d8a7f3f2a78b868bec6c7d1deea927b82d81aa6d1677e0461a3920dc9
size 3800

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:462eaba7681f5a43f3012f8b6445180173b398c932a3cd8ec2e15cb1df9d9c4e
size 4327
oid sha256:d5fdc46ee866e088e0ec3221145a3d2d954a0bcb6d25cbb4d538978272f34949
size 4871

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2ef489dc0837b382ad7c7ead6b7c7042dfbfba39902d4cc81b5f3805d5b03967
size 9175
oid sha256:1b926c8335eca5530d8704739cecae0799cc651139daedb1f88ac85b0ee1bd5d
size 9484

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d1fb97a28b5f754150343a3dc3c6974ac9abbb1577a44d88db61f0169983db0
size 11083
oid sha256:7f79389f79d91ac6749f21c27a592edfd2cff6efbb1d46296a26ae60d4e721f8
size 10103

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c78c6310b6b716e9fdb1605b5b356fa7e1b0fbed9f6e6ff0d705d5152ce52665
size 11148
oid sha256:322c7e061f8565efdc19642e27353ec3073ee43d8c17fbef8c13be3bb60d11dc
size 10190

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4b00b7801fd844035d9235f3d1163587e9847001b918b2d971ea7e917485371
size 13683
oid sha256:544e7bac188d0869f98ec075fa0e73ab831e4dafe40c1520dce194df6a53c9b8
size 12737

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0b6f27c2361cc100b6928195522e0c8e76ee3ceeda814bd036d0636957024c9f
size 22761
oid sha256:2ccc08769974e4088702d2c95fd274af7e02095955953b424a6313d656a77735
size 19974

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:99d6c1d6b092a2feba2aebe2e09c521c3cc9682f3d748927cdc3cbaa38448b28
size 710
oid sha256:5ec2dd66678dc1a0ae3bd4bf6667fbb8ceb8a8af63ca878c490545e303c9d1ad
size 851

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad5beec2c4a09ef8c4718bb39bda5b2d0bfcccfa4634b7458ac748d2fda498fe
size 11738
oid sha256:c1179b300b35d526bab148833ab6240f1207b8ade36674b1f47cc5a2d47a084c
size 10603

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:04a3b84c668758b586f4d998f080ef96b09f726b0298b28f5dd3d739b0e90744
size 13138
oid sha256:f666fe67ee4a1c7152fc6190affba95ea4cbd857d96bac0968e5f1fd89792d32
size 13486

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:21f13832d15b9491e8b73711b3081fa56c08ca69bd9b165c323dec0ba4e6f99f
size 11358
oid sha256:5b05406a1d95f0709a7aaab7c1f57ba161b7907b76746f61788cfe527796a489
size 4131

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:04a3b84c668758b586f4d998f080ef96b09f726b0298b28f5dd3d739b0e90744
size 13138
oid sha256:be52d36cc8f616a781c8b1416ca0bf6207b9acd580e9c06e1ee5ad434d48ab38
size 13481

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:829f7e7315a5a0cc3e88115529305ddb0c53a104863a8a66f6ad1f2efc440109
size 12231
oid sha256:33c99b8f0fb5d10a273a90946767f93ab6cd2dd1942f9829d695987db30dccfa
size 12488

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ea7ca66c31474c0bb9673a0d85c1c7465e387ebabf4e2d1e8f9daebfc7c8f34
size 13956
oid sha256:af2c0201c59065a500ae985e9b7ca164e5bcb4ce2d8d8305103398830472e07c
size 14206

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6dd98ac441f3c20ea999f058c7b21601d5981d46e9b77709c25f2930a64edb93
size 17148
oid sha256:4cef17988c4a3a667dede3dd86ed61d0507a84e5b846f52459683fd04e5a396a
size 17297

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5c4772d9b9dc57c4b5d47450ec9d02d96e40656cf2015f171b5425945f8a598
size 18726
oid sha256:9699a81572c03c2bc47d8bbdd1d64df26f87df3d4ad59fb6f164f6e82786d91d
size 18853

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bb025c4470cec1b0d6544924e46b84cbdb90d75da5d0f879f2c7d7ec9875dee2
size 20574
oid sha256:4fb1f59c5393debdff9bd4b7a6c222b7a0686e6d5ef24363e3d5c94ba9b5bc27
size 20725

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c4abaa06827cb779026f8fbb655692bdd8adab37ae5b00c3ae18ebea456eb8d9
size 13459
oid sha256:8ffa8ca6a60de9fe26a191edc2127886c61c072c1aa2b91fe3125512fe40e1b3
size 13848

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:37332e56f71506663b00deacc52adaa7574167565e395217b493d235965b29b9
size 11563
oid sha256:abf1d0f323795c0aaff0ff8b488d9866c5b2f7c64aad83701cb1f60e22668b0e
size 4161

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3435ade8f7988779280820342e16881b049f717735d2218ac5a971a1bd807db1
size 13448
oid sha256:9b0f3c41248138bd501ae844e5b54fb9f49e5d22bab9b2ef0a0654034708b99f
size 14027

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0098aa45e820544dd16a58396fa70860886b7d79900916ed97376a8328a5ff2
size 13367
oid sha256:6a3f839d64984b9fda4125c6643f4699add6f95373a2194c5726ed3740565a47
size 13725

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7729495277b80a42e24dd8f40cdd8a280443575710fb4e199e4871c28b558271
size 14253
oid sha256:2ac143bc73612cecfffbec049a7b68234e7bf7581e680c3f996a977c6d671cc1
size 14865

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a2304c234b93bdabaa018263dec70e62090ad1bbb7005ef62643b88600a863fb
size 12157
oid sha256:4eb9dab20d5a03c0adde05a9b741d9e1b0fb8c3d79054a8bc5788de496e5c7f8
size 12420

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:54b0da9646b7f4cf83df784d69dfbec48e0bdc1788d70a9872817543f72f57c1
size 16829
oid sha256:0f56ee78cc2fd698ac8ea84912648f0b49d4b4d66b439f6976447c56a44c2998
size 16909

4
tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:233d8c6c9e101dddf5d210d47c7a20807f8f956738289068ea03b774258ef8c6
size 182754
oid sha256:00836a98742d177a2376af32d8d858fcf9f26a4da5a311dd5faf5cd80f233c0b
size 184397

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:964e8e5c160d7cd57e3b1fc584a39c1c8c61b835ff6af8264ec0631175286fca
size 10794
oid sha256:801067dfb19b2a9a1fbd14b771e780b475c21f3ccdc1e709bbc20d62061ad1d1
size 8782

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2997d51c4ca534df2561f2c3bf1801f04631277b4259e25d9ef3fc164212f722
size 11223
oid sha256:ccbdd3dcdbc888923e85495fbd7837478cca813be6ecece63ee645bdf39d436f
size 10325

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e8bd1219a9363bcc8dc088fb0a0a17c5e1914d418c89f4affc3fb9abf236f705
size 10725
oid sha256:60d1be2ffa6d50f97561b92efe8d0c0337f9c121582e38c9ab9af75be8eed32d
size 8539

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d1fb97a28b5f754150343a3dc3c6974ac9abbb1577a44d88db61f0169983db0
size 11083
oid sha256:7f79389f79d91ac6749f21c27a592edfd2cff6efbb1d46296a26ae60d4e721f8
size 10103

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5e62ccc2cca36898a01445ef03361b84ff19aa1c0498e9dcbf21703dc1c009dd
size 11278
oid sha256:75fb59ea2947efb1abf73cd824e54b6e8f6271343bd2fdadb005b29476988921
size 9423

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f8f1f14a3c32d8d7e0406c2cf9480aa78dcef7bf223966e80f620680ef68375c
size 12064
oid sha256:9c2d94791af40e001fc44b23eeecac7a606492c747b22ede0cdc7069ef121cb8
size 11193

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7756d2402f4cf7d221d832d921fcff80fef6a36da5373cd9cb46b8e0f00e2fb1
size 11273
oid sha256:016de6e82b6fb03fd55168ea7fc12ab245d0e0387ca7c32d3ef1158a85a8facd
size 9330

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d49ac2a6529959eb2a5d257c804ba576f9426fafe918c7fb267fc397477666dd
size 12051
oid sha256:9219cc118fe7195b730cbe2e6407cde54e6f4c7930a71b7418bc7c273aa4120c
size 11050

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d56651460aa5c942d800f2c92f0934f4c53b0f83e6fe8a2eb891a85bd9d93542
size 10244
oid sha256:2b243340f372220033b349a96cbd7ecd732395fa15e4d1ed62048d2031c42794
size 8398

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d2912a3cd8088e9ad5e4e33b7c85825aedf9f910c32abbe78f3560510024b770
size 10141
oid sha256:fdd2745aba2f09bb4a09e881339fe62774ce5658902aa9d83b3a1e0718260084
size 8694

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:58aea9e0fb398d84dc2f27e1b06ad53d195f2dd06bb1f489c6e51fae5724867d
size 7478
oid sha256:281bd00550ab1cf9b05011750e6de330cdd42a8644ecf3b7c176bd5c6e94c59b
size 6098

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:49b3eaeedfdd1c90b5ec48bf07e387d6cac8062ba15826971c22868b29ecb769
size 7462
oid sha256:e755ed61d7eab2c297f4b6592366b54ac801cbdb3d920741dfdd04dfaf73f9b9
size 6086

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e27f9c12e743d3bcc328dc7e5708c738c5a8ccba3ac99a465bb2fbc045afdc45
size 28062
oid sha256:8e8afa56c5abb0e4b5895f35415db1178d041120d9f8306902f554cfaaada88d
size 26540

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3dfbc7ca2c42fba08daff44f29fdc64f76cb5515ee8f0fd5798270ed64fdeb27
size 26282
oid sha256:a2c174ef54b68f025352e25800f655fe3b94a0d3f75cb48bd2ac0e8d6931faf8
size 24827

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad0e7cf115b2057fa223c45340f67b1659be6f6acefad32e0b02ca35327ffdf8
size 28052
oid sha256:6b56ceae2f350a1402beecc5b5e2930be1011a95fbf224cccf73b96f3931b646
size 26531

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7ca5e0729371222626d711a3f425c602198f5b51c8629e59027e37d63fadf5fa
size 25870
oid sha256:049ee7fc2bb758609a64149c338bfae2eab44755f53e6b7c25a5e8b8725ed8ac
size 24416

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bf1df2d5b53bc779f01e6ad32ca502cd5fb40826cb1fbf5ddc55b769f68e0d8c
size 28060
oid sha256:72c487a2fa3d608021b26a4d6b4517f8548fdcfc62fbafdd8649015dbec8ff87
size 26504

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f21b8a574beb978befbb44e65def5a4d818fbca6be211cccc455fd819c278531
size 34325
oid sha256:099733c9d4490c86cfbb10a016e2dd073539a95f9d5ff9018cf9b5be5404fa13
size 33435

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ced1fc39a97381af2ef369b94351a96930a58052343dcaa464b12da1747906f
size 37066
oid sha256:27f2a2b21f8ae878e15120ea5a4a983bde7984b3468dc8426055885efc278fe6
size 35547

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b60fa32cf39ee70103d8498e5dc751fe2d5801fa30ff9f0948376fac64cb1021
size 41427
oid sha256:6b5cbe60e26e123e5a5cdf5a4e88305340f76d32a9c64a220c1fa7512f84e786
size 39442

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce56cc11a369838e011a796cc380f9f06ca915a845e5b6b0425cb7366f162a5c
size 27303
oid sha256:102cceb79acb1dfd7ec8887b4906e33456c774d48320d1624b3c73975d26f145
size 25981

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3dfbc7ca2c42fba08daff44f29fdc64f76cb5515ee8f0fd5798270ed64fdeb27
size 26282
oid sha256:a2c174ef54b68f025352e25800f655fe3b94a0d3f75cb48bd2ac0e8d6931faf8
size 24827

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9ce82b69d9dde2621a3d54647e7a659d440e2f9b0102831773a97abca4bcfa4c
size 27248
oid sha256:e61629aeefac7e0a1a6b46b44ad86ed4a5ba0908bb3febc18bb5f9f3ded1c08d
size 25751

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71d45938ec05003c5dcae0962ac6847041410123ba1dc2debbbf41e22ac2d91a
size 27519
oid sha256:45b1b48e1df393f4c435189c71a6bd3bccfe7a055d76d414a8d0c009b59fa0a0
size 26145

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:75bd6138967d8795d0be7fe2e76c889718276924d702349105003c24f08957e2
size 25559
oid sha256:d6d186f9e547f658b719bc033e3b110d64cf2a02caecc510d4e2f88359e69746
size 24176

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b280a880b864a14225fd433378dede02cfad897059939cebbe0e04659de6d5a9
size 25382
oid sha256:339b3299984f1450f1a8200e487964c0338b511b82e459d67a3583d0bd46b805
size 24013

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3c94d6ae8ad8ddab17a5ad1cfc73e711912a3f13e6bd3dfe9e5f0c8ec2c004af
size 33257
oid sha256:5335c6184829fdc405475bd34d2fae60cf6d5ae050b4d671ac5dd25242ff1368
size 31888

Loading…
Cancel
Save