Browse Source

Moves the new methods to Imaging.Helpers.ImageMaths and auto-cleanup (reordering of methods) of the ImageMaths class

Former-commit-id: f487450c325c000cc431e0413e18d6cef6870f43
Former-commit-id: 17caf80f09314734fd748a89b9b823a0e5646ae4
pull/17/head
Thomas Broust 11 years ago
parent
commit
7291ced730
  1. 10
      src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs
  2. 168
      src/ImageProcessor/Imaging/Helpers/ImageMaths.cs
  3. 65
      src/ImageProcessor/Imaging/Rotation.cs
  4. 2
      src/ImageProcessor/Processors/Rotate.cs
  5. 2
      src/ImageProcessor/Processors/RotateInside.cs

10
src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs → src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs

@ -1,11 +1,11 @@
namespace ImageProcessor.UnitTests.Imaging namespace ImageProcessor.UnitTests.Imaging.Helpers
{ {
using System.Drawing; using System.Drawing;
using FluentAssertions; using FluentAssertions;
using ImageProcessor.Imaging; using ImageProcessor.Imaging.Helpers;
using NUnit.Framework; using NUnit.Framework;
public class RotationUnitTests public class ImageMathsUnitTests
{ {
[Test] [Test]
[TestCase(100, 100, 45, 141, 141)] [TestCase(100, 100, 45, 141, 141)]
@ -13,7 +13,7 @@
[TestCase(100, 200, 50, 217, 205)] [TestCase(100, 200, 50, 217, 205)]
public void NewSizeAfterRotationIsCalculated(int width, int height, float angle, int expectedWidth, int expectedHeight) public void NewSizeAfterRotationIsCalculated(int width, int height, float angle, int expectedWidth, int expectedHeight)
{ {
Size result = Rotation.NewSizeAfterRotation(width, height, angle); Size result = ImageMaths.GetBoundingRotatedRectangle(width, height, angle);
result.Width.Should().Be(expectedWidth, "because the rotated width should have been calculated"); result.Width.Should().Be(expectedWidth, "because the rotated width should have been calculated");
result.Height.Should().Be(expectedHeight, "because the rotated height should have been calculated"); result.Height.Should().Be(expectedHeight, "because the rotated height should have been calculated");
@ -28,7 +28,7 @@
[TestCase(600, 450, 45, 1.64f)] [TestCase(600, 450, 45, 1.64f)]
public void RotationZoomIsCalculated(int imageWidth, int imageHeight, float angle, float expected) public void RotationZoomIsCalculated(int imageWidth, int imageHeight, float angle, float expected)
{ {
float result = Rotation.ZoomAfterRotation(imageWidth, imageHeight, angle); float result = ImageMaths.ZoomAfterRotation(imageWidth, imageHeight, angle);
result.Should().BeApproximately(expected, 0.01f, "because the zoom level after rotation should have been calculated"); result.Should().BeApproximately(expected, 0.01f, "because the zoom level after rotation should have been calculated");
} }

168
src/ImageProcessor/Imaging/Helpers/ImageMaths.cs

@ -12,7 +12,6 @@ namespace ImageProcessor.Imaging.Helpers
{ {
using System; using System;
using System.Drawing; using System.Drawing;
using ImageProcessor.Imaging.Colors; using ImageProcessor.Imaging.Colors;
/// <summary> /// <summary>
@ -20,6 +19,27 @@ namespace ImageProcessor.Imaging.Helpers
/// </summary> /// </summary>
public static class ImageMaths public static class ImageMaths
{ {
/// <summary>
/// Gets a <see cref="Rectangle"/> representing the child centered relative to the parent.
/// </summary>
/// <param name="parent">
/// The parent <see cref="Rectangle"/>.
/// </param>
/// <param name="child">
/// The child <see cref="Rectangle"/>.
/// </param>
/// <returns>
/// The centered <see cref="Rectangle"/>.
/// </returns>
public static RectangleF CenteredRectangle(Rectangle parent, Rectangle child)
{
float x = (parent.Width - child.Width) / 2.0F;
float y = (parent.Height - child.Height) / 2.0F;
int width = child.Width;
int height = child.Height;
return new RectangleF(x, y, width, height);
}
/// <summary> /// <summary>
/// Restricts a value to be within a specified range. /// Restricts a value to be within a specified range.
/// </summary> /// </summary>
@ -53,6 +73,20 @@ namespace ImageProcessor.Imaging.Helpers
return value; return value;
} }
/// <summary>
/// Returns the given degrees converted to radians.
/// </summary>
/// <param name="angleInDegrees">
/// The angle in degrees.
/// </param>
/// <returns>
/// The <see cref="double"/> representing the degree as radians.
/// </returns>
public static double DegreesToRadians(double angleInDegrees)
{
return angleInDegrees * (Math.PI / 180);
}
/// <summary> /// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given points. /// Gets the bounding <see cref="Rectangle"/> from the given points.
/// </summary> /// </summary>
@ -70,6 +104,38 @@ namespace ImageProcessor.Imaging.Helpers
return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
} }
/// <summary>
/// Calculates the new size after rotation.
/// </summary>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="angle">The angle of rotation.</param>
/// <returns>The new size of the image</returns>
public static Size GetBoundingRotatedRectangle(int width, int height, float angle)
{
double widthAsDouble = width;
double heightAsDouble = height;
double radians = DegreesToRadians(angle);
double radiansSin = Math.Sin(radians);
double radiansCos = Math.Cos(radians);
double width1 = (heightAsDouble * radiansSin) + (widthAsDouble * radiansCos);
double height1 = (widthAsDouble * radiansSin) + (heightAsDouble * radiansCos);
// Find dimensions in the other direction
radiansSin = Math.Sin(-radians);
radiansCos = Math.Cos(-radians);
double width2 = (heightAsDouble * radiansSin) + (widthAsDouble * radiansCos);
double height2 = (widthAsDouble * radiansSin) + (heightAsDouble * radiansCos);
// Get the external vertex for the rotation
Size result = new Size();
result.Width = Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2)));
result.Height = Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2)));
return result;
}
/// <summary> /// <summary>
/// Finds the bounding rectangle based on the first instance of any color component other /// Finds the bounding rectangle based on the first instance of any color component other
/// than the given one. /// than the given one.
@ -101,12 +167,15 @@ namespace ImageProcessor.Imaging.Helpers
case RgbaComponent.R: case RgbaComponent.R:
delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).R != b; delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).R != b;
break; break;
case RgbaComponent.G: case RgbaComponent.G:
delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).G != b; delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).G != b;
break; break;
case RgbaComponent.A: case RgbaComponent.A:
delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).A != b; delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).A != b;
break; break;
default: default:
delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).B != b; delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).B != b;
break; break;
@ -188,24 +257,31 @@ namespace ImageProcessor.Imaging.Helpers
} }
/// <summary> /// <summary>
/// Gets a <see cref="Rectangle"/> representing the child centered relative to the parent. /// Rotates one point around another
/// <see href="http://stackoverflow.com/questions/13695317/rotate-a-point-around-another-point"/>
/// </summary> /// </summary>
/// <param name="parent"> /// <param name="pointToRotate">The point to rotate.</param>
/// The parent <see cref="Rectangle"/>. /// <param name="angleInDegrees">The rotation angle in degrees.</param>
/// </param> /// <param name="centerPoint">The centre point of rotation. If not set the point will equal
/// <param name="child"> /// <see cref="Point.Empty"/>
/// The child <see cref="Rectangle"/>.
/// </param> /// </param>
/// <returns> /// <returns>Rotated point</returns>
/// The centered <see cref="Rectangle"/>. public static Point RotatePoint(Point pointToRotate, double angleInDegrees, Point? centerPoint = null)
/// </returns>
public static RectangleF CenteredRectangle(Rectangle parent, Rectangle child)
{ {
float x = (parent.Width - child.Width) / 2.0F; Point center = centerPoint ?? Point.Empty;
float y = (parent.Height - child.Height) / 2.0F;
int width = child.Width; double angleInRadians = DegreesToRadians(angleInDegrees);
int height = child.Height; double cosTheta = Math.Cos(angleInRadians);
return new RectangleF(x, y, width, height); double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)((cosTheta * (pointToRotate.X - center.X)) -
((sinTheta * (pointToRotate.Y - center.Y)) + center.X)),
Y =
(int)((sinTheta * (pointToRotate.X - center.X)) +
((cosTheta * (pointToRotate.Y - center.Y)) + center.Y))
};
} }
/// <summary> /// <summary>
@ -221,55 +297,33 @@ namespace ImageProcessor.Imaging.Helpers
{ {
return new[] return new[]
{ {
new Point(rectangle.Left, rectangle.Top), new Point(rectangle.Left, rectangle.Top),
new Point(rectangle.Right, rectangle.Top), new Point(rectangle.Right, rectangle.Top),
new Point(rectangle.Right, rectangle.Bottom), new Point(rectangle.Right, rectangle.Bottom),
new Point(rectangle.Left, rectangle.Bottom) new Point(rectangle.Left, rectangle.Bottom)
}; };
} }
/// <summary> /// <summary>
/// Returns the given degrees converted to radians. /// Calculates the zoom needed after the rotation.
/// </summary> /// </summary>
/// <param name="angleInDegrees"> /// <param name="imageWidth">Width of the image.</param>
/// The angle in degrees. /// <param name="imageHeight">Height of the image.</param>
/// </param> /// <param name="angle">The angle.</param>
/// <returns> /// <remarks>
/// The <see cref="double"/> representing the degree as radians. /// Based on <see href="http://math.stackexchange.com/questions/1070853/"/>
/// </returns> /// </remarks>
public static double DegreesToRadians(double angleInDegrees) /// <returns>The zoom needed</returns>
public static float ZoomAfterRotation(int imageWidth, int imageHeight, float angle)
{ {
return angleInDegrees * (Math.PI / 180); double radians = angle * Math.PI / 180d;
} double radiansSin = Math.Sin(radians);
double radiansCos = Math.Cos(radians);
/// <summary> double widthRotated = (imageWidth * radiansCos) + (imageHeight * radiansSin);
/// Rotates one point around another double heightRotated = (imageWidth * radiansSin) + (imageHeight * radiansCos);
/// <see href="http://stackoverflow.com/questions/13695317/rotate-a-point-around-another-point"/>
/// </summary>
/// <param name="pointToRotate">The point to rotate.</param>
/// <param name="angleInDegrees">The rotation angle in degrees.</param>
/// <param name="centerPoint">The centre point of rotation. If not set the point will equal
/// <see cref="Point.Empty"/>
/// </param>
/// <returns>Rotated point</returns>
public static Point RotatePoint(Point pointToRotate, double angleInDegrees, Point? centerPoint = null)
{
Point center = centerPoint ?? Point.Empty;
double angleInRadians = DegreesToRadians(angleInDegrees); return (float)Math.Max(widthRotated / imageWidth, heightRotated / imageHeight);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
((cosTheta * (pointToRotate.X - center.X)) -
((sinTheta * (pointToRotate.Y - center.Y)) + center.X)),
Y =
(int)
((sinTheta * (pointToRotate.X - center.X)) +
((cosTheta * (pointToRotate.Y - center.Y)) + center.Y))
};
} }
} }
} }

