Skip to content

Commit 0103d81

Browse files
Fix off-by-one error when centering a transform.
1 parent 9dda64a commit 0103d81

File tree

65 files changed

+409
-198
lines changed

Some content is hidden

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

65 files changed

+409
-198
lines changed

src/ImageSharp/Processing/AffineTransformBuilder.cs

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ namespace SixLabors.ImageSharp.Processing;
1111
/// </summary>
1212
public class AffineTransformBuilder
1313
{
14-
private readonly List<Func<Size, Matrix3x2>> matrixFactories = new List<Func<Size, Matrix3x2>>();
14+
private readonly List<Func<Size, Matrix3x2>> transformMatrixFactories = new();
15+
private readonly List<Func<Size, Matrix3x2>> boundsMatrixFactories = new();
1516

1617
/// <summary>
1718
/// Prepends a rotation matrix using the given rotation angle in degrees
@@ -29,7 +30,9 @@ public AffineTransformBuilder PrependRotationDegrees(float degrees)
2930
/// <param name="radians">The amount of rotation, in radians.</param>
3031
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
3132
public AffineTransformBuilder PrependRotationRadians(float radians)
32-
=> this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
33+
=> this.Prepend(
34+
size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size),
35+
size => TransformUtils.CreateRotationBoundsMatrixRadians(radians, size));
3336

3437
/// <summary>
3538
/// Prepends a rotation matrix using the given rotation in degrees at the given origin.
@@ -65,7 +68,9 @@ public AffineTransformBuilder AppendRotationDegrees(float degrees)
6568
/// <param name="radians">The amount of rotation, in radians.</param>
6669
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
6770
public AffineTransformBuilder AppendRotationRadians(float radians)
68-
=> this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
71+
=> this.Append(
72+
size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size),
73+
size => TransformUtils.CreateRotationBoundsMatrixRadians(radians, size));
6974

7075
/// <summary>
7176
/// Appends a rotation matrix using the given rotation in degrees at the given origin.
@@ -140,7 +145,9 @@ public AffineTransformBuilder AppendScale(Vector2 scales)
140145
/// <param name="degreesY">The Y angle, in degrees.</param>
141146
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
142147
public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
143-
=> this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
148+
=> this.Prepend(
149+
size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size),
150+
size => TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, size));
144151

145152
/// <summary>
146153
/// Prepends a centered skew matrix from the give angles in radians.
@@ -149,7 +156,9 @@ public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
149156
/// <param name="radiansY">The Y angle, in radians.</param>
150157
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
151158
public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
152-
=> this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
159+
=> this.Prepend(
160+
size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size),
161+
size => TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size));
153162

154163
/// <summary>
155164
/// Prepends a skew matrix using the given angles in degrees at the given origin.
@@ -178,7 +187,9 @@ public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY,
178187
/// <param name="degreesY">The Y angle, in degrees.</param>
179188
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
180189
public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
181-
=> this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
190+
=> this.Append(
191+
size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size),
192+
size => TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, size));
182193

183194
/// <summary>
184195
/// Appends a centered skew matrix from the give angles in radians.
@@ -187,7 +198,9 @@ public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
187198
/// <param name="radiansY">The Y angle, in radians.</param>
188199
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
189200
public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
190-
=> this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
201+
=> this.Append(
202+
size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size),
203+
size => TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size));
191204

192205
/// <summary>
193206
/// Appends a skew matrix using the given angles in degrees at the given origin.
@@ -254,7 +267,7 @@ public AffineTransformBuilder AppendTranslation(Vector2 position)
254267
public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix)
255268
{
256269
CheckDegenerate(matrix);
257-
return this.Prepend(_ => matrix);
270+
return this.Prepend(_ => matrix, _ => matrix);
258271
}
259272

260273
/// <summary>
@@ -270,7 +283,7 @@ public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix)
270283
public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix)
271284
{
272285
CheckDegenerate(matrix);
273-
return this.Append(_ => matrix);
286+
return this.Append(_ => matrix, _ => matrix);
274287
}
275288

276289
/// <summary>
@@ -281,7 +294,7 @@ public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix)
281294
public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));
282295

283296
/// <summary>
284-
/// Returns the combined matrix for a given source rectangle.
297+
/// Returns the combined transform matrix for a given source rectangle.
285298
/// </summary>
286299
/// <param name="sourceRectangle">The rectangle in the source image.</param>
287300
/// <exception cref="DegenerateTransformException">
@@ -296,11 +309,11 @@ public Matrix3x2 BuildMatrix(Rectangle sourceRectangle)
296309
Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle));
297310

298311
// Translate the origin matrix to cater for source rectangle offsets.
299-
var matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
312+
Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
300313

301314
Size size = sourceRectangle.Size;
302315

303-
foreach (Func<Size, Matrix3x2> factory in this.matrixFactories)
316+
foreach (Func<Size, Matrix3x2> factory in this.transformMatrixFactories)
304317
{
305318
matrix *= factory(size);
306319
}
@@ -310,6 +323,32 @@ public Matrix3x2 BuildMatrix(Rectangle sourceRectangle)
310323
return matrix;
311324
}
312325

