From 2af1ed6d5f3103929413b2593656809f95d273f6 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Tue, 24 Feb 2015 17:49:20 +0100 Subject: [PATCH 01/15] Adds empty classes for rotation Former-commit-id: dac0dff3e2b0dc374a36a0d15573bdd36cc3cc17 Former-commit-id: 90eb30bcae5952f5d7727add156ce443476a631a --- src/ImageProcessor/ImageFactory.cs | 17 +++++++ src/ImageProcessor/ImageProcessor.csproj | 1 + src/ImageProcessor/Processors/RotateInside.cs | 46 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/ImageProcessor/Processors/RotateInside.cs diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 6ac5e542eb..a4f132325b 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -895,6 +895,23 @@ namespace ImageProcessor return this; } + /// + /// Rotates the image inside its area; keeps the area straight. + /// + /// The angle at which to rotate the image in degrees + /// The current instance of the class. + /// + public ImageFactory RotateInside(float degrees) + { + if (this.ShouldProcess) + { + RotateInside rotate = new RotateInside { DynamicParameter = degrees }; + this.CurrentImageFormat.ApplyProcessor(rotate.ProcessImage, this); + } + + return this; + } + /// /// Adds rounded corners to the current image. /// diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 88d010e60e..c5187d3ed8 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -233,6 +233,7 @@ + diff --git a/src/ImageProcessor/Processors/RotateInside.cs b/src/ImageProcessor/Processors/RotateInside.cs new file mode 100644 index 0000000000..919f846b46 --- /dev/null +++ b/src/ImageProcessor/Processors/RotateInside.cs @@ -0,0 +1,46 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates methods to rotate an image. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + using System; + using System.Collections.Generic; + using System.Drawing; + + /// + /// Encapsulates the methods to rotate the inside of an image + /// + public class RotateInside : IGraphicsProcessor + { + /// + /// Gets or sets the DynamicParameter. + /// + public dynamic DynamicParameter { get; set; } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings { get; set; } + + /// + /// Processes the image. + /// + /// The current instance of the class containing + /// the image to process. + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + // TODO: Implement this method + throw new NotImplementedException(); + } + } +} \ No newline at end of file From 2473211dd2577cb4876337246e88b09879352fc9 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Tue, 24 Feb 2015 17:52:31 +0100 Subject: [PATCH 02/15] 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 9f42ba3f33..870f22855f 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 b1ae1daaf5..b47e4b5bcb 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 0000000000..32eb72e3ea --- /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 0000000000..36fe5fdcc6 --- /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 c5187d3ed8..94848c1a8e 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 0000000000..8ae02f719d --- /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 2cd57a665e..d0715884f2 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 919f846b46..f958db8d7b 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 2f753e3b7e..e198b5672e 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 From ec93b1ed52977448ebcbee597578c4bdfefd7f53 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 09:44:00 +0100 Subject: [PATCH 03/15] Rotates the image inside the area, but leaves blank spaces Former-commit-id: 8d0a064cfcce90ba1de9fb5f42283f03bb36d6c6 Former-commit-id: f06de58db373573c1a8453c3a0fa2c3d6af1e13b --- .../ImageFactoryUnitTests.cs | 3 ++ .../ImageProcessor.UnitTests.csproj | 1 - .../Processors/RotateInsideUnitTests.cs | 16 -------- src/ImageProcessor/Processors/RotateInside.cs | 41 +++++++------------ 4 files changed, 18 insertions(+), 43 deletions(-) delete mode 100644 src/ImageProcessor.UnitTests/Processors/RotateInsideUnitTests.cs diff --git a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index 870f22855f..1adb0b3698 100644 --- a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -469,6 +469,7 @@ namespace ImageProcessor.UnitTests [Test] public void ImageIsRotatedInside() { + int i = 0; foreach (ImageFactory imageFactory in this.ListInputImages()) { Image original = (Image)imageFactory.Image.Clone(); @@ -476,6 +477,8 @@ namespace ImageProcessor.UnitTests 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"); + + imageFactory.Format(new ImageProcessor.Imaging.Formats.JpegFormat()).Save("./output/rotateinside-" + i++.ToString() + ".jpg"); } } diff --git a/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj b/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj index b47e4b5bcb..54ca14e2ca 100644 --- a/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj +++ b/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj @@ -65,7 +65,6 @@ - diff --git a/src/ImageProcessor.UnitTests/Processors/RotateInsideUnitTests.cs b/src/ImageProcessor.UnitTests/Processors/RotateInsideUnitTests.cs deleted file mode 100644 index 36fe5fdcc6..0000000000 --- a/src/ImageProcessor.UnitTests/Processors/RotateInsideUnitTests.cs +++ /dev/null @@ -1,16 +0,0 @@ -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/Processors/RotateInside.cs b/src/ImageProcessor/Processors/RotateInside.cs index f958db8d7b..05675be311 100644 --- a/src/ImageProcessor/Processors/RotateInside.cs +++ b/src/ImageProcessor/Processors/RotateInside.cs @@ -74,32 +74,21 @@ namespace ImageProcessor.Processors return image; } + /// + /// Rotates the inside of an image to the given angle at the given position. + /// + /// The image to rotate + /// The horizontal pixel coordinate at which to rotate the image. + /// The vertical pixel coordinate at which to rotate the image. + /// The angle in degrees at which to rotate the image. + /// The image rotated to the given angle at the given position. + /// + /// Based on + /// 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); + Bitmap newImage = new Bitmap(image.Width, image.Height); newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); // Make a graphics object from the empty bitmap @@ -112,16 +101,16 @@ namespace ImageProcessor.Processors graphics.CompositingQuality = CompositingQuality.HighQuality; // Put the rotation point in the "center" of the image - graphics.TranslateTransform(rotateAtX + x, rotateAtY + y); + graphics.TranslateTransform(rotateAtX, rotateAtY); // Rotate the image graphics.RotateTransform(angle); // Move the image back - graphics.TranslateTransform(-rotateAtX - x, -rotateAtY - y); + graphics.TranslateTransform(-rotateAtX * 2, -rotateAtY * 2); // Draw passed in image onto graphics object - graphics.DrawImage(image, new PointF(x, y)); + graphics.DrawImage(image, new PointF(rotateAtX, rotateAtY)); } return newImage; From 03d6e9454fc5592c74344b83f6ce39956fa6d8a4 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 10:04:03 +0100 Subject: [PATCH 04/15] Changes the method to return a factor instead of a percentage Former-commit-id: 75fa0d3daf53263ab1b411b910d1b90580e7ec7d Former-commit-id: c19f6cbbf92b6fca98a0f05eec8ccc4d31f9934b --- .../Imaging/RotationUnitTests.cs | 11 +++++++++++ src/ImageProcessor/Imaging/Rotation.cs | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs index 32eb72e3ea..6e71079c62 100644 --- a/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs @@ -18,5 +18,16 @@ 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"); } + + [Test] + [TestCase(100, 100, 45, 1.41f)] + [TestCase(100, 100, 15, 1.22f)] + [TestCase(100, 200, 45, 2.12f)] + public void RotationZoomIsCalculated(int imageWidth, int imageHeight, float angle, float expected) + { + float result = Rotation.ZoomAfterRotation(imageWidth, imageHeight, angle); + + result.Should().BeApproximately(expected, 0.01f, "because the zoom level after rotation should have been calculated"); + } } } \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Rotation.cs b/src/ImageProcessor/Imaging/Rotation.cs index 8ae02f719d..b2ecd30cc8 100644 --- a/src/ImageProcessor/Imaging/Rotation.cs +++ b/src/ImageProcessor/Imaging/Rotation.cs @@ -39,5 +39,24 @@ return result; } + + /// + /// Calculates the zoom needed after the rotation. + /// + /// Width of the image. + /// Height of the image. + /// The angle. + /// The zoom needed + 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, heightRotated) / 100); + } } } \ No newline at end of file From 52dfb4541c6ac627b1c799e84e42bc4ca671d91a Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 10:24:15 +0100 Subject: [PATCH 05/15] Fixes the zoom calculation Former-commit-id: 081d9d9fabd87ebf4d9643b49d5e80cd772dde48 Former-commit-id: 8f41c7f5228450698bc8e4cc3d76bc4cb902e435 --- src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs | 3 +++ src/ImageProcessor/Imaging/Rotation.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs index 6e71079c62..0e33eb5dc3 100644 --- a/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs @@ -23,6 +23,9 @@ [TestCase(100, 100, 45, 1.41f)] [TestCase(100, 100, 15, 1.22f)] [TestCase(100, 200, 45, 2.12f)] + [TestCase(200, 100, 45, 2.12f)] + [TestCase(600, 450, 20, 1.39f)] + [TestCase(600, 450, 45, 1.64f)] public void RotationZoomIsCalculated(int imageWidth, int imageHeight, float angle, float expected) { float result = Rotation.ZoomAfterRotation(imageWidth, imageHeight, angle); diff --git a/src/ImageProcessor/Imaging/Rotation.cs b/src/ImageProcessor/Imaging/Rotation.cs index b2ecd30cc8..421216e00a 100644 --- a/src/ImageProcessor/Imaging/Rotation.cs +++ b/src/ImageProcessor/Imaging/Rotation.cs @@ -56,7 +56,7 @@ double widthRotated = (imageWidth * radiansCos) + (imageHeight * radiansSin); double heightRotated = (imageWidth * radiansSin) + (imageHeight * radiansCos); - return (float)(Math.Max(widthRotated, heightRotated) / 100); + return (float)Math.Max(widthRotated / imageWidth, heightRotated / imageHeight); } } } \ No newline at end of file From 38798a0ec923178c8ad868ad4b0119e4aedf2e2c Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 10:26:19 +0100 Subject: [PATCH 06/15] Fixes XML comments Former-commit-id: 1fa77b4c002f4d0feb2adf7e9850d4f1ffde0c1b Former-commit-id: 69b31a8d16c0b32cda5430b27ce387c3363bc69e --- src/ImageProcessor/ImageFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index a4f132325b..7255d90f80 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -899,6 +899,7 @@ namespace ImageProcessor /// Rotates the image inside its area; keeps the area straight. /// /// The angle at which to rotate the image in degrees + /// /// The current instance of the class. /// public ImageFactory RotateInside(float degrees) From e5154d3bb5608e31f8cc8596ed001c4f6a52f21c Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 10:27:00 +0100 Subject: [PATCH 07/15] Zooms the image when doing an 'inside rotation' Former-commit-id: cb58553b092c507ec2b0ec88628ee2f140f25bdf Former-commit-id: d004bc57d5c645e5b95e75cf3f5890419e2a23e5 --- src/ImageProcessor/Processors/RotateInside.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ImageProcessor/Processors/RotateInside.cs b/src/ImageProcessor/Processors/RotateInside.cs index 05675be311..cd740a981d 100644 --- a/src/ImageProcessor/Processors/RotateInside.cs +++ b/src/ImageProcessor/Processors/RotateInside.cs @@ -83,7 +83,7 @@ namespace ImageProcessor.Processors /// The angle in degrees at which to rotate the image. /// The image rotated to the given angle at the given position. /// - /// Based on + /// Based on the Rotate effect /// private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle) { @@ -91,6 +91,8 @@ namespace ImageProcessor.Processors Bitmap newImage = new Bitmap(image.Width, image.Height); newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + float zoom = Imaging.Rotation.ZoomAfterRotation(image.Width, image.Height, angle); + // Make a graphics object from the empty bitmap using (Graphics graphics = Graphics.FromImage(newImage)) { @@ -106,6 +108,9 @@ namespace ImageProcessor.Processors // Rotate the image graphics.RotateTransform(angle); + // Zooms the image to fit the area + graphics.ScaleTransform(zoom, zoom); + // Move the image back graphics.TranslateTransform(-rotateAtX * 2, -rotateAtY * 2); From 93eac1fb657492dda9141ebf34498d8d4971ce12 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 10:27:48 +0100 Subject: [PATCH 08/15] Adds reference for calculation Former-commit-id: 186a948ca7cf34b489a65d878feb97069fd82b03 Former-commit-id: 36bf23f00f9304f618acc77dddbfd03f706a368e --- src/ImageProcessor/Imaging/Rotation.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ImageProcessor/Imaging/Rotation.cs b/src/ImageProcessor/Imaging/Rotation.cs index 421216e00a..0b841c297a 100644 --- a/src/ImageProcessor/Imaging/Rotation.cs +++ b/src/ImageProcessor/Imaging/Rotation.cs @@ -46,6 +46,9 @@ /// Width of the image. /// Height of the image. /// The angle. + /// + /// Based on + /// /// The zoom needed public static float ZoomAfterRotation(int imageWidth, int imageHeight, float angle) { From 849b6f936ddf62c022ae27b404fdfda6443de897 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 10:51:24 +0100 Subject: [PATCH 09/15] Adds a 'rotate layer' to provide a new 'resize' parameter Former-commit-id: f5ad9f7c78aae6a11245490e39274078489af081 Former-commit-id: 381fce4fcaaf617203e3e4e294146bbf1b7a5b38 --- .../ImageFactoryUnitTests.cs | 25 ++++++++++++++-- src/ImageProcessor/ImageFactory.cs | 4 +-- src/ImageProcessor/ImageProcessor.csproj | 1 + .../Imaging/RotateInsideLayer.cs | 30 +++++++++++++++++++ src/ImageProcessor/Processors/RotateInside.cs | 13 ++++---- 5 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 src/ImageProcessor/Imaging/RotateInsideLayer.cs diff --git a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index 1adb0b3698..14d4c16cf4 100644 --- a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -466,19 +466,38 @@ namespace ImageProcessor.UnitTests } } + /// + /// Tests that the image's inside is rotated + /// [Test] public void ImageIsRotatedInside() { - int i = 0; foreach (ImageFactory imageFactory in this.ListInputImages()) { Image original = (Image)imageFactory.Image.Clone(); - imageFactory.RotateInside(45); + imageFactory.RotateInside(new RotateInsideLayer { Angle = 45, KeepImageDimensions = true }); 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 image's inside is rotated and resized + /// + [Test] + public void ImageIsRotatedInsideAndResized() + { + int i = 0; + foreach (ImageFactory imageFactory in this.ListInputImages()) + { + Image original = (Image)imageFactory.Image.Clone(); + imageFactory.RotateInside(new RotateInsideLayer { Angle = 45, KeepImageDimensions = false }); + + imageFactory.Image.Width.Should().NotBe(original.Width, "because the rotated image dimensions should have changed"); + imageFactory.Image.Height.Should().NotBe(original.Height, "because the rotated image dimensions should have changed"); - imageFactory.Format(new ImageProcessor.Imaging.Formats.JpegFormat()).Save("./output/rotateinside-" + i++.ToString() + ".jpg"); + imageFactory.Format(new ImageProcessor.Imaging.Formats.JpegFormat()).Save("./output/rotateinsideresized-" + i++.ToString() + ".jpg"); } } diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 7255d90f80..2355111b2f 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -902,11 +902,11 @@ namespace ImageProcessor /// /// The current instance of the class. /// - public ImageFactory RotateInside(float degrees) + public ImageFactory RotateInside(RotateInsideLayer rotateLayer) { if (this.ShouldProcess) { - RotateInside rotate = new RotateInside { DynamicParameter = degrees }; + RotateInside rotate = new RotateInside { DynamicParameter = rotateLayer }; this.CurrentImageFormat.ApplyProcessor(rotate.ProcessImage, this); } diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 94848c1a8e..919f7d8fe5 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -214,6 +214,7 @@ + diff --git a/src/ImageProcessor/Imaging/RotateInsideLayer.cs b/src/ImageProcessor/Imaging/RotateInsideLayer.cs new file mode 100644 index 0000000000..969c94a6e7 --- /dev/null +++ b/src/ImageProcessor/Imaging/RotateInsideLayer.cs @@ -0,0 +1,30 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates the properties required to add an rotation layer to an image. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + /// + /// A rotation layer to apply an inside rotation to an image + /// + public class RotateInsideLayer + { + /// + /// Gets or sets the rotation angle. + /// + public float Angle { get; set; } + + /// + /// Gets or sets a value indicating whether to keep the image dimensions. + /// If set to true, the image is zoomed inside the area. + /// If set to false, the area is resized to match the rotated image. + /// + public bool KeepImageDimensions { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Processors/RotateInside.cs b/src/ImageProcessor/Processors/RotateInside.cs index cd740a981d..6071b5ae57 100644 --- a/src/ImageProcessor/Processors/RotateInside.cs +++ b/src/ImageProcessor/Processors/RotateInside.cs @@ -4,7 +4,7 @@ // Licensed under the Apache License, Version 2.0. // // -// Encapsulates methods to rotate an image. +// Encapsulates methods to rotate the inside of an image. // // -------------------------------------------------------------------------------------------------------------------- @@ -15,6 +15,7 @@ namespace ImageProcessor.Processors using System.Drawing; using System.Drawing.Drawing2D; using ImageProcessor.Common.Exceptions; + using ImageProcessor.Imaging; /// /// Encapsulates the methods to rotate the inside of an image @@ -49,14 +50,14 @@ namespace ImageProcessor.Processors try { - float angle = this.DynamicParameter; + RotateInsideLayer rotateLayer = 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); + newImage = this.RotateImage(image, rotateAtX, rotateAtY, rotateLayer); image.Dispose(); image = newImage; @@ -85,13 +86,13 @@ namespace ImageProcessor.Processors /// /// Based on the Rotate effect /// - private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle) + private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, RotateInsideLayer rotateLayer) { // Create a new empty bitmap to hold rotated image Bitmap newImage = new Bitmap(image.Width, image.Height); newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); - float zoom = Imaging.Rotation.ZoomAfterRotation(image.Width, image.Height, angle); + float zoom = Imaging.Rotation.ZoomAfterRotation(image.Width, image.Height, rotateLayer.Angle); // Make a graphics object from the empty bitmap using (Graphics graphics = Graphics.FromImage(newImage)) @@ -106,7 +107,7 @@ namespace ImageProcessor.Processors graphics.TranslateTransform(rotateAtX, rotateAtY); // Rotate the image - graphics.RotateTransform(angle); + graphics.RotateTransform(rotateLayer.Angle); // Zooms the image to fit the area graphics.ScaleTransform(zoom, zoom); From 3bde971ad3c5deddbe912cfb36d266b711e7a374 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 11:20:18 +0100 Subject: [PATCH 10/15] Fixes XML comments Former-commit-id: ddb31f48eec01fc451f7f4fa05c69eb6ee046094 Former-commit-id: 8ee5d4a1fd22d5b529b461b19d97998f03e26dca --- src/ImageProcessor/ImageFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 2355111b2f..dd2e51e3da 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -898,9 +898,9 @@ namespace ImageProcessor /// /// Rotates the image inside its area; keeps the area straight. /// - /// The angle at which to rotate the image in degrees + /// The rotation layer parameters. /// - /// The current instance of the class. + /// The current instance of the class. /// public ImageFactory RotateInside(RotateInsideLayer rotateLayer) { From e132c884db03ceea61717ec9055d03a8b11b1952 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 12:13:19 +0100 Subject: [PATCH 11/15] Allows the rotation of the image without zooming (doesn't quite work yet) Former-commit-id: ae242ce87afa102bdc870c2cb236304def86bc63 Former-commit-id: 2415b892082557e0691ac98186a83f301de7c61b --- src/ImageProcessor/Processors/RotateInside.cs | 76 +++++++++++++------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/ImageProcessor/Processors/RotateInside.cs b/src/ImageProcessor/Processors/RotateInside.cs index 6071b5ae57..e0a24ea898 100644 --- a/src/ImageProcessor/Processors/RotateInside.cs +++ b/src/ImageProcessor/Processors/RotateInside.cs @@ -52,12 +52,8 @@ namespace ImageProcessor.Processors { RotateInsideLayer rotateLayer = 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, rotateLayer); + newImage = this.RotateImage(image, rotateLayer); image.Dispose(); image = newImage; @@ -79,21 +75,32 @@ namespace ImageProcessor.Processors /// Rotates the inside of an image to the given angle at the given position. /// /// The image to rotate - /// The horizontal pixel coordinate at which to rotate the image. - /// The vertical pixel coordinate at which to rotate the image. - /// The angle in degrees at which to rotate the image. - /// The image rotated to the given angle at the given position. - /// + /// The rotation layer. + /// /// Based on the Rotate effect /// - private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, RotateInsideLayer rotateLayer) + /// The image rotated to the given angle at the given position. + private Bitmap RotateImage(Image image, RotateInsideLayer rotateLayer) { - // Create a new empty bitmap to hold rotated image - Bitmap newImage = new Bitmap(image.Width, image.Height); - newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + Size newSize = new Size(image.Width, image.Height); float zoom = Imaging.Rotation.ZoomAfterRotation(image.Width, image.Height, rotateLayer.Angle); + // if we don't keep the image dimensions, calculate the new ones + if (!rotateLayer.KeepImageDimensions) + { + newSize.Width = (int)(newSize.Width / zoom); + newSize.Height = (int)(newSize.Height / zoom); + } + + // Center of the image + float rotateAtX = Math.Abs(image.Width / 2); + float rotateAtY = Math.Abs(image.Height / 2); + + // Create a new empty bitmap to hold rotated image + Bitmap newImage = new Bitmap(newSize.Width, newSize.Height); + newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + // Make a graphics object from the empty bitmap using (Graphics graphics = Graphics.FromImage(newImage)) { @@ -103,20 +110,41 @@ namespace ImageProcessor.Processors graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; - // Put the rotation point in the "center" of the image - graphics.TranslateTransform(rotateAtX, rotateAtY); + if (rotateLayer.KeepImageDimensions) + { + // Put the rotation point in the "center" of the image + graphics.TranslateTransform(rotateAtX, rotateAtY); + + // Rotate the image + graphics.RotateTransform(rotateLayer.Angle); + + // Zooms the image to fit the area + graphics.ScaleTransform(zoom, zoom); - // Rotate the image - graphics.RotateTransform(rotateLayer.Angle); + // Move the image back + graphics.TranslateTransform(-rotateAtX, -rotateAtY); - // Zooms the image to fit the area - graphics.ScaleTransform(zoom, zoom); + // Draw passed in image onto graphics object + graphics.DrawImage(image, new PointF(0, 0)); + } + else + { + // calculate the difference between the center of the original image and the center of the new image + int diffX = (image.Width - newSize.Width) / 2; + int diffY = (image.Height - newSize.Height) / 2; + + // Put the rotation point in the "center" of the old image + graphics.TranslateTransform(diffX, diffY); - // Move the image back - graphics.TranslateTransform(-rotateAtX * 2, -rotateAtY * 2); + // Rotate the image + graphics.RotateTransform(rotateLayer.Angle); - // Draw passed in image onto graphics object - graphics.DrawImage(image, new PointF(rotateAtX, rotateAtY)); + // Move the image back + graphics.TranslateTransform(-diffX * 2, -diffY * 2); + + // Draw passed in image onto graphics object + graphics.DrawImage(image, new PointF(0, 0)); + } } return newImage; From 86ee3a41daebf89aa3e58185d86b79119bb11d44 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 14:01:34 +0100 Subject: [PATCH 12/15] Fixes the rotation calculations Former-commit-id: a8b3a255e6f4881e02b0fcc3aa9c1e22d84fd014 Former-commit-id: 7d263535078966c8773e28278d61913aeca166d9 --- src/ImageProcessor/Processors/RotateInside.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageProcessor/Processors/RotateInside.cs b/src/ImageProcessor/Processors/RotateInside.cs index e0a24ea898..b715aa4163 100644 --- a/src/ImageProcessor/Processors/RotateInside.cs +++ b/src/ImageProcessor/Processors/RotateInside.cs @@ -134,16 +134,16 @@ namespace ImageProcessor.Processors int diffY = (image.Height - newSize.Height) / 2; // Put the rotation point in the "center" of the old image - graphics.TranslateTransform(diffX, diffY); + graphics.TranslateTransform(rotateAtX - diffX, rotateAtY - diffY); // Rotate the image graphics.RotateTransform(rotateLayer.Angle); // Move the image back - graphics.TranslateTransform(-diffX * 2, -diffY * 2); + graphics.TranslateTransform(-(rotateAtX - diffX), -(rotateAtY - diffY)); // Draw passed in image onto graphics object - graphics.DrawImage(image, new PointF(0, 0)); + graphics.DrawImage(image, new PointF(-diffX, -diffY)); } } From 0d7c0d2333ca7f2ec566fec1098d5f9e3135711e Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 14:02:55 +0100 Subject: [PATCH 13/15] Removes saving of the test images Former-commit-id: 7e86424dbb0674fc62b1488518ade46eff5083d4 Former-commit-id: 293846a5c7b59dfd79c4cf133ad5f2edbf1a3ccf --- src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index 14d4c16cf4..e4eb9db824 100644 --- a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -488,7 +488,6 @@ namespace ImageProcessor.UnitTests [Test] public void ImageIsRotatedInsideAndResized() { - int i = 0; foreach (ImageFactory imageFactory in this.ListInputImages()) { Image original = (Image)imageFactory.Image.Clone(); @@ -496,8 +495,6 @@ namespace ImageProcessor.UnitTests imageFactory.Image.Width.Should().NotBe(original.Width, "because the rotated image dimensions should have changed"); imageFactory.Image.Height.Should().NotBe(original.Height, "because the rotated image dimensions should have changed"); - - imageFactory.Format(new ImageProcessor.Imaging.Formats.JpegFormat()).Save("./output/rotateinsideresized-" + i++.ToString() + ".jpg"); } } From fd92343b0c3c2adc87e3f4aa3c624c39ea81baf8 Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 15:47:05 +0100 Subject: [PATCH 14/15] Moves the new methods to Imaging.Helpers.ImageMaths and auto-cleanup (reordering of methods) of the ImageMaths class Former-commit-id: 95c1fd0f4d0cb88994167ca2679aa4e3e07961e3 Former-commit-id: c0e799a4576f17abc343e2bcc4a7465fe0cc3cdb --- .../ImageMathsUnitTests.cs} | 10 +- .../Imaging/Helpers/ImageMaths.cs | 168 ++++++++++++------ src/ImageProcessor/Imaging/Rotation.cs | 65 ------- src/ImageProcessor/Processors/Rotate.cs | 2 +- src/ImageProcessor/Processors/RotateInside.cs | 2 +- 5 files changed, 118 insertions(+), 129 deletions(-) rename src/ImageProcessor.UnitTests/Imaging/{RotationUnitTests.cs => Helpers/ImageMathsUnitTests.cs} (78%) delete mode 100644 src/ImageProcessor/Imaging/Rotation.cs diff --git a/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs similarity index 78% rename from src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs rename to src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs index 0e33eb5dc3..9b42e7f858 100644 --- a/src/ImageProcessor.UnitTests/Imaging/RotationUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs @@ -1,11 +1,11 @@ -namespace ImageProcessor.UnitTests.Imaging +namespace ImageProcessor.UnitTests.Imaging.Helpers { using System.Drawing; using FluentAssertions; - using ImageProcessor.Imaging; + using ImageProcessor.Imaging.Helpers; using NUnit.Framework; - public class RotationUnitTests + public class ImageMathsUnitTests { [Test] [TestCase(100, 100, 45, 141, 141)] @@ -13,7 +13,7 @@ [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); + Size result = ImageMaths.GetBoundingRotatedRectangle(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"); @@ -28,7 +28,7 @@ [TestCase(600, 450, 45, 1.64f)] 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"); } diff --git a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs index af05939762..60b35989f1 100644 --- a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs @@ -12,7 +12,6 @@ namespace ImageProcessor.Imaging.Helpers { using System; using System.Drawing; - using ImageProcessor.Imaging.Colors; /// @@ -20,6 +19,27 @@ namespace ImageProcessor.Imaging.Helpers /// public static class ImageMaths { + /// + /// Gets a representing the child centered relative to the parent. + /// + /// + /// The parent . + /// + /// + /// The child . + /// + /// + /// The centered . + /// + 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); + } + /// /// Restricts a value to be within a specified range. /// @@ -53,6 +73,20 @@ namespace ImageProcessor.Imaging.Helpers return value; } + /// + /// Returns the given degrees converted to radians. + /// + /// + /// The angle in degrees. + /// + /// + /// The representing the degree as radians. + /// + public static double DegreesToRadians(double angleInDegrees) + { + return angleInDegrees * (Math.PI / 180); + } + /// /// Gets the bounding from the given points. /// @@ -70,6 +104,38 @@ namespace ImageProcessor.Imaging.Helpers return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); } + /// + /// 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 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; + } + /// /// Finds the bounding rectangle based on the first instance of any color component other /// than the given one. @@ -101,12 +167,15 @@ namespace ImageProcessor.Imaging.Helpers case RgbaComponent.R: delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).R != b; break; + case RgbaComponent.G: delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).G != b; break; + case RgbaComponent.A: delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).A != b; break; + default: delegateFunc = (fastBitmap, x, y, b) => fastBitmap.GetPixel(x, y).B != b; break; @@ -188,24 +257,31 @@ namespace ImageProcessor.Imaging.Helpers } /// - /// Gets a representing the child centered relative to the parent. + /// Rotates one point around another + /// /// - /// - /// The parent . - /// - /// - /// The child . + /// The point to rotate. + /// The rotation angle in degrees. + /// The centre point of rotation. If not set the point will equal + /// /// - /// - /// The centered . - /// - public static RectangleF CenteredRectangle(Rectangle parent, Rectangle child) + /// Rotated point + public static Point RotatePoint(Point pointToRotate, double angleInDegrees, Point? centerPoint = null) { - 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); + Point center = centerPoint ?? Point.Empty; + + double angleInRadians = DegreesToRadians(angleInDegrees); + 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)) + }; } /// @@ -221,55 +297,33 @@ namespace ImageProcessor.Imaging.Helpers { return new[] { - new Point(rectangle.Left, rectangle.Top), - new Point(rectangle.Right, rectangle.Top), - new Point(rectangle.Right, rectangle.Bottom), + new Point(rectangle.Left, rectangle.Top), + new Point(rectangle.Right, rectangle.Top), + new Point(rectangle.Right, rectangle.Bottom), new Point(rectangle.Left, rectangle.Bottom) }; } /// - /// Returns the given degrees converted to radians. + /// Calculates the zoom needed after the rotation. /// - /// - /// The angle in degrees. - /// - /// - /// The representing the degree as radians. - /// - public static double DegreesToRadians(double angleInDegrees) + /// Width of the image. + /// Height of the image. + /// The angle. + /// + /// Based on + /// + /// The zoom needed + 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); - /// - /// Rotates one point around another - /// - /// - /// The point to rotate. - /// The rotation angle in degrees. - /// The centre point of rotation. If not set the point will equal - /// - /// - /// Rotated point - public static Point RotatePoint(Point pointToRotate, double angleInDegrees, Point? centerPoint = null) - { - Point center = centerPoint ?? Point.Empty; + double widthRotated = (imageWidth * radiansCos) + (imageHeight * radiansSin); + double heightRotated = (imageWidth * radiansSin) + (imageHeight * radiansCos); - double angleInRadians = DegreesToRadians(angleInDegrees); - 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)) - }; + return (float)Math.Max(widthRotated / imageWidth, heightRotated / imageHeight); } } -} +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Rotation.cs b/src/ImageProcessor/Imaging/Rotation.cs deleted file mode 100644 index 0b841c297a..0000000000 --- a/src/ImageProcessor/Imaging/Rotation.cs +++ /dev/null @@ -1,65 +0,0 @@ -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; - } - - /// - /// Calculates the zoom needed after the rotation. - /// - /// Width of the image. - /// Height of the image. - /// The angle. - /// - /// Based on - /// - /// The zoom needed - 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); - } - } -} \ No newline at end of file diff --git a/src/ImageProcessor/Processors/Rotate.cs b/src/ImageProcessor/Processors/Rotate.cs index d0715884f2..cb8cc17330 100644 --- a/src/ImageProcessor/Processors/Rotate.cs +++ b/src/ImageProcessor/Processors/Rotate.cs @@ -104,7 +104,7 @@ namespace ImageProcessor.Processors /// 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 y = (newSize.Height - image.Height) / 2; diff --git a/src/ImageProcessor/Processors/RotateInside.cs b/src/ImageProcessor/Processors/RotateInside.cs index b715aa4163..8d4436cdab 100644 --- a/src/ImageProcessor/Processors/RotateInside.cs +++ b/src/ImageProcessor/Processors/RotateInside.cs @@ -84,7 +84,7 @@ namespace ImageProcessor.Processors { 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 (!rotateLayer.KeepImageDimensions) From 4d9f300aacef012e52955cc5688b1d8a499ef16d Mon Sep 17 00:00:00 2001 From: Thomas Broust Date: Wed, 25 Feb 2015 16:12:52 +0100 Subject: [PATCH 15/15] Switches to rectangle structure for the rotation calculations Former-commit-id: cc6abe13bd9824d32250738b25486cc5c9195849 Former-commit-id: 5bfec4b1d233e2390c92a5e565e537a84c380c8c --- .../ImageProcessor.UnitTests.csproj | 2 +- .../Imaging/Helpers/ImageMathsUnitTests.cs | 22 +++++++++++++++++-- src/ImageProcessor/ImageProcessor.csproj | 1 - .../Imaging/Helpers/ImageMaths.cs | 10 +++++---- src/ImageProcessor/Processors/Rotate.cs | 2 +- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj b/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj index 54ca14e2ca..1d808f9637 100644 --- a/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj +++ b/src/ImageProcessor.UnitTests/ImageProcessor.UnitTests.csproj @@ -64,7 +64,7 @@ - + diff --git a/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs index 9b42e7f858..3e1ffdfb36 100644 --- a/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/Helpers/ImageMathsUnitTests.cs @@ -5,20 +5,38 @@ using ImageProcessor.Imaging.Helpers; using NUnit.Framework; + /// + /// Test harness for the image math unit tests + /// public class ImageMathsUnitTests { + /// + /// Tests that the bounding rectangle of a rotated image is calculated + /// + /// The width of the image. + /// The height of the image. + /// The rotation angle. + /// The expected width. + /// The expected height. [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) + public void BoundingRotatedRectangleIsCalculated(int width, int height, float angle, int expectedWidth, int expectedHeight) { - Size result = ImageMaths.GetBoundingRotatedRectangle(width, height, angle); + Rectangle result = ImageMaths.GetBoundingRotatedRectangle(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"); } + /// + /// Tests that the zoom needed for an "inside" rotation is calculated + /// + /// Width of the image. + /// Height of the image. + /// The rotation angle. + /// The expected zoom. [Test] [TestCase(100, 100, 45, 1.41f)] [TestCase(100, 100, 15, 1.22f)] diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 919f7d8fe5..aeb5403efe 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -215,7 +215,6 @@ - diff --git a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs index 60b35989f1..76ed51d777 100644 --- a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs @@ -111,7 +111,7 @@ namespace ImageProcessor.Imaging.Helpers /// The height of the image. /// The angle of rotation. /// The new size of the image - public static Size GetBoundingRotatedRectangle(int width, int height, float angle) + public static Rectangle GetBoundingRotatedRectangle(int width, int height, float angle) { double widthAsDouble = width; double heightAsDouble = height; @@ -129,9 +129,11 @@ namespace ImageProcessor.Imaging.Helpers 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))); + Rectangle result = new Rectangle( + 0, + 0, + Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2))), + Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2)))); return result; } diff --git a/src/ImageProcessor/Processors/Rotate.cs b/src/ImageProcessor/Processors/Rotate.cs index cb8cc17330..9d51bbc8d4 100644 --- a/src/ImageProcessor/Processors/Rotate.cs +++ b/src/ImageProcessor/Processors/Rotate.cs @@ -104,7 +104,7 @@ namespace ImageProcessor.Processors /// private Bitmap RotateImage(Image image, float rotateAtX, float rotateAtY, float angle) { - Size newSize = Imaging.Helpers.ImageMaths.GetBoundingRotatedRectangle(image.Width, image.Height, angle); + Rectangle newSize = Imaging.Helpers.ImageMaths.GetBoundingRotatedRectangle(image.Width, image.Height, angle); int x = (newSize.Width - image.Width) / 2; int y = (newSize.Height - image.Height) / 2;