diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index b04275ff10..6e509e4e08 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -79,7 +79,7 @@ namespace ImageProcessor.PlayGround //.Resize(new Size((int)(size.Width * 1.1), 0)) //.ContentAwareResize(layer) .Constrain(size) - .Mask(mask) + //.Mask(mask) //.Format(new PngFormat()) //.BackgroundColor(Color.HotPink) //.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80) diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 9d0a96b57c..dbbbffe881 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -193,6 +193,7 @@ + @@ -209,6 +210,7 @@ + diff --git a/src/ImageProcessor/Imaging/ResizeLayer.cs b/src/ImageProcessor/Imaging/ResizeLayer.cs index d4c114b1ee..36176090c9 100644 --- a/src/ImageProcessor/Imaging/ResizeLayer.cs +++ b/src/ImageProcessor/Imaging/ResizeLayer.cs @@ -11,6 +11,8 @@ namespace ImageProcessor.Imaging { #region Using + + using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -41,18 +43,30 @@ namespace ImageProcessor.Imaging /// /// The center coordinates (Default null) /// + /// + /// The maximum size to resize an image to. + /// Used to restrict resizing based on calculated resizing + /// + /// + /// The range of sizes to restrict resizing an image to. + /// Used to restrict resizing based on calculated resizing + /// public ResizeLayer( Size size, ResizeMode resizeMode = ResizeMode.Pad, AnchorPosition anchorPosition = AnchorPosition.Center, bool upscale = true, - float[] centerCoordinates = null) + float[] centerCoordinates = null, + Size? maxSize = null, + List restrictedSizes = null) { this.Size = size; this.Upscale = upscale; this.ResizeMode = resizeMode; this.AnchorPosition = anchorPosition; this.CenterCoordinates = centerCoordinates ?? new float[] { }; + this.MaxSize = maxSize; + this.RestrictedSizes = restrictedSizes ?? new List(); } #endregion @@ -62,6 +76,16 @@ namespace ImageProcessor.Imaging /// public Size Size { get; set; } + /// + /// Gets or sets the max size. + /// + public Size? MaxSize { get; set; } + + /// + /// Gets or sets the restricted range of sizes. to restrict resizing methods to. + /// + public List RestrictedSizes { get; set; } + /// /// Gets or sets the resize mode. /// @@ -81,7 +105,6 @@ namespace ImageProcessor.Imaging /// Gets or sets the center coordinates. /// public float[] CenterCoordinates { get; set; } - #endregion /// @@ -109,7 +132,9 @@ namespace ImageProcessor.Imaging && this.ResizeMode == resizeLayer.ResizeMode && this.AnchorPosition == resizeLayer.AnchorPosition && this.Upscale == resizeLayer.Upscale - && this.CenterCoordinates.SequenceEqual(resizeLayer.CenterCoordinates); + && this.CenterCoordinates.SequenceEqual(resizeLayer.CenterCoordinates) + && this.MaxSize == resizeLayer.MaxSize + && this.RestrictedSizes.SequenceEqual(resizeLayer.RestrictedSizes); } /// @@ -123,6 +148,8 @@ namespace ImageProcessor.Imaging unchecked { int hashCode = this.Size.GetHashCode(); + hashCode = (hashCode * 397) ^ this.MaxSize.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.RestrictedSizes != null ? this.RestrictedSizes.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (int)this.ResizeMode; hashCode = (hashCode * 397) ^ (int)this.AnchorPosition; hashCode = (hashCode * 397) ^ this.Upscale.GetHashCode(); diff --git a/src/ImageProcessor/Imaging/Resizer.cs b/src/ImageProcessor/Imaging/Resizer.cs new file mode 100644 index 0000000000..ed774ceea0 --- /dev/null +++ b/src/ImageProcessor/Imaging/Resizer.cs @@ -0,0 +1,365 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Provides methods to resize images. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + using System; + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Imaging; + using System.Linq; + + using ImageProcessor.Common.Exceptions; + + /// + /// Provides methods to resize images. + /// + public class Resizer + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The to resize the image to. + /// + public Resizer(Size size) + { + this.ResizeLayer = new ResizeLayer(size); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The . + /// + public Resizer(ResizeLayer resizeLayer) + { + this.ResizeLayer = resizeLayer; + } + + /// + /// Gets or sets the . + /// + public ResizeLayer ResizeLayer { get; set; } + + /// + /// Resizes the given image. + /// + /// + /// The source to resize + /// + /// + /// The resized . + /// + public Image ResizeImage(Image source) + { + int width = this.ResizeLayer.Size.Width; + int height = this.ResizeLayer.Size.Height; + ResizeMode mode = this.ResizeLayer.ResizeMode; + AnchorPosition anchor = this.ResizeLayer.AnchorPosition; + bool upscale = this.ResizeLayer.Upscale; + float[] centerCoordinates = this.ResizeLayer.CenterCoordinates; + int maxWidth = this.ResizeLayer.MaxSize.HasValue ? this.ResizeLayer.MaxSize.Value.Width : int.MaxValue; + int maxHeight = this.ResizeLayer.MaxSize.HasValue ? this.ResizeLayer.MaxSize.Value.Height : int.MaxValue; + List restrictedSizes = this.ResizeLayer.RestrictedSizes; + + return this.ResizeImage(source, width, height, maxWidth, maxHeight, restrictedSizes, mode, anchor, upscale, centerCoordinates); + } + + /// + /// Resizes the given image. + /// + /// + /// The source to resize + /// + /// + /// The width to resize the image to. + /// + /// + /// The height to resize the image to. + /// + /// + /// The default max width to resize the image to. + /// + /// + /// The default max height to resize the image to. + /// + /// + /// A containing image resizing restrictions. + /// + /// + /// The mode with which to resize the image. + /// + /// + /// The anchor position to place the image at. + /// + /// + /// Whether to allow up-scaling of images. (Default true) + /// + /// + /// If the resize mode is crop, you can set a specific center coordinate, use as alternative to anchorPosition + /// + /// + /// The resized . + /// + private Image ResizeImage( + Image source, + int width, + int height, + int maxWidth, + int maxHeight, + List restrictedSizes, + ResizeMode resizeMode = ResizeMode.Pad, + AnchorPosition anchorPosition = AnchorPosition.Center, + bool upscale = true, + float[] centerCoordinates = null) + { + Bitmap newImage = null; + + try + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationWidth = width; + int destinationHeight = height; + + maxWidth = maxWidth > 0 ? maxWidth : int.MaxValue; + maxHeight = maxHeight > 0 ? maxHeight : int.MaxValue; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + int destinationX = 0; + int destinationY = 0; + + // Change the destination rectangle coordinates if padding and + // there has been a set width and height. + if (resizeMode == ResizeMode.Pad && width > 0 && height > 0) + { + double ratio; + + if (percentHeight < percentWidth) + { + ratio = percentHeight; + destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); + destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); + } + else + { + ratio = percentWidth; + destinationY = Convert.ToInt32((height - (sourceHeight * ratio)) / 2); + destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); + } + } + + // Change the destination rectangle coordinates if cropping and + // there has been a set width and height. + if (resizeMode == ResizeMode.Crop && width > 0 && height > 0) + { + double ratio; + + if (percentHeight < percentWidth) + { + ratio = percentWidth; + + if (centerCoordinates != null && centerCoordinates.Any()) + { + double center = -(ratio * sourceHeight) * centerCoordinates[0]; + destinationY = (int)center + (height / 2); + + if (destinationY > 0) + { + destinationY = 0; + } + + if (destinationY < (int)(height - (sourceHeight * ratio))) + { + destinationY = (int)(height - (sourceHeight * ratio)); + } + } + else + { + switch (anchorPosition) + { + case AnchorPosition.Top: + destinationY = 0; + break; + case AnchorPosition.Bottom: + destinationY = (int)(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)((height - (sourceHeight * ratio)) / 2); + break; + } + } + + destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); + } + else + { + ratio = percentHeight; + + if (centerCoordinates != null && centerCoordinates.Any()) + { + double center = -(ratio * sourceWidth) * centerCoordinates[1]; + destinationX = (int)center + (width / 2); + + if (destinationX > 0) + { + destinationX = 0; + } + + if (destinationX < (int)(width - (sourceWidth * ratio))) + { + destinationX = (int)(width - (sourceWidth * ratio)); + } + } + else + { + switch (anchorPosition) + { + case AnchorPosition.Left: + destinationX = 0; + break; + case AnchorPosition.Right: + destinationX = (int)(width - (sourceWidth * ratio)); + break; + default: + destinationX = (int)((width - (sourceWidth * ratio)) / 2); + break; + } + } + + destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); + } + } + + // Constrain the image to fit the maximum possible height or width. + if (resizeMode == ResizeMode.Max) + { + // If either is 0, we don't need to figure out orientation + if (width > 0 && height > 0) + { + // Integers must be cast to doubles to get needed precision + double ratio = (double)height / width; + double sourceRatio = (double)sourceHeight / sourceWidth; + + if (sourceRatio < ratio) + { + height = 0; + } + else + { + width = 0; + } + } + } + + // If height or width is not passed we assume that the standard ratio is to be kept. + if (height == 0) + { + destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); + height = destinationHeight; + } + + if (width == 0) + { + destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); + width = destinationWidth; + } + + // Restrict sizes + if (restrictedSizes != null && restrictedSizes.Any()) + { + bool reject = true; + foreach (Size restrictedSize in restrictedSizes) + { + if (restrictedSize.Height == 0 || restrictedSize.Width == 0) + { + if (restrictedSize.Width == width || restrictedSize.Height == height) + { + reject = false; + } + } + else if (restrictedSize.Width == width && restrictedSize.Height == height) + { + reject = false; + } + } + + if (reject) + { + return source; + } + } + + if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight) + { + // Exit if upscaling is not allowed. + if ((width > sourceWidth || height > sourceHeight) && upscale == false && resizeMode != ResizeMode.Stretch) + { + return source; + } + + newImage = new Bitmap(width, height, PixelFormat.Format32bppPArgb); + + using (Graphics graphics = Graphics.FromImage(newImage)) + { + // We want to use two different blending algorithms for enlargement/shrinking. + if (source.Width < destinationWidth && source.Height < destinationHeight) + { + // We are making it larger. + graphics.SmoothingMode = SmoothingMode.AntiAlias; + } + else + { + // We are making it smaller. + graphics.SmoothingMode = SmoothingMode.None; + } + + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + + // An unwanted border appears when using InterpolationMode.HighQualityBicubic to resize the image + // as the algorithm appears to be pulling averaging detail from surrounding 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(destinationX, destinationY, destinationWidth, destinationHeight); + graphics.DrawImage(source, destRect, 0, 0, sourceWidth, sourceHeight, GraphicsUnit.Pixel, wrapMode); + } + + // Reassign the image. + source.Dispose(); + source = newImage; + } + } + } + catch (Exception ex) + { + if (newImage != null) + { + newImage.Dispose(); + } + + throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); + } + + return source; + } + } +} diff --git a/src/ImageProcessor/Processors/Overlay.cs b/src/ImageProcessor/Processors/Overlay.cs new file mode 100644 index 0000000000..ff4579bd70 --- /dev/null +++ b/src/ImageProcessor/Processors/Overlay.cs @@ -0,0 +1,63 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Adds an image overlay to the current image. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + using System; + using System.Collections.Generic; + using System.Drawing; + + /// + /// Adds an image overlay to the current image. + /// + public class Overlay : IGraphicsProcessor + { + /// + /// Initializes a new instance of the class. + /// + public Overlay() + { + this.Settings = new Dictionary(); + } + + /// + /// Gets or sets the dynamic parameter. + /// + 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) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/ImageProcessor/Processors/Resize.cs b/src/ImageProcessor/Processors/Resize.cs index 34f598eadf..b2e0bc80cf 100644 --- a/src/ImageProcessor/Processors/Resize.cs +++ b/src/ImageProcessor/Processors/Resize.cs @@ -13,10 +13,7 @@ namespace ImageProcessor.Processors using System; using System.Collections.Generic; using System.Drawing; - using System.Drawing.Drawing2D; - using System.Drawing.Imaging; using System.Globalization; - using System.Linq; using ImageProcessor.Common.Exceptions; using ImageProcessor.Imaging; @@ -31,6 +28,7 @@ namespace ImageProcessor.Processors /// public Resize() { + this.RestrictedSizes = new List(); this.Settings = new Dictionary(); } @@ -68,298 +66,34 @@ namespace ImageProcessor.Processors /// The processed image from the current instance of the class. /// public Image ProcessImage(ImageFactory factory) - { - int width = this.DynamicParameter.Size.Width ?? 0; - int height = this.DynamicParameter.Size.Height ?? 0; - ResizeMode mode = this.DynamicParameter.ResizeMode; - AnchorPosition anchor = this.DynamicParameter.AnchorPosition; - bool upscale = this.DynamicParameter.Upscale; - float[] centerCoordinates = this.DynamicParameter.CenterCoordinates; - - int defaultMaxWidth; - int defaultMaxHeight; - - int.TryParse(this.Settings["MaxWidth"], NumberStyles.Any, CultureInfo.InvariantCulture, out defaultMaxWidth); - int.TryParse(this.Settings["MaxHeight"], NumberStyles.Any, CultureInfo.InvariantCulture, out defaultMaxHeight); - - return this.ResizeImage(factory, width, height, defaultMaxWidth, defaultMaxHeight, this.RestrictedSizes, mode, anchor, upscale, centerCoordinates); - } - - /// - /// The resize image. - /// - /// - /// The current instance of the class containing - /// the image to process. - /// - /// - /// The width to resize the image to. - /// - /// - /// The height to resize the image to. - /// - /// - /// The default max width to resize the image to. - /// - /// - /// The default max height to resize the image to. - /// - /// - /// A containing image resizing restrictions. - /// - /// - /// The mode with which to resize the image. - /// - /// - /// The anchor position to place the image at. - /// - /// - /// Whether to allow up-scaling of images. (Default true) - /// - /// - /// If the resize mode is crop, you can set a specific center coordinate, use as alternative to anchorPosition - /// - /// - /// The processed image from the current instance of the class. - /// - private Image ResizeImage( - ImageFactory factory, - int width, - int height, - int defaultMaxWidth, - int defaultMaxHeight, - List restrictedSizes, - ResizeMode resizeMode = ResizeMode.Pad, - AnchorPosition anchorPosition = AnchorPosition.Center, - bool upscale = true, - float[] centerCoordinates = null) { Bitmap newImage = null; Image image = factory.Image; try { - int sourceWidth = image.Width; - int sourceHeight = image.Height; + ResizeLayer resizeLayer = this.DynamicParameter; + + // Augment the layer with the extra information. + resizeLayer.RestrictedSizes = this.RestrictedSizes; + Size maxSize = new Size(); - int destinationWidth = width; - int destinationHeight = height; + int maxWidth; + int maxHeight; + int.TryParse(this.Settings["MaxWidth"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxWidth); + int.TryParse(this.Settings["MaxHeight"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxHeight); - int maxWidth = defaultMaxWidth > 0 ? defaultMaxWidth : int.MaxValue; - int maxHeight = defaultMaxHeight > 0 ? defaultMaxHeight : int.MaxValue; + maxSize.Width = maxHeight; + maxSize.Height = maxHeight; - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - int destinationX = 0; - int destinationY = 0; - - // Change the destination rectangle coordinates if padding and - // there has been a set width and height. - if (resizeMode == ResizeMode.Pad && width > 0 && height > 0) - { - double ratio; - - if (percentHeight < percentWidth) - { - ratio = percentHeight; - destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - } - else - { - ratio = percentWidth; - destinationY = Convert.ToInt32((height - (sourceHeight * ratio)) / 2); - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - } - } - - // Change the destination rectangle coordinates if cropping and - // there has been a set width and height. - if (resizeMode == ResizeMode.Crop && width > 0 && height > 0) - { - double ratio; + resizeLayer.MaxSize = maxSize; - if (percentHeight < percentWidth) - { - ratio = percentWidth; + Resizer resizer = new Resizer(resizeLayer); + newImage = (Bitmap)resizer.ResizeImage(image); - if (centerCoordinates != null && centerCoordinates.Any()) - { - double center = -(ratio * sourceHeight) * centerCoordinates[0]; - destinationY = (int)center + (height / 2); - - if (destinationY > 0) - { - destinationY = 0; - } - - if (destinationY < (int)(height - (sourceHeight * ratio))) - { - destinationY = (int)(height - (sourceHeight * ratio)); - } - } - else - { - switch (anchorPosition) - { - case AnchorPosition.Top: - destinationY = 0; - break; - case AnchorPosition.Bottom: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } - - destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); - } - else - { - ratio = percentHeight; - - if (centerCoordinates != null && centerCoordinates.Any()) - { - double center = -(ratio * sourceWidth) * centerCoordinates[1]; - destinationX = (int)center + (width / 2); - - if (destinationX > 0) - { - destinationX = 0; - } - - if (destinationX < (int)(width - (sourceWidth * ratio))) - { - destinationX = (int)(width - (sourceWidth * ratio)); - } - } - else - { - switch (anchorPosition) - { - case AnchorPosition.Left: - destinationX = 0; - break; - case AnchorPosition.Right: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = (int)((width - (sourceWidth * ratio)) / 2); - break; - } - } - - destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); - } - } - - // Constrain the image to fit the maximum possible height or width. - if (resizeMode == ResizeMode.Max) - { - // If either is 0, we don't need to figure out orientation - if (width > 0 && height > 0) - { - // Integers must be cast to doubles to get needed precision - double ratio = (double)height / width; - double sourceRatio = (double)sourceHeight / sourceWidth; - - if (sourceRatio < ratio) - { - height = 0; - } - else - { - width = 0; - } - } - } - - // If height or width is not passed we assume that the standard ratio is to be kept. - if (height == 0) - { - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - height = destinationHeight; - } - - if (width == 0) - { - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - width = destinationWidth; - } - - // Restrict sizes - if (restrictedSizes != null && restrictedSizes.Any()) - { - bool reject = true; - foreach (Size restrictedSize in restrictedSizes) - { - if (restrictedSize.Height == 0 || restrictedSize.Width == 0) - { - if (restrictedSize.Width == width || restrictedSize.Height == height) - { - reject = false; - } - } - else if (restrictedSize.Width == width && restrictedSize.Height == height) - { - reject = false; - } - } - - if (reject) - { - return image; - } - } - - if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight) - { - // Exit if upscaling is not allowed. - if ((width > sourceWidth || height > sourceHeight) && upscale == false && resizeMode != ResizeMode.Stretch) - { - return image; - } - - newImage = new Bitmap(width, height, PixelFormat.Format32bppPArgb); - - using (Graphics graphics = Graphics.FromImage(newImage)) - { - // We want to use two different blending algorithms for enlargement/shrinking. - if (image.Width < destinationWidth && image.Height < destinationHeight) - { - // We are making it larger. - graphics.SmoothingMode = SmoothingMode.AntiAlias; - } - else - { - // We are making it smaller. - graphics.SmoothingMode = SmoothingMode.None; - } - - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - - // An unwanted border appears when using InterpolationMode.HighQualityBicubic to resize the image - // as the algorithm appears to be pulling averaging detail from surrounding 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(destinationX, destinationY, destinationWidth, destinationHeight); - graphics.DrawImage(image, destRect, 0, 0, sourceWidth, sourceHeight, GraphicsUnit.Pixel, wrapMode); - } - - // Reassign the image. - image.Dispose(); - image = newImage; - } - } + // Reassign the image. + image.Dispose(); + image = newImage; } catch (Exception ex) {