326+
/// <summary>
327+
/// Returns the size of a rectangle large enough to contain the transformed source rectangle.
328+
/// </summary>
329+
/// <param name="sourceRectangle">The rectangle in the source image.</param>
330+
/// <exception cref="DegenerateTransformException">
331+
/// The resultant matrix is degenerate containing one or more values equivalent
332+
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
333+
/// for linear transforms.
334+
/// </exception>
335+
/// <returns>The <see cref="Size"/>.</returns>
336+
public Size GetTransformedSize(Rectangle sourceRectangle)
337+
{
338+
Size size = sourceRectangle.Size;
339+
340+
// Translate the origin matrix to cater for source rectangle offsets.
341+
Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
342+
343+
foreach (Func<Size, Matrix3x2> factory in this.boundsMatrixFactories)
344+
{
345+
matrix *= factory(size);
346+
CheckDegenerate(matrix);
347+
}
348+
349+
return TransformUtils.GetTransformedSize(size, matrix);
350+
}
351+
313352
private static void CheckDegenerate(Matrix3x2 matrix)
314353
{
315354
if (TransformUtils.IsDegenerate(matrix))
@@ -318,15 +357,17 @@ private static void CheckDegenerate(Matrix3x2 matrix)
318357
}
319358
}
320359

321-
private AffineTransformBuilder Prepend(Func<Size, Matrix3x2> factory)
360+
private AffineTransformBuilder Prepend(Func<Size, Matrix3x2> transformFactory, Func<Size, Matrix3x2> boundsFactory)
322361
{
323-
this.matrixFactories.Insert(0, factory);
362+
this.transformMatrixFactories.Insert(0, transformFactory);
363+
this.boundsMatrixFactories.Insert(0, boundsFactory);
324364
return this;
325365
}
326366

327-
private AffineTransformBuilder Append(Func<Size, Matrix3x2> factory)
367+
private AffineTransformBuilder Append(Func<Size, Matrix3x2> transformFactory, Func<Size, Matrix3x2> boundsFactory)
328368
{
329-
this.matrixFactories.Add(factory);
369+
this.transformMatrixFactories.Add(transformFactory);
370+
this.boundsMatrixFactories.Add(boundsFactory);
330371
return this;
331372
}
332373
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public static IImageProcessingContext Transform(
5151
IResampler sampler)
5252
{
5353
Matrix3x2 transform = builder.BuildMatrix(sourceRectangle);
54-
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform);
54+
Size targetDimensions = builder.GetTransformedSize(sourceRectangle);
5555
return source.Transform(sourceRectangle, transform, targetDimensions, sampler);
5656
}
5757

@@ -113,7 +113,7 @@ public static IImageProcessingContext Transform(
113113
IResampler sampler)
114114
{
115115
Matrix4x4 transform = builder.BuildMatrix(sourceRectangle);
116-
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform);
116+
Size targetDimensions = builder.GetTransformedSize(sourceRectangle);
117117
return source.Transform(sourceRectangle, transform, targetDimensions, sampler);
118118
}
119119

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ public static float GetSamplingRadius<TResampler>(in TResampler sampler, int sou
3838
/// </summary>
3939
/// <param name="radius">The radius.</param>
4040
/// <param name="center">The center position.</param>
41-
/// <param name="min">The min allowed amouunt.</param>
42-
/// <param name="max">The max allowed amouunt.</param>
41+
/// <param name="min">The min allowed amount.</param>
42+
/// <param name="max">The max allowed amount.</param>
4343
/// <returns>The <see cref="int"/>.</returns>
4444
[MethodImpl(InliningOptions.ShortMethod)]
4545
public static int GetRangeStart(float radius, float center, int min, int max)
@@ -51,8 +51,8 @@ public static int GetRangeStart(float radius, float center, int min, int max)
5151
/// </summary>
5252
/// <param name="radius">The radius.</param>
5353
/// <param name="center">The center position.</param>
54-
/// <param name="min">The min allowed amouunt.</param>
55-
/// <param name="max">The max allowed amouunt.</param>
54+
/// <param name="min">The min allowed amount.</param>
55+
/// <param name="max">The max allowed amount.</param>
5656
/// <returns>The <see cref="int"/>.</returns>
5757
[MethodImpl(InliningOptions.ShortMethod)]
5858
public static int GetRangeEnd(float radius, float center, int min, int max)

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ 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.CreateRotationMatrixDegrees(degrees, sourceSize),
31+
TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize),
32+
TransformUtils.CreateRotationBoundsMatrixDegrees(degrees, sourceSize),
3233
sampler,
3334
sourceSize)
3435
=> this.Degrees = degrees;
3536

3637
// Helper constructor
37-
private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize)
38-
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, rotationMatrix))
38+
private RotateProcessor(Matrix3x2 rotationMatrix, Matrix3x2 boundsMatrix, IResampler sampler, Size sourceSize)
39+
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, boundsMatrix))
3940
{
4041
}
4142

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ 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.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize),
33+
TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize),
34+
TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, sourceSize),
3435
sampler,
3536
sourceSize)
3637
{
@@ -39,8 +40,8 @@ public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size so
3940
}
4041

4142
// Helper constructor:
42-
private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize)
43-
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix))
43+
private SkewProcessor(Matrix3x2 skewMatrix, Matrix3x2 boundsMatrix, IResampler sampler, Size sourceSize)
44+
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, boundsMatrix))
4445
{
4546
}
4647

0 commit comments

Comments
 (0)