diff --git a/src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs b/src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs index a42ed735c..c47fc1611 100644 --- a/src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs +++ b/src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs @@ -1,13 +1,9 @@ // -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) James South. -// Licensed under the Apache License, Version 2.0. +// // Licensed under the Apache License, Version 2.0. // -// -// Encapsulates methods to correctly parse querystring parameters. -// // -------------------------------------------------------------------------------------------------------------------- - namespace ImageProcessor.Web.Helpers { using System; @@ -18,7 +14,6 @@ namespace ImageProcessor.Web.Helpers using System.Text.RegularExpressions; using ImageProcessor.Common.Extensions; - using ImageProcessor.Imaging; using ImageProcessor.Web.Extensions; /// @@ -46,21 +41,6 @@ namespace ImageProcessor.Web.Helpers /// private static readonly Regex In100RangeRegex = new Regex(@"(-?0*(?:100|[1-9][0-9]?))", RegexOptions.Compiled); - /// - /// The sharpen regex. - /// - private static readonly Regex BlurSharpenRegex = new Regex(@"(blur|sharpen)=\d+", RegexOptions.Compiled); - - /// - /// The sigma regex. - /// - private static readonly Regex SigmaRegex = new Regex(@"sigma(=|-)\d+(.?\d+)?", RegexOptions.Compiled); - - /// - /// The threshold regex. - /// - private static readonly Regex ThresholdRegex = new Regex(@"threshold(=|-)\d+", RegexOptions.Compiled); - /// /// Returns the correct containing the angle for the given string. /// @@ -147,100 +127,6 @@ namespace ImageProcessor.Web.Helpers return value; } - /// - /// Returns the correct for the given string. - /// - /// - /// The input string containing the value to parse. - /// - /// - /// The maximum size to set the Gaussian kernel to. - /// - /// - /// The maximum Sigma value (standard deviation) for Gaussian function used to calculate the kernel. - /// - /// - /// The maximum threshold value, which is added to each weighted sum of pixels. - /// - /// - /// The correct . - /// - public static GaussianLayer ParseGaussianLayer(string input, int maxSize, double maxSigma, int maxThreshold) - { - int size = ParseBlurSharpen(input); - double sigma = ParseSigma(input); - int threshold = ParseThreshold(input); - - size = maxSize < size ? maxSize : size; - sigma = maxSigma < sigma ? maxSigma : sigma; - threshold = maxThreshold < threshold ? maxThreshold : threshold; - - return new GaussianLayer(size, sigma, threshold); - } - - /// - /// Returns the correct containing the blur value - /// for the given string. - /// - /// - /// The input string containing the value to parse. - /// - /// - /// The correct for the given string. - /// - private static int ParseBlurSharpen(string input) - { - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (Match match in BlurSharpenRegex.Matches(input)) - { - return Convert.ToInt32(match.Value.Split('=')[1]); - } - - return 0; - } - - /// - /// Returns the correct containing the sigma value - /// for the given string. - /// - /// - /// The input string containing the value to parse. - /// - /// - /// The correct for the given string. - /// - private static double ParseSigma(string input) - { - foreach (Match match in SigmaRegex.Matches(input)) - { - // split on text- - return Convert.ToDouble(match.Value.Split(new[] { '=', '-' })[1]); - } - - return 1.4d; - } - - /// - /// Returns the correct containing the threshold value - /// for the given string. - /// - /// - /// The input string containing the value to parse. - /// - /// - /// The correct for the given string. - /// - private static int ParseThreshold(string input) - { - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (Match match in ThresholdRegex.Matches(input)) - { - return Convert.ToInt32(match.Value.Split(new[] { '=', '-' })[1]); - } - - return 0; - } - /// /// Builds a regular expression for the three main colour types. /// diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/ExtendedColorTypeConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/ExtendedColorTypeConverter.cs new file mode 100644 index 000000000..eec3d145c --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/ExtendedColorTypeConverter.cs @@ -0,0 +1,210 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// The extended color type converter allows conversion of system and web colors. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Helpers +{ + using System; + using System.Collections; + using System.ComponentModel; + using System.Drawing; + using System.Globalization; + using System.Text; + using System.Text.RegularExpressions; + + /// + /// The extended color type converter allows conversion of system and web colors. + /// + public class ExtendedColorTypeConverter : ColorConverter + { + /// + /// The web color regex. + /// + private static readonly Regex WebColorRegex = new Regex("([0-9a-fA-F]{3}){1,2}", RegexOptions.Compiled); + + /// + /// The html system color table map. + /// + private static Hashtable htmlSystemColorTable; + + /// + /// Converts the given object to the type of this converter, using the specified context and culture + /// information. + /// + /// + /// An that represents the converted value. + /// + /// + /// An that provides a format context. + /// + /// + /// The to use as the current culture. + /// + /// The to convert. + /// The conversion cannot be performed. + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + string s = value as string; + if (s != null) + { + string colorText = s.Trim(); + Color c = Color.Empty; + + // Empty color + if (string.IsNullOrEmpty(colorText)) + { + return c; + } + + // Special case. HTML requires LightGrey, but System.Drawing.KnownColor has LightGray + if (colorText.Equals("LightGrey", StringComparison.OrdinalIgnoreCase)) + { + return Color.LightGray; + } + + // Hex based color values. + char hash = colorText[0]; + if (hash == '#' || WebColorRegex.IsMatch(colorText)) + { + if (hash != '#') + { + colorText = "#" + colorText; + } + + if (colorText.Length == 7) + { + return Color.FromArgb( + Convert.ToInt32(colorText.Substring(1, 2), 16), + Convert.ToInt32(colorText.Substring(3, 2), 16), + Convert.ToInt32(colorText.Substring(5, 2), 16)); + } + + // Length is 4 + string r = char.ToString(colorText[1]); + string g = char.ToString(colorText[2]); + string b = char.ToString(colorText[3]); + + return Color.FromArgb( + Convert.ToInt32(r + r, 16), + Convert.ToInt32(g + g, 16), + Convert.ToInt32(b + b, 16)); + } + + // System color + if (htmlSystemColorTable == null) + { + InitializeHtmlSystemColorTable(); + } + + if (htmlSystemColorTable != null) + { + object o = htmlSystemColorTable[colorText]; + if (o != null) + { + return (Color)o; + } + } + } + + // ColorConverter handles all named and KnownColors + return base.ConvertFrom(context, culture, value); + } + + /// + /// Converts the given value object to the specified type, using the specified context and culture + /// information. + /// + /// + /// An that represents the converted value. + /// + /// + /// An that provides a format context. + /// + /// + /// A . If null is passed, the current culture is assumed. + /// + /// The to convert. + /// + /// The to convert the parameter to. + /// + /// + /// The parameter is null. + /// + /// The conversion cannot be performed. + /// + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == null) + { + throw new ArgumentNullException("destinationType"); + } + + if (destinationType == typeof(string)) + { + if (value != null) + { + Color color = (Color)value; + + if (color == Color.Empty) + { + return string.Empty; + } + + if (color.IsKnownColor == false) + { + // In the Web scenario, colors should be formatted in #RRGGBB notation + StringBuilder sb = new StringBuilder("#", 7); + sb.Append(color.R.ToString("X2", CultureInfo.InvariantCulture)); + sb.Append(color.G.ToString("X2", CultureInfo.InvariantCulture)); + sb.Append(color.B.ToString("X2", CultureInfo.InvariantCulture)); + return sb.ToString(); + } + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + /// + /// Initializes color table mapping system colors to known colors. + /// + private static void InitializeHtmlSystemColorTable() + { + Hashtable hashTable = new Hashtable(StringComparer.OrdinalIgnoreCase); + hashTable["activeborder"] = Color.FromKnownColor(KnownColor.ActiveBorder); + hashTable["activecaption"] = Color.FromKnownColor(KnownColor.ActiveCaption); + hashTable["appworkspace"] = Color.FromKnownColor(KnownColor.AppWorkspace); + hashTable["background"] = Color.FromKnownColor(KnownColor.Desktop); + hashTable["buttonface"] = Color.FromKnownColor(KnownColor.Control); + hashTable["buttonhighlight"] = Color.FromKnownColor(KnownColor.ControlLightLight); + hashTable["buttonshadow"] = Color.FromKnownColor(KnownColor.ControlDark); + hashTable["buttontext"] = Color.FromKnownColor(KnownColor.ControlText); + hashTable["captiontext"] = Color.FromKnownColor(KnownColor.ActiveCaptionText); + hashTable["graytext"] = Color.FromKnownColor(KnownColor.GrayText); + hashTable["highlight"] = Color.FromKnownColor(KnownColor.Highlight); + hashTable["highlighttext"] = Color.FromKnownColor(KnownColor.HighlightText); + hashTable["inactiveborder"] = Color.FromKnownColor(KnownColor.InactiveBorder); + hashTable["inactivecaption"] = Color.FromKnownColor(KnownColor.InactiveCaption); + hashTable["inactivecaptiontext"] = Color.FromKnownColor(KnownColor.InactiveCaptionText); + hashTable["infobackground"] = Color.FromKnownColor(KnownColor.Info); + hashTable["infotext"] = Color.FromKnownColor(KnownColor.InfoText); + hashTable["menu"] = Color.FromKnownColor(KnownColor.Menu); + hashTable["menutext"] = Color.FromKnownColor(KnownColor.MenuText); + hashTable["scrollbar"] = Color.FromKnownColor(KnownColor.ScrollBar); + hashTable["threeddarkshadow"] = Color.FromKnownColor(KnownColor.ControlDarkDark); + hashTable["threedface"] = Color.FromKnownColor(KnownColor.Control); + hashTable["threedhighlight"] = Color.FromKnownColor(KnownColor.ControlLight); + hashTable["threedlightshadow"] = Color.FromKnownColor(KnownColor.ControlLightLight); + hashTable["window"] = Color.FromKnownColor(KnownColor.Window); + hashTable["windowframe"] = Color.FromKnownColor(KnownColor.WindowFrame); + hashTable["windowtext"] = Color.FromKnownColor(KnownColor.WindowText); + htmlSystemColorTable = hashTable; + } + } +} diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/GenericArrayTypeConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/GenericArrayTypeConverter.cs new file mode 100644 index 000000000..8e8ea1063 --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/GenericArrayTypeConverter.cs @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Converts the value of an string to and from a Array{T}. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Helpers +{ + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.Linq; + + /// + /// Converts the value of an string to and from a Array{T}. + /// + /// + /// The type to convert from. + /// + public class GenericArrayTypeConverter : GenericListTypeConverter + { + /// + /// Converts the given object to the type of this converter, using the specified context and culture + /// information. + /// + /// + /// An that represents the converted value. + /// + /// + /// An that provides a format context. + /// + /// + /// The to use as the current culture. + /// + /// The to convert. + /// The conversion cannot be performed. + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + object result = base.ConvertFrom(context, culture, value); + IList list = result as IList; + return list != null ? list.ToArray() : result; + } + } +} diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/GenericListTypeConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/GenericListTypeConverter.cs new file mode 100644 index 000000000..caf0bf281 --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/GenericListTypeConverter.cs @@ -0,0 +1,161 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Converts the value of an string to and from a List{T}. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Helpers +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.Linq; + + /// + /// Converts the value of an string to and from a List{T}. + /// + /// + /// The type to convert from. + /// + public class GenericListTypeConverter : TypeConverter + { + /// + /// The type converter. + /// + private readonly TypeConverter typeConverter; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Thrown if no converter exists for the given type. + /// + public GenericListTypeConverter() + { + Type type = typeof(T); + this.typeConverter = TypeDescriptor.GetConverter(type); + if (this.typeConverter == null) + { + throw new InvalidOperationException("No type converter exists for type " + type.FullName); + } + } + + /// + /// Returns whether this converter can convert an object of the given type to the type of this converter, + /// using the specified context. + /// + /// + /// true if this converter can perform the conversion; otherwise, false. + /// + /// + /// An that provides a + /// format context. + /// + /// A that represents the type you want to convert from. + /// + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + { + string[] items = this.GetStringArray(sourceType.ToString()); + return items.Any(); + } + + return base.CanConvertFrom(context, sourceType); + } + + /// + /// Converts the given object to the type of this converter, using the specified context and culture + /// information. + /// + /// + /// An that represents the converted value. + /// + /// + /// An that provides a format context. + /// + /// + /// The to use as the current culture. + /// + /// The to convert. + /// The conversion cannot be performed. + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + string input = value as string; + if (input != null) + { + string[] items = this.GetStringArray(input); + + List result = new List(); + + Array.ForEach( + items, + s => + { + object item = this.typeConverter.ConvertFromInvariantString(s); + if (item != null) + { + result.Add((T)item); + } + }); + + return result; + } + + return base.ConvertFrom(context, culture, value); + } + + /// + /// Converts the given value object to the specified type, using the specified context and culture + /// information. + /// + /// + /// An that represents the converted value. + /// + /// + /// An that provides a format context. + /// + /// + /// A . If null is passed, the current culture is assumed. + /// + /// The to convert. + /// + /// The to convert the parameter to. + /// + /// + /// The parameter is null. + /// + /// The conversion cannot be performed. + /// + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string)) + { + return string.Join(",", (IList)value); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + /// + /// Splits a string by comma to return an array of string values. + /// + /// + /// The input string to split. + /// + /// + /// The array from the comma separated values. + /// + protected string[] GetStringArray(string input) + { + string[] result = input.Split(',').Select(s => s.Trim()).ToArray(); + + return result; + } + } +} diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs new file mode 100644 index 000000000..b8abcc898 --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs @@ -0,0 +1,220 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// The query parameter parser that converts string values to different types. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Helpers +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.ComponentModel; + using System.Drawing; + using System.Globalization; + using System.Linq.Expressions; + + /// + /// The query parameter parser that converts string values to different types. + /// + public class QueryParamParser + { + /// + /// A new instance of the class. + /// with lazy initialization. + /// + private static readonly Lazy Lazy = new Lazy(() => new QueryParamParser()); + + /// + /// The cache for storing created default types. + /// + private static readonly ConcurrentDictionary TypeDefaultsCache = new ConcurrentDictionary(); + + /// + /// Prevents a default instance of the class from being created. + /// + private QueryParamParser() + { + this.AddColorConverters(); + this.AddListConverters(); + this.AddArrayConverters(); + } + + /// + /// Gets the current instance. + /// + public static QueryParamParser Instance + { + get + { + return Lazy.Value; + } + } + + /// + /// Parses the given string value converting it to the given type. + /// + /// + /// The value to parse. + /// + /// + /// The to use as the current culture. + /// If not set will parse using + /// + /// + /// The to convert the string to. + /// + /// + /// The . + /// + public T ParseValue(string value, CultureInfo culture = null) + { + return (T)this.ParseValue(typeof(T), value, culture); + } + + /// + /// Parses the given string value converting it to the given type. + /// + /// + /// The to convert the string to. + /// + /// + /// The value to parse. + /// + /// + /// The to use as the current culture. + /// If not set will parse using + /// + /// + /// The . + /// + public object ParseValue(Type type, string value, CultureInfo culture = null) + { + if (culture == null) + { + culture = CultureInfo.InvariantCulture; + } + + TypeConverter converter = TypeDescriptor.GetConverter(type); + try + { + // ReSharper disable once AssignNullToNotNullAttribute + return converter.ConvertFrom(null, culture, value); + } + catch + { + // Return the default value + return TypeDefaultsCache.GetOrAdd(type, t => this.GetDefaultValue(type)); + } + } + + /// + /// Adds a type converter to the parser. + /// + /// + /// The to add a converter for. + /// + /// + /// The type of to add. + /// + /// + /// The . + /// + public TypeDescriptionProvider AddTypeConverter(Type type, Type converterType) + { + return TypeDescriptor.AddAttributes(type, new TypeConverterAttribute(converterType)); + } + + /// + /// Adds color converters. + /// + private void AddColorConverters() + { + this.AddTypeConverter(typeof(Color), typeof(ExtendedColorTypeConverter)); + } + + /// + /// Adds a selection of default list type converters. + /// + private void AddListConverters() + { + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + + this.AddTypeConverter(typeof(List), typeof(GenericListTypeConverter)); + } + + /// + /// Adds a selection of default array type converters. + /// + private void AddArrayConverters() + { + this.AddTypeConverter(typeof(sbyte[]), typeof(GenericArrayTypeConverter)); + this.AddTypeConverter(typeof(byte[]), typeof(GenericArrayTypeConverter)); + + this.AddTypeConverter(typeof(short[]), typeof(GenericArrayTypeConverter)); + this.AddTypeConverter(typeof(ushort[]), typeof(GenericArrayTypeConverter)); + + this.AddTypeConverter(typeof(int[]), typeof(GenericArrayTypeConverter)); + this.AddTypeConverter(typeof(uint[]), typeof(GenericArrayTypeConverter)); + + this.AddTypeConverter(typeof(long[]), typeof(GenericArrayTypeConverter)); + this.AddTypeConverter(typeof(ulong[]), typeof(GenericArrayTypeConverter)); + + this.AddTypeConverter(typeof(decimal[]), typeof(GenericArrayTypeConverter)); + this.AddTypeConverter(typeof(float[]), typeof(GenericArrayTypeConverter)); + this.AddTypeConverter(typeof(double[]), typeof(GenericArrayTypeConverter)); + + this.AddTypeConverter(typeof(string[]), typeof(GenericArrayTypeConverter)); + } + + /// + /// Returns the default value for the given type. + /// + /// + /// The to return. + /// + /// + /// The representing the default value. + /// + /// + /// Thrown if the given is null. + /// + private object GetDefaultValue(Type type) + { + // Validate parameters. + if (type == null) + { + throw new ArgumentNullException("type"); + } + + // We want an Func which returns the default. + // Create that expression here. + // Have to convert to object. + // The default value, always get what the *code* tells us. + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Default(type), typeof(object))); + + // Compile and return the value. + return e.Compile()(); + } + } +} diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 4eacffb2f..6e11da1ee 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -53,6 +53,10 @@ + + + + diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings b/src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings index ce2072422..bbce28395 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings @@ -1,2 +1,3 @@  - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/src/ImageProcessor.Web/Processors/Alpha.cs b/src/ImageProcessor.Web/Processors/Alpha.cs index eec352608..1c93e1422 100644 --- a/src/ImageProcessor.Web/Processors/Alpha.cs +++ b/src/ImageProcessor.Web/Processors/Alpha.cs @@ -11,7 +11,11 @@ namespace ImageProcessor.Web.Processors { using System; + using System.Collections.Specialized; using System.Text.RegularExpressions; + using System.Web; + + using ImageProcessor.Imaging.Helpers; using ImageProcessor.Processors; using ImageProcessor.Web.Helpers; @@ -23,7 +27,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"alpha=[^&|,]+", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"alpha=[^&]", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -63,25 +67,16 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - int percentage = Math.Abs(CommonParameterParserUtility.ParseIn100Range(match.Value)); - this.Processor.DynamicParameter = percentage; - } - - index += 1; - } + this.SortOrder = match.Index; + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + int percentage = QueryParamParser.Instance.ParseValue(queryCollection["alpha"]); + percentage = ImageMaths.Clamp(percentage, 0, 100); + this.Processor.DynamicParameter = percentage; } return this.SortOrder; diff --git a/src/ImageProcessor.Web/Processors/AutoRotate.cs b/src/ImageProcessor.Web/Processors/AutoRotate.cs index 6fc01dfc6..4f9887b2e 100644 --- a/src/ImageProcessor.Web/Processors/AutoRotate.cs +++ b/src/ImageProcessor.Web/Processors/AutoRotate.cs @@ -69,23 +69,12 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - } - - index += 1; - } + this.SortOrder = match.Index; } return this.SortOrder; diff --git a/src/ImageProcessor.Web/Processors/BackgroundColor.cs b/src/ImageProcessor.Web/Processors/BackgroundColor.cs index e8e88dd55..e12c201c1 100644 --- a/src/ImageProcessor.Web/Processors/BackgroundColor.cs +++ b/src/ImageProcessor.Web/Processors/BackgroundColor.cs @@ -10,7 +10,11 @@ namespace ImageProcessor.Web.Processors { + using System.Collections.Specialized; + using System.Drawing; using System.Text.RegularExpressions; + using System.Web; + using ImageProcessor.Processors; using ImageProcessor.Web.Helpers; @@ -22,7 +26,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"bgcolor(=|-)[^&]+", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"bgcolor=[^&]", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -62,24 +66,15 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - this.Processor.DynamicParameter = CommonParameterParserUtility.ParseColor(match.Value.Split(new[] { '=', '-' })[1]); - } - - index += 1; - } + this.SortOrder = match.Index; + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + Color color = QueryParamParser.Instance.ParseValue(queryCollection["bgcolor"]); + this.Processor.DynamicParameter = color; } return this.SortOrder; diff --git a/src/ImageProcessor.Web/Processors/Brightness.cs b/src/ImageProcessor.Web/Processors/Brightness.cs index abd8528d1..46c36d2a9 100644 --- a/src/ImageProcessor.Web/Processors/Brightness.cs +++ b/src/ImageProcessor.Web/Processors/Brightness.cs @@ -10,7 +10,11 @@ namespace ImageProcessor.Web.Processors { + using System.Collections.Specialized; using System.Text.RegularExpressions; + using System.Web; + + using ImageProcessor.Imaging.Helpers; using ImageProcessor.Processors; using ImageProcessor.Web.Helpers; @@ -22,7 +26,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"brightness=[^&|,]+", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"brightness=[^&]", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -62,25 +66,16 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - int percentage = CommonParameterParserUtility.ParseIn100Range(match.Value); - this.Processor.DynamicParameter = percentage; - } - - index += 1; - } + this.SortOrder = match.Index; + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + int percentage = QueryParamParser.Instance.ParseValue(queryCollection["brightness"]); + percentage = ImageMaths.Clamp(percentage, 0, 100); + this.Processor.DynamicParameter = percentage; } return this.SortOrder; diff --git a/src/ImageProcessor.Web/Processors/Contrast.cs b/src/ImageProcessor.Web/Processors/Contrast.cs index 2b9a501af..137e1f25e 100644 --- a/src/ImageProcessor.Web/Processors/Contrast.cs +++ b/src/ImageProcessor.Web/Processors/Contrast.cs @@ -10,7 +10,11 @@ namespace ImageProcessor.Web.Processors { + using System.Collections.Specialized; using System.Text.RegularExpressions; + using System.Web; + + using ImageProcessor.Imaging.Helpers; using ImageProcessor.Processors; using ImageProcessor.Web.Helpers; @@ -22,7 +26,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"contrast=[^&|,]+", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"contrast=[^&]", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -62,25 +66,16 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - int percentage = CommonParameterParserUtility.ParseIn100Range(match.Value); - this.Processor.DynamicParameter = percentage; - } - - index += 1; - } + this.SortOrder = match.Index; + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + int percentage = QueryParamParser.Instance.ParseValue(queryCollection["contrast"]); + percentage = ImageMaths.Clamp(percentage, 0, 100); + this.Processor.DynamicParameter = percentage; } return this.SortOrder; diff --git a/src/ImageProcessor.Web/Processors/Crop.cs b/src/ImageProcessor.Web/Processors/Crop.cs index 688b66fdd..b1eda64aa 100644 --- a/src/ImageProcessor.Web/Processors/Crop.cs +++ b/src/ImageProcessor.Web/Processors/Crop.cs @@ -10,12 +10,13 @@ namespace ImageProcessor.Web.Processors { - using System.Text; + using System.Collections.Specialized; using System.Text.RegularExpressions; + using System.Web; using ImageProcessor.Imaging; using ImageProcessor.Processors; - using ImageProcessor.Web.Extensions; + using ImageProcessor.Web.Helpers; /// /// Crops an image to the given directions. @@ -24,19 +25,8 @@ namespace ImageProcessor.Web.Processors { /// /// The regular expression to search strings for. - /// /// - private static readonly Regex QueryRegex = new Regex(@"(crop=|cropmode=)[^&]+", RegexOptions.Compiled); - - /// - /// The coordinate regex. - /// - private static readonly Regex CoordinateRegex = new Regex(@"crop=\d+(.\d+)?[,-]\d+(.\d+)?[,-]\d+(.\d+)?[,-]\d+(.\d+)?", RegexOptions.Compiled); - - /// - /// The mode regex. - /// - private static readonly Regex ModeRegex = new Regex(@"cropmode=(pixels|percent)", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"\b(?!entropy)crop\b[=]", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -70,99 +60,30 @@ namespace ImageProcessor.Web.Processors /// /// The position in the original string where the first character of the captured substring was found. /// - /// The query string to search. + /// + /// 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; + Match match = this.RegexPattern.Match(queryString); - // First merge the matches so we can parse . - StringBuilder stringBuilder = new StringBuilder(); - - foreach (Match match in this.RegexPattern.Matches(queryString)) - { - // Match but ignore entropy - if (match.Success && !queryString.ToUpperInvariant().Contains("ENTROPYCROP=")) - { - 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) + if (match.Success) { - // Match syntax - string toParse = stringBuilder.ToString(); - - float[] coordinates = this.ParseCoordinates(toParse); - CropMode cropMode = this.ParseMode(toParse); + this.SortOrder = match.Index; + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + float[] coordinates = QueryParamParser.Instance.ParseValue(queryCollection["crop"]); + // Default CropMode.Pixels will be returned. + CropMode cropMode = QueryParamParser.Instance.ParseValue(queryCollection["cropmode"]); CropLayer cropLayer = new CropLayer(coordinates[0], coordinates[1], coordinates[2], coordinates[3], cropMode); this.Processor.DynamicParameter = cropLayer; } return this.SortOrder; } - - /// - /// Returns the correct for the given string. - /// - /// - /// The input string containing the value to parse. - /// - /// - /// The correct . - /// - private CropMode ParseMode(string input) - { - foreach (Match match in ModeRegex.Matches(input)) - { - // Split on = - string mode = match.Value.Split('=')[1]; - - switch (mode) - { - case "percent": - return CropMode.Percentage; - case "pixels": - return CropMode.Pixels; - } - } - - return CropMode.Pixels; - } - - /// - /// Returns the crop coordinates for the given string. - /// - /// - /// The input string containing the value to parse. - /// - /// - /// The array containing the crop coordinates. - /// - private float[] ParseCoordinates(string input) - { - float[] floats = { }; - - foreach (Match match in CoordinateRegex.Matches(input)) - { - floats = match.Value.ToPositiveFloatArray(); - } - - return floats; - } } } diff --git a/src/ImageProcessor.Web/Processors/DetectEdges.cs b/src/ImageProcessor.Web/Processors/DetectEdges.cs index 3a65730ad..5dd2312e7 100644 --- a/src/ImageProcessor.Web/Processors/DetectEdges.cs +++ b/src/ImageProcessor.Web/Processors/DetectEdges.cs @@ -12,15 +12,18 @@ namespace ImageProcessor.Web.Processors { using System; using System.Collections.Generic; + using System.Collections.Specialized; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; + using System.Web; using System.Web.Compilation; using ImageProcessor.Common.Extensions; using ImageProcessor.Imaging.Filters.EdgeDetection; using ImageProcessor.Processors; + using ImageProcessor.Web.Helpers; /// /// Produces an image with the detected edges highlighted. @@ -32,11 +35,6 @@ namespace ImageProcessor.Web.Processors /// private static readonly Regex QueryRegex = BuildRegex(); - /// - /// The regular expression to search strings for the greyscale attribute. - /// - private static readonly Regex GreyscaleRegex = new Regex(@"greyscale=false", RegexOptions.Compiled); - /// /// The edge detectors. /// @@ -85,36 +83,15 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - // 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(queryString); - } - - index += 1; - } - } - - if (this.SortOrder < int.MaxValue) + if (match.Success) { - // Match syntax - string toParse = stringBuilder.ToString(); - IEdgeFilter filter = this.ParseFilter(toParse); - bool greyscale = !GreyscaleRegex.IsMatch(toParse); - + this.SortOrder = match.Index; + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + IEdgeFilter filter = (IEdgeFilter)detectors[queryCollection["detectedges"]]; + bool greyscale = QueryParamParser.Instance.ParseValue(queryCollection["greyscale"]); this.Processor.DynamicParameter = new Tuple(filter, greyscale); } @@ -148,19 +125,5 @@ namespace ImageProcessor.Web.Processors return new Regex(stringBuilder.ToString(), RegexOptions.IgnoreCase); } - - /// - /// Parses the input string to return the correct . - /// - /// - /// The identifier. - /// - /// - /// The . - /// - private IEdgeFilter ParseFilter(string identifier) - { - return (IEdgeFilter)detectors[this.RegexPattern.Match(identifier).Value.Split('=')[1]]; - } } } diff --git a/src/ImageProcessor.Web/Processors/EntropyCrop.cs b/src/ImageProcessor.Web/Processors/EntropyCrop.cs index ac1cafb0c..1db05e71c 100644 --- a/src/ImageProcessor.Web/Processors/EntropyCrop.cs +++ b/src/ImageProcessor.Web/Processors/EntropyCrop.cs @@ -10,11 +10,13 @@ namespace ImageProcessor.Web.Processors { - using System.Globalization; + using System.Collections.Specialized; using System.Text.RegularExpressions; + using System.Web; using ImageProcessor.Common.Extensions; using ImageProcessor.Processors; + using ImageProcessor.Web.Helpers; /// /// Performs a crop on an image to the area of greatest entropy. @@ -24,7 +26,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"entropycrop=(\d+)[^&]+|entropycrop", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"entropycrop(=)?[^&]*", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -64,56 +66,21 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - byte threshold = this.ParseThreshold(match.Value); - this.Processor.DynamicParameter = threshold; - } + this.SortOrder = match.Index; + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + byte threshold = QueryParamParser.Instance.ParseValue(queryCollection["entropycrop"]); - index += 1; - } + // Fallback to the default if 0. + this.Processor.DynamicParameter = threshold > 0 ? threshold : (byte)128; } return this.SortOrder; } - - /// - /// 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 byte ParseThreshold(string input) - { - foreach (Match match in QueryRegex.Matches(input)) - { - if (!match.Value.Contains("=")) - { - continue; - } - - // Split on threshold - int threshold; - int.TryParse(match.Value.Split('=')[1], NumberStyles.Any, CultureInfo.InvariantCulture, out threshold); - return threshold.ToByte(); - } - - // No threshold - matches the EntropyCrop default. - return 128; - } } } diff --git a/src/ImageProcessor.Web/Processors/Filter.cs b/src/ImageProcessor.Web/Processors/Filter.cs index f9c8fec7c..89e7899c2 100644 --- a/src/ImageProcessor.Web/Processors/Filter.cs +++ b/src/ImageProcessor.Web/Processors/Filter.cs @@ -11,6 +11,7 @@ namespace ImageProcessor.Web.Processors { using System; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -29,6 +30,12 @@ namespace ImageProcessor.Web.Processors /// private static readonly Regex QueryRegex = BuildRegex(); + /// + /// The filter cache. + /// + private static readonly ConcurrentDictionary FilterCache + = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// /// Initializes a new instance of the class. /// @@ -72,24 +79,13 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - this.Processor.DynamicParameter = this.ParseFilter(match.Value.Split('=')[1]); - } - - index += 1; - } + this.SortOrder = match.Index; + this.Processor.DynamicParameter = this.ParseFilter(match.Value.Split('=')[1]); } return this.SortOrder; @@ -144,14 +140,18 @@ namespace ImageProcessor.Web.Processors private IMatrixFilter ParseFilter(string identifier) { const BindingFlags Flags = BindingFlags.Public | BindingFlags.Static; - - Type type = typeof(MatrixFilters); - PropertyInfo filter = - type.GetProperties(Flags) - .Where(p => p.PropertyType.IsAssignableFrom(typeof(IMatrixFilter))) - .First(p => p.Name.Equals(identifier, StringComparison.InvariantCultureIgnoreCase)); - - return filter.GetValue(null, null) as IMatrixFilter; + return FilterCache.GetOrAdd( + identifier, + f => + { + Type type = typeof(MatrixFilters); + PropertyInfo filter = + type.GetProperties(Flags) + .Where(p => p.PropertyType.IsAssignableFrom(typeof(IMatrixFilter))) + .First(p => p.Name.Equals(identifier, StringComparison.InvariantCultureIgnoreCase)); + + return filter.GetValue(null, null) as IMatrixFilter; + }); } } } diff --git a/src/ImageProcessor.Web/Processors/Flip.cs b/src/ImageProcessor.Web/Processors/Flip.cs index 085962030..0e51ba4ca 100644 --- a/src/ImageProcessor.Web/Processors/Flip.cs +++ b/src/ImageProcessor.Web/Processors/Flip.cs @@ -64,36 +64,27 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - string direction = match.Value.Split('=')[1]; + this.SortOrder = match.Index; - switch (direction) - { - case "horizontal": - this.Processor.DynamicParameter = RotateFlipType.RotateNoneFlipX; - break; - case "vertical": - this.Processor.DynamicParameter = RotateFlipType.RotateNoneFlipY; - break; - default: - this.Processor.DynamicParameter = RotateFlipType.RotateNoneFlipXY; - break; - } - } + // We do not use the full enum so use switch. + string direction = match.Value.Split('=')[1]; - index += 1; + switch (direction) + { + case "horizontal": + this.Processor.DynamicParameter = RotateFlipType.RotateNoneFlipX; + break; + case "vertical": + this.Processor.DynamicParameter = RotateFlipType.RotateNoneFlipY; + break; + default: + this.Processor.DynamicParameter = RotateFlipType.RotateNoneFlipXY; + break; } } diff --git a/src/ImageProcessor.Web/Processors/Format.cs b/src/ImageProcessor.Web/Processors/Format.cs index 7b57d7ddd..35cf89906 100644 --- a/src/ImageProcessor.Web/Processors/Format.cs +++ b/src/ImageProcessor.Web/Processors/Format.cs @@ -70,28 +70,16 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) + ISupportedImageFormat format = this.ParseFormat(match.Value.Split('=')[1]); + if (format != null) { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - - ISupportedImageFormat format = this.ParseFormat(match.Value.Split('=')[1]); - if (format != null) - { - this.Processor.DynamicParameter = format; - } - } - - index += 1; + this.SortOrder = match.Index; + this.Processor.DynamicParameter = format; } } @@ -108,7 +96,7 @@ namespace ImageProcessor.Web.Processors { StringBuilder stringBuilder = new StringBuilder(); - // png8 is a special case for determining indexed pngs. + // png8 is a special case for determining indexed png's. stringBuilder.Append("format=(png8"); foreach (ISupportedImageFormat imageFormat in ImageProcessorBootstrapper.Instance.SupportedImageFormats) { diff --git a/src/ImageProcessor.Web/Processors/GaussianBlur.cs b/src/ImageProcessor.Web/Processors/GaussianBlur.cs index d6e66232b..cf06185c6 100644 --- a/src/ImageProcessor.Web/Processors/GaussianBlur.cs +++ b/src/ImageProcessor.Web/Processors/GaussianBlur.cs @@ -10,8 +10,13 @@ namespace ImageProcessor.Web.Processors { + using System.Collections.Specialized; using System.Globalization; using System.Text.RegularExpressions; + using System.Web; + + using ImageProcessor.Imaging; + using ImageProcessor.Imaging.Helpers; using ImageProcessor.Processors; using ImageProcessor.Web.Helpers; @@ -23,7 +28,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"blur=[^&]+", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"blur=[^&]", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -65,34 +70,33 @@ namespace ImageProcessor.Web.Processors /// 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)) + Match match = this.RegexPattern.Match(queryString); + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; + this.SortOrder = match.Index; + + int maxSize; + double maxSigma; + int maxThreshold; + + int.TryParse(this.Processor.Settings["MaxSize"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxSize); + double.TryParse(this.Processor.Settings["MaxSigma"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxSigma); + int.TryParse(this.Processor.Settings["MaxThreshold"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxThreshold); + + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + int size = QueryParamParser.Instance.ParseValue(queryCollection["blur"]); - // Normalise and set the variables. - int maxSize; - double maxSigma; - int maxThreshold; + // Fall back to default sigma. + double sigma = queryCollection["sigma"] != null ? QueryParamParser.Instance.ParseValue(queryCollection["sigma"]) : 1.4d; + int threshold = QueryParamParser.Instance.ParseValue(queryCollection["threshold"]); - int.TryParse(this.Processor.Settings["MaxSize"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxSize); - double.TryParse(this.Processor.Settings["MaxSigma"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxSigma); - int.TryParse(this.Processor.Settings["MaxThreshold"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxThreshold); - - this.Processor.DynamicParameter = CommonParameterParserUtility.ParseGaussianLayer(queryString, maxSize, maxSigma, maxThreshold); - } + // Normalize and set the variables. + size = ImageMaths.Clamp(size, 0, maxSize); + sigma = ImageMaths.Clamp(sigma, 0, maxSigma); + threshold = ImageMaths.Clamp(threshold, 0, maxThreshold); - index += 1; - } + this.Processor.DynamicParameter = new GaussianLayer(size, sigma, threshold); } return this.SortOrder; diff --git a/src/ImageProcessor.Web/Processors/GaussianSharpen.cs b/src/ImageProcessor.Web/Processors/GaussianSharpen.cs index 67adf7b3d..a23d915ec 100644 --- a/src/ImageProcessor.Web/Processors/GaussianSharpen.cs +++ b/src/ImageProcessor.Web/Processors/GaussianSharpen.cs @@ -10,8 +10,13 @@ namespace ImageProcessor.Web.Processors { + using System.Collections.Specialized; using System.Globalization; using System.Text.RegularExpressions; + using System.Web; + + using ImageProcessor.Imaging; + using ImageProcessor.Imaging.Helpers; using ImageProcessor.Processors; using ImageProcessor.Web.Helpers; @@ -23,7 +28,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"sharpen=[^&]+", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"sharpen=[^&]", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -65,34 +70,33 @@ namespace ImageProcessor.Web.Processors /// 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)) + Match match = this.RegexPattern.Match(queryString); + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; + this.SortOrder = match.Index; + + int maxSize; + double maxSigma; + int maxThreshold; + + int.TryParse(this.Processor.Settings["MaxSize"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxSize); + double.TryParse(this.Processor.Settings["MaxSigma"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxSigma); + int.TryParse(this.Processor.Settings["MaxThreshold"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxThreshold); + + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); + int size = QueryParamParser.Instance.ParseValue(queryCollection["sharpen"]); - // Normalise and set the variables. - int maxSize; - double maxSigma; - int maxThreshold; + // Fall back to default sigma. + double sigma = queryCollection["sigma"] != null ? QueryParamParser.Instance.ParseValue(queryCollection["sigma"]) : 1.4d; + int threshold = QueryParamParser.Instance.ParseValue(queryCollection["threshold"]); - int.TryParse(this.Processor.Settings["MaxSize"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxSize); - double.TryParse(this.Processor.Settings["MaxSigma"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxSigma); - int.TryParse(this.Processor.Settings["MaxThreshold"], NumberStyles.Any, CultureInfo.InvariantCulture, out maxThreshold); - - this.Processor.DynamicParameter = CommonParameterParserUtility.ParseGaussianLayer(queryString, maxSize, maxSigma, maxThreshold); - } + // Normalize and set the variables. + size = ImageMaths.Clamp(size, 0, maxSize); + sigma = ImageMaths.Clamp(sigma, 0, maxSigma); + threshold = ImageMaths.Clamp(threshold, 0, maxThreshold); - index += 1; - } + this.Processor.DynamicParameter = new GaussianLayer(size, sigma, threshold); } return this.SortOrder; diff --git a/src/ImageProcessor.Web/Processors/Halftone.cs b/src/ImageProcessor.Web/Processors/Halftone.cs index 7a824ac0a..11276d60a 100644 --- a/src/ImageProcessor.Web/Processors/Halftone.cs +++ b/src/ImageProcessor.Web/Processors/Halftone.cs @@ -62,25 +62,14 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - bool comicMode = match.Value.Contains("comic"); - this.Processor.DynamicParameter = comicMode; - } - - index += 1; - } + this.SortOrder = match.Index; + bool comicMode = match.Value.Contains("comic"); + this.Processor.DynamicParameter = comicMode; } return this.SortOrder; diff --git a/src/ImageProcessor.Web/Processors/Hue.cs b/src/ImageProcessor.Web/Processors/Hue.cs index a92f809f6..43bdbae00 100644 --- a/src/ImageProcessor.Web/Processors/Hue.cs +++ b/src/ImageProcessor.Web/Processors/Hue.cs @@ -11,11 +11,15 @@ namespace ImageProcessor.Web.Processors { using System; + using System.Collections.Specialized; using System.Globalization; using System.Text; using System.Text.RegularExpressions; + using System.Web; + using ImageProcessor.Imaging.Helpers; using ImageProcessor.Processors; + using ImageProcessor.Web.Helpers; /// /// Encapsulates methods to adjust the hue component of an image. @@ -25,17 +29,7 @@ namespace ImageProcessor.Web.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"(hue=|hue.\w+=)[^&]+", RegexOptions.Compiled); - - /// - /// The hue regex. - /// - private static readonly Regex HueRegex = new Regex(@"hue=\d+", RegexOptions.Compiled); - - /// - /// The rotate regex. - /// - private static readonly Regex RotateRegex = new Regex(@"hue.rotate=true", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"hue=[^&]", RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -75,61 +69,22 @@ namespace ImageProcessor.Web.Processors /// public int MatchRegexIndex(string queryString) { - int index = 0; - - // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + Match match = this.RegexPattern.Match(queryString); - // First merge the matches so we can parse . - StringBuilder stringBuilder = new StringBuilder(); - - foreach (Match match in this.RegexPattern.Matches(queryString)) + if (match.Success) { - if (match.Success) - { - if (index == 0) - { - // Set the index on the first instance only. - this.SortOrder = match.Index; - } + this.SortOrder = match.Index; + NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString); - stringBuilder.Append(match.Value); + int degrees = QueryParamParser.Instance.ParseValue(queryCollection["hue"]); + bool rotate = QueryParamParser.Instance.ParseValue(queryCollection["hue.rotate"]); + degrees = ImageMaths.Clamp(degrees, 0, 360); - index += 1; - } - } - - if (this.SortOrder < int.MaxValue) - { - // Match syntax - string toParse = stringBuilder.ToString(); - int degrees = this.ParseDegrees(toParse); - bool rotate = RotateRegex.Match(toParse).Success; this.Processor.DynamicParameter = new Tuple(degrees, rotate); } return this.SortOrder; } - - /// - /// Returns the angle to alter the hue. - /// - /// - /// The input containing the value to parse. - /// - /// - /// The representing the angle. - /// - public int ParseDegrees(string input) - { - int degrees = 0; - - foreach (Match match in HueRegex.Matches(input)) - { - degrees = int.Parse(match.Value.Split('=')[1], CultureInfo.InvariantCulture); - } - - return Math.Max(0, Math.Min(360, degrees)); - } } } diff --git a/src/ImageProcessor/Common/Extensions/AssemblyExtensions.cs b/src/ImageProcessor/Common/Extensions/AssemblyExtensions.cs index e37480ff0..c5c0883e8 100644 --- a/src/ImageProcessor/Common/Extensions/AssemblyExtensions.cs +++ b/src/ImageProcessor/Common/Extensions/AssemblyExtensions.cs @@ -12,6 +12,7 @@ namespace ImageProcessor.Common.Extensions { using System; using System.Collections.Generic; + using System.IO; using System.Linq; using System.Reflection; @@ -46,5 +47,35 @@ namespace ImageProcessor.Common.Extensions return ex.Types.Where(t => t != null); } } + + /// + /// Returns the identifying the file used to load the assembly + /// + /// + /// The to get the name from. + /// + /// The + public static FileInfo GetAssemblyFile(this Assembly assembly) + { + string codeBase = assembly.CodeBase; + Uri uri = new Uri(codeBase); + string path = uri.LocalPath; + return new FileInfo(path); + } + + /// + /// Returns the identifying the file used to load the assembly + /// + /// + /// The to get the name from. + /// + /// The + public static FileInfo GetAssemblyFile(this AssemblyName assemblyName) + { + var codeBase = assemblyName.CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + return new FileInfo(path); + } } } \ No newline at end of file diff --git a/src/ImageProcessor/Common/Helpers/IOHelper.cs b/src/ImageProcessor/Common/Helpers/IOHelper.cs new file mode 100644 index 000000000..8e31874e0 --- /dev/null +++ b/src/ImageProcessor/Common/Helpers/IOHelper.cs @@ -0,0 +1,136 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Provides helper method for traversing the file system. +// +// Adapted from identically named class within +// +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Common.Helpers +{ + using System; + using System.Globalization; + using System.IO; + using System.Reflection; + + using ImageProcessor.Common.Extensions; + + /// + /// Provides helper method for traversing the file system. + /// + /// Adapted from identically named class within + /// + /// + internal class IOHelper + { + /// + /// The root directory. + /// + private static string rootDirectory; + + /// + /// Maps a virtual path to a physical path. + /// + /// + /// The virtual path to map. + /// + /// + /// The representing the physical path. + /// + public static string MapPath(string virtualPath) + { + // Check if the path is already mapped + // UNC Paths start with "\\". If the site is running off a network drive mapped paths + // will look like "\\Whatever\Boo\Bar" + if ((virtualPath.Length >= 2 && virtualPath[1] == Path.VolumeSeparatorChar) + || virtualPath.StartsWith(@"\\")) + { + return virtualPath; + } + + char separator = Path.DirectorySeparatorChar; + string root = GetRootDirectorySafe(); + string newPath = virtualPath.TrimStart('~', '/').Replace('/', separator); + return root + separator.ToString(CultureInfo.InvariantCulture) + newPath; + } + + /// + /// Gets the root directory bin folder for the currently running application. + /// + /// + /// The representing the root directory bin folder. + /// + public static string GetRootDirectoryBinFolder() + { + string binFolder = string.Empty; + if (string.IsNullOrEmpty(rootDirectory)) + { + DirectoryInfo directoryInfo = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + if (directoryInfo != null) + { + binFolder = directoryInfo.FullName; + } + + return binFolder; + } + + binFolder = Path.Combine(GetRootDirectorySafe(), "bin"); + +#if DEBUG + string debugFolder = Path.Combine(binFolder, "debug"); + if (Directory.Exists(debugFolder)) + { + return debugFolder; + } +#endif + string releaseFolder = Path.Combine(binFolder, "release"); + if (Directory.Exists(releaseFolder)) + { + return releaseFolder; + } + + if (Directory.Exists(binFolder)) + { + return binFolder; + } + + return rootDirectory; + } + + /// + /// Returns the path to the root of the application, by getting the path to where the assembly where this + /// method is included is present, then traversing until it's past the /bin directory. I.e. this makes it work + /// even if the assembly is in a /bin/debug or /bin/release folder + /// + /// + /// The representing the root path of the currently running application. + internal static string GetRootDirectorySafe() + { + if (string.IsNullOrEmpty(rootDirectory) == false) + { + return rootDirectory; + } + + string codeBase = Assembly.GetExecutingAssembly().CodeBase; + Uri uri = new Uri(codeBase); + string path = uri.LocalPath; + string baseDirectory = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(baseDirectory)) + { + throw new Exception( + "No root directory could be resolved. Please ensure that your solution is correctly configured."); + } + + rootDirectory = baseDirectory.Contains("bin") + ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) + : baseDirectory; + + return rootDirectory; + } + } +} diff --git a/src/ImageProcessor/Common/Helpers/TypeFinder.cs b/src/ImageProcessor/Common/Helpers/TypeFinder.cs new file mode 100644 index 000000000..bebd004f3 --- /dev/null +++ b/src/ImageProcessor/Common/Helpers/TypeFinder.cs @@ -0,0 +1,288 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// A utility class to find all classes of a certain type by reflection in the current bin folder +// of the web application. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Common.Helpers +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Security; + using System.Threading; + + using ImageProcessor.Common.Extensions; + + /// + /// A utility class to find all classes of a certain type by reflection in the current bin folder + /// of the web application. + /// + /// + /// Adapted from identically named class within + /// + internal static class TypeFinder + { + /// + /// The local filtered assembly cache. + /// + private static readonly HashSet LocalFilteredAssemblyCache = new HashSet(); + + /// + /// The local filtered assembly cache locker. + /// + private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim(); + + /// + /// The reader-writer lock implementation. + /// + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + + /// + /// An assembly filter collection to filter out known types that definitely don't contain types + /// we'd like to find or plugins. + /// Umbraco uses ImageProcessor in it's core so add common exclusion files from that. + /// + /// + /// NOTE the comma versus period... comma delimits the name in an Assembly FullName property so + /// if it ends with comma then its an exact name match. + /// + private static readonly string[] KnownAssemblyExclusionFilter = + { + "mscorlib,", "System.", "Antlr3.", "Autofac.", + "Autofac,", "Castle.", "ClientDependency.", + "DataAnnotationsExtensions.", + "DataAnnotationsExtensions,", "Dynamic,", + "HtmlDiff,", "Iesi.Collections,", "log4net,", + "Microsoft.", "Newtonsoft.", "NHibernate.", + "NHibernate,", "NuGet.", "RouteDebugger,", + "SqlCE4Umbraco,", "umbraco.datalayer,", + "umbraco.interfaces,", + "umbraco.webservices", "Lucene.", "Examine,", + "Examine.", "ServiceStack.", "MySql.", + "HtmlAgilityPack.", "TidyNet.", + "ICSharpCode.", "CookComputing.", + "AutoMapper,", "AutoMapper.", + "AzureDirectory,", "itextsharp,", + "UrlRewritingNet.", "HtmlAgilityPack,", + "MiniProfiler,", "Moq,", "nunit.framework,", + "TidyNet,", "WebDriver," + }; + + /// + /// A collection of all assemblies. + /// + private static HashSet allAssemblies; + + /// + /// The bin folder assemblies. + /// + private static HashSet binFolderAssemblies; + + /// + /// Lazily loads a reference to all assemblies and only local assemblies. + /// This is a modified version of: + /// + /// + /// + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// loaded in the CLR, not all assemblies. + /// See these threads: + /// + /// + /// + /// + /// + /// The . + /// + internal static HashSet GetAllAssemblies() + { + using (UpgradeableReadLock locker = new UpgradeableReadLock(Locker)) + { + if (allAssemblies == null) + { + locker.UpgradeToWriteLock(); + + try + { + // NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + string binFolder = IOHelper.GetRootDirectoryBinFolder(); + List binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + HashSet assemblies = new HashSet(); + + foreach (string file in binAssemblyFiles) + { + try + { + AssemblyName assemblyName = AssemblyName.GetAssemblyName(file); + assemblies.Add(Assembly.Load(assemblyName)); + } + catch (Exception ex) + { + if (ex is SecurityException || ex is BadImageFormatException) + { + // Swallow exception but allow debugging. + Debug.WriteLine(ex.Message); + } + else + { + throw; + } + } + } + + // If for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. + if (!assemblies.Any()) + { + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(assembly); + } + } + + // Here we are trying to get the App_Code assembly + string[] fileExtensions = { ".cs", ".vb" }; + DirectoryInfo appCodeFolder = new DirectoryInfo(IOHelper.MapPath("~/App_code")); + + // Check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) + { + Assembly appCodeAssembly = Assembly.Load("App_Code"); + if (!assemblies.Contains(appCodeAssembly)) + { + assemblies.Add(appCodeAssembly); + } + } + + // Now set the allAssemblies + allAssemblies = new HashSet(assemblies); + } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + { + throw; + } + + binFolderAssemblies = allAssemblies; + } + } + + return allAssemblies; + } + } + + /// + /// Returns only assemblies found in the bin folder that have been loaded into the app domain. + /// + /// + /// The collection of assemblies. + /// + internal static HashSet GetBinAssemblies() + { + if (binFolderAssemblies == null) + { + using (new WriteLock(Locker)) + { + Assembly[] assemblies = GetAssembliesWithKnownExclusions().ToArray(); + DirectoryInfo binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + // ReSharper disable once PossibleNullReferenceException + List binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + IEnumerable domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName); + HashSet safeDomainAssemblies = new HashSet(); + HashSet binFolderAssemblyList = new HashSet(); + + foreach (Assembly assembly in assemblies) + { + safeDomainAssemblies.Add(assembly); + } + + foreach (AssemblyName assemblyName in domainAssemblyNames) + { + Assembly foundAssembly = safeDomainAssemblies + .FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile()); + + if (foundAssembly != null) + { + binFolderAssemblyList.Add(foundAssembly); + } + } + + binFolderAssemblies = new HashSet(binFolderAssemblyList); + } + } + + return binFolderAssemblies; + } + + /// + /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan + /// and excluding the ones passed in and excluding the exclusion list filter, the results of this are + /// cached for performance reasons. + /// + /// + /// An to exclude. + /// + /// The collection of local assemblies. + internal static HashSet GetAssembliesWithKnownExclusions( + IEnumerable excludeFromResults = null) + { + using (UpgradeableReadLock locker = new UpgradeableReadLock(LocalFilteredAssemblyCacheLocker)) + { + if (LocalFilteredAssemblyCache.Any()) + { + return LocalFilteredAssemblyCache; + } + + locker.UpgradeToWriteLock(); + + IEnumerable assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); + foreach (Assembly assembly in assemblies) + { + LocalFilteredAssemblyCache.Add(assembly); + } + + return LocalFilteredAssemblyCache; + } + } + + /// + /// Return a distinct list of found local Assemblies and excluding the ones passed in and excluding the exclusion list filter + /// + /// + /// An to exclude. + /// + /// + /// An array containing exclusion filters. + /// + /// The collection of filtered local assemblies. + private static IEnumerable GetFilteredAssemblies( + IEnumerable excludeFromResults = null, + string[] exclusionFilter = null) + { + if (excludeFromResults == null) + { + excludeFromResults = new HashSet(); + } + + if (exclusionFilter == null) + { + exclusionFilter = new string[] { }; + } + + return GetAllAssemblies() + .Where(x => !excludeFromResults.Contains(x) + && !x.GlobalAssemblyCache + && !exclusionFilter.Any(f => x.FullName.StartsWith(f))); + } + } +} diff --git a/src/ImageProcessor/Common/Helpers/UpgradeableReadLock.cs b/src/ImageProcessor/Common/Helpers/UpgradeableReadLock.cs new file mode 100644 index 000000000..fca1fb407 --- /dev/null +++ b/src/ImageProcessor/Common/Helpers/UpgradeableReadLock.cs @@ -0,0 +1,126 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Provides a convenience methodology for implementing upgradeable locked access to resources. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Common.Helpers +{ + using System; + using System.Threading; + + /// + /// Provides a convenience methodology for implementing upgradeable locked access to resources. + /// + /// + /// Adapted from identically named class within + /// + internal sealed class UpgradeableReadLock : IDisposable + { + /// + /// The locker to lock against. + /// + private readonly ReaderWriterLockSlim locker; + + /// + /// A value indicating whether the locker has been upgraded to a writeable lock. + /// + private bool upgraded; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The locker. + /// + public UpgradeableReadLock(ReaderWriterLockSlim locker) + { + this.locker = locker; + this.locker.EnterUpgradeableReadLock(); + } + + /// + /// Finalizes an instance of the class. + /// + /// + /// Use C# destructor syntax for finalization code. + /// This destructor will run only if the Dispose method + /// does not get called. + /// It gives your base class the opportunity to finalize. + /// Do not provide destructors in types derived from this class. + /// + ~UpgradeableReadLock() + { + // Do not re-create Dispose clean-up code here. + // Calling Dispose(false) is optimal in terms of + // readability and maintainability. + this.Dispose(false); + } + + /// + /// Tries to enter the locker in write mode. + /// + public void UpgradeToWriteLock() + { + this.locker.EnterWriteLock(); + this.upgraded = true; + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + public void Dispose() + { + this.Dispose(true); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + private void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + if (this.upgraded) + { + this.locker.ExitWriteLock(); + } + + this.locker.ExitUpgradeableReadLock(); + } + + // Note disposing is done. + this.isDisposed = true; + } + } +} diff --git a/src/ImageProcessor/Common/Helpers/WriteLock.cs b/src/ImageProcessor/Common/Helpers/WriteLock.cs new file mode 100644 index 000000000..7037fde9e --- /dev/null +++ b/src/ImageProcessor/Common/Helpers/WriteLock.cs @@ -0,0 +1,107 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Provides a convenience methodology for implementing writeable locked access to resources. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Common.Helpers +{ + using System; + using System.Threading; + + /// + /// Provides a convenience methodology for implementing writable locked access to resources. + /// + /// + /// Adapted from identically named class within + /// + internal sealed class WriteLock : IDisposable + { + /// + /// The locker to lock against. + /// + private readonly ReaderWriterLockSlim locker; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The locker. + /// + public WriteLock(ReaderWriterLockSlim locker) + { + this.locker = locker; + this.locker.EnterWriteLock(); + } + + /// + /// Finalizes an instance of the class. + /// + /// + /// Use C# destructor syntax for finalization code. + /// This destructor will run only if the Dispose method + /// does not get called. + /// It gives your base class the opportunity to finalize. + /// Do not provide destructors in types derived from this class. + /// + ~WriteLock() + { + // Do not re-create Dispose clean-up code here. + // Calling Dispose(false) is optimal in terms of + // readability and maintainability. + this.Dispose(false); + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + public void Dispose() + { + this.Dispose(true); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + private void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + this.locker.ExitWriteLock(); + } + + // Note disposing is done. + this.isDisposed = true; + } + } +} diff --git a/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs b/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs index 89b4ddee1..30ff475bb 100644 --- a/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs +++ b/src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs @@ -4,25 +4,22 @@ // Licensed under the Apache License, Version 2.0. // // -// The ImageProcessor bootstrapper. +// The ImageProcessor bootstrapper containing initialization code for extending ImageProcessor. // // -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Configuration { using System; - using System.Collections; using System.Collections.Generic; - using System.Diagnostics; - using System.IO; using System.Linq; - using System.Reflection; using ImageProcessor.Common.Extensions; + using ImageProcessor.Common.Helpers; using ImageProcessor.Imaging.Formats; /// - /// The ImageProcessor bootstrapper. + /// The ImageProcessor bootstrapper containing initialization code for extending ImageProcessor. /// public class ImageProcessorBootstrapper { @@ -68,93 +65,18 @@ namespace ImageProcessor.Configuration /// private void LoadSupportedImageFormats() { + Type type = typeof(ISupportedImageFormat); if (this.SupportedImageFormats == null) { - Type type = typeof(ISupportedImageFormat); - - // Get any referenced but not used assemblies. - Assembly executingAssembly = Assembly.GetExecutingAssembly(); - string targetBasePath = Path.GetDirectoryName(new Uri(executingAssembly.Location).LocalPath); - - // ReSharper disable once AssignNullToNotNullAttribute - FileInfo[] files = new DirectoryInfo(targetBasePath).GetFiles("*.dll", SearchOption.AllDirectories); - - HashSet found = new HashSet(); - foreach (FileInfo fileInfo in files) - { - try - { - AssemblyName assemblyName = AssemblyName.GetAssemblyName(fileInfo.FullName); - - if (!AppDomain.CurrentDomain.GetAssemblies() - .Any(a => AssemblyName.ReferenceMatchesDefinition(assemblyName, a.GetName()))) - { - // In a web app, this assembly will automatically be bound from the - // Asp.Net Temporary folder from where the site actually runs. - Assembly.Load(assemblyName); - this.LoadReferencedAssemblies(found, Assembly.Load(assemblyName)); - } - } - catch (Exception ex) - { - // Log the exception for debugging only. There could be any old junk - // thrown in to the bin folder by someone else. - Debug.WriteLine(ex.Message); - } - } - - List availableTypes = AppDomain.CurrentDomain - .GetAssemblies() - .SelectMany(a => a.GetLoadableTypes()) - .Where(t => type.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract) - .ToList(); - - this.SupportedImageFormats = availableTypes - .Select(f => (Activator.CreateInstance(f) as ISupportedImageFormat)).ToList(); - } - } - - /// - /// Loads any referenced assemblies into the current application domain. - /// - /// - /// The collection containing the name of already found assemblies. - /// - /// - /// The assembly to load from. - /// - private void LoadReferencedAssemblies(HashSet found, Assembly assembly) - { - // Used to avoid duplicates - ArrayList results = new ArrayList(); - - // Resulting info - Stack stack = new Stack(); - - // Stack of names - // Store root assembly (level 0) directly into results list - stack.Push(assembly.ToString()); - - // Do a pre-order, non-recursive traversal - while (stack.Count > 0) - { - string info = (string)stack.Pop(); - - // Get next assembly - if (!found.Contains(info)) - { - found.Add(info); - results.Add(info); - - // Store it to results ArrayList - Assembly child = Assembly.Load(info); - AssemblyName[] subchild = child.GetReferencedAssemblies(); - - for (int i = subchild.Length - 1; i >= 0; --i) - { - stack.Push(subchild[i].ToString()); - } - } + List availableTypes = + TypeFinder.GetAssembliesWithKnownExclusions() + .SelectMany(a => a.GetLoadableTypes()) + .Where(t => type.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract) + .ToList(); + + this.SupportedImageFormats = + availableTypes.Select(f => (Activator.CreateInstance(f) as ISupportedImageFormat)) + .ToList(); } } } diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index ffc86d7a8..a2dce12c9 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -23,6 +23,7 @@ namespace ImageProcessor using ImageProcessor.Imaging.Filters.EdgeDetection; using ImageProcessor.Imaging.Filters.Photo; using ImageProcessor.Imaging.Formats; + using ImageProcessor.Imaging.Helpers; using ImageProcessor.Processors; #endregion @@ -311,10 +312,7 @@ namespace ImageProcessor if (this.ShouldProcess) { // Sanitize the input. - if (percentage > 100 || percentage < 0) - { - percentage = 0; - } + percentage = ImageMaths.Clamp(percentage, 0, 100); Alpha alpha = new Alpha { DynamicParameter = percentage }; this.CurrentImageFormat.ApplyProcessor(alpha.ProcessImage, this); @@ -546,16 +544,27 @@ namespace ImageProcessor /// /// Whether to flip the image vertically. /// + /// + /// Whether to flip the image both vertically and horizontally. + /// /// /// The current instance of the class. /// - public ImageFactory Flip(bool flipVertically = false) + public ImageFactory Flip(bool flipVertically = false, bool flipBoth = false) { if (this.ShouldProcess) { - RotateFlipType rotateFlipType = flipVertically - ? RotateFlipType.RotateNoneFlipY - : RotateFlipType.RotateNoneFlipX; + RotateFlipType rotateFlipType; + if (flipBoth) + { + rotateFlipType = RotateFlipType.RotateNoneFlipXY; + } + else + { + rotateFlipType = flipVertically + ? RotateFlipType.RotateNoneFlipY + : RotateFlipType.RotateNoneFlipX; + } Flip flip = new Flip { DynamicParameter = rotateFlipType }; this.CurrentImageFormat.ApplyProcessor(flip.ProcessImage, this); diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index c55f51fd2..773ac2ba2 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -126,9 +126,13 @@ + + + + diff --git a/src/ImageProcessor/Imaging/Filters/Photo/ColorMatrixes.cs b/src/ImageProcessor/Imaging/Filters/Photo/ColorMatrixes.cs index b1ab14b1c..36a72a161 100644 --- a/src/ImageProcessor/Imaging/Filters/Photo/ColorMatrixes.cs +++ b/src/ImageProcessor/Imaging/Filters/Photo/ColorMatrixes.cs @@ -219,7 +219,7 @@ namespace ImageProcessor.Imaging.Filters.Photo new float[] { 0, 1, 0, 0, 0 }, new float[] { 0, 0, 1, 0, 0 }, new float[] { 0, 0, 0, 1, 0 }, - new[] { .25f, .25f, .25f, 0, 1 } + new[] { .10f, .10f, .10f, 0, 1 } })); } } diff --git a/src/ImageProcessor/Settings.StyleCop b/src/ImageProcessor/Settings.StyleCop index 91085624e..094f2cb82 100644 --- a/src/ImageProcessor/Settings.StyleCop +++ b/src/ImageProcessor/Settings.StyleCop @@ -16,6 +16,7 @@ specifier ss subfile + umbraco ver