From 897972f2c04f86973997a071a85fe2a7ae04435e Mon Sep 17 00:00:00 2001 From: James South Date: Sat, 8 Nov 2014 12:40:03 +0000 Subject: [PATCH] Adding Overlay to ImageProcessor.Web Former-commit-id: 44c974a8eccc401c5042feafc3296072337b88ca Former-commit-id: 7ccac61927cde0398d204c868b14890c87ac9071 --- .../{masks => imageprocessor/mask}/mask.png | 0 .../overlay/monster.png.REMOVED.git-id | 1 + .../RegularExpressionUnitTests.cs | 14 + .../Configuration/Resources/processing.config | 7 +- .../Helpers/ImageHelpers.cs | 4 +- .../ImageProcessor.Web.csproj | 1 + src/ImageProcessor.Web/Processors/Mask.cs | 16 +- src/ImageProcessor.Web/Processors/Overlay.cs | 244 ++++++++++++++++++ src/ImageProcessor.Web/Processors/Resize.cs | 1 + .../Processors/Watermark.cs | 7 +- src/ImageProcessor/ImageProcessor.csproj | 2 +- src/ImageProcessor/Processors/Overlay.cs | 7 +- 12 files changed, 286 insertions(+), 18 deletions(-) rename src/ImageProcessor.UnitTests/Images/{masks => imageprocessor/mask}/mask.png (100%) create mode 100644 src/ImageProcessor.UnitTests/Images/imageprocessor/overlay/monster.png.REMOVED.git-id create mode 100644 src/ImageProcessor.Web/Processors/Overlay.cs diff --git a/src/ImageProcessor.UnitTests/Images/masks/mask.png b/src/ImageProcessor.UnitTests/Images/imageprocessor/mask/mask.png similarity index 100% rename from src/ImageProcessor.UnitTests/Images/masks/mask.png rename to src/ImageProcessor.UnitTests/Images/imageprocessor/mask/mask.png diff --git a/src/ImageProcessor.UnitTests/Images/imageprocessor/overlay/monster.png.REMOVED.git-id b/src/ImageProcessor.UnitTests/Images/imageprocessor/overlay/monster.png.REMOVED.git-id new file mode 100644 index 0000000000..c13b65e9eb --- /dev/null +++ b/src/ImageProcessor.UnitTests/Images/imageprocessor/overlay/monster.png.REMOVED.git-id @@ -0,0 +1 @@ +4edf74a6857665c8efe2d3282c25907f5b20ca81 \ No newline at end of file diff --git a/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs b/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs index 963cd1b825..7df015d630 100644 --- a/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs +++ b/src/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs @@ -459,6 +459,20 @@ namespace ImageProcessor.Web.UnitTests DropShadow = true, FontFamily = new FontFamily("arial") } + }, + { + "watermark=watermark goodness&color=fff&fontsize=36&fontstyle=italic&fontopacity=80&watermark.position=30,150&textshadow=true&fontfamily=arial", + new TextLayer + { + Text = "watermark goodness", + FontColor = ColorTranslator.FromHtml("#" + "ffffff"), + FontSize = 36, + Style = FontStyle.Italic, + Opacity = 80, + Position = new Point(30, 150), + DropShadow = true, + FontFamily = new FontFamily("arial") + } } }; diff --git a/src/ImageProcessor.Web/Configuration/Resources/processing.config b/src/ImageProcessor.Web/Configuration/Resources/processing.config index ab0eb300a6..8be8188c9c 100644 --- a/src/ImageProcessor.Web/Configuration/Resources/processing.config +++ b/src/ImageProcessor.Web/Configuration/Resources/processing.config @@ -30,10 +30,15 @@ - + + + + + + diff --git a/src/ImageProcessor.Web/Helpers/ImageHelpers.cs b/src/ImageProcessor.Web/Helpers/ImageHelpers.cs index 536b6f05db..cfa6186fd1 100644 --- a/src/ImageProcessor.Web/Helpers/ImageHelpers.cs +++ b/src/ImageProcessor.Web/Helpers/ImageHelpers.cs @@ -28,9 +28,9 @@ namespace ImageProcessor.Web.Helpers /// /// The exclude regex for matching things to ignore when parsing image extensions. - /// I'd like to make something more extensible than this. + /// TODO: This is hacky and awful and should go. /// - private static readonly Regex ExcludeRegex = new Regex(@"mask=[\w+-]+.", RegexOptions.IgnoreCase); + private static readonly Regex ExcludeRegex = new Regex(@"(mask|overlay)=[\w+-]+.", RegexOptions.IgnoreCase); /// /// The image format regex. diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 7859ba25a1..1c7e29c403 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -50,6 +50,7 @@ + diff --git a/src/ImageProcessor.Web/Processors/Mask.cs b/src/ImageProcessor.Web/Processors/Mask.cs index f5ad7d454c..241cae8741 100644 --- a/src/ImageProcessor.Web/Processors/Mask.cs +++ b/src/ImageProcessor.Web/Processors/Mask.cs @@ -31,17 +31,17 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"(mask=|maskposition=)[^&]+", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"(mask=|mask.\w+=)[^&]+", RegexOptions.Compiled); /// /// The mask image regex. /// - private static readonly Regex PixelRegex = new Regex(@"mask=[\w+-]+." + ImageHelpers.ExtensionRegexPattern); + private static readonly Regex ImageRegex = new Regex(@"mask=[\w+-]+." + ImageHelpers.ExtensionRegexPattern); /// /// The point regex. /// - private static readonly Regex PointRegex = new Regex(@"maskposition=\d+,\d+", RegexOptions.Compiled); + private static readonly Regex PointRegex = new Regex(@"mask.position=\d+,\d+", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -110,8 +110,8 @@ namespace ImageProcessor.Web.Processors // Match syntax string toParse = stringBuilder.ToString(); Image image = this.ParseImage(toParse); - Point? rectangle = this.ParsePoint(toParse); - this.Processor.DynamicParameter = new Tuple(image, rectangle); + Point? position = this.ParsePoint(toParse); + this.Processor.DynamicParameter = new Tuple(image, position); } return this.SortOrder; @@ -136,7 +136,7 @@ namespace ImageProcessor.Web.Processors if (!string.IsNullOrWhiteSpace(path) && path.StartsWith("~/")) { - Match match = PixelRegex.Match(input); + Match match = ImageRegex.Match(input); if (match.Success) { @@ -168,8 +168,8 @@ namespace ImageProcessor.Web.Processors private Point? ParsePoint(string input) { int[] dimensions = { }; - - foreach (Match match in PointRegex.Matches(input)) + Match match = PointRegex.Match(input); + if (match.Success) { dimensions = match.Value.ToPositiveIntegerArray(); } diff --git a/src/ImageProcessor.Web/Processors/Overlay.cs b/src/ImageProcessor.Web/Processors/Overlay.cs new file mode 100644 index 0000000000..b2121b6a8d --- /dev/null +++ b/src/ImageProcessor.Web/Processors/Overlay.cs @@ -0,0 +1,244 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Adds an image overlay to the current image. +// If the overlay is larger than the image it will be scaled to match the image. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Processors +{ + using System; + using System.Drawing; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using System.Web.Hosting; + + using ImageProcessor.Imaging; + using ImageProcessor.Processors; + using ImageProcessor.Web.Extensions; + using ImageProcessor.Web.Helpers; + + /// + /// Adds an image overlay to the current image. + /// If the overlay is larger than the image it will be scaled to match the image. + /// + public class Overlay : IWebGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = new Regex(@"(overlay=|overlay.\w+=)[^&]+", RegexOptions.Compiled); + + /// + /// The overlay image regex. + /// + private static readonly Regex ImageRegex = new Regex(@"overlay=[\w+-]+." + ImageHelpers.ExtensionRegexPattern); + + /// + /// The point regex. + /// + private static readonly Regex PointRegex = new Regex(@"overlay.position=\d+,\d+", RegexOptions.Compiled); + + /// + /// The size regex. + /// + private static readonly Regex SizeRegex = new Regex(@"overlay.size=\d+,\d+", RegexOptions.Compiled); + + /// + /// The opacity regex. + /// + private static readonly Regex OpacityRegex = new Regex(@"overlay.opacity=\d+", RegexOptions.Compiled); + + /// + /// Initializes a new instance of the class. + /// + public Overlay() + { + this.Processor = new ImageProcessor.Processors.Overlay(); + } + + /// + /// 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; + + // First merge the matches so we can parse . + StringBuilder stringBuilder = new StringBuilder(); + + 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; + } + + stringBuilder.Append(match.Value); + + index += 1; + } + } + + if (this.SortOrder < int.MaxValue) + { + // Match syntax + string toParse = stringBuilder.ToString(); + this.Processor.DynamicParameter = new ImageLayer + { + Image = this.ParseImage(toParse), + Position = this.ParsePoint(toParse), + Opacity = this.ParseOpacity(toParse), + Size = this.ParseSize(toParse) + }; + } + + return this.SortOrder; + } + + /// + /// Returns the correct size of pixels. + /// + /// + /// The input containing the value to parse. + /// + /// + /// The representing the pixel size. + /// + public Image ParseImage(string input) + { + Image image = null; + + // Correctly parse the path. + string path; + this.Processor.Settings.TryGetValue("VirtualPath", out path); + + if (!string.IsNullOrWhiteSpace(path) && path.StartsWith("~/")) + { + Match match = ImageRegex.Match(input); + + if (match.Success) + { + string imagePath = HostingEnvironment.MapPath(path); + if (imagePath != null) + { + imagePath = Path.Combine(imagePath, match.Value.Split('=')[1]); + using (ImageFactory factory = new ImageFactory()) + { + factory.Load(imagePath); + image = new Bitmap(factory.Image); + } + } + } + } + + return image; + } + + /// + /// Returns the correct for the given string. + /// + /// + /// The input string containing the value to parse. + /// + /// + /// The correct + /// + private Point? ParsePoint(string input) + { + int[] dimensions = { }; + + Match match = PointRegex.Match(input); + if (match.Success) + { + dimensions = match.Value.ToPositiveIntegerArray(); + } + + if (dimensions.Length == 2) + { + return new Point(dimensions[0], dimensions[1]); + } + + return null; + } + + /// + /// Returns the correct for the given string. + /// + /// + /// The input string containing the value to parse. + /// + /// + /// The correct + /// + private int ParseOpacity(string input) + { + int opacity = 100; + Match match = OpacityRegex.Match(input); + if (match.Success) + { + opacity = Math.Abs(CommonParameterParserUtility.ParseIn100Range(match.Value.Split('=')[1])); + } + + return opacity; + } + + /// + /// Returns the correct for the given string. + /// + /// + /// The input string containing the value to parse. + /// + /// + /// The . + /// + private Size ParseSize(string input) + { + Size size = Size.Empty; + Match match = SizeRegex.Match(input); + if (match.Success) + { + int[] dimensions = match.Value.ToPositiveIntegerArray(); + size = new Size(dimensions[0], dimensions[1]); + } + + return size; + } + } +} diff --git a/src/ImageProcessor.Web/Processors/Resize.cs b/src/ImageProcessor.Web/Processors/Resize.cs index a634c23277..59b3973585 100644 --- a/src/ImageProcessor.Web/Processors/Resize.cs +++ b/src/ImageProcessor.Web/Processors/Resize.cs @@ -112,6 +112,7 @@ namespace ImageProcessor.Web.Processors if (match.Success) { // We don't want any resize carve or percentile crops requests to interfere. + // TODO: This is hacky and awful and should go. if (match.Value.ToUpperInvariant().Contains("CARVE") || match.Value.ToUpperInvariant().Contains("PERCENT")) { break; diff --git a/src/ImageProcessor.Web/Processors/Watermark.cs b/src/ImageProcessor.Web/Processors/Watermark.cs index b61bf13b0c..92afc3228f 100644 --- a/src/ImageProcessor.Web/Processors/Watermark.cs +++ b/src/ImageProcessor.Web/Processors/Watermark.cs @@ -39,7 +39,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for the position attribute. /// - private static readonly Regex PositionRegex = new Regex(@"(text)?position(=|-)\d+[-,]\d+", RegexOptions.Compiled); + private static readonly Regex PositionRegex = new Regex(@"(watermark.position|textposition|[^.](&,=)?position)(=|-)\d+[-,]\d+", RegexOptions.Compiled); /// /// The regular expression to search strings for the font size attribute. @@ -59,7 +59,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for the opacity attribute. /// - private static readonly Regex OpacityRegex = new Regex(@"((font)?)opacity(=|-)(?:100|[1-9]?[0-9])", RegexOptions.Compiled); + private static readonly Regex OpacityRegex = new Regex(@"(watermark.opacity|fontopacity|[^.](&,=)?opacity)(=|-)(?:100|[1-9]?[0-9])", RegexOptions.Compiled); /// /// The regular expression to search strings for the shadow attribute. @@ -182,7 +182,8 @@ namespace ImageProcessor.Web.Processors { foreach (Match match in PositionRegex.Matches(input)) { - int[] position = match.Value.ToPositiveIntegerArray(); + // Chop off the leading legacy support '=' + int[] position = match.Value.TrimStart('=').ToPositiveIntegerArray(); if (position != null) { diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index dc77e9c2dc..0ad2b8a89d 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -159,7 +159,7 @@ - + diff --git a/src/ImageProcessor/Processors/Overlay.cs b/src/ImageProcessor/Processors/Overlay.cs index d3fb2cce84..7551a95904 100644 --- a/src/ImageProcessor/Processors/Overlay.cs +++ b/src/ImageProcessor/Processors/Overlay.cs @@ -20,7 +20,8 @@ namespace ImageProcessor.Processors using ImageProcessor.Imaging.Helpers; /// - /// Adds an image overlay to the current image. + /// Adds an image overlay to the current image. + /// If the overlay is larger than the image it will be scaled to match the image. /// public class Overlay : IGraphicsProcessor { @@ -77,8 +78,8 @@ namespace ImageProcessor.Processors Size size = imageLayer.Size; int width = image.Width; int height = image.Height; - int overlayWidth = Math.Min(image.Size.Width, size.Width); - int overlayHeight = Math.Min(image.Size.Height, size.Height); + int overlayWidth = size != Size.Empty ? Math.Min(image.Size.Width, size.Width) : image.Size.Width; + int overlayHeight = size != Size.Empty ? Math.Min(image.Size.Height, size.Height) : image.Size.Height; Point? position = imageLayer.Position; int opacity = imageLayer.Opacity;