diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 42d555f20..eab75cfdb 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -20,33 +20,55 @@ namespace SixLabors.ImageSharp.Processing /// Prepends a centered rotation matrix using the given rotation in radians. /// /// The amount of rotation, in radians. + /// The rotation center point /// The . - public AffineTransformBuilder PrependCenteredRotationRadians(float radians) - => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + public AffineTransformBuilder PrependRotationRadians(float radians, Vector2 centerPoint) + => this.PrependMatrix(Matrix3x2.CreateRotation(radians, centerPoint)); /// /// Appends a centered rotation matrix using the given rotation in radians. /// /// The amount of rotation, in radians. + /// The rotation center point + /// The . + public AffineTransformBuilder AppendRotationRadians(float radians, Vector2 centerPoint) + => this.AppendMatrix(Matrix3x2.CreateRotation(radians, centerPoint)); + + /// + /// Prepends a rotation matrix using the given rotation angle in radians + /// and the image center point as rotation center. + /// + /// The amount of rotation, in radians. + /// The . + public AffineTransformBuilder PrependRotationRadians(float radians) + => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + + /// + /// Appends a rotation matrix using the given rotation angle in radians + /// and the image center point as rotation center. + /// + /// The amount of rotation, in radians. /// The . - public AffineTransformBuilder AppendCenteredRotationRadians(float radians) + public AffineTransformBuilder AppendRotationRadians(float radians) => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); /// - /// Prepends a centered rotation matrix using the given rotation in degrees. + /// Prepends a rotation matrix using the given rotation angle in degrees + /// and the image center point as rotation center. /// /// The amount of rotation, in degrees. /// The . - public AffineTransformBuilder PrependCenteredRotationDegrees(float degrees) - => this.PrependCenteredRotationRadians(ImageMaths.DegreesToRadians(degrees)); + public AffineTransformBuilder PrependRotationDegrees(float degrees) + => this.PrependRotationRadians(ImageMaths.DegreesToRadians(degrees)); /// - /// Appends a centered rotation matrix using the given rotation in degrees. + /// Appends a rotation matrix using the given rotation angle in degrees + /// and the image center point as rotation center. /// /// The amount of rotation, in degrees. /// The . - public AffineTransformBuilder AppendCenteredRotationDegrees(float degrees) - => this.AppendCenteredRotationRadians(ImageMaths.DegreesToRadians(degrees)); + public AffineTransformBuilder AppendRotationDegrees(float degrees) + => this.AppendRotationRadians(ImageMaths.DegreesToRadians(degrees)); /// /// Prepends a scale matrix from the given uniform scale. diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index 9bcbaf834..8bde90d51 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -67,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing /// /// The amount of rotation, in radians. /// The . - public ProjectiveTransformBuilder PrependCenteredRotationRadians(float radians) + public ProjectiveTransformBuilder PrependRotationRadians(float radians) { var m = new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, this.Size)); return this.PrependMatrix(m); @@ -78,27 +79,51 @@ namespace SixLabors.ImageSharp.Processing /// /// The amount of rotation, in radians. /// The . - public ProjectiveTransformBuilder AppendCenteredRotationRadians(float radians) + public ProjectiveTransformBuilder AppendRotationRadians(float radians) { var m = new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, this.Size)); return this.AppendMatrix(m); } + /// + /// Appends a centered rotation matrix using the given rotation in radians. + /// + /// The amount of rotation, in radians. + /// The rotation center. + /// The . + internal ProjectiveTransformBuilder PrependRotationRadians(float radians, Vector2 centerPoint) + { + var m = Matrix4x4.CreateRotationZ(radians, new Vector3(centerPoint, 0)); + return this.PrependMatrix(m); + } + + /// + /// Appends a centered rotation matrix using the given rotation in radians. + /// + /// The amount of rotation, in radians. + /// The rotation center. + /// The . + internal ProjectiveTransformBuilder AppendRotationRadians(float radians, Vector2 centerPoint) + { + var m = Matrix4x4.CreateRotationZ(radians, new Vector3(centerPoint, 0)); + return this.AppendMatrix(m); + } + /// /// Prepends a centered rotation matrix using the given rotation in degrees. /// /// The amount of rotation, in degrees. /// The . public ProjectiveTransformBuilder PrependCenteredRotationDegrees(float degrees) - => this.PrependCenteredRotationRadians(ImageMaths.DegreesToRadians(degrees)); + => this.PrependRotationRadians(ImageMaths.DegreesToRadians(degrees)); /// /// Appends a centered rotation matrix using the given rotation in degrees. /// /// The amount of rotation, in degrees. /// The . - public ProjectiveTransformBuilder AppendCenteredRotationDegrees(float degrees) - => this.AppendCenteredRotationRadians(ImageMaths.DegreesToRadians(degrees)); + public ProjectiveTransformBuilder AppendRotationDegrees(float degrees) + => this.AppendRotationRadians(ImageMaths.DegreesToRadians(degrees)); /// /// Prepends a scale matrix from the given uniform scale. diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index f3772e3aa..374454afb 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendCenteredRotationDegrees(45F) + .AppendRotationDegrees(45F) .AppendScale(new SizeF(.25F, .25F)) .AppendTranslation(new PointF(10, 10)); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs index cbbf59caf..e02585168 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs @@ -14,11 +14,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { protected override void AppendTranslation(AffineTransformBuilder builder, PointF translate) => builder.AppendTranslation(translate); protected override void AppendScale(AffineTransformBuilder builder, SizeF scale) => builder.AppendScale(scale); - protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians) => builder.AppendCenteredRotationRadians(radians); + protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians) => builder.AppendRotationRadians(radians); + protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians, Vector2 center) => + builder.AppendRotationRadians(radians, center); protected override void PrependTranslation(AffineTransformBuilder builder, PointF translate) => builder.PrependTranslation(translate); protected override void PrependScale(AffineTransformBuilder builder, SizeF scale) => builder.PrependScale(scale); - protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians) => builder.PrependCenteredRotationRadians(radians); + protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians) => builder.PrependRotationRadians(radians); + protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians, Vector2 center) => + builder.PrependRotationRadians(radians, center); protected override AffineTransformBuilder CreateBuilder(Rectangle rectangle) => new AffineTransformBuilder(); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs index 0c167d7f9..ed6d3ef2b 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms using (Image image = provider.GetImage()) { AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendCenteredRotationDegrees(30); + .AppendRotationDegrees(30); image.Mutate(c => c.Transform(builder, resampler)); image.DebugSave(provider, resamplerName); @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { image.DebugSave(provider, $"_original"); AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendCenteredRotationDegrees(angleDeg) + .AppendRotationDegrees(angleDeg) .AppendScale(new SizeF(sx, sy)) .AppendTranslation(new PointF(tx, ty)); @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms using (Image image = provider.GetImage()) { AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendCenteredRotationDegrees(angleDeg) + .AppendRotationDegrees(angleDeg) .AppendScale(new SizeF(s, s)); image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); @@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms using (Image image = provider.GetImage()) { AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendCenteredRotationDegrees(50) + .AppendRotationDegrees(50) .AppendScale(new SizeF(.6F, .6F)); image.Mutate(i => i.Transform(builder, sampler)); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs index 14ed57033..01448ac59 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs @@ -39,11 +39,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms protected override void AppendTranslation(ProjectiveTransformBuilder builder, PointF translate) => builder.AppendTranslation(translate); protected override void AppendScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.AppendScale(scale); - protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.AppendCenteredRotationRadians(radians); + protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.AppendRotationRadians(radians); + protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 center) => + builder.AppendRotationRadians(radians, center); protected override void PrependTranslation(ProjectiveTransformBuilder builder, PointF translate) => builder.PrependTranslation(translate); protected override void PrependScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.PrependScale(scale); - protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.PrependCenteredRotationRadians(radians); + protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.PrependRotationRadians(radians); + protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 center) => + builder.PrependRotationRadians(radians, center); protected override Vector2 Execute( ProjectiveTransformBuilder builder, diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index 648ed5861..badf43013 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -87,7 +87,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [InlineData(200, 100, 10, 42, 84)] [InlineData(200, 100, 100, 42, 84)] [InlineData(100, 200, -10, 42, 84)] - public void RotateDegrees_ShouldCreateCenteredMatrix(int width, int height, float deg, float x, float y) + public void AppendRotationDegrees_WithoutSpecificRotationCenter_RotationIsCenteredAroundImageCenter( + int width, + int height, + float deg, + float x, + float y) { var size = new Size(width, height); TBuilder builder = this.CreateBuilder(size); @@ -97,13 +102,41 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(deg, size); - var position = new Vector2(x, y); + var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); Assert.Equal(actual, expected, Comparer); } - + + [Theory] + [InlineData(200, 100, 10, 30, 61, 42, 84)] + [InlineData(200, 100, 100, 30, 10, 20, 84)] + [InlineData(100, 200, -10, 30, 20, 11, 84)] + public void AppendRotationDegrees_WithRotationCenter( + int width, + int height, + float deg, + float cx, + float cy, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(size); + + var centerPoint = new Vector2(cx, cy); + this.AppendRotationDegrees(builder, deg, centerPoint); + + var matrix = Matrix3x2.CreateRotation(ImageMaths.DegreesToRadians(deg), centerPoint); + + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + + Assert.Equal(actual, expected, Comparer); + } + [Fact] public void AppendPrependOpposite() { @@ -116,10 +149,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms // Forwards this.AppendRotationRadians(b1, pi); this.AppendScale(b1, new SizeF(2, 0.5f)); + this.AppendRotationRadians(b1, pi / 2, new Vector2(-0.5f, -0.1f)); this.AppendTranslation(b1, new PointF(123, 321)); // Backwards this.PrependTranslation(b2, new PointF(123, 321)); + this.PrependRotationRadians(b2, pi / 2, new Vector2(-0.5f, -0.1f)); this.PrependScale(b2, new SizeF(2, 0.5f)); this.PrependRotationRadians(b2, pi); @@ -152,14 +187,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms protected abstract void AppendTranslation(TBuilder builder, PointF translate); protected abstract void AppendScale(TBuilder builder, SizeF scale); protected abstract void AppendRotationRadians(TBuilder builder, float radians); + protected abstract void AppendRotationRadians(TBuilder builder, float radians, Vector2 center); protected abstract void PrependTranslation(TBuilder builder, PointF translate); protected abstract void PrependScale(TBuilder builder, SizeF scale); protected abstract void PrependRotationRadians(TBuilder builder, float radians); + protected abstract void PrependRotationRadians(TBuilder b1, float v, Vector2 vector2); protected virtual void AppendRotationDegrees(TBuilder builder, float degrees) => this.AppendRotationRadians(builder, ImageMaths.DegreesToRadians(degrees)); + protected virtual void AppendRotationDegrees(TBuilder builder, float degrees, Vector2 center) => + this.AppendRotationRadians(builder, ImageMaths.DegreesToRadians(degrees), center); + protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); private static float Sqrt(float a) => (float)Math.Sqrt(a);