Browse Source

Initial addition of new query parser

Former-commit-id: 8a5d5ec6199e8f0b209993d5742fc6a27f33427b
Former-commit-id: a0d212c778da02827b67958c8169d120a0fedb9e
Former-commit-id: 0681f7bedb807673142b13a9b0ad7d594f067915
af/merge-core
James South 11 years ago
parent
commit
4bce3ddef8
  1. 116
      src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs
  2. 210
      src/ImageProcessor.Web/Helpers/QuerystringParser/ExtendedColorTypeConverter.cs
  3. 48
      src/ImageProcessor.Web/Helpers/QuerystringParser/GenericArrayTypeConverter.cs
  4. 161
      src/ImageProcessor.Web/Helpers/QuerystringParser/GenericListTypeConverter.cs
  5. 220
      src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs
  6. 4
      src/ImageProcessor.Web/ImageProcessor.Web.csproj
  7. 3
      src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings
  8. 29
      src/ImageProcessor.Web/Processors/Alpha.cs
  9. 17
      src/ImageProcessor.Web/Processors/AutoRotate.cs
  10. 27
      src/ImageProcessor.Web/Processors/BackgroundColor.cs
  11. 29
      src/ImageProcessor.Web/Processors/Brightness.cs
  12. 29
      src/ImageProcessor.Web/Processors/Contrast.cs
  13. 107
      src/ImageProcessor.Web/Processors/Crop.cs
  14. 55
      src/ImageProcessor.Web/Processors/DetectEdges.cs
  15. 55
      src/ImageProcessor.Web/Processors/EntropyCrop.cs
  16. 46
      src/ImageProcessor.Web/Processors/Filter.cs
  17. 41
      src/ImageProcessor.Web/Processors/Flip.cs
  18. 26
      src/ImageProcessor.Web/Processors/Format.cs
  19. 52
      src/ImageProcessor.Web/Processors/GaussianBlur.cs
  20. 52
      src/ImageProcessor.Web/Processors/GaussianSharpen.cs
  21. 21
      src/ImageProcessor.Web/Processors/Halftone.cs
  22. 69
      src/ImageProcessor.Web/Processors/Hue.cs
  23. 31
      src/ImageProcessor/Common/Extensions/AssemblyExtensions.cs
  24. 136
      src/ImageProcessor/Common/Helpers/IOHelper.cs
  25. 288
      src/ImageProcessor/Common/Helpers/TypeFinder.cs
  26. 126
      src/ImageProcessor/Common/Helpers/UpgradeableReadLock.cs
  27. 107
      src/ImageProcessor/Common/Helpers/WriteLock.cs
  28. 104
      src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs
  29. 25
      src/ImageProcessor/ImageFactory.cs
  30. 4
      src/ImageProcessor/ImageProcessor.csproj
  31. 2
      src/ImageProcessor/Imaging/Filters/Photo/ColorMatrixes.cs
  32. 1
      src/ImageProcessor/Settings.StyleCop

116
src/ImageProcessor.Web/Helpers/CommonParameterParserUtility.cs

@ -1,13 +1,9 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CommonParameterParserUtility.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// // Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates methods to correctly parse querystring parameters.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
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;
/// <summary>
@ -46,21 +41,6 @@ namespace ImageProcessor.Web.Helpers
/// </summary>
private static readonly Regex In100RangeRegex = new Regex(@"(-?0*(?:100|[1-9][0-9]?))", RegexOptions.Compiled);
/// <summary>
/// The sharpen regex.
/// </summary>
private static readonly Regex BlurSharpenRegex = new Regex(@"(blur|sharpen)=\d+", RegexOptions.Compiled);
/// <summary>
/// The sigma regex.
/// </summary>
private static readonly Regex SigmaRegex = new Regex(@"sigma(=|-)\d+(.?\d+)?", RegexOptions.Compiled);
/// <summary>
/// The threshold regex.
/// </summary>
private static readonly Regex ThresholdRegex = new Regex(@"threshold(=|-)\d+", RegexOptions.Compiled);
/// <summary>
/// Returns the correct <see cref="T:System.Int32"/> containing the angle for the given string.
/// </summary>
@ -147,100 +127,6 @@ namespace ImageProcessor.Web.Helpers
return value;
}
/// <summary>
/// Returns the correct <see cref="GaussianLayer"/> for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <param name="maxSize">
/// The maximum size to set the Gaussian kernel to.
/// </param>
/// <param name="maxSigma">
/// The maximum Sigma value (standard deviation) for Gaussian function used to calculate the kernel.
/// </param>
/// <param name="maxThreshold">
/// The maximum threshold value, which is added to each weighted sum of pixels.
/// </param>
/// <returns>
/// The correct <see cref="GaussianLayer"/> .
/// </returns>
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);
}
/// <summary>
/// Returns the correct <see cref="T:System.Int32"/> containing the blur value
/// for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The correct <see cref="T:System.Int32"/> for the given string.
/// </returns>
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;
}
/// <summary>
/// Returns the correct <see cref="T:System.Double"/> containing the sigma value
/// for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The correct <see cref="T:System.Double"/> for the given string.
/// </returns>
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;
}
/// <summary>
/// Returns the correct <see cref="T:System.Int32"/> containing the threshold value
/// for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The correct <see cref="T:System.Int32"/> for the given string.
/// </returns>
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;
}
/// <summary>
/// Builds a regular expression for the three main colour types.
/// </summary>

