diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index 662a41f1b..ab4394feb 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -112,15 +112,13 @@ namespace ImageProcessorCore /// /// Returns the given degrees converted to radians. /// - /// - /// The angle in degrees. - /// + /// The angle in degrees. /// - /// The representing the degree as radians. + /// The representing the degree as radians. /// - public static double DegreesToRadians(double angleInDegrees) + public static float DegreesToRadians(float degrees) { - return angleInDegrees * (PI / 180); + return degrees * (PI / 180); } /// @@ -140,6 +138,24 @@ namespace ImageProcessorCore return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); } + // http://gamedev.stackexchange.com/questions/22840/create-a-rectangle-struct-to-be-rotated-and-have-a-intersects-function + public static Rectangle GetBoundingRotatedRectangle(Rectangle rectangle, float degrees, Point center) + { + float radians = DegreesToRadians(degrees); + Matrix3x2 matrix = Matrix3x2.CreateRotation(radians, center.ToVector2()); + + Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + + Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop), Vector2.Min(leftBottom, rightBottom)); + Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop), Vector2.Max(leftBottom, rightBottom)); + + // TODO: minY is wrong - negative + return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y)); + } + /// /// Calculates the new size after rotation. /// diff --git a/src/ImageProcessorCore/Numerics/Point.cs b/src/ImageProcessorCore/Numerics/Point.cs index 472a1c773..f1fe3c5ec 100644 --- a/src/ImageProcessorCore/Numerics/Point.cs +++ b/src/ImageProcessorCore/Numerics/Point.cs @@ -159,26 +159,26 @@ namespace ImageProcessorCore } /// - /// Rotates a point around a given a rotation matrix. + /// Creates a rotation matrix for the given point and angle. /// - /// The point to rotate - /// Rotation matrix used - /// - public static Point Rotate( Point point, Matrix3x2 rotation ) + /// The origin point to rotate around + /// Rotation in degrees + /// The rotation + public static Matrix3x2 CreateRotation(Point origin, float degrees) { - return new Point( Vector2.Transform( point.backingVector, rotation ) ); + float radians = (float)ImageMaths.DegreesToRadians(degrees); + return Matrix3x2.CreateRotation(radians, origin.backingVector); } /// - /// Creates a rotation matrix for + /// Rotates a point around a given a rotation matrix. /// - /// The origin point to rotate around - /// Rotation in degrees - /// - public static Matrix3x2 CreateRotatation( Point origin, float degrees ) + /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Rotate(Point point, Matrix3x2 rotation) { - float radians = (float)ImageMaths.DegreesToRadians( degrees ); - return Matrix3x2.CreateRotation( radians, origin.backingVector ); + return new Point(Vector2.Transform(point.backingVector, rotation)); } /// @@ -187,26 +187,48 @@ namespace ImageProcessorCore /// The point to rotate /// The center point to rotate around. /// The angle in degrees. - /// + /// The rotated public static Point Rotate(Point point, Point origin, float degrees) { - float radians = (float)ImageMaths.DegreesToRadians(degrees); - return new Point(Vector2.Transform(point.backingVector, Matrix3x2.CreateRotation(radians, origin.backingVector))); + return new Point(Vector2.Transform(point.backingVector, CreateRotation(origin, degrees))); } /// - /// Skews a point around a given origin by the specified angles in degrees. + /// Creates a skew matrix for the given point and angle. + /// + /// The origin point to rotate around + /// The x-angle in degrees. + /// The y-angle in degrees. + /// The rotation + public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) + { + float radiansX = (float)ImageMaths.DegreesToRadians(degreesX); + float radiansY = (float)ImageMaths.DegreesToRadians(degreesY); + return Matrix3x2.CreateSkew(radiansX, radiansY, origin.backingVector); + } + + /// + /// Skews a point using a given a skew matrix. /// /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Skew(Point point, Matrix3x2 skew) + { + return new Point(Vector2.Transform(point.backingVector, skew)); + } + + /// + /// Skews a point around a given origin by the specified angles in degrees. + /// + /// The point to skew. /// The center point to rotate around. /// The x-angle in degrees. /// The y-angle in degrees. - /// + /// The skewed public static Point Skew(Point point, Point origin, float degreesX, float degreesY) { - float radiansX = (float)ImageMaths.DegreesToRadians(degreesX); - float radiansY = (float)ImageMaths.DegreesToRadians(degreesY); - return new Point(Vector2.Transform(point.backingVector, Matrix3x2.CreateSkew(degreesX, degreesY, origin.backingVector))); + return new Point(Vector2.Transform(point.backingVector, CreateSkew(origin, degreesX, degreesY))); } /// @@ -223,8 +245,7 @@ namespace ImageProcessorCore return "Point [ Empty ]"; } - return - $"Point [ X={this.X}, Y={this.Y} ]"; + return $"Point [ X={this.X}, Y={this.Y} ]"; } /// diff --git a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs index f28723276..45dc3c913 100644 --- a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs +++ b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs @@ -239,7 +239,7 @@ namespace ImageProcessorCore.Samplers /// /// The image to rotate. /// The angle in degrees to perform the rotation. - /// The center point at which to skew the image. + /// The center point at which to rotate the image. /// Whether to expand the image to fit the rotated result. /// A delegate which is called as progress is made processing the image. /// The @@ -291,7 +291,7 @@ namespace ImageProcessorCore.Samplers /// The public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null) { - return Skew(source, degreesX, degreesY, Rectangle.Center(source.Bounds), progressHandler); + return Skew(source, degreesX, degreesY, Point.Empty, progressHandler); } /// diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs index 1de1b57af..a14b3cee1 100644 --- a/src/ImageProcessorCore/Samplers/Rotate.cs +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -71,7 +71,7 @@ namespace ImageProcessorCore.Samplers { // First find out how the target rectangle should be. Rectangle rectangle = ImageMaths.GetBoundingRotatedRectangle(source.Width, source.Height, -this.angle); - + Rectangle rectangle2 = ImageMaths.GetBoundingRotatedRectangle(sourceRectangle, -this.angle, this.Center); ResizeOptions options = new ResizeOptions { Size = new Size(rectangle.Width, rectangle.Height), @@ -102,7 +102,7 @@ namespace ImageProcessorCore.Samplers int endX = this.firstPass.Bounds.Right; float negativeAngle = -this.angle; Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center; - Matrix3x2 rotation = Point.CreateRotatation(centre, negativeAngle); + Matrix3x2 rotation = Point.CreateRotation(centre, negativeAngle); // Since we are not working in parallel we use full height and width of the first pass image. Parallel.For( diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs index 17e42833f..1d88811d1 100644 --- a/src/ImageProcessorCore/Samplers/Skew.cs +++ b/src/ImageProcessorCore/Samplers/Skew.cs @@ -5,6 +5,7 @@ namespace ImageProcessorCore.Samplers { + using System.Numerics; using System.Threading.Tasks; /// @@ -89,10 +90,7 @@ namespace ImageProcessorCore.Samplers float negativeAngleX = -this.angleX; float negativeAngleY = -this.angleY; Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; - - // Scaling factors - float widthFactor = source.Width / (float)target.Width; - float heightFactor = source.Height / (float)target.Height; + Matrix3x2 skew = Point.CreateSkew(centre, negativeAngleX, negativeAngleY); Parallel.For( startY, @@ -102,20 +100,21 @@ namespace ImageProcessorCore.Samplers if (y >= targetY && y < targetBottom) { // Y coordinates of source points - int originY = (int)((y - targetY) * heightFactor); + int originY = y - targetY; for (int x = startX; x < endX; x++) { // X coordinates of source points - int originX = (int)((x - startX) * widthFactor); + int originX = x - startX; // Skew at the centre point - Point skewed = Point.Skew(new Point(originX, originY), centre, negativeAngleX, negativeAngleY); + Point skewed = Point.Skew(new Point(originX, originY), skew); if (sourceRectangle.Contains(skewed.X, skewed.Y)) { target[x, y] = source[skewed.X, skewed.Y]; } } + this.OnRowProcessed(); } }); diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index 845bf0b1b..7d57934f0 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -452,6 +452,7 @@ Directory.CreateDirectory("TestOutput/Skew"); } + // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) @@ -463,7 +464,7 @@ using (Image image = new Image(stream)) using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) { - image.Skew(45, 45, this.ProgressUpdate) + image.Skew(20, 10, this.ProgressUpdate) .Save(output); } diff --git a/tests/TestWebsites/MVC/Properties/launchSettings.json b/tests/TestWebsites/MVC/Properties/launchSettings.json new file mode 100644 index 000000000..0ce7c413b --- /dev/null +++ b/tests/TestWebsites/MVC/Properties/launchSettings.json @@ -0,0 +1,19 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55993/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file