65
src/ImageProcessor/Imaging/Rotation.cs

@ -1,65 +0,0 @@
namespace ImageProcessor.Imaging
{
using System;
using System.Drawing;
/// <summary>
/// Provides rotation calculation methods
/// </summary>
internal class Rotation
{
/// <summary>
/// Calculates the new size after rotation.
/// </summary>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="angle">The angle of rotation.</param>
/// <returns>The new size of the image</returns>
public static Size NewSizeAfterRotation(int width, int height, float angle)
{
double widthAsDouble = width;
double heightAsDouble = height;
double radians = angle * Math.PI / 180d;
double radiansSin = Math.Sin(radians);
double radiansCos = Math.Cos(radians);
double width1 = (heightAsDouble * radiansSin) + (widthAsDouble * radiansCos);
double height1 = (widthAsDouble * radiansSin) + (heightAsDouble * radiansCos);
// Find dimensions in the other direction
radiansSin = Math.Sin(-radians);
radiansCos = Math.Cos(-radians);
double width2 = (heightAsDouble * radiansSin) + (widthAsDouble * radiansCos);
double height2 = (widthAsDouble * radiansSin) + (heightAsDouble * radiansCos);
// Get the external vertex for the rotation
Size result = new Size();
result.Width = Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2)));
result.Height = Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2)));
return result;
}
/// <summary>
/// Calculates the zoom needed after the rotation.
/// </summary>
/// <param name="imageWidth">Width of the image.</param>
/// <param name="imageHeight">Height of the image.</param>
/// <param name="angle">The angle.</param>
/// <remarks>
/// Based on <see href="http://math.stackexchange.com/questions/1070853/"/>
/// </remarks>
/// <returns>The zoom needed</returns>
public static float ZoomAfterRotation(int imageWidth, int imageHeight, float angle)
{
double radians = angle * Math.PI / 180d;
double radiansSin = Math.Sin(radians);
double radiansCos = Math.Cos(radians);
double widthRotated = (imageWidth * radiansCos) + (imageHeight * radiansSin);
double heightRotated = (imageWidth * radiansSin) + (imageHeight * radiansCos);
return (float)Math.Max(widthRotated / imageWidth, heightRotated / imageHeight);
}
}
}

