// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // // // Encapsulates methods to add rounded corners to an image. // // -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Processors { #region Using using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; 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], NumberStyles.Any, CultureInfo.InvariantCulture, 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); 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; int cornerDiameter = cornerRadius * 2; // 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, cornerDiameter, cornerDiameter, 180, 90); } else { path.AddLine(0, 0, 0, 0); } // Determined if the top right has a rounded corner if (topRight) { path.AddArc(0 + width - cornerDiameter, 0, cornerDiameter, cornerDiameter, 270, 90); } else { path.AddLine(width, 0, width, 0); } // Determined if the bottom left has a rounded corner if (bottomRight) { path.AddArc(0 + width - cornerDiameter, 0 + height - cornerDiameter, cornerDiameter, cornerDiameter, 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 - cornerDiameter, cornerDiameter, cornerDiameter, 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], NumberStyles.Any, CultureInfo.InvariantCulture, 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 } }