diff --git a/README.md b/README.md index 28e4d1af6..a891a3a50 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Core plugins at present include: - Reset (Resets the image to its original loaded state) - Resize - Rotate (Rotate the image through 360º) + - RoundedCorners (Add rounded corners to each optional corner) - Saturation - Vignette (Adds a vignette effect to images) - Watermark (Set a text watermark) diff --git a/src/ImageProcessor.Tests/RegularExpressionUnitTests.cs b/src/ImageProcessor.Tests/RegularExpressionUnitTests.cs index 40f00bb63..b305f540b 100644 --- a/src/ImageProcessor.Tests/RegularExpressionUnitTests.cs +++ b/src/ImageProcessor.Tests/RegularExpressionUnitTests.cs @@ -56,24 +56,44 @@ namespace ImageProcessor.Tests Assert.AreEqual(Expected, actual); } - /// - /// The contrast regex unit test. - /// - [TestMethod] - public void TestContrastRegex() - { - const string Querystring = "contrast=56"; - const int Expected = 56; - - Contrast contrast = new Contrast(); - contrast.MatchRegexIndex(Querystring); - - int actual = contrast.DynamicParameter; - - Assert.AreEqual(Expected, actual); - } - - /// + /// + /// The contrast regex unit test. + /// + [TestMethod] + public void TestContrastRegex() + { + const string Querystring = "contrast=56"; + const int Expected = 56; + + Contrast contrast = new Contrast(); + contrast.MatchRegexIndex(Querystring); + + int actual = contrast.DynamicParameter; + + Assert.AreEqual(Expected, actual); + } + + /// + /// The constrain regex unit test. + /// + [TestMethod] + public void TestConstrainRegex() + { + const string Querystring = "constrain=100,200"; + const int ExpectedWidth = 100; + const int ExpectedHeight = 200; + + Constrain contrast = new Constrain(); + contrast.MatchRegexIndex(Querystring); + + int actualWidth = contrast.DynamicParameter.Width; + int actualHeight = contrast.DynamicParameter.Height; + + Assert.AreEqual(ExpectedWidth, actualWidth); + Assert.AreEqual(ExpectedHeight, actualHeight); + } + + /// /// The rotate regex unit test. /// [TestMethod] diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 038064bff..1efd252d1 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -81,7 +81,9 @@ + + diff --git a/src/ImageProcessor/Processors/Constrain.cs b/src/ImageProcessor/Processors/Constrain.cs new file mode 100644 index 000000000..014fe1152 --- /dev/null +++ b/src/ImageProcessor/Processors/Constrain.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Drawing; +using System.Text.RegularExpressions; +using ImageProcessor.Helpers.Extensions; + +namespace ImageProcessor.Processors +{ + /// + /// Constrains an image to the given dimensions. + /// + public class Constrain : ResizeBase + { + private static readonly Regex QueryRegex = new Regex(@"constrain=\d+,\d+", RegexOptions.Compiled); + + public override Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + + public override dynamic DynamicParameter { get; set; } + public override int SortOrder { get; protected set; } + public override Dictionary Settings { get; set; } + + public override 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; + int[] constraints = match.Value.ToPositiveIntegerArray(); + + int x = constraints[0]; + int y = constraints[1]; + + this.DynamicParameter = new Size(x, y); + } + + index += 1; + } + } + + return this.SortOrder; + } + + public override Image ProcessImage(ImageFactory factory) + { + + double constrainedWidth = DynamicParameter.Width; + double constrainedHeight = DynamicParameter.Height; + + var original = factory.Image; + double width = original.Width; + double height = original.Height; + + if (width > constrainedWidth || height > constrainedHeight) + { + + double constraintRatio = constrainedHeight / constrainedWidth; + double originalRatio = height / width; + + Size newSize = originalRatio < constraintRatio + ? new Size((int)constrainedWidth, 0) + : new Size(0, (int)constrainedHeight); + + int defaultMaxWidth; + int defaultMaxHeight; + int.TryParse(this.Settings["MaxWidth"], out defaultMaxWidth); + int.TryParse(this.Settings["MaxHeight"], out defaultMaxHeight); + + return ResizeImage(factory, newSize.Width, newSize.Height, defaultMaxWidth, defaultMaxHeight); + } + return factory.Image; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Processors/Resize.cs b/src/ImageProcessor/Processors/Resize.cs index 43cc489a4..0c8fbc267 100644 --- a/src/ImageProcessor/Processors/Resize.cs +++ b/src/ImageProcessor/Processors/Resize.cs @@ -20,7 +20,7 @@ namespace ImageProcessor.Processors /// /// Resizes an image to the given dimensions. /// - public class Resize : IGraphicsProcessor + public class Resize : ResizeBase { /// /// The regular expression to search strings for. @@ -31,7 +31,7 @@ namespace ImageProcessor.Processors /// /// Gets the regular expression to search strings for. /// - public Regex RegexPattern + public override Regex RegexPattern { get { @@ -42,7 +42,7 @@ namespace ImageProcessor.Processors /// /// Gets or sets DynamicParameter. /// - public dynamic DynamicParameter + public override dynamic DynamicParameter { get; set; @@ -51,16 +51,16 @@ namespace ImageProcessor.Processors /// /// Gets the order in which this processor is to be used in a chain. /// - public int SortOrder + public override int SortOrder { get; - private set; + protected set; } /// /// Gets or sets any additional settings required by the processor. /// - public Dictionary Settings + public override Dictionary Settings { get; set; @@ -75,7 +75,7 @@ namespace ImageProcessor.Processors /// /// The zero-based starting position in the original string where the captured substring was found. /// - public int MatchRegexIndex(string queryString) + public override int MatchRegexIndex(string queryString) { int index = 0; @@ -121,96 +121,20 @@ namespace ImageProcessor.Processors /// /// The processed image from the current instance of the class. /// - public Image ProcessImage(ImageFactory factory) + public override Image ProcessImage(ImageFactory factory) { - Bitmap newImage = null; - Image image = factory.Image; - try - { - int width = this.DynamicParameter.Width ?? 0; - int height = this.DynamicParameter.Height ?? 0; - int sourceWidth = image.Width; - int sourceHeight = image.Height; - int defaultMaxWidth; - int defaultMaxHeight; - int.TryParse(this.Settings["MaxWidth"], out defaultMaxWidth); - int.TryParse(this.Settings["MaxHeight"], out defaultMaxHeight); - int maxWidth = defaultMaxWidth > 0 ? defaultMaxWidth : int.MaxValue; - int maxHeight = defaultMaxHeight > 0 ? defaultMaxHeight : int.MaxValue; - - // If height or width is not passed we assume that the standard ratio is to be kept. - if (height == 0) - { - // Bit of simple fractional maths here. - float percentWidth = Math.Abs(width / (float)sourceWidth); - height = (int)Math.Floor(sourceHeight * percentWidth); - } - - if (width == 0) - { - float percentHeight = Math.Abs(height / (float)sourceHeight); - width = (int)Math.Floor(sourceWidth * percentHeight); - } - - if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight) - { - // Dont use an object initializer here. - newImage = new Bitmap(width, height, PixelFormat.Format32bppPArgb); - newImage.Tag = image.Tag; - - using (Graphics graphics = Graphics.FromImage(newImage)) - { - // We want to use two different blending algorithms for enlargement/shrinking. - // Bicubic is better enlarging for whilst Bilinear is better for shrinking. - // http://www.codinghorror.com/blog/2007/07/better-image-resizing.html - if (image.Width < width && image.Height < height) - { - // We are making it larger. - graphics.SmoothingMode = SmoothingMode.AntiAlias; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - } - else - { - // We are making it smaller. - graphics.SmoothingMode = SmoothingMode.None; - - // Contrary to everything I have read bicubic is producing the best results. - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.None; - graphics.CompositingQuality = CompositingQuality.HighSpeed; - } - - // An unwanted border appears when using InterpolationMode.HighQualityBicubic to resize the image - // as the algorithm appears to be pulling averaging detail from surFlooring pixels beyond the edge - // of the image. Using the ImageAttributes class to specify that the pixels beyond are simply mirror - // images of the pixels within solves this problem. - using (ImageAttributes wrapMode = new ImageAttributes()) - { - wrapMode.SetWrapMode(WrapMode.TileFlipXY); - Rectangle destRect = new Rectangle(0, 0, width, height); - graphics.DrawImage(image, destRect, 0, 0, sourceWidth, sourceHeight, GraphicsUnit.Pixel, wrapMode); - } + int width = this.DynamicParameter.Width ?? 0; + int height = this.DynamicParameter.Height ?? 0; - // Reassign the image. - image.Dispose(); - image = newImage; - } - } - } - catch - { - if (newImage != null) - { - newImage.Dispose(); - } - } + int defaultMaxWidth; + int defaultMaxHeight; + int.TryParse(this.Settings["MaxWidth"], out defaultMaxWidth); + int.TryParse(this.Settings["MaxHeight"], out defaultMaxHeight); - return image; + return ResizeImage(factory, width, height, defaultMaxWidth, defaultMaxHeight); } - #endregion + #endregion } } diff --git a/src/ImageProcessor/Processors/ResizeBase.cs b/src/ImageProcessor/Processors/ResizeBase.cs new file mode 100644 index 000000000..23f9dbbde --- /dev/null +++ b/src/ImageProcessor/Processors/ResizeBase.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Text.RegularExpressions; + +namespace ImageProcessor.Processors +{ + public abstract class ResizeBase : IGraphicsProcessor + { + public abstract Regex RegexPattern { get; } + public abstract dynamic DynamicParameter { get; set; } + public abstract int SortOrder { get; protected set; } + public abstract Dictionary Settings { get; set; } + public abstract int MatchRegexIndex(string queryString); + public abstract Image ProcessImage(ImageFactory factory); + + protected Image ResizeImage(ImageFactory factory, int width, int height, int defaultMaxWidth, int defaultMaxHeight) + { + Bitmap newImage = null; + Image image = factory.Image; + + try + { + int sourceWidth = image.Width; + int sourceHeight = image.Height; + + int maxWidth = defaultMaxWidth > 0 ? defaultMaxWidth : int.MaxValue; + int maxHeight = defaultMaxHeight > 0 ? defaultMaxHeight : int.MaxValue; + + // If height or width is not passed we assume that the standard ratio is to be kept. + if (height == 0) + { + // Bit of simple fractional maths here. + float percentWidth = Math.Abs(width/(float) sourceWidth); + height = (int) Math.Floor(sourceHeight*percentWidth); + } + + if (width == 0) + { + float percentHeight = Math.Abs(height/(float) sourceHeight); + width = (int) Math.Floor(sourceWidth*percentHeight); + } + + if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight) + { + // Dont use an object initializer here. + newImage = new Bitmap(width, height, PixelFormat.Format32bppPArgb); + newImage.Tag = image.Tag; + + using (Graphics graphics = Graphics.FromImage(newImage)) + { + // We want to use two different blending algorithms for enlargement/shrinking. + // Bicubic is better enlarging for whilst Bilinear is better for shrinking. + // http://www.codinghorror.com/blog/2007/07/better-image-resizing.html + if (image.Width < width && image.Height < height) + { + // We are making it larger. + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + } + else + { + // We are making it smaller. + graphics.SmoothingMode = SmoothingMode.None; + + // Contrary to everything I have read bicubic is producing the best results. + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.None; + graphics.CompositingQuality = CompositingQuality.HighSpeed; + } + + // An unwanted border appears when using InterpolationMode.HighQualityBicubic to resize the image + // as the algorithm appears to be pulling averaging detail from surFlooring pixels beyond the edge + // of the image. Using the ImageAttributes class to specify that the pixels beyond are simply mirror + // images of the pixels within solves this problem. + using (ImageAttributes wrapMode = new ImageAttributes()) + { + wrapMode.SetWrapMode(WrapMode.TileFlipXY); + Rectangle destRect = new Rectangle(0, 0, width, height); + graphics.DrawImage(image, destRect, 0, 0, sourceWidth, sourceHeight, GraphicsUnit.Pixel, wrapMode); + } + + // Reassign the image. + image.Dispose(); + image = newImage; + } + } + } + catch + { + if (newImage != null) + { + newImage.Dispose(); + } + } + + return image; + } + } +} \ No newline at end of file