Skip to content

Commit c579547

Browse files
Separate transform spaces
1 parent e1555fd commit c579547

File tree

75 files changed

+262
-185
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+262
-185
lines changed

src/ImageSharp/Processing/AffineTransformBuilder.cs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,28 @@ public class AffineTransformBuilder
1313
{
1414
private readonly List<Func<Size, Matrix3x2>> transformMatrixFactories = new();
1515

16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="AffineTransformBuilder"/> class.
18+
/// </summary>
19+
public AffineTransformBuilder()
20+
: this(TransformSpace.Pixel)
21+
{
22+
}
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="AffineTransformBuilder"/> class.
26+
/// </summary>
27+
/// <param name="transformSpace">
28+
/// The <see cref="TransformSpace"/> to use when applying the affine transform.
29+
/// </param>
30+
public AffineTransformBuilder(TransformSpace transformSpace)
31+
=> this.TransformSpace = transformSpace;
32+
33+
/// <summary>
34+
/// Gets the <see cref="TransformSpace"/> to use when applying the affine transform.
35+
/// </summary>
36+
public TransformSpace TransformSpace { get; }
37+
1638
/// <summary>
1739
/// Prepends a rotation matrix using the given rotation angle in degrees
1840
/// and the image center point as rotation center.
@@ -30,7 +52,7 @@ public AffineTransformBuilder PrependRotationDegrees(float degrees)
3052
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
3153
public AffineTransformBuilder PrependRotationRadians(float radians)
3254
=> this.Prepend(
33-
size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size));
55+
size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace));
3456

3557
/// <summary>
3658
/// Prepends a rotation matrix using the given rotation in degrees at the given origin.
@@ -66,7 +88,7 @@ public AffineTransformBuilder AppendRotationDegrees(float degrees)
6688
/// <param name="radians">The amount of rotation, in radians.</param>
6789
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
6890
public AffineTransformBuilder AppendRotationRadians(float radians)
69-
=> this.Append(size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size));
91+
=> this.Append(size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace));
7092

7193
/// <summary>
7294
/// Appends a rotation matrix using the given rotation in degrees at the given origin.
@@ -141,7 +163,7 @@ public AffineTransformBuilder AppendScale(Vector2 scales)
141163
/// <param name="degreesY">The Y angle, in degrees.</param>
142164
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
143165
public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
144-
=> this.Prepend(size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size));
166+
=> this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY));
145167

146168
/// <summary>
147169
/// Prepends a centered skew matrix from the give angles in radians.
@@ -150,7 +172,7 @@ public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
150172
/// <param name="radiansY">The Y angle, in radians.</param>
151173
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
152174
public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
153-
=> this.Prepend(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size));
175+
=> this.Prepend(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace));
154176

155177
/// <summary>
156178
/// Prepends a skew matrix using the given angles in degrees at the given origin.
@@ -179,7 +201,7 @@ public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY,
179201
/// <param name="degreesY">The Y angle, in degrees.</param>
180202
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
181203
public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
182-
=> this.Append(size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size));
204+
=> this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY));
183205

184206
/// <summary>
185207
/// Appends a centered skew matrix from the give angles in radians.
@@ -188,7 +210,7 @@ public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
188210
/// <param name="radiansY">The Y angle, in radians.</param>
189211
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
190212
public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
191-
=> this.Append(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size));
213+
=> this.Append(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace));
192214

193215
/// <summary>
194216
/// Appends a skew matrix using the given angles in degrees at the given origin.
@@ -334,7 +356,7 @@ public Size GetTransformedSize(Rectangle sourceRectangle)
334356
CheckDegenerate(matrix);
335357
}
336358

337-
return TransformUtils.GetTransformedSize(matrix, size);
359+
return TransformUtils.GetTransformedSize(matrix, size, this.TransformSpace);
338360
}
339361

340362
private static void CheckDegenerate(Matrix3x2 matrix)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ public RotateProcessor(float degrees, Size sourceSize)
2828
/// <param name="sourceSize">The source image size</param>
2929
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
3030
: this(
31-
TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize),
31+
TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize, TransformSpace.Pixel),
3232
sampler,
3333
sourceSize)
3434
=> this.Degrees = degrees;
3535