210
src/ImageProcessor.Web/Helpers/QuerystringParser/ExtendedColorTypeConverter.cs

@ -0,0 +1,210 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ExtendedColorTypeConverter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The extended color type converter allows conversion of system and web colors.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
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;
/// <summary>
/// The extended color type converter allows conversion of system and web colors.
/// </summary>
public class ExtendedColorTypeConverter : ColorConverter
{
/// <summary>
/// The web color regex.
/// </summary>
private static readonly Regex WebColorRegex = new Regex("([0-9a-fA-F]{3}){1,2}", RegexOptions.Compiled);
/// <summary>
/// The html system color table map.
/// </summary>
private static Hashtable htmlSystemColorTable;
/// <summary>
/// Converts the given object to the type of this converter, using the specified context and culture
/// information.
/// </summary>
/// <returns>
/// An <see cref="T:System.Object"/> that represents the converted value.
/// </returns>
/// <param name="context">
/// An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.
/// </param>
/// <param name="culture">
/// The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.
/// </param>
/// <param name="value">The <see cref="T:System.Object"/> to convert. </param>
/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed.</exception>
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);
}
/// <summary>
/// Converts the given value object to the specified type, using the specified context and culture
/// information.
/// </summary>
/// <returns>
/// An <see cref="T:System.Object"/> that represents the converted value.
/// </returns>
/// <param name="context">
/// An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.
/// </param>
/// <param name="culture">
/// A <see cref="T:System.Globalization.CultureInfo"/>. If null is passed, the current culture is assumed.
/// </param>
/// <param name="value">The <see cref="T:System.Object"/> to convert. </param>
/// <param name="destinationType">
/// The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.
/// </param>
/// <exception cref="T:System.ArgumentNullException">
/// The <paramref name="destinationType"/> parameter is null.
/// </exception>
/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed.
/// </exception>
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);
}
/// <summary>
/// Initializes color table mapping system colors to known colors.
/// </summary>
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;
}
}
}

48
src/ImageProcessor.Web/Helpers/QuerystringParser/GenericArrayTypeConverter.cs

@ -0,0 +1,48 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GenericArrayTypeConverter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Converts the value of an string to and from a Array{T}.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Helpers
{
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
/// <summary>
/// Converts the value of an string to and from a Array{T}.
/// </summary>
/// <typeparam name="T">
/// The type to convert from.
/// </typeparam>
public class GenericArrayTypeConverter<T> : GenericListTypeConverter<T>
{
/// <summary>
/// Converts the given object to the type of this converter, using the specified context and culture
/// information.
/// </summary>
/// <returns>
/// An <see cref="T:System.Object"/> that represents the converted value.
/// </returns>
/// <param name="context">
/// An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.
/// </param>
/// <param name="culture">
/// The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.
/// </param>
/// <param name="value">The <see cref="T:System.Object"/> to convert. </param>
/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed.</exception>
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
object result = base.ConvertFrom(context, culture, value);
IList<T> list = result as IList<T>;
return list != null ? list.ToArray() : result;
}
}
}

161
src/ImageProcessor.Web/Helpers/QuerystringParser/GenericListTypeConverter.cs