2
src/ImageProcessor/Processors/Rotate.cs

@ -104,7 +104,7 @@ namespace ImageProcessor.Processors
/// </remarks> /// </remarks>
private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle) private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle)
{ {
Size newSize = Imaging.Rotation.NewSizeAfterRotation(image.Width, image.Height, angle); Size newSize = Imaging.Helpers.ImageMaths.GetBoundingRotatedRectangle(image.Width, image.Height, angle);
int x = (newSize.Width - image.Width) / 2; int x = (newSize.Width - image.Width) / 2;
int y = (newSize.Height - image.Height) / 2; int y = (newSize.Height - image.Height) / 2;

2
src/ImageProcessor/Processors/RotateInside.cs

@ -84,7 +84,7 @@ namespace ImageProcessor.Processors
{ {
Size newSize = new Size(image.Width, image.Height); Size newSize = new Size(image.Width, image.Height);
float zoom = Imaging.Rotation.ZoomAfterRotation(image.Width, image.Height, rotateLayer.Angle); float zoom = Imaging.Helpers.ImageMaths.ZoomAfterRotation(image.Width, image.Height, rotateLayer.Angle);
// if we don't keep the image dimensions, calculate the new ones // if we don't keep the image dimensions, calculate the new ones
if (!rotateLayer.KeepImageDimensions) if (!rotateLayer.KeepImageDimensions)

Loading…
Cancel
Save