3636
// Helper constructor
3737
private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize)
38-
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(rotationMatrix, sourceSize))
38+
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(rotationMatrix, sourceSize, TransformSpace.Pixel))
3939
{
4040
}
4141

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public SkewProcessor(float degreesX, float degreesY, Size sourceSize)
3030
/// <param name="sourceSize">The source image size</param>
3131
public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize)
3232
: this(
33-
TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize),
33+
TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize, TransformSpace.Pixel),
3434
sampler,
3535
sourceSize)
3636
{
@@ -40,7 +40,7 @@ public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size so
4040

4141
// Helper constructor:
4242
private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize)
43-
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(skewMatrix, sourceSize))
43+
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(skewMatrix, sourceSize, TransformSpace.Pixel))
4444
{
4545
}
4646

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

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -83,53 +83,60 @@ public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix)
8383
/// </summary>
8484
/// <param name="degrees">The amount of rotation, in degrees.</param>
8585
/// <param name="size">The source image size.</param>
86+
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when creating the centered matrix.</param>
8687
/// <returns>The <see cref="Matrix3x2"/>.</returns>
8788
[MethodImpl(MethodImplOptions.AggressiveInlining)]
88-
public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size)
89-
=> CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), size);
89+
public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size, TransformSpace transformSpace)
90+
=> CreateRotationTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degrees), size, transformSpace);
9091

9192
/// <summary>
9293
/// Creates a centered rotation transform matrix using the given rotation in radians and the source size.
9394
/// </summary>
9495
/// <param name="radians">The amount of rotation, in radians.</param>
9596
/// <param name="size">The source image size.</param>
97+
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when creating the centered matrix.</param>
9698
/// <returns>The <see cref="Matrix3x2"/>.</returns>
9799
[MethodImpl(MethodImplOptions.AggressiveInlining)]
98-
public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size)
99-
=> CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateRotation(radians, PointF.Empty), size);
100+
public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size, TransformSpace transformSpace)
101+
=> CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateRotation(radians, PointF.Empty), size, transformSpace);
100102

101103
/// <summary>
102104
/// Creates a centered skew transform matrix from the give angles in degrees and the source size.
103105
/// </summary>
104106
/// <param name="degreesX">The X angle, in degrees.</param>
105107
/// <param name="degreesY">The Y angle, in degrees.</param>
106108
/// <param name="size">The source image size.</param>
109+
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when creating the centered matrix.</param>
107110
/// <returns>The <see cref="Matrix3x2"/>.</returns>
108111
[MethodImpl(MethodImplOptions.AggressiveInlining)]
109-
public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size)
110-
=> CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), size);
112+
public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size, TransformSpace transformSpace)
113+
=> CreateSkewTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), size, transformSpace);
111114

112115
/// <summary>
113116
/// Creates a centered skew transform matrix from the give angles in radians and the source size.
114117
/// </summary>
115118
/// <param name="radiansX">The X angle, in radians.</param>
116119
/// <param name="radiansY">The Y angle, in radians.</param>
117120
/// <param name="size">The source image size.</param>
121+
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when creating the centered matrix.</param>
118122
/// <returns>The <see cref="Matrix3x2"/>.</returns>
119123
[MethodImpl(MethodImplOptions.AggressiveInlining)]
120-
public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size)
121-
=> CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty), size);
124+
public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size, TransformSpace transformSpace)
125+
=> CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty), size, transformSpace);
122126

123127
/// <summary>
124128
/// Gets the centered transform matrix based upon the source rectangle.
125129
/// </summary>
126130
/// <param name="matrix">The transformation matrix.</param>
127131
/// <param name="size">The source image size.</param>
132+
/// <param name="transformSpace">
133+
/// The <see cref="TransformSpace"/> to use when creating the centered matrix.
134+
/// </param>
128135
/// <returns>The <see cref="Matrix3x2"/></returns>
129136
[MethodImpl(MethodImplOptions.AggressiveInlining)]
130-
public static Matrix3x2 CreateCenteredTransformMatrix(Matrix3x2 matrix, Size size)
137+
public static Matrix3x2 CreateCenteredTransformMatrix(Matrix3x2 matrix, Size size, TransformSpace transformSpace)
131138
{
132-
Size destinationSize = GetUnboundedTransformedSize(matrix, size);
139+
Size transformSize = GetUnboundedTransformedSize(matrix, size, transformSpace);
133140

134141
// We invert the matrix to handle the transformation from screen to world space.
135142
// This ensures scaling matrices are correct.
@@ -138,8 +145,10 @@ public static Matrix3x2 CreateCenteredTransformMatrix(Matrix3x2 matrix, Size siz
138145
// The source size is provided using the coordinate space of the source image.
139146
// however the transform should always be applied in the pixel space.
140147
// To account for this we offset by the size - 1 to translate to the pixel space.
141-
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(destinationSize.Width - 1), -(destinationSize.Height - 1)) * .5F);
142-
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(size.Width - 1, size.Height - 1) * .5F);
148+
float offset = transformSpace == TransformSpace.Pixel ? 1F : 0F;
149+
150+
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(transformSize.Width - offset), -(transformSize.Height - offset)) * .5F);
151+
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(size.Width - offset, size.Height - offset) * .5F);
143152

