diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index 3c90df9bb5..ac6765f9c1 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -103,13 +103,14 @@ namespace ImageProcessor.PlayGround //.Format(new PngFormat()) //.BackgroundColor(Color.Cyan) //.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128) - .Resize(size) + //.Resize(size) //.Resize(new ResizeLayer(size, ResizeMode.Max)) // .Resize(new ResizeLayer(size, ResizeMode.Stretch)) //.DetectEdges(new Laplacian3X3EdgeFilter(), true) //.DetectEdges(new LaplacianOfGaussianEdgeFilter()) //.EntropyCrop() - .Halftone(true) + //.Halftone(true) + .RotateBounded(5, false) //.Filter(MatrixFilters.Invert) //.Contrast(50) //.Filter(MatrixFilters.Comic) diff --git a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index e4eb9db824..847c639048 100644 --- a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -17,7 +17,6 @@ namespace ImageProcessor.UnitTests using ImageProcessor.Imaging; using ImageProcessor.Imaging.Filters.EdgeDetection; using ImageProcessor.Imaging.Filters.Photo; - using ImageProcessor.Imaging.Formats; using NUnit.Framework; @@ -133,7 +132,6 @@ namespace ImageProcessor.UnitTests imageFactory.Image, "because the alpha operation should have been applied on {0}", imageFactory.ImagePath); - } } @@ -475,7 +473,7 @@ namespace ImageProcessor.UnitTests foreach (ImageFactory imageFactory in this.ListInputImages()) { Image original = (Image)imageFactory.Image.Clone(); - imageFactory.RotateInside(new RotateInsideLayer { Angle = 45, KeepImageDimensions = true }); + imageFactory.RotateBounded(45, 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"); @@ -491,7 +489,7 @@ namespace ImageProcessor.UnitTests foreach (ImageFactory imageFactory in this.ListInputImages()) { Image original = (Image)imageFactory.Image.Clone(); - imageFactory.RotateInside(new RotateInsideLayer { Angle = 45, KeepImageDimensions = false }); + imageFactory.RotateBounded(45); 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"); diff --git a/src/ImageProcessor.Web/Configuration/Resources/processing.config.transform b/src/ImageProcessor.Web/Configuration/Resources/processing.config.transform index 62f4804f2b..a908e46c5d 100644 --- a/src/ImageProcessor.Web/Configuration/Resources/processing.config.transform +++ b/src/ImageProcessor.Web/Configuration/Resources/processing.config.transform @@ -50,6 +50,7 @@ + diff --git a/src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs b/src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs index a7113d37f2..a42ed735c6 100644 --- a/src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs +++ b/src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs @@ -39,7 +39,7 @@ namespace ImageProcessor.Web.Helpers /// /// The regular expression to search strings for angles. /// - private static readonly Regex AngleRegex = new Regex(@"(^(rotate|angle)|[^.](&,)?rotate|angle)(=|-)[^&|,]+", RegexOptions.Compiled); + private static readonly Regex AngleRegex = new Regex(@"(^(rotate(bounded)?|angle)|[^.](&,)?rotate(bounded)?|angle)(=|-)[^&|,]+", RegexOptions.Compiled); /// /// The regular expression to search strings for values between 1 and 100. diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 27949c77b5..d0c533a3de 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -61,6 +61,7 @@ + diff --git a/src/ImageProcessor.Web/Processors/RotateBounded.cs b/src/ImageProcessor.Web/Processors/RotateBounded.cs new file mode 100644 index 0000000000..eeffec5270 --- /dev/null +++ b/src/ImageProcessor.Web/Processors/RotateBounded.cs @@ -0,0 +1,106 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates methods to rotate an image without expanding the canvas. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Processors +{ + using System; + using System.Text.RegularExpressions; + using ImageProcessor.Processors; + using ImageProcessor.Web.Helpers; + + /// + /// Encapsulates methods to rotate an image without expanding the canvas. + /// + public class RotateBounded : IWebGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = new Regex(@"rotatebounded=[^&]+", RegexOptions.Compiled); + + /// + /// The regular expression to search for. + /// + private static readonly Regex BoundRegex = new Regex(@"rotatebounded.keepsize=true", RegexOptions.Compiled); + + /// + /// Initializes a new instance of the class. + /// + public RotateBounded() + { + this.Processor = new ImageProcessor.Processors.RotateBounded(); + } + + #region IGraphicsProcessor Members + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets the associated graphics processor. + /// + public IGraphicsProcessor Processor { get; private set; } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + float angle = CommonParameterParserUtility.ParseAngle(match.Value); + bool keepSize = BoundRegex.Match(queryString).Success; + Tuple rotateParams = new Tuple(angle, keepSize); + + this.Processor.DynamicParameter = rotateParams; + } + + index += 1; + } + } + + return this.SortOrder; + } + #endregion + } +} diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index dd2e51e3da..3f63979c9f 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -896,17 +896,28 @@ namespace ImageProcessor } /// - /// Rotates the image inside its area; keeps the area straight. + /// Rotates the image without expanding the canvas to fit the image. /// - /// The rotation layer parameters. + /// + /// The angle at which to rotate the image in degrees. + /// + /// + /// Whether to keep the original image dimensions. + /// + /// If set to true, the image is zoomed to fit the bounding area. + /// + /// + /// If set to false, the area is cropped to fit the rotated image. + /// + /// /// /// The current instance of the class. /// - public ImageFactory RotateInside(RotateInsideLayer rotateLayer) + public ImageFactory RotateBounded(float degrees, bool keepSize = false) { if (this.ShouldProcess) { - RotateInside rotate = new RotateInside { DynamicParameter = rotateLayer }; + RotateBounded rotate = new RotateBounded { DynamicParameter = new Tuple(degrees, keepSize) }; this.CurrentImageFormat.ApplyProcessor(rotate.ProcessImage, this); } diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index aeb5403efe..5e2977ed41 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -214,7 +214,6 @@ - @@ -234,7 +233,7 @@ - + diff --git a/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs b/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs index 1156435107..d56df76ad9 100644 --- a/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs @@ -393,23 +393,9 @@ namespace ImageProcessor.Imaging.Filters.Artistic foreach (float angle in angles) { - double radians = ImageMaths.DegreesToRadians(angle); - double radiansSin = Math.Sin(radians); - double radiansCos = Math.Cos(radians); - double width1 = (height * radiansSin) + (width * radiansCos); - double height1 = (width * radiansSin) + (height * radiansCos); - - // Find dimensions in the other direction - radiansSin = Math.Sin(-radians); - radiansCos = Math.Cos(-radians); - double width2 = (height * radiansSin) + (width * radiansCos); - double height2 = (width * radiansSin) + (height * radiansCos); - - int maxW = Math.Max(maxWidth, Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2)))); - int maxH = Math.Max(maxHeight, Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2)))); - - maxHeight = maxH; - maxWidth = maxW; + Size rotatedSize = ImageMaths.GetBoundingRotatedRectangle(width, height, angle).Size; + maxWidth = Math.Max(maxWidth, rotatedSize.Width); + maxHeight = Math.Max(maxHeight, rotatedSize.Height); } return new Rectangle(0, 0, maxWidth, maxHeight); diff --git a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs index 76ed51d777..d232198409 100644 --- a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs @@ -109,24 +109,21 @@ namespace ImageProcessor.Imaging.Helpers /// /// The width of the image. /// The height of the image. - /// The angle of rotation. + /// The angle of rotation. /// The new size of the image - public static Rectangle GetBoundingRotatedRectangle(int width, int height, float angle) + public static Rectangle GetBoundingRotatedRectangle(int width, int height, float angleInDegrees) { - double widthAsDouble = width; - double heightAsDouble = height; - - double radians = DegreesToRadians(angle); + double radians = DegreesToRadians(angleInDegrees); double radiansSin = Math.Sin(radians); double radiansCos = Math.Cos(radians); - double width1 = (heightAsDouble * radiansSin) + (widthAsDouble * radiansCos); - double height1 = (widthAsDouble * radiansSin) + (heightAsDouble * radiansCos); + double width1 = (height * radiansSin) + (width * radiansCos); + double height1 = (width * radiansSin) + (height * 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); + double width2 = (height * radiansSin) + (width * radiansCos); + double height2 = (width * radiansSin) + (height * radiansCos); // Get the external vertex for the rotation Rectangle result = new Rectangle( @@ -307,18 +304,19 @@ namespace ImageProcessor.Imaging.Helpers } /// - /// Calculates the zoom needed after the rotation. + /// Calculates the zoom needed after the rotation to ensure the canvas is covered + /// by the rotated image. /// /// Width of the image. /// Height of the image. - /// The angle. + /// The angle in degrees. /// /// Based on /// /// The zoom needed - public static float ZoomAfterRotation(int imageWidth, int imageHeight, float angle) + public static float ZoomAfterRotation(int imageWidth, int imageHeight, float angleInDegrees) { - double radians = angle * Math.PI / 180d; + double radians = DegreesToRadians(angleInDegrees); double radiansSin = Math.Sin(radians); double radiansCos = Math.Cos(radians); diff --git a/src/ImageProcessor/Imaging/RotateInsideLayer.cs b/src/ImageProcessor/Imaging/RotateInsideLayer.cs deleted file mode 100644 index 969c94a6e7..0000000000 --- a/src/ImageProcessor/Imaging/RotateInsideLayer.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// 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/RotateBounded.cs similarity index 70% rename from src/ImageProcessor/Processors/RotateInside.cs rename to src/ImageProcessor/Processors/RotateBounded.cs index 8d4436cdab..20d3a7b2c6 100644 --- a/src/ImageProcessor/Processors/RotateInside.cs +++ b/src/ImageProcessor/Processors/RotateBounded.cs @@ -1,10 +1,10 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // // -// Encapsulates methods to rotate the inside of an image. +// Encapsulates the methods to rotate an image without expanding the canvas. // // -------------------------------------------------------------------------------------------------------------------- @@ -15,12 +15,11 @@ 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 + /// Encapsulates the methods to rotate an image without expanding the canvas. /// - public class RotateInside : IGraphicsProcessor + public class RotateBounded : IGraphicsProcessor { /// /// Gets or sets the DynamicParameter. @@ -50,10 +49,10 @@ namespace ImageProcessor.Processors try { - RotateInsideLayer rotateLayer = this.DynamicParameter; + Tuple rotateParams = this.DynamicParameter; // Create a rotated image. - newImage = this.RotateImage(image, rotateLayer); + newImage = this.RotateImage(image, rotateParams.Item1, rotateParams.Item2); image.Dispose(); image = newImage; @@ -74,20 +73,35 @@ namespace ImageProcessor.Processors /// /// Rotates the inside of an image to the given angle at the given position. /// - /// The image to rotate - /// The rotation layer. + /// + /// The image to rotate + /// + /// + /// The angle in degrees. + /// + /// + /// Whether to keep the image dimensions. + /// + /// If set to true, the image is zoomed to fit the bounding area. + /// + /// + /// If set to false, the area is cropped to fit the rotated image. + /// + /// /// /// Based on the Rotate effect /// - /// The image rotated to the given angle at the given position. - private Bitmap RotateImage(Image image, RotateInsideLayer rotateLayer) + /// + /// The image rotated to the given angle at the given position. + /// + private Bitmap RotateImage(Image image, float angleInDegrees, bool keepSize) { Size newSize = new Size(image.Width, image.Height); - float zoom = Imaging.Helpers.ImageMaths.ZoomAfterRotation(image.Width, image.Height, rotateLayer.Angle); + float zoom = Imaging.Helpers.ImageMaths.ZoomAfterRotation(image.Width, image.Height, angleInDegrees); // if we don't keep the image dimensions, calculate the new ones - if (!rotateLayer.KeepImageDimensions) + if (!keepSize) { newSize.Width = (int)(newSize.Width / zoom); newSize.Height = (int)(newSize.Height / zoom); @@ -110,13 +124,13 @@ namespace ImageProcessor.Processors graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; - if (rotateLayer.KeepImageDimensions) + if (keepSize) { // Put the rotation point in the "center" of the image graphics.TranslateTransform(rotateAtX, rotateAtY); // Rotate the image - graphics.RotateTransform(rotateLayer.Angle); + graphics.RotateTransform(angleInDegrees); // Zooms the image to fit the area graphics.ScaleTransform(zoom, zoom); @@ -129,21 +143,22 @@ namespace ImageProcessor.Processors } 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; + // Calculate the difference between the center of the original image and the center + // of the new image. + int x = (image.Width - newSize.Width) / 2; + int y = (image.Height - newSize.Height) / 2; // Put the rotation point in the "center" of the old image - graphics.TranslateTransform(rotateAtX - diffX, rotateAtY - diffY); + graphics.TranslateTransform(rotateAtX - x, rotateAtY - y); // Rotate the image - graphics.RotateTransform(rotateLayer.Angle); + graphics.RotateTransform(angleInDegrees); // Move the image back - graphics.TranslateTransform(-(rotateAtX - diffX), -(rotateAtY - diffY)); + graphics.TranslateTransform(-(rotateAtX - x), -(rotateAtY - y)); // Draw passed in image onto graphics object - graphics.DrawImage(image, new PointF(-diffX, -diffY)); + graphics.DrawImage(image, new PointF(-x, -y)); } } diff --git a/src/TestWebsites/MVC/Views/Home/Index.cshtml b/src/TestWebsites/MVC/Views/Home/Index.cshtml index 6a1121433b..1784b4d36a 100644 --- a/src/TestWebsites/MVC/Views/Home/Index.cshtml +++ b/src/TestWebsites/MVC/Views/Home/Index.cshtml @@ -7,22 +7,17 @@

