diff --git a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs index b116f0909..282216b2e 100644 --- a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs @@ -231,7 +231,7 @@ namespace ImageProcessor.Web.HttpModules } // Store the response type in the context for later retrieval. - context.Items[CachedResponseTypeKey] = ImageUtils.GetResponseType(imageName).ToDescription(); + context.Items[CachedResponseTypeKey] = ImageUtils.GetResponseType(fullPath).ToDescription(); // The cached file is valid so just rewrite the path. context.RewritePath(cache.GetVirtualCachedPath(), false); diff --git a/src/ImageProcessor.Web/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web/Properties/AssemblyInfo.cs index 6df2b8c83..5323aecf8 100644 --- a/src/ImageProcessor.Web/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor.Web/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.2.2.0")] -[assembly: AssemblyFileVersion("2.2.2.0")] +[assembly: AssemblyVersion("2.2.3.0")] +[assembly: AssemblyFileVersion("2.2.3.0")] diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index b9f710a55..328bd4e2d 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -92,14 +92,14 @@ namespace ImageProcessor public bool ShouldProcess { get; private set; } /// - /// Gets or sets the quality of output for jpeg images as a percentile. + /// Gets the file format of the image. /// - internal int JpegQuality { get; set; } + public ImageFormat ImageFormat { get; private set; } /// - /// Gets or sets the file format of the image. + /// Gets or sets the quality of output for jpeg images as a percentile. /// - internal ImageFormat ImageFormat { get; set; } + internal int JpegQuality { get; set; } #endregion #region Methods @@ -472,6 +472,32 @@ namespace ImageProcessor return this; } + /// + /// Adds rounded corners to the current image. + /// + /// + /// The containing the properties to round corners on the image. + /// + /// + /// The current instance of the class. + /// + public ImageFactory RoundedCorners(RoundedCornerLayer roundedCornerLayer) + { + if (this.ShouldProcess) + { + if (roundedCornerLayer.Radius < 0) + { + roundedCornerLayer.Radius = 0; + } + + RoundedCorners roundedCorners = new RoundedCorners { DynamicParameter = roundedCornerLayer }; + + this.Image = roundedCorners.ProcessImage(this); + } + + return this; + } + /// /// Changes the saturation of the current image. /// @@ -561,7 +587,7 @@ namespace ImageProcessor // Fix the colour palette of gif images. this.FixGifs(); - if (object.Equals(this.ImageFormat, ImageFormat.Jpeg)) + if (this.ImageFormat.Equals(ImageFormat.Jpeg)) { // Jpegs can be saved with different settings to include a quality setting for the JPEG compression. // This improves output compression and quality. @@ -601,7 +627,7 @@ namespace ImageProcessor // Fix the colour palette of gif images. this.FixGifs(); - if (object.Equals(this.ImageFormat, ImageFormat.Jpeg)) + if (this.ImageFormat.Equals(ImageFormat.Jpeg)) { // Jpegs can be saved with different settings to include a quality setting for the JPEG compression. // This improves output compression and quality. @@ -658,7 +684,7 @@ namespace ImageProcessor // Dispose of any managed resources here. if (this.Image != null) { - // Dispose of the memorystream from Load and the image. + // Dispose of the memory stream from Load and the image. if (this.Image.Tag != null) { ((IDisposable)this.Image.Tag).Dispose(); diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 3d2a3f197..df2f93ff8 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -78,10 +78,12 @@ + + diff --git a/src/ImageProcessor/Imaging/ImageUtils.cs b/src/ImageProcessor/Imaging/ImageUtils.cs index 0846a1a40..8a62f2e3c 100644 --- a/src/ImageProcessor/Imaging/ImageUtils.cs +++ b/src/ImageProcessor/Imaging/ImageUtils.cs @@ -12,6 +12,7 @@ namespace ImageProcessor.Imaging using System.Drawing.Imaging; using System.IO; using System.Linq; + using System.Text.RegularExpressions; using System.Threading.Tasks; #endregion @@ -21,32 +22,36 @@ namespace ImageProcessor.Imaging public static class ImageUtils { /// - /// Returns the correct response type based on the given file extension. + /// The image format regex. /// - /// The string containing the filename to check against. - /// The correct response type based on the given file extension. - public static ResponseType GetResponseType(string fileName) + private static readonly Regex FormatRegex = new Regex(@"j(pg|peg)|bmp|png|gif", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.RightToLeft); + + /// + /// Returns the correct response type based on the given request path. + /// + /// + /// The request to match. + /// + /// + /// The correct . + /// + public static ResponseType GetResponseType(string request) { - string extension = Path.GetExtension(fileName); - if (extension != null) + foreach (Match match in FormatRegex.Matches(request)) { - string ext = extension.ToUpperInvariant(); - - switch (ext) + switch (match.Value) { - case ".PNG": + case "png": return ResponseType.Png; - case ".BMP": + case "bmp": return ResponseType.Bmp; - case ".GIF": + case "gif": return ResponseType.Gif; default: - // Should be a jpeg. return ResponseType.Jpeg; } } - // TODO: Should we call this on bad request? return ResponseType.Jpeg; } @@ -143,10 +148,10 @@ namespace ImageProcessor.Imaging } /// - /// Returns an instance of EncodingParameters for jpeg comression. + /// Returns an instance of EncodingParameters for jpeg compression. /// /// The quality to return the image at. - /// The encodingParameters for jpeg comression. + /// The encodingParameters for jpeg compression. public static EncoderParameters GetEncodingParameters(int quality) { EncoderParameters encoderParameters = null; diff --git a/src/ImageProcessor/Imaging/RotateLayer.cs b/src/ImageProcessor/Imaging/RotateLayer.cs index bce5ff1fc..0b7c956ba 100644 --- a/src/ImageProcessor/Imaging/RotateLayer.cs +++ b/src/ImageProcessor/Imaging/RotateLayer.cs @@ -8,15 +8,13 @@ namespace ImageProcessor.Imaging { #region Using - using System; - using System.Collections.Generic; using System.Drawing; #endregion /// /// Encapsulates the properties required to rotate an image. /// - public class RotateLayer : IEqualityComparer + public class RotateLayer { #region Constructors /// @@ -69,55 +67,37 @@ namespace ImageProcessor.Imaging #endregion /// - /// Returns a value indicating whether this instance is equal to another . + /// Returns a value that indicates whether the specified object is an + /// object that is equivalent to + /// this object. /// - /// - /// The other to compare to. + /// + /// The object to test. /// /// - /// True if this instance is equal to another ; otherwise, false. + /// True if the given object is an object that is equivalent to + /// this object; otherwise, false. /// - public bool Equals(RotateLayer other) + public override bool Equals(object obj) { - return this.Angle.Equals(other.Angle) && this.BackgroundColor.Equals(other.BackgroundColor); - } + RotateLayer rotate = obj as RotateLayer; - /// - /// Determines whether the specified objects are equal. - /// - /// - /// true if the specified objects are equal; otherwise, false. - /// - /// - /// The first object of type to compare. - /// - /// - /// The second object of type to compare. - /// - public bool Equals(RotateLayer x, RotateLayer y) - { - return x.Angle.Equals(y.Angle) && x.BackgroundColor.Equals(y.BackgroundColor); + if (rotate == null) + { + return false; + } + + return this.Angle == rotate.Angle && this.BackgroundColor == rotate.BackgroundColor; } /// - /// Returns a hash code for the specified object. + /// Returns a hash code value that represents this object. /// /// - /// A hash code for the specified object. + /// A hash code that represents this object. /// - /// - /// The for which a hash code is to be returned. - /// - /// - /// The type of is a reference type and is null. - /// - public int GetHashCode(RotateLayer obj) + public override int GetHashCode() { - if (obj == null) - { - throw new ArgumentNullException(); - } - return this.Angle.GetHashCode() + this.BackgroundColor.GetHashCode(); } } diff --git a/src/ImageProcessor/Imaging/RoundedCornerLayer.cs b/src/ImageProcessor/Imaging/RoundedCornerLayer.cs new file mode 100644 index 000000000..89faa8319 --- /dev/null +++ b/src/ImageProcessor/Imaging/RoundedCornerLayer.cs @@ -0,0 +1,165 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + #region Using + using System.Drawing; + #endregion + + /// + /// Encapsulates the properties required to add rounded corners to an image. + /// + public class RoundedCornerLayer + { + #region Constructors + /// + /// Initializes a new instance of the class. + /// + public RoundedCornerLayer() + { + this.Radius = 10; + this.BackgroundColor = Color.Transparent; + this.TopLeft = true; + this.TopRight = true; + this.BottomLeft = true; + this.BottomRight = true; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The radius at which the corner will be done. + /// + /// + /// Set if top left is rounded + /// + /// + /// Set if top right is rounded + /// + /// + /// Set if bottom left is rounded + /// + /// + /// Set if bottom right is rounded + /// + public RoundedCornerLayer(int radius, bool topLeft, bool topRight, bool bottomLeft, bool bottomRight) + { + this.Radius = radius; + this.BackgroundColor = Color.Transparent; + this.TopLeft = topLeft; + this.TopRight = topRight; + this.BottomLeft = bottomLeft; + this.BottomRight = bottomRight; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The radius at which the corner will be done. + /// + /// + /// The to set as the background color. + /// Used for image formats that do not support transparency + /// + /// + /// Set if top left is rounded + /// + /// + /// Set if top right is rounded + /// + /// + /// Set if bottom left is rounded + /// + /// + /// Set if bottom right is rounded + /// + public RoundedCornerLayer(int radius, Color backgroundColor, bool topLeft, bool topRight, bool bottomLeft, bool bottomRight) + { + this.Radius = radius; + this.BackgroundColor = backgroundColor; + this.TopLeft = topLeft; + this.TopRight = topRight; + this.BottomLeft = bottomLeft; + this.BottomRight = bottomRight; + } + #endregion + + #region Properties + /// + /// Gets or sets the radius of the corners. + /// + public int Radius { get; set; } + + /// + /// Gets or sets the background color. + /// + public Color BackgroundColor { get; set; } + + /// + /// Gets or sets a value indicating whether top left corners are to be added. + /// + public bool TopLeft { get; set; } + + /// + /// Gets or sets a value indicating whether top right corners are to be added. + /// + public bool TopRight { get; set; } + + /// + /// Gets or sets a value indicating whether bottom left corners are to be added. + /// + public bool BottomLeft { get; set; } + + /// + /// Gets or sets a value indicating whether bottom right corners are to be added. + /// + public bool BottomRight { get; set; } + #endregion + + /// + /// Returns a value that indicates whether the specified object is an + /// object that is equivalent to + /// this object. + /// + /// + /// The object to test. + /// + /// + /// True if the given object is an object that is equivalent to + /// this object; otherwise, false. + /// + public override bool Equals(object obj) + { + RoundedCornerLayer rounded = obj as RoundedCornerLayer; + + if (rounded == null) + { + return false; + } + + return this.Radius == rounded.Radius && this.BackgroundColor == rounded.BackgroundColor + && this.TopLeft == rounded.TopLeft && this.TopRight == rounded.TopRight + && this.BottomLeft == rounded.BottomLeft && this.BottomRight == rounded.BottomRight; + } + + /// + /// Returns a hash code value that represents this object. + /// + /// + /// A hash code that represents this object. + /// + public override int GetHashCode() + { + return this.Radius.GetHashCode() + this.BackgroundColor.GetHashCode() + + this.TopLeft.GetHashCode() + this.TopRight.GetHashCode() + + this.BottomLeft.GetHashCode() + this.BottomRight.GetHashCode(); + } + } +} diff --git a/src/ImageProcessor/Processors/Format.cs b/src/ImageProcessor/Processors/Format.cs index 30b513ed0..336e5dd4c 100644 --- a/src/ImageProcessor/Processors/Format.cs +++ b/src/ImageProcessor/Processors/Format.cs @@ -129,7 +129,7 @@ namespace ImageProcessor.Processors } // Set the internal property. - factory.ImageFormat = imageFormat; + factory.Format(imageFormat); return factory.Image; } diff --git a/src/ImageProcessor/Processors/RoundedCorners.cs b/src/ImageProcessor/Processors/RoundedCorners.cs new file mode 100644 index 000000000..9c817c6cc --- /dev/null +++ b/src/ImageProcessor/Processors/RoundedCorners.cs @@ -0,0 +1,350 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Text.RegularExpressions; + using ImageProcessor.Imaging; + #endregion + + /// + /// Encapsulates methods to add rounded corners to an image. + /// + public class RoundedCorners : IGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = new Regex(@"roundedcorners=(\d+|[^&]*)", RegexOptions.Compiled); + + /// + /// The regular expression to search strings for the angle attribute. + /// + private static readonly Regex RadiusRegex = new Regex(@"radius-(\d+)", RegexOptions.Compiled); + + /// + /// The regular expression to search strings for the color attribute. + /// + private static readonly Regex ColorRegex = new Regex(@"bgcolor-([0-9a-fA-F]{3}){1,2}", RegexOptions.Compiled); + + /// + /// The regular expression to search strings for the top left attribute. + /// + private static readonly Regex TopLeftRegex = new Regex(@"tl-(true|false)", RegexOptions.Compiled); + + /// + /// The regular expression to search strings for the top right attribute. + /// + private static readonly Regex TopRightRegex = new Regex(@"tr-(true|false)", RegexOptions.Compiled); + + /// + /// The regular expression to search strings for the bottom left attribute. + /// + private static readonly Regex BottomLeftRegex = new Regex(@"bl-(true|false)", RegexOptions.Compiled); + + /// + /// The regular expression to search strings for the bottom right attribute. + /// + private static readonly Regex BottomRightRegex = new Regex(@"br-(true|false)", RegexOptions.Compiled); + + #region IGraphicsProcessor Members + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets or sets DynamicParameter. + /// + public dynamic DynamicParameter + { + get; + set; + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get; + 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; + + RoundedCornerLayer roundedCornerLayer; + + string toParse = match.Value; + + if (toParse.Contains("bgcolor")) + { + roundedCornerLayer = new RoundedCornerLayer(this.ParseRadius(toParse), this.ParseColor(toParse), this.ParseCorner(TopLeftRegex, toParse), this.ParseCorner(TopRightRegex, toParse), this.ParseCorner(BottomLeftRegex, toParse), this.ParseCorner(BottomRightRegex, toParse)); + } + else + { + int radius; + int.TryParse(match.Value.Split('=')[1], out radius); + + roundedCornerLayer = new RoundedCornerLayer(radius, this.ParseCorner(TopLeftRegex, toParse), this.ParseCorner(TopRightRegex, toParse), this.ParseCorner(BottomLeftRegex, toParse), this.ParseCorner(BottomRightRegex, toParse)); + } + + this.DynamicParameter = roundedCornerLayer; + } + + index += 1; + } + } + + return this.SortOrder; + } + + /// + /// Processes the image. + /// + /// + /// The 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) + { + Bitmap newImage = null; + Image image = factory.Image; + + try + { + RoundedCornerLayer roundedCornerLayer = this.DynamicParameter; + int radius = roundedCornerLayer.Radius; + Color backgroundColor = roundedCornerLayer.BackgroundColor; + bool topLeft = roundedCornerLayer.TopLeft; + bool topRight = roundedCornerLayer.TopRight; + bool bottomLeft = roundedCornerLayer.BottomLeft; + bool bottomRight = roundedCornerLayer.BottomRight; + + // Create a rotated image. + newImage = this.RoundCornerImage(image, radius, backgroundColor, topLeft, topRight, bottomLeft, bottomRight); + newImage.Tag = image.Tag; + + image.Dispose(); + image = newImage; + } + catch + { + if (newImage != null) + { + newImage.Dispose(); + } + } + + return image; + } + #endregion + + #region Private Methods + /// + /// Adds rounded corners to the image + /// + /// The image to add corners too + /// The radius of the corners. + /// The background color to fill an image with. + /// If the top left corner will have a rounded corner? + /// If the top right corner will have a rounded corner? + /// If the bottom left corner will have a rounded corner? + /// If the bottom right corner will have a rounded corner? + /// The image with rounded corners. + private Bitmap RoundCornerImage(Image image, int cornerRadius, Color backgroundColor, bool topLeft = false, bool topRight = false, bool bottomLeft = false, bool bottomRight = false) + { + int width = image.Width; + int height = image.Height; + + // Create a new empty bitmap to hold rotated image + Bitmap newImage = new Bitmap(image.Width, image.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.HighQuality; + + // Contrary to everything I have read bicubic is producing the best results. + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighSpeed; + + // Fill the background. + graphics.Clear(backgroundColor); + + // Add rounded corners + using (GraphicsPath path = new GraphicsPath()) + { + // Determined if the top left has a rounded corner + if (topLeft) + { + path.AddArc(0, 0, cornerRadius, cornerRadius, 180, 90); + } + else + { + path.AddLine(0, 0, 0, 0); + } + + // Determined if the top right has a rounded corner + if (topRight) + { + path.AddArc(0 + width - cornerRadius, 0, cornerRadius, cornerRadius, 270, 90); + } + else + { + path.AddLine(width, 0, width, 0); + } + + // Determined if the bottom left has a rounded corner + if (bottomRight) + { + path.AddArc(0 + width - cornerRadius, 0 + height - cornerRadius, cornerRadius, cornerRadius, 0, 90); + } + else + { + path.AddLine(width, height, width, height); + } + + // Determined if the bottom right has a rounded corner + if (bottomLeft) + { + path.AddArc(0, 0 + height - cornerRadius, cornerRadius, cornerRadius, 90, 90); + } + else + { + path.AddLine(0, height, 0, height); + } + + using (Brush brush = new TextureBrush(image)) + { + graphics.FillPath(brush, path); + } + } + } + + return newImage; + } + + /// + /// Returns the correct containing the radius for the given string. + /// + /// + /// The input string containing the value to parse. + /// + /// + /// The correct containing the radius for the given string. + /// + private int ParseRadius(string input) + { + foreach (Match match in RadiusRegex.Matches(input)) + { + // Split on radius- + int radius; + int.TryParse(match.Value.Split('-')[1], out radius); + return radius; + } + + // No rotate - matches the RotateLayer default. + return 0; + } + + /// + /// Returns the correct for the given string. + /// + /// + /// The input string containing the value to parse. + /// + /// + /// The correct + /// + private Color ParseColor(string input) + { + foreach (Match match in ColorRegex.Matches(input)) + { + // split on color-hex + return ColorTranslator.FromHtml("#" + match.Value.Split('-')[1]); + } + + return Color.Transparent; + } + + /// + /// Returns a either true or false. + /// + /// + /// The corner. + /// + /// + /// The input string containing the value to parse. + /// + /// + /// The correct true or false. + /// + private bool ParseCorner(Regex corner, string input) + { + foreach (Match match in corner.Matches(input)) + { + // Split on corner- + bool cornerRound; + bool.TryParse(match.Value.Split('-')[1], out cornerRound); + return cornerRound; + } + + // No rotate - matches the RotateLayer default. + return true; + } + #endregion + } +} diff --git a/src/ImageProcessor/Properties/AssemblyInfo.cs b/src/ImageProcessor/Properties/AssemblyInfo.cs index c921411c0..49264fada 100644 --- a/src/ImageProcessor/Properties/AssemblyInfo.cs +++ b/src/ImageProcessor/Properties/AssemblyInfo.cs @@ -32,6 +32,6 @@ using System.Security; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.5.0.0")] -[assembly: AssemblyFileVersion("1.5.0.0")] +[assembly: AssemblyVersion("1.6.0.0")] +[assembly: AssemblyFileVersion("1.6.0.0")] diff --git a/src/Nuget/ImageProcessor.1.6.0.0.nupkg b/src/Nuget/ImageProcessor.1.6.0.0.nupkg new file mode 100644 index 000000000..ad93e2986 Binary files /dev/null and b/src/Nuget/ImageProcessor.1.6.0.0.nupkg differ diff --git a/src/Nuget/ImageProcessor.Web.2.2.3.0.nupkg.REMOVED.git-id b/src/Nuget/ImageProcessor.Web.2.2.3.0.nupkg.REMOVED.git-id new file mode 100644 index 000000000..ff1ef9fb9 --- /dev/null +++ b/src/Nuget/ImageProcessor.Web.2.2.3.0.nupkg.REMOVED.git-id @@ -0,0 +1 @@ +2f145d3c3bc539fb4f43d963ef9a863e553785ad \ No newline at end of file