144153
// Translate back to world space.
145154
Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
@@ -161,12 +170,6 @@ public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner
161170
{
162171
Matrix4x4 matrix = Matrix4x4.Identity;
163172

164-
// The source size is provided using the Coordinate/Geometric space of the source image.
165-
// However, the transform should always be applied in the Discrete/Pixel space to ensure
166-
// that the transformation fully encompasses all pixels without clipping at the edges.
167-
// To account for this, we subtract [1,1] from the size to translate to the Discrete/Pixel space.
168-
// size -= new Size(1, 1);
169-
170173
/*
171174
* SkMatrix is laid out in the following manner:
172175
*
@@ -280,11 +283,10 @@ public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner
280283
/// </summary>
281284
/// <param name="matrix">The transformation matrix.</param>
282285
/// <param name="size">The source size.</param>
283-
/// <returns>
284-
/// The <see cref="Size"/>.
285-
/// </returns>
286-
public static Size GetTransformedSize(Matrix3x2 matrix, Size size)
287-
=> GetTransformedSize(matrix, size, true);
286+
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when calculating the size.</param>
287+
/// <returns>The <see cref="Size"/>.</returns>
288+
public static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace)
289+
=> GetTransformedSize(matrix, size, transformSpace, true);
288290

289291
/// <summary>
290292
/// Returns the size relative to the source for the given transformation matrix.
@@ -355,22 +357,22 @@ public static Size GetTransformedSize(Matrix4x4 matrix, Size size)
355357
/// </summary>
356358
/// <param name="matrix">The transformation matrix.</param>
357359
/// <param name="size">The source size.</param>
358-
/// <returns>
359-
/// The <see cref="Size"/>.
360-
/// </returns>
361-
private static Size GetUnboundedTransformedSize(Matrix3x2 matrix, Size size)
362-
=> GetTransformedSize(matrix, size, false);
360+
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when calculating the size.</param>
361+
/// <returns>The <see cref="Size"/>.</returns>
362+
private static Size GetUnboundedTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace)
363+
=> GetTransformedSize(matrix, size, transformSpace, false);
363364

364365
/// <summary>
365366
/// Returns the size relative to the source for the given transformation matrix.
366367
/// </summary>
367368
/// <param name="matrix">The transformation matrix.</param>
368369
/// <param name="size">The source size.</param>
370+
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when calculating the size.</param>
369371
/// <param name="constrain">Whether to constrain the size to ensure that the dimensions are positive.</param>
370372
/// <returns>
371373
/// The <see cref="Size"/>.
372374
/// </returns>
373-
private static Size GetTransformedSize(Matrix3x2 matrix, Size size, bool constrain)
375+
private static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace, bool constrain)
374376
{
375377
Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!");
376378

@@ -381,9 +383,13 @@ private static Size GetTransformedSize(Matrix3x2 matrix, Size size, bool constra
381383

382384
// Define an offset size to translate between coordinate space and pixel space.
383385
// Compute scaling factors from the matrix
384-
float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2)
385-
float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2)
386-
SizeF offsetSize = new(scaleX, scaleY);
386+
SizeF offsetSize = SizeF.Empty;
387+
if (transformSpace == TransformSpace.Pixel)
388+
{
389+
float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2)
390+
float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2)
391+
offsetSize = new(scaleX, scaleY);
392+
}
387393

388394
// Subtract the offset size to translate to the pixel space.
389395
if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds))

0 commit comments

Comments
 (0)