Resized

- + - @*

Foreign language test.

- - -

Strange name

- *@
- @*
+

Cropped

Cropped Percent

-
*@ +
@@ -34,191 +29,191 @@
@*
-
-

Resize Crop

-
- -
-
- - -
-
-
-
-
-

Resize Max

-
- -
-
- -
-
-
-
-
-

Resize Max - No Upscale

-
- -
-
- -
-
-
-
-
-

Resize Stretch

-
- -
-
- -
-
-
-
-

Filter

-
-
-

blackwhite

- -
-
-

comic

- -
-
-
-
-

lomograph

- -
-
-

greyscale

- -
-
-
-
-

polaroid

- -
-
-

sepia

- -
-
-
-
-

gotham

- -
-
-

hisatch

- -
-
-
-
-

losatch

- -
-
-
-
-
-
-

Watermark

- -
-
-

Format

- -
-
-
-
-
-
-

Rotate

- -
-
-

Quality

- -
-
-
-
-
-
-

Alpha

- -
-
-

Remote

-
-
-
-
-
-
-

Flip - horizontal

- -
-
-

Flip - vertical

- -
-
-
-
-
-
-

Gaussian Blur

- -
-
-

Gaussian Sharpen

- -
-
-
-
-
-
-

Tint rgba

- -
-
-