@ -0,0 +1,161 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GenericListTypeConverter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Converts the value of an string to and from a List{T}.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Web.Helpers
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
/// <summary>
/// Converts the value of an string to and from a List{T}.
/// </summary>
/// <typeparam name="T">
/// The type to convert from.
/// </typeparam>
public class GenericListTypeConverter<T> : TypeConverter
{
/// <summary>
/// The type converter.
/// </summary>
private readonly TypeConverter typeConverter;
/// <summary>
/// Initializes a new instance of the <see cref="GenericListTypeConverter{T}"/> class.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if no converter exists for the given type.
/// </exception>
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);
}
}
/// <summary>
/// Returns whether this converter can convert an object of the given type to the type of this converter,
/// using the specified context.
/// </summary>
/// <returns>
/// true if this converter can perform the conversion; otherwise, false.
/// </returns>
/// <param name="context">
/// An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a
/// format context. </param>
/// <param name="sourceType">
/// A <see cref="T:System.Type"/> that represents the type you want to convert from.
/// </param>
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);
}
/// <summary>
/// Converts the given object to the type of this converter, using the specified context and culture
/// information.
/// </summary>
/// <returns>
/// An <see cref="T:System.Object"/> that represents the converted value.
/// </returns>
/// <param name="context">
/// An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.
/// </param>
/// <param name="culture">
/// The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.
/// </param>
/// <param name="value">The <see cref="T:System.Object"/> to convert. </param>
/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed.</exception>
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string input = value as string;
if (input != null)
{
string[] items = this.GetStringArray(input);
List<T> result = new List<T>();
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);
}
/// <summary>
/// Converts the given value object to the specified type, using the specified context and culture
/// information.
/// </summary>
/// <returns>
/// An <see cref="T:System.Object"/> that represents the converted value.
/// </returns>
/// <param name="context">
/// An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.
/// </param>
/// <param name="culture">
/// A <see cref="T:System.Globalization.CultureInfo"/>. If null is passed, the current culture is assumed.
/// </param>
/// <param name="value">The <see cref="T:System.Object"/> to convert. </param>
/// <param name="destinationType">
/// The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.
/// </param>
/// <exception cref="T:System.ArgumentNullException">
/// The <paramref name="destinationType"/> parameter is null.
/// </exception>
/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed.
/// </exception>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return string.Join(",", (IList<T>)value);
}
return base.ConvertTo(context, culture, value, destinationType);
}
/// <summary>
/// Splits a string by comma to return an array of string values.
/// </summary>
/// <param name="input">
/// The input string to split.
/// </param>
/// <returns>
/// The <see cref="string"/> array from the comma separated values.
/// </returns>
protected string[] GetStringArray(string input)
{
string[] result = input.Split(',').Select(s => s.Trim()).ToArray();
return result;
}
}
}

220
src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs

@ -0,0 +1,220 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="QueryParamParser.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The query parameter parser that converts string values to different types.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
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;
/// <summary>
/// The query parameter parser that converts string values to different types.
/// </summary>
public class QueryParamParser
{
/// <summary>
/// A new instance of the <see cref="QueryParamParser"/> class.
/// with lazy initialization.
/// </summary>
private static readonly Lazy<QueryParamParser> Lazy = new Lazy<QueryParamParser>(() => new QueryParamParser());
/// <summary>
/// The cache for storing created default types.
/// </summary>
private static readonly ConcurrentDictionary<Type, object> TypeDefaultsCache = new ConcurrentDictionary<Type, object>();
/// <summary>
/// Prevents a default instance of the <see cref="QueryParamParser"/> class from being created.
/// </summary>
private QueryParamParser()
{
this.AddColorConverters();
this.AddListConverters();
this.AddArrayConverters();
}
/// <summary>
/// Gets the current <see cref="QueryParamParser"/> instance.
/// </summary>
public static QueryParamParser Instance
{
get
{
return Lazy.Value;
}
}
/// <summary>
/// Parses the given string value converting it to the given type.
/// </summary>
/// <param name="value">
/// The <see cref="String"/> value to parse.
/// </param>
/// <param name="culture">
/// The <see cref="CultureInfo"/> to use as the current culture.
/// <remarks>If not set will parse using <see cref="CultureInfo.InvariantCulture"/></remarks>
/// </param>
/// <typeparam name="T">
/// The <see cref="Type"/> to convert the string to.
/// </typeparam>
/// <returns>
/// The <see cref="T"/>.
/// </returns>
public T ParseValue<T>(string value, CultureInfo culture = null)
{
return (T)this.ParseValue(typeof(T), value, culture);
}
/// <summary>
/// Parses the given string value converting it to the given type.
/// </summary>
/// <param name="type">
/// The <see cref="Type"/> to convert the string to.
/// </param>
/// <param name="value">
/// The <see cref="String"/> value to parse.
/// </param>
/// <param name="culture">
/// The <see cref="CultureInfo"/> to use as the current culture.
/// <remarks>If not set will parse using <see cref="CultureInfo.InvariantCulture"/></remarks>
/// </param>
/// <returns>
/// The <see cref="object"/>.
/// </returns>
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));
}
}
/// <summary>
/// Adds a type converter to the parser.
/// </summary>
/// <param name="type">
/// The <see cref="Type"/> to add a converter for.
/// </param>
/// <param name="converterType">
/// The type of <see cref="TypeConverter"/> to add.
/// </param>
/// <returns>
/// The <see cref="TypeDescriptionProvider"/>.
/// </returns>
public TypeDescriptionProvider AddTypeConverter(Type type, Type converterType)
{
return TypeDescriptor.AddAttributes(type, new TypeConverterAttribute(converterType));
}
/// <summary>
/// Adds color converters.
/// </summary>
private void AddColorConverters()
{
this.AddTypeConverter(typeof(Color), typeof(ExtendedColorTypeConverter));
}
/// <summary>
/// Adds a selection of default list type converters.
/// </summary>
private void AddListConverters()
{
this.AddTypeConverter(typeof(List<sbyte>), typeof(GenericListTypeConverter<sbyte>));
this.AddTypeConverter(typeof(List<byte>), typeof(GenericListTypeConverter<byte>));
this.AddTypeConverter(typeof(List<short>), typeof(GenericListTypeConverter<short>));
this.AddTypeConverter(typeof(List<ushort>), typeof(GenericListTypeConverter<ushort>));
this.AddTypeConverter(typeof(List<int>), typeof(GenericListTypeConverter<int>));
this.AddTypeConverter(typeof(List<uint>), typeof(GenericListTypeConverter<uint>));
this.AddTypeConverter(typeof(List<long>), typeof(GenericListTypeConverter<long>));
this.AddTypeConverter(typeof(List<ulong>), typeof(GenericListTypeConverter<ulong>));
this.AddTypeConverter(typeof(List<decimal>), typeof(GenericListTypeConverter<decimal>));
this.AddTypeConverter(typeof(List<float>), typeof(GenericListTypeConverter<float>));
this.AddTypeConverter(typeof(List<double>), typeof(GenericListTypeConverter<double>));
this.AddTypeConverter(typeof(List<string>), typeof(GenericListTypeConverter<string>));
}
/// <summary>
/// Adds a selection of default array type converters.
/// </summary>
private void AddArrayConverters()
{
this.AddTypeConverter(typeof(sbyte[]), typeof(GenericArrayTypeConverter<sbyte>));
this.AddTypeConverter(typeof(byte[]), typeof(GenericArrayTypeConverter<byte>));
this.AddTypeConverter(typeof(short[]), typeof(GenericArrayTypeConverter<short>));
this.AddTypeConverter(typeof(ushort[]), typeof(GenericArrayTypeConverter<ushort>));
this.AddTypeConverter(typeof(int[]), typeof(GenericArrayTypeConverter<int>));
this.AddTypeConverter(typeof(uint[]), typeof(GenericArrayTypeConverter<uint>));
this.AddTypeConverter(typeof(long[]), typeof(GenericArrayTypeConverter<long>));
this.AddTypeConverter(typeof(ulong[]), typeof(GenericArrayTypeConverter<ulong>));
this.AddTypeConverter(typeof(decimal[]), typeof(GenericArrayTypeConverter<decimal>));
this.AddTypeConverter(typeof(float[]), typeof(GenericArrayTypeConverter<float>));
this.AddTypeConverter(typeof(double[]), typeof(GenericArrayTypeConverter<double>));
this.AddTypeConverter(typeof(string[]), typeof(GenericArrayTypeConverter<string>));
}
/// <summary>
/// Returns the default value for the given type.
/// </summary>
/// <param name="type">
/// The <see cref="Type"/> to return.
/// </param>
/// <returns>
/// The <see cref="object"/> representing the default value.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown if the given <see cref="Type"/> is null.
/// </exception>
private object GetDefaultValue(Type type)
{
// Validate parameters.
if (type == null)
{
throw new ArgumentNullException("type");
}
// We want an Func<object> which returns the default.
// Create that expression here.
// Have to convert to object.
// The default value, always get what the *code* tells us.
Expression<Func<object>> e =
Expression.Lambda<Func<object>>(
Expression.Convert(Expression.Default(type), typeof(object)));
// Compile and return the value.
return e.Compile()();
}
}
}

