From 6f76ef23f76d2aedccc9b7436b80849649f46bc6 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Tue, 24 Feb 2015 17:52:31 +0100 Subject: [PATCH] Refactors the rotation and adds a few unit tests Former-commit-id: 1ae4da1184336db1522c1a35f68235c0a28b0413 Former-commit-id: e8a76a404d1d986a5e9e9469c274ce705879b964 --- .../ImageFactoryUnitTests.cs | 13 +++ .../ImageProcessor.UnitTests.csproj | 2 + .../Imaging/RotationUnitTests.cs | 22 +++++ .../Processors/RotateInsideUnitTests.cs | 16 ++++ src/ImageProcessor/ImageProcessor.csproj | 1 + src/ImageProcessor/Imaging/Rotation.cs | 43 +++++++++ src/ImageProcessor/Processors/Rotate.cs | 25 +----- src/ImageProcessor/Processors/RotateInside.cs | 88 ++++++++++++++++++- src/ImageProcessor/Properties/AssemblyInfo.cs | 3 + 9 files changed, 190 insertions(+), 23 deletions(-) create mode 100644 src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs create mode 100644 src/ImageProcessor.UnitTests/Processors/RotateInsideUnitTests.cs create mode 100644 src/ImageProcessor/Imaging/Rotation.cs diff --git a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index 9f42ba3f3..870f22855 100644 --- a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -466,6 +466,19 @@ namespace ImageProcessor.UnitTests } } + [Test] + public void ImageIsRotatedInside() + { + foreach (ImageFactory imageFactory in this.ListInputImages()) + { + Image original = (Image)imageFactory.Image.Clone(); + imageFactory.RotateInside(45); + + imageFactory.Image.Width.Should().Be(original.Width, "because the rotated image dimensions should not have changed"); + imageFactory.Image.Height.Should().Be(original.Height, "because the rotated image dimensions should not have changed"); + } + } + /// /// Tests that the images hue has been altered. /// diff --git a/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj b/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj index b1ae1daaf..b47e4b5bc 100644 --- a/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj +++ b/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj @@ -64,6 +64,8 @@ + + diff --git a/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs new file mode 100644 index 000000000..32eb72e3e --- /dev/null +++ b/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs @@ -0,0 +1,22 @@ +namespace ImageProcessor.UnitTests.Imaging +{ + using System.Drawing; + using FluentAssertions; + using ImageProcessor.Imaging; + using NUnit.Framework; + + public class RotationUnitTests + { + [Test] + [TestCase(100, 100, 45, 141, 141)] + [TestCase(100, 100, 30, 137, 137)] + [TestCase(100, 200, 50, 217, 205)] + public void NewSizeAfterRotationIsCalculated(int width, int height, float angle, int expectedWidth, int expectedHeight) + { + Size result = Rotation.NewSizeAfterRotation(width, height, angle); + + 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"); + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor.UnitTests/Processors/RotateInsideUnitTests.cs b/src/ImageProcessor.UnitTests/Processors/RotateInsideUnitTests.cs new file mode 100644 index 000000000..36fe5fdcc --- /dev/null +++ b/src/ImageProcessor.UnitTests/Processors/RotateInsideUnitTests.cs @@ -0,0 +1,16 @@ +namespace ImageProcessor.UnitTests.Processors +{ + using System.Collections.Generic; + using ImageProcessor.Processors; + using NUnit.Framework; + + public class RotateInsideUnitTests + { + [Test] + [TestCase(100, 100, 15, 150)] + public void ZoomIsCalculated(int width, int height, float angle, float expected) + { + + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index c5187d3ed..94848c1a8 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -214,6 +214,7 @@ + diff --git a/src/ImageProcessor/Imaging/Rotation.cs b/src/ImageProcessor/Imaging/Rotation.cs new file mode 100644 index 000000000..8ae02f719 --- /dev/null +++ b/src/ImageProcessor/Imaging/Rotation.cs @@ -0,0 +1,43 @@ +namespace ImageProcessor.Imaging +{ + using System; + using System.Drawing; + + /// + /// Provides rotation calculation methods + /// + internal class Rotation + { + /// + /// Calculates the new size after rotation. + /// + /// The width of the image. + /// The height of the image. + /// The angle of rotation. + /// The new size of the image + 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; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Processors/Rotate.cs b/src/ImageProcessor/Processors/Rotate.cs index 2cd57a665..d0715884f 100644 --- a/src/ImageProcessor/Processors/Rotate.cs +++ b/src/ImageProcessor/Processors/Rotate.cs @@ -104,30 +104,13 @@ namespace ImageProcessor.Processors /// private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle) { - double widthAsDouble = image.Width; - double heightAsDouble = image.Height; + Size newSize = Imaging.Rotation.NewSizeAfterRotation(image.Width, image.Height, angle); - 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 - int width = Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2))); - int height = Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2))); - - int x = (width - image.Width) / 2; - int y = (height - image.Height) / 2; + int x = (newSize.Width - image.Width) / 2; + int y = (newSize.Height - image.Height) / 2; // Create a new empty bitmap to hold rotated image - Bitmap newImage = new Bitmap(width, height); + Bitmap newImage = new Bitmap(newSize.Width, newSize.Height); newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); // Make a graphics object from the empty bitmap diff --git a/src/ImageProcessor/Processors/RotateInside.cs b/src/ImageProcessor/Processors/RotateInside.cs index 919f846b4..f958db8d7 100644 --- a/src/ImageProcessor/Processors/RotateInside.cs +++ b/src/ImageProcessor/Processors/RotateInside.cs @@ -13,6 +13,8 @@ namespace ImageProcessor.Processors using System; using System.Collections.Generic; using System.Drawing; + using System.Drawing.Drawing2D; + using ImageProcessor.Common.Exceptions; /// /// Encapsulates the methods to rotate the inside of an image @@ -37,10 +39,92 @@ namespace ImageProcessor.Processors /// /// The processed image from the current instance of the class. /// + /// + /// Based on + /// public Image ProcessImage(ImageFactory factory) { - // TODO: Implement this method - throw new NotImplementedException(); + Bitmap newImage = null; + Image image = factory.Image; + + try + { + float angle = this.DynamicParameter; + + // Center of the image + float rotateAtX = Math.Abs(image.Width / 2); + float rotateAtY = Math.Abs(image.Height / 2); + + // Create a rotated image. + newImage = this.RotateImage(image, rotateAtX, rotateAtY, angle); + + image.Dispose(); + image = newImage; + } + catch (Exception ex) + { + if (newImage != null) + { + newImage.Dispose(); + } + + throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); + } + + return image; + } + + private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle) + { + double widthAsDouble = image.Width; + double heightAsDouble = image.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 + int width = Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2))); + int height = Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2))); + + int x = (width - image.Width) / 2; + int y = (height - image.Height) / 2; + + // Create a new empty bitmap to hold rotated image + Bitmap newImage = new Bitmap(width, height); + newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + // Make a graphics object from the empty bitmap + using (Graphics graphics = Graphics.FromImage(newImage)) + { + // Reduce the jagged edge. + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + + // Put the rotation point in the "center" of the image + graphics.TranslateTransform(rotateAtX + x, rotateAtY + y); + + // Rotate the image + graphics.RotateTransform(angle); + + // Move the image back + graphics.TranslateTransform(-rotateAtX - x, -rotateAtY - y); + + // Draw passed in image onto graphics object + graphics.DrawImage(image, new PointF(x, y)); + } + + return newImage; } } } \ No newline at end of file diff --git a/src/ImageProcessor/Properties/AssemblyInfo.cs b/src/ImageProcessor/Properties/AssemblyInfo.cs index 2f753e3b7..e198b5672 100644 --- a/src/ImageProcessor/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor/Properties/AssemblyInfo.cs @@ -9,6 +9,7 @@ // -------------------------------------------------------------------------------------------------------------------- using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -42,3 +43,5 @@ using System.Runtime.InteropServices; // by using the '*' as shown below: [assembly: AssemblyVersion("2.2.0.0")] [assembly: AssemblyFileVersion("2.2.0.0")] + +[assembly: InternalsVisibleTo("ImageProcessor.UnitTests")] \ No newline at end of file