Browse Source

Begin smarter rotate/skew maths [skip ci]

Former-commit-id: b2ad022ef5c2d8acc22e5a8d4133fc3cb26aac65
Former-commit-id: d0631f99f8aef3b71b44252f0c17ded9a926ec3d
Former-commit-id: 5c7ff70d77fe8d5e56cab00db8fa681912fe4006
af/merge-core
James Jackson-South 10 years ago
parent
commit
44b613f95b
  1. 28
      src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
  2. 67
      src/ImageProcessorCore/Numerics/Point.cs
  3. 4
      src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
  4. 4
      src/ImageProcessorCore/Samplers/Rotate.cs
  5. 13
      src/ImageProcessorCore/Samplers/Skew.cs
  6. 3
      tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
  7. 19
      tests/TestWebsites/MVC/Properties/launchSettings.json

28
src/ImageProcessorCore/Common/Helpers/ImageMaths.cs

@ -112,15 +112,13 @@ namespace ImageProcessorCore
/// <summary>
/// Returns the given degrees converted to radians.
/// </summary>
/// <param name="angleInDegrees">
/// The angle in degrees.
/// </param>
/// <param name="degrees">The angle in degrees.</param>
/// <returns>
/// The <see cref="double"/> representing the degree as radians.
/// The <see cref="float"/> representing the degree as radians.
/// </returns>
public static double DegreesToRadians(double angleInDegrees)
public static float DegreesToRadians(float degrees)
{
return angleInDegrees * (PI / 180);
return degrees * (PI / 180);
}
/// <summary>
@ -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));
}
/// <summary>
/// Calculates the new size after rotation.
/// </summary>

67
src/ImageProcessorCore/Numerics/Point.cs

@ -159,26 +159,26 @@ namespace ImageProcessorCore
}
/// <summary>
/// Rotates a point around a given a rotation matrix.
/// Creates a rotation matrix for the given point and angle.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="rotation">Rotation matrix used</param>
/// <returns></returns>
public static Point Rotate( Point point, Matrix3x2 rotation )
/// <param name="origin">The origin point to rotate around</param>
/// <param name="degrees">Rotation in degrees</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
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);
}
/// <summary>
/// Creates a rotation matrix for
/// Rotates a point around a given a rotation matrix.
/// </summary>
/// <param name="origion">The origin point to rotate around</param>
/// <param name="degrees">Rotation in degrees</param>
/// <returns></returns>
public static Matrix3x2 CreateRotatation( Point origin, float degrees )
/// <param name="point">The point to rotate</param>
/// <param name="rotation">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
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));
}
/// <summary>
@ -187,26 +187,48 @@ namespace ImageProcessorCore
/// <param name="point">The point to rotate</param>
/// <param name="origin">The center point to rotate around.</param>
/// <param name="degrees">The angle in degrees.</param>
/// <returns></returns>
/// <returns>The rotated <see cref="Point"/></returns>
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)));
}
/// <summary>
/// Skews a point around a given origin by the specified angles in degrees.
/// Creates a skew matrix for the given point and angle.
/// </summary>
/// <param name="origin">The origin point to rotate around</param>
/// <param name="degreesX">The x-angle in degrees.</param>
/// <param name="degreesY">The y-angle in degrees.</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
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);
}
/// <summary>
/// Skews a point using a given a skew matrix.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="skew">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
public static Point Skew(Point point, Matrix3x2 skew)
{
return new Point(Vector2.Transform(point.backingVector, skew));
}
/// <summary>
/// Skews a point around a given origin by the specified angles in degrees.
/// </summary>
/// <param name="point">The point to skew.</param>
/// <param name="origin">The center point to rotate around.</param>
/// <param name="degreesX">The x-angle in degrees.</param>
/// <param name="degreesY">The y-angle in degrees.</param>
/// <returns></returns>
/// <returns>The skewed <see cref="Point"/></returns>
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)));
}
/// <inheritdoc/>
@ -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} ]";
}
/// <inheritdoc/>

4
src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs

@ -239,7 +239,7 @@ namespace ImageProcessorCore.Samplers
/// </summary>
/// <param name="source">The image to rotate.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="center">The center point at which to skew the image.</param>
/// <param name="center">The center point at which to rotate the image.</param>
/// <param name="expand">Whether to expand the image to fit the rotated result.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
@ -291,7 +291,7 @@ namespace ImageProcessorCore.Samplers
/// <returns>The <see cref="Image"/></returns>
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);
}
/// <summary>

4
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(

13
src/ImageProcessorCore/Samplers/Skew.cs

@ -5,6 +5,7 @@
namespace ImageProcessorCore.Samplers
{
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
@ -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();
}
});

3
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);
}

19
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"
}
}
}
}
Loading…
Cancel
Save