4
src/ImageProcessor.Web/ImageProcessor.Web.csproj

@ -53,6 +53,10 @@
<Compile Include="Configuration\Shared\SettingElementCollection.cs" />
<Compile Include="Extensions\TypeInitializationExtensions.cs" />
<Compile Include="Helpers\ProcessQueryStringEventArgs.cs" />
<Compile Include="Helpers\QuerystringParser\ExtendedColorTypeConverter.cs" />
<Compile Include="Helpers\QuerystringParser\QueryParamParser.cs" />
<Compile Include="Helpers\QuerystringParser\GenericArrayTypeConverter.cs" />
<Compile Include="Helpers\QuerystringParser\GenericListTypeConverter.cs" />
<Compile Include="Processors\DetectEdges.cs" />
<Compile Include="Processors\EntropyCrop.cs" />
<Compile Include="Processors\Halftone.cs" />

3
src/ImageProcessor.Web/ImageProcessor.Web.csproj.DotSettings

@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=configuration_005Cshared/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=configuration_005Cshared/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helpers_005Cquerystringparser/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

29
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
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"alpha=[^&|,]+", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"alpha=[^&]", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="Alpha"/> class.
@ -63,25 +67,16 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<int>(queryCollection["alpha"]);
percentage = ImageMaths.Clamp(percentage, 0, 100);
this.Processor.DynamicParameter = percentage;
}
return this.SortOrder;

17
src/ImageProcessor.Web/Processors/AutoRotate.cs

@ -69,23 +69,12 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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;

27
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
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"bgcolor(=|-)[^&]+", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"bgcolor=[^&]", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="BackgroundColor"/> class.
@ -62,24 +66,15 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<Color>(queryCollection["bgcolor"]);
this.Processor.DynamicParameter = color;
}
return this.SortOrder;