Tint Hex

- -
-
-
*@ +
+

Resize Crop

+
+ +
+
+ + +
+
+ +
+
+

Resize Max

+
+ +
+
+ +
+
+
+
+
+

Resize Max - No Upscale

+
+ +
+
+ +
+
+
+
+
+

Resize Stretch

+
+ +
+
+ +
+
+
+
+

Filter

+
+
+

blackwhite

+ +
+
+

comic

+ +
+
+
+
+

lomograph

+ +
+
+

greyscale

+ +
+
+
+
+

polaroid

+ +
+
+

sepia

+ +
+
+
+
+

gotham

+ +
+
+

hisatch

+ +
+
+
+
+

losatch

+ +
+
+
+
+
+
+

Watermark

+ +
+
+

Format

+ +
+
+
+
+
+
+

Rotate

+ +
+
+

Quality

+ +
+
+
+
+
+
+

Alpha

+ +
+
+

Remote

+
+
+
+
+
+
+

Flip - horizontal

+ +
+
+

Flip - vertical

+ +
+
+
+
+
+
+

Gaussian Blur

+ +
+
+

Gaussian Sharpen

+ +
+
+
+
+
+
+

Tint rgba

+ +
+
+

Tint Hex

+ +
+
+
*@ @*
-

Color Profiles

-
-
-
-

CMYK resized jpg

- -
+

Color Profiles

+
+
+
+

CMYK resized jpg

+ +
-
-

sRGB resized jpg

- -
-
-
-
-
-

Rounding

- - -
-
-
*@ \ No newline at end of file +
+

sRGB resized jpg

+ +
+ + +
+
+

Rounding

+ + +
+
+ *@ \ No newline at end of file diff --git a/src/TestWebsites/MVC/config/imageprocessor/processing.config b/src/TestWebsites/MVC/config/imageprocessor/processing.config index fee55869fc..4b41cafd36 100644 --- a/src/TestWebsites/MVC/config/imageprocessor/processing.config +++ b/src/TestWebsites/MVC/config/imageprocessor/processing.config @@ -50,6 +50,7 @@ +