29
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
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"brightness=[^&|,]+", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"brightness=[^&]", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="Brightness"/> class.
@ -62,25 +66,16 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<int>(queryCollection["brightness"]);
percentage = ImageMaths.Clamp(percentage, 0, 100);
this.Processor.DynamicParameter = percentage;
}
return this.SortOrder;

29
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
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"contrast=[^&|,]+", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"contrast=[^&]", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="Contrast"/> class.
@ -62,25 +66,16 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<int>(queryCollection["contrast"]);
percentage = ImageMaths.Clamp(percentage, 0, 100);
this.Processor.DynamicParameter = percentage;
}
return this.SortOrder;

107
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;
/// <summary>
/// Crops an image to the given directions.
@ -24,19 +25,8 @@ namespace ImageProcessor.Web.Processors
{
/// <summary>
/// The regular expression to search strings for.
/// <see href="http://stackoverflow.com/a/6400969/427899"/>
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"(crop=|cropmode=)[^&]+", RegexOptions.Compiled);
/// <summary>
/// The coordinate regex.
/// </summary>
private static readonly Regex CoordinateRegex = new Regex(@"crop=\d+(.\d+)?[,-]\d+(.\d+)?[,-]\d+(.\d+)?[,-]\d+(.\d+)?", RegexOptions.Compiled);
/// <summary>
/// The mode regex.
/// </summary>
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);
/// <summary>
/// Initializes a new instance of the <see cref="Crop"/> class.
@ -70,99 +60,30 @@ namespace ImageProcessor.Web.Processors
/// <summary>
/// The position in the original string where the first character of the captured substring was found.
/// </summary>
/// <param name="queryString">The query string to search.</param>
/// <param name="queryString">
/// The query string to search.
/// </param>
/// <returns>
/// The zero-based starting position in the original string where the captured substring was found.
/// </returns>
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<float[]>(queryCollection["crop"]);
// Default CropMode.Pixels will be returned.
CropMode cropMode = QueryParamParser.Instance.ParseValue<CropMode>(queryCollection["cropmode"]);
CropLayer cropLayer = new CropLayer(coordinates[0], coordinates[1], coordinates[2], coordinates[3], cropMode);
this.Processor.DynamicParameter = cropLayer;
}
return this.SortOrder;
}
/// <summary>
/// Returns the correct <see cref="CropMode"/> for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The correct <see cref="CropMode"/>.
/// </returns>
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;
}
/// <summary>
/// Returns the crop coordinates for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The <see cref="float"/> array containing the crop coordinates.
/// </returns>
private float[] ParseCoordinates(string input)
{
float[] floats = { };
foreach (Match match in CoordinateRegex.Matches(input))
{
floats = match.Value.ToPositiveFloatArray();
}
return floats;
}
}
}

55
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;
/// <summary>
/// Produces an image with the detected edges highlighted.
@ -32,11 +35,6 @@ namespace ImageProcessor.Web.Processors
/// </summary>
private static readonly Regex QueryRegex = BuildRegex();
/// <summary>
/// The regular expression to search strings for the greyscale attribute.
/// </summary>
private static readonly Regex GreyscaleRegex = new Regex(@"greyscale=false", RegexOptions.Compiled);
/// <summary>
/// The edge detectors.
/// </summary>
@ -85,36 +83,15 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<bool>(queryCollection["greyscale"]);
this.Processor.DynamicParameter = new Tuple<IEdgeFilter, bool>(filter, greyscale);
}
@ -148,19 +125,5 @@ namespace ImageProcessor.Web.Processors
return new Regex(stringBuilder.ToString(), RegexOptions.IgnoreCase);
}
/// <summary>
/// Parses the input string to return the correct <see cref="IEdgeFilter"/>.
/// </summary>
/// <param name="identifier">
/// The identifier.
/// </param>
/// <returns>
/// The <see cref="IEdgeFilter"/>.
/// </returns>
private IEdgeFilter ParseFilter(string identifier)
{
return (IEdgeFilter)detectors[this.RegexPattern.Match(identifier).Value.Split('=')[1]];
}
}
}

55
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;
/// <summary>
/// Performs a crop on an image to the area of greatest entropy.
@ -24,7 +26,7 @@ namespace ImageProcessor.Web.Processors
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"entropycrop=(\d+)[^&]+|entropycrop", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"entropycrop(=)?[^&]*", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="EntropyCrop"/> class.
@ -64,56 +66,21 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<byte>(queryCollection["entropycrop"]);
index += 1;
}
// Fallback to the default if 0.
this.Processor.DynamicParameter = threshold > 0 ? threshold : (byte)128;
}
return this.SortOrder;
}
/// <summary>
/// Returns the correct <see cref="T:System.Int32"/> containing the radius for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The correct <see cref="T:System.Int32"/> containing the radius for the given string.
/// </returns>
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;
}
}
}

46
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
/// </summary>
private static readonly Regex QueryRegex = BuildRegex();
/// <summary>
/// The filter cache.
/// </summary>
private static readonly ConcurrentDictionary<string, IMatrixFilter> FilterCache
= new ConcurrentDictionary<string, IMatrixFilter>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Initializes a new instance of the <see cref="Filter"/> class.
/// </summary>
@ -72,24 +79,13 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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;
});
}
}
}

41
src/ImageProcessor.Web/Processors/Flip.cs

@ -64,36 +64,27 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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;
}
}

26
src/ImageProcessor.Web/Processors/Format.cs

@ -70,28 +70,16 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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)
{

52
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
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"blur=[^&]+", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"blur=[^&]", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlur"/> class.
@ -65,34 +70,33 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<int>(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<double>(queryCollection["sigma"]) : 1.4d;
int threshold = QueryParamParser.Instance.ParseValue<int>(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;

52
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
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"sharpen=[^&]+", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"sharpen=[^&]", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpen"/> class.
@ -65,34 +70,33 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<int>(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<double>(queryCollection["sigma"]) : 1.4d;
int threshold = QueryParamParser.Instance.ParseValue<int>(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;

21
src/ImageProcessor.Web/Processors/Halftone.cs

@ -62,25 +62,14 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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;

69
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;
/// <summary>
/// Encapsulates methods to adjust the hue component of an image.
@ -25,17 +29,7 @@ namespace ImageProcessor.Web.Processors
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"(hue=|hue.\w+=)[^&]+", RegexOptions.Compiled);
/// <summary>
/// The hue regex.
/// </summary>
private static readonly Regex HueRegex = new Regex(@"hue=\d+", RegexOptions.Compiled);
/// <summary>
/// The rotate regex.
/// </summary>
private static readonly Regex RotateRegex = new Regex(@"hue.rotate=true", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"hue=[^&]", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="Hue"/> class.
@ -75,61 +69,22 @@ namespace ImageProcessor.Web.Processors
/// </returns>
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<int>(queryCollection["hue"]);
bool rotate = QueryParamParser.Instance.ParseValue<bool>(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<int, bool>(degrees, rotate);
}
return this.SortOrder;
}
/// <summary>
/// Returns the angle to alter the hue.
/// </summary>
/// <param name="input">
/// The input containing the value to parse.
/// </param>
/// <returns>
/// The <see cref="int"/> representing the angle.
/// </returns>
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));
}
}
}

31
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);
}
}
/// <summary>
/// Returns the <see cref="FileInfo"/> identifying the file used to load the assembly
/// </summary>
/// <param name="assembly">
/// The <see cref="Assembly"/> to get the name from.
/// </param>
/// <returns>The <see cref="FileInfo"/></returns>
public static FileInfo GetAssemblyFile(this Assembly assembly)
{
string codeBase = assembly.CodeBase;
Uri uri = new Uri(codeBase);
string path = uri.LocalPath;
return new FileInfo(path);
}
/// <summary>
/// Returns the <see cref="FileInfo"/> identifying the file used to load the assembly
/// </summary>
/// <param name="assemblyName">
/// The <see cref="AssemblyName"/> to get the name from.
/// </param>
/// <returns>The <see cref="FileInfo"/></returns>
public static FileInfo GetAssemblyFile(this AssemblyName assemblyName)
{
var codeBase = assemblyName.CodeBase;
var uri = new Uri(codeBase);
var path = uri.LocalPath;
return new FileInfo(path);
}
}
}

136
src/ImageProcessor/Common/Helpers/IOHelper.cs

@ -0,0 +1,136 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IOHelper.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Provides helper method for traversing the file system.
// <remarks>
// Adapted from identically named class within <see href="https://github.com/umbraco/Umbraco-CMS" />
// </remarks>
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Common.Helpers
{
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using ImageProcessor.Common.Extensions;
/// <summary>
/// Provides helper method for traversing the file system.
/// <remarks>
/// Adapted from identically named class within <see href="https://github.com/umbraco/Umbraco-CMS"/>
/// </remarks>
/// </summary>
internal class IOHelper
{
/// <summary>
/// The root directory.
/// </summary>
private static string rootDirectory;
/// <summary>
/// Maps a virtual path to a physical path.
/// </summary>
/// <param name="virtualPath">
/// The virtual path to map.
/// </param>
/// <returns>
/// The <see cref="string"/> representing the physical path.
/// </returns>
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;
}
/// <summary>
/// Gets the root directory bin folder for the currently running application.
/// </summary>
/// <returns>
/// The <see cref="string"/> representing the root directory bin folder.
/// </returns>
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;
}
/// <summary>
/// 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
/// </summary>
/// <returns>
/// The <see cref="string"/> representing the root path of the currently running application.</returns>
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;
}
}
}

288
src/ImageProcessor/Common/Helpers/TypeFinder.cs

@ -0,0 +1,288 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TypeFinder.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// A utility class to find all classes of a certain type by reflection in the current bin folder
// of the web application.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
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;
/// <summary>
/// A utility class to find all classes of a certain type by reflection in the current bin folder
/// of the web application.
/// </summary>
/// <remarks>
/// Adapted from identically named class within <see href="https://github.com/umbraco/Umbraco-CMS"/>
/// </remarks>
internal static class TypeFinder
{
/// <summary>
/// The local filtered assembly cache.
/// </summary>
private static readonly HashSet<Assembly> LocalFilteredAssemblyCache = new HashSet<Assembly>();
/// <summary>
/// The local filtered assembly cache locker.
/// </summary>
private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim();
/// <summary>
/// The reader-writer lock implementation.
/// </summary>
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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,"
};
/// <summary>
/// A collection of all assemblies.
/// </summary>
private static HashSet<Assembly> allAssemblies;
/// <summary>
/// The bin folder assemblies.
/// </summary>
private static HashSet<Assembly> binFolderAssemblies;
/// <summary>
/// Lazily loads a reference to all assemblies and only local assemblies.
/// This is a modified version of:
/// <see href="http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder"/>
/// </summary>
/// <remarks>
/// 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:
/// <see href="http://issues.umbraco.org/issue/U5-198"/>
/// <see cref="http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app"/>
/// <see cref="http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl"/>
/// </remarks>
/// <returns>
/// The <see cref="HashSet{Assembly}"/>.
/// </returns>
internal static HashSet<Assembly> 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<string> binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList();
HashSet<Assembly> assemblies = new HashSet<Assembly>();
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<Assembly>(assemblies);
}
catch (InvalidOperationException e)
{
if (!(e.InnerException is SecurityException))
{
throw;
}
binFolderAssemblies = allAssemblies;
}
}
return allAssemblies;
}
}
/// <summary>
/// Returns only assemblies found in the bin folder that have been loaded into the app domain.
/// </summary>
/// <returns>
/// The collection of assemblies.
/// </returns>
internal static HashSet<Assembly> GetBinAssemblies()
{
if (binFolderAssemblies == null)
{
using (new WriteLock(Locker))
{
Assembly[] assemblies = GetAssembliesWithKnownExclusions().ToArray();
DirectoryInfo binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
// ReSharper disable once PossibleNullReferenceException
List<string> binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
IEnumerable<AssemblyName> domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName);
HashSet<Assembly> safeDomainAssemblies = new HashSet<Assembly>();
HashSet<Assembly> binFolderAssemblyList = new HashSet<Assembly>();
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<Assembly>(binFolderAssemblyList);
}
}
return binFolderAssemblies;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="excludeFromResults">
/// An <see cref="IEnumerable{Assembly}"/> to exclude.
/// </param>
/// <returns>The collection of local assemblies.</returns>
internal static HashSet<Assembly> GetAssembliesWithKnownExclusions(
IEnumerable<Assembly> excludeFromResults = null)
{
using (UpgradeableReadLock locker = new UpgradeableReadLock(LocalFilteredAssemblyCacheLocker))
{
if (LocalFilteredAssemblyCache.Any())
{
return LocalFilteredAssemblyCache;
}
locker.UpgradeToWriteLock();
IEnumerable<Assembly> assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter);
foreach (Assembly assembly in assemblies)
{
LocalFilteredAssemblyCache.Add(assembly);
}
return LocalFilteredAssemblyCache;
}
}
/// <summary>
/// Return a distinct list of found local Assemblies and excluding the ones passed in and excluding the exclusion list filter
/// </summary>
/// <param name="excludeFromResults">
/// An <see cref="IEnumerable{Assembly}"/> to exclude.
/// </param>
/// <param name="exclusionFilter">
/// An <see cref="string"/> array containing exclusion filters.
/// </param>
/// <returns>The collection of filtered local assemblies.</returns>
private static IEnumerable<Assembly> GetFilteredAssemblies(
IEnumerable<Assembly> excludeFromResults = null,
string[] exclusionFilter = null)
{
if (excludeFromResults == null)
{
excludeFromResults = new HashSet<Assembly>();
}
if (exclusionFilter == null)
{
exclusionFilter = new string[] { };
}
return GetAllAssemblies()
.Where(x => !excludeFromResults.Contains(x)
&& !x.GlobalAssemblyCache
&& !exclusionFilter.Any(f => x.FullName.StartsWith(f)));
}
}
}

126
src/ImageProcessor/Common/Helpers/UpgradeableReadLock.cs

@ -0,0 +1,126 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UpgradeableReadLock.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Provides a convenience methodology for implementing upgradeable locked access to resources.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Common.Helpers
{
using System;
using System.Threading;
/// <summary>
/// Provides a convenience methodology for implementing upgradeable locked access to resources.
/// </summary>
/// <remarks>
/// Adapted from identically named class within <see href="https://github.com/umbraco/Umbraco-CMS"/>
/// </remarks>
internal sealed class UpgradeableReadLock : IDisposable
{
/// <summary>
/// The locker to lock against.
/// </summary>
private readonly ReaderWriterLockSlim locker;
/// <summary>
/// A value indicating whether the locker has been upgraded to a writeable lock.
/// </summary>
private bool upgraded;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// 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.
/// </remarks>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="UpgradeableReadLock"/> class.
/// </summary>
/// <param name="locker">
/// The locker.
/// </param>
public UpgradeableReadLock(ReaderWriterLockSlim locker)
{
this.locker = locker;
this.locker.EnterUpgradeableReadLock();
}
/// <summary>
/// Finalizes an instance of the <see cref="UpgradeableReadLock"/> class.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
~UpgradeableReadLock()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
this.Dispose(false);
}
/// <summary>
/// Tries to enter the locker in write mode.
/// </summary>
public void UpgradeToWriteLock()
{
this.locker.EnterWriteLock();
this.upgraded = true;
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
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);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
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;
}
}
}

107
src/ImageProcessor/Common/Helpers/WriteLock.cs

@ -0,0 +1,107 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WriteLock.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Provides a convenience methodology for implementing writeable locked access to resources.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Common.Helpers
{
using System;
using System.Threading;
/// <summary>
/// Provides a convenience methodology for implementing writable locked access to resources.
/// </summary>
/// <remarks>
/// Adapted from identically named class within <see href="https://github.com/umbraco/Umbraco-CMS"/>
/// </remarks>
internal sealed class WriteLock : IDisposable
{
/// <summary>
/// The locker to lock against.
/// </summary>
private readonly ReaderWriterLockSlim locker;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// 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.
/// </remarks>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="WriteLock"/> class.
/// </summary>
/// <param name="locker">
/// The locker.
/// </param>
public WriteLock(ReaderWriterLockSlim locker)
{
this.locker = locker;
this.locker.EnterWriteLock();
}
/// <summary>
/// Finalizes an instance of the <see cref="WriteLock"/> class.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
~WriteLock()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
this.Dispose(false);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
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);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
private void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.locker.ExitWriteLock();
}
// Note disposing is done.
this.isDisposed = true;
}
}
}

104
src/ImageProcessor/Configuration/ImageProcessorBootstrapper.cs

@ -4,25 +4,22 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The ImageProcessor bootstrapper.
// The ImageProcessor bootstrapper containing initialization code for extending ImageProcessor.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
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;
/// <summary>
/// The ImageProcessor bootstrapper.
/// The ImageProcessor bootstrapper containing initialization code for extending ImageProcessor.
/// </summary>
public class ImageProcessorBootstrapper
{
@ -68,93 +65,18 @@ namespace ImageProcessor.Configuration
/// </summary>
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<string> found = new HashSet<string>();
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<Type> 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();
}
}
/// <summary>
/// Loads any referenced assemblies into the current application domain.
/// </summary>
/// <param name="found">
/// The collection containing the name of already found assemblies.
/// </param>
/// <param name="assembly">
/// The assembly to load from.
/// </param>
private void LoadReferencedAssemblies(HashSet<string> 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<Type> 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();
}
}
}

25
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
/// <param name="flipVertically">
/// Whether to flip the image vertically.
/// </param>
/// <param name="flipBoth">
/// Whether to flip the image both vertically and horizontally.
/// </param>
/// <returns>
/// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
/// </returns>
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);

4
src/ImageProcessor/ImageProcessor.csproj

@ -126,9 +126,13 @@
<ItemGroup>
<Compile Include="Common\Extensions\AssemblyExtensions.cs" />
<Compile Include="Common\Extensions\EnumerableExtensions.cs" />
<Compile Include="Common\Helpers\WriteLock.cs" />
<Compile Include="Common\Helpers\UpgradeableReadLock.cs" />
<Compile Include="Common\Helpers\TypeFinder.cs" />
<Compile Include="Configuration\ImageProcessorBootstrapper.cs" />
<Compile Include="Common\Exceptions\ImageProcessingException.cs" />
<Compile Include="Common\Extensions\DoubleExtensions.cs" />
<Compile Include="Common\Helpers\IOHelper.cs" />
<Compile Include="Configuration\NativeBinaryFactory.cs" />
<Compile Include="Configuration\NativeMethods.cs" />
<Compile Include="Imaging\Colors\CmykColor.cs" />

2
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 }
}));
}
}

1
src/ImageProcessor/Settings.StyleCop

@ -16,6 +16,7 @@
<Value>specifier</Value>
<Value>ss</Value>
<Value>subfile</Value>
<Value>umbraco</Value>
<Value>ver</Value>
</CollectionProperty>
</GlobalSettings>

Loading…
Cancel
Save