Browse Source

Adding pad functionality to resize

Former-commit-id: 853c80838d0240a914034b86d8278658777f9f57
af/merge-core
James South 12 years ago
parent
commit
c18aac95bb
  1. 2
      src/ImageProcessor/ImageFactory.cs
  2. 2
      src/ImageProcessor/ImageProcessor.csproj
  3. 115
      src/ImageProcessor/Imaging/ResizeLayer.cs
  4. 28
      src/ImageProcessor/Imaging/ResizeMode.cs
  5. 4
      src/ImageProcessor/Processors/Constrain.cs
  6. 151
      src/ImageProcessor/Processors/Resize.cs
  7. 66
      src/ImageProcessor/Processors/ResizeBase.cs
  8. 2
      src/ImageProcessor/Processors/Rotate.cs
  9. 5
      src/ImageProcessor/Settings.StyleCop
  10. 14
      src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml

2
src/ImageProcessor/ImageFactory.cs

@ -375,8 +375,6 @@ namespace ImageProcessor
return this;
}
/// <summary>
/// Crops the current image to the given location and size.
/// </summary>

2
src/ImageProcessor/ImageProcessor.csproj

@ -62,6 +62,7 @@
<Compile Include="Helpers\Extensions\StringExtensions.cs" />
<Compile Include="ImageFactory.cs" />
<Compile Include="Imaging\ColorQuantizer.cs" />
<Compile Include="Imaging\ResizeLayer.cs" />
<Compile Include="Imaging\Filters\BlackWhiteMatrixFilter.cs" />
<Compile Include="Imaging\Filters\ColorMatrixes.cs" />
<Compile Include="Imaging\Filters\ComicMatrixFilter.cs" />
@ -75,6 +76,7 @@
<Compile Include="Imaging\Filters\SepiaMatrixFilter.cs" />
<Compile Include="Imaging\Filters\IMatrixFilter.cs" />
<Compile Include="Imaging\ImageUtils.cs" />
<Compile Include="Imaging\ResizeMode.cs" />
<Compile Include="Imaging\ResponseType.cs" />
<Compile Include="Imaging\RotateLayer.cs" />
<Compile Include="Imaging\RoundedCornerLayer.cs" />

115
src/ImageProcessor/Imaging/ResizeLayer.cs

@ -0,0 +1,115 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ResizeLayer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates the properties required to resize an image.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
{
#region Using
using System.Drawing;
#endregion
/// <summary>
/// Encapsulates the properties required to resize an image.
/// </summary>
public class ResizeLayer
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ResizeLayer"/> class.
/// </summary>
public ResizeLayer()
{
this.ResizeMode = ResizeMode.Pad;
this.BackgroundColor = Color.Transparent;
}
/// <summary>
/// Initializes a new instance of the <see cref="ResizeLayer"/> class.
/// </summary>
/// <param name="resizeMode">
/// The resize mode to apply to resized image.
/// </param>
public ResizeLayer(ResizeMode resizeMode)
{
this.ResizeMode = resizeMode;
this.BackgroundColor = Color.Transparent;
}
/// <summary>
/// Initializes a new instance of the <see cref="ResizeLayer"/> class.
/// </summary>
/// <param name="resizeMode">
/// The resize mode to apply to resized image.
/// </param>
/// <param name="backgroundColor">
/// The <see cref="T:System.Drawing.Color"/> to set as the background color.
/// <remarks>Used for image formats that do not support transparency</remarks>
/// </param>
public ResizeLayer(ResizeMode resizeMode, Color backgroundColor)
{
this.ResizeMode = resizeMode;
this.BackgroundColor = backgroundColor;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the size.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to ResizeMode the layer.
/// </summary>
public ResizeMode ResizeMode { get; set; }
/// <summary>
/// Gets or sets the background color.
/// </summary>
public Color BackgroundColor { get; set; }
#endregion
/// <summary>
/// Returns a value that indicates whether the specified object is an
/// <see cref="ResizeLayer"/> object that is equivalent to
/// this <see cref="ResizeLayer"/> object.
/// </summary>
/// <param name="obj">
/// The object to test.
/// </param>
/// <returns>
/// True if the given object is an <see cref="ResizeLayer"/> object that is equivalent to
/// this <see cref="ResizeLayer"/> object; otherwise, false.
/// </returns>
public override bool Equals(object obj)
{
ResizeLayer resizeLayer = obj as ResizeLayer;
if (resizeLayer == null)
{
return false;
}
return this.Size == resizeLayer.Size
&& this.ResizeMode == resizeLayer.ResizeMode
&& this.BackgroundColor == resizeLayer.BackgroundColor;
}
/// <summary>
/// Returns a hash code value that represents this object.
/// </summary>
/// <returns>
/// A hash code that represents this object.
/// </returns>
public override int GetHashCode()
{
return this.Size.GetHashCode() + this.ResizeMode.GetHashCode() + this.BackgroundColor.GetHashCode();
}
}
}

28
src/ImageProcessor/Imaging/ResizeMode.cs

@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ResizeMode.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Enumerated resize modes to apply to resized images.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
{
/// <summary>
/// Enumerated resize modes to apply to resized images.
/// </summary>
public enum ResizeMode
{
/// <summary>
/// Pads the resized image to fit the bounds of its container.
/// </summary>
Pad,
/// <summary>
/// Stretches the resized image to fit the bounds of its container.
/// </summary>
Stretch
}
}

4
src/ImageProcessor/Processors/Constrain.cs

@ -15,6 +15,8 @@ namespace ImageProcessor.Processors
using System.Drawing;
using System.Text.RegularExpressions;
using ImageProcessor.Helpers.Extensions;
using ImageProcessor.Imaging;
#endregion
/// <summary>
@ -126,7 +128,7 @@ namespace ImageProcessor.Processors
int.TryParse(this.Settings["MaxWidth"], out defaultMaxWidth);
int.TryParse(this.Settings["MaxHeight"], out defaultMaxHeight);
return this.ResizeImage(factory, newSize.Width, newSize.Height, defaultMaxWidth, defaultMaxHeight);
return this.ResizeImage(factory, newSize.Width, newSize.Height, defaultMaxWidth, defaultMaxHeight, ResizeMode.Pad, Color.Transparent);
}
return factory.Image;

151
src/ImageProcessor/Processors/Resize.cs

@ -11,10 +11,15 @@
namespace ImageProcessor.Processors
{
#region Using
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using ImageProcessor.Helpers.Extensions;
using ImageProcessor.Imaging;
#endregion
/// <summary>
@ -25,7 +30,22 @@ namespace ImageProcessor.Processors
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"(width|height)=\d+", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"((width|height)=\d+)|(mode=(pad|stretch|crop))|(bgcolor-([0-9a-fA-F]{3}){1,2})", RegexOptions.Compiled);
/// <summary>
/// The regular expression to search strings for the size attribute.
/// </summary>
private static readonly Regex SizeRegex = new Regex(@"(width|height)=\d+");
/// <summary>
/// The regular expression to search strings for the mode attribute.
/// </summary>
private static readonly Regex ModeRegex = new Regex(@"mode=(pad|stretch|crop)");
/// <summary>
/// The regular expression to search strings for the color attribute.
/// </summary>
private static readonly Regex ColorRegex = new Regex(@"bgcolor-([0-9a-fA-F]{3}){1,2}", RegexOptions.Compiled);
#region IGraphicsProcessor Members
/// <summary>
@ -81,7 +101,10 @@ namespace ImageProcessor.Processors
// Set the sort order to max to allow filtering.
this.SortOrder = int.MaxValue;
Size size = new Size();
ResizeLayer resizeLayer = new ResizeLayer();
// First merge the matches so we can parse .
StringBuilder stringBuilder = new StringBuilder();
foreach (Match match in this.RegexPattern.Matches(queryString))
{
@ -93,21 +116,20 @@ namespace ImageProcessor.Processors
this.SortOrder = match.Index;
}
// Match syntax
if (match.Value.Contains("width"))
{
size.Width = match.Value.ToPositiveIntegerArray()[0];
}
else
{
size.Height = match.Value.ToPositiveIntegerArray()[0];
}
stringBuilder.Append(match.Value);
index += 1;
}
}
this.DynamicParameter = size;
// Match syntax
string toParse = stringBuilder.ToString();
resizeLayer.Size = this.ParseSize(toParse);
resizeLayer.ResizeMode = this.ParseMode(toParse);
resizeLayer.BackgroundColor = this.ParseColor(toParse);
this.DynamicParameter = resizeLayer;
return this.SortOrder;
}
@ -123,16 +145,115 @@ namespace ImageProcessor.Processors
/// </returns>
public override Image ProcessImage(ImageFactory factory)
{
int width = this.DynamicParameter.Width ?? 0;
int height = this.DynamicParameter.Height ?? 0;
int width = this.DynamicParameter.Size.Width ?? 0;
int height = this.DynamicParameter.Size.Height ?? 0;
ResizeMode mode = this.DynamicParameter.ResizeMode;
Color backgroundColor = this.DynamicParameter.BackgroundColor;
int defaultMaxWidth;
int defaultMaxHeight;
int.TryParse(this.Settings["MaxWidth"], out defaultMaxWidth);
int.TryParse(this.Settings["MaxHeight"], out defaultMaxHeight);
return this.ResizeImage(factory, width, height, defaultMaxWidth, defaultMaxHeight);
return this.ResizeImage(factory, width, height, defaultMaxWidth, defaultMaxHeight, mode, backgroundColor);
}
#endregion
/// <summary>
/// Returns the correct <see cref="Size"/> for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The <see cref="Size"/>.
/// </returns>
private Size ParseSize(string input)
{
const string Width = "width";
const string Height = "height";
Size size = new Size();
// First merge the matches so we can parse .
StringBuilder stringBuilder = new StringBuilder();
foreach (Match match in SizeRegex.Matches(input))
{
stringBuilder.Append(match.Value);
}
// First cater for single dimensions.
string value = stringBuilder.ToString();
if (input.Contains(Width) && !input.Contains(Height))
{
size = new Size(value.ToPositiveIntegerArray()[0], 0);
}
if (input.Contains(Height) && !input.Contains(Width))
{
size = new Size(0, value.ToPositiveIntegerArray()[0]);
}
// Both dimensions supplied.
if (input.Contains(Height) && input.Contains(Width))
{
int[] dimensions = value.ToPositiveIntegerArray();
// Check the order in which they have been supplied.
size = input.IndexOf(Width, StringComparison.Ordinal) < input.IndexOf(Height, StringComparison.Ordinal)
? new Size(dimensions[0], dimensions[1])
: new Size(dimensions[1], dimensions[0]);
}
return size;
}
/// <summary>
/// Returns the correct <see cref="ResizeMode"/> for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The correct <see cref="ResizeMode"/>.
/// </returns>
private ResizeMode ParseMode(string input)
{
foreach (Match match in ModeRegex.Matches(input))
{
// Split on =
string mode = match.Value.Split('=')[1];
switch (mode)
{
case "stretch":
return ResizeMode.Stretch;
default:
return ResizeMode.Pad;
}
}
return ResizeMode.Pad;
}
/// <summary>
/// Returns the correct <see cref="T:System.Drawing.Color"/> for the given string.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The correct <see cref="T:System.Drawing.Color"/>
/// </returns>
private Color ParseColor(string input)
{
foreach (Match match in ColorRegex.Matches(input))
{
// split on color-hex
return ColorTranslator.FromHtml("#" + match.Value.Split('-')[1]);
}
return Color.Transparent;
}
}
}

66
src/ImageProcessor/Processors/ResizeBase.cs

@ -17,6 +17,9 @@ namespace ImageProcessor.Processors
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text.RegularExpressions;
using ImageProcessor.Imaging;
#endregion
/// <summary>
@ -87,10 +90,16 @@ namespace ImageProcessor.Processors
/// <param name="defaultMaxHeight">
/// The default max height to resize the image to.
/// </param>
/// <param name="resizeMode">
/// Whether to pad the image to fill the set size.
/// </param>
/// <param name="backgroundColor">
/// The background color to pad the image with.
/// </param>
/// <returns>
/// The processed image from the current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
/// </returns>
protected Image ResizeImage(ImageFactory factory, int width, int height, int defaultMaxWidth, int defaultMaxHeight)
protected Image ResizeImage(ImageFactory factory, int width, int height, int defaultMaxWidth, int defaultMaxHeight, ResizeMode resizeMode, Color backgroundColor)
{
Bitmap newImage = null;
Image image = factory.Image;
@ -100,26 +109,56 @@ namespace ImageProcessor.Processors
int sourceWidth = image.Width;
int sourceHeight = image.Height;
int destinationWidth = width;
int destinationHeight = height;
int maxWidth = defaultMaxWidth > 0 ? defaultMaxWidth : int.MaxValue;
int maxHeight = defaultMaxHeight > 0 ? defaultMaxHeight : int.MaxValue;
// Fractional variants for preserving aspect ratio.
double percentHeight = Math.Abs(height / (double)sourceHeight);
double percentWidth = Math.Abs(width / (double)sourceWidth);
int destinationX = 0;
int destinationY = 0;
// Change the destination rectangle coordinates if padding and
// there has been a set width and height.
if (resizeMode == ResizeMode.Pad && width > 0 && height > 0)
{
double ratio;
if (percentHeight < percentWidth)
{
ratio = percentHeight;
destinationX = (int)((width - (sourceWidth * ratio)) / 2);
destinationWidth = (int)Math.Floor(sourceWidth * percentHeight);
}
else
{
ratio = percentWidth;
destinationY = (int)((height - (sourceHeight * ratio)) / 2);
destinationHeight = (int)Math.Floor(sourceHeight * percentWidth);
}
}
// If height or width is not passed we assume that the standard ratio is to be kept.
if (height == 0)
{
// Bit of simple fractional maths here.
float percentWidth = Math.Abs(width / (float)sourceWidth);
height = (int)Math.Floor(sourceHeight * percentWidth);
destinationHeight = (int)Math.Floor(sourceHeight * percentWidth);
height = destinationHeight;
}
if (width == 0)
{
float percentHeight = Math.Abs(height / (float)sourceHeight);
width = (int)Math.Floor(sourceWidth * percentHeight);
destinationWidth = (int)Math.Floor(sourceWidth * percentHeight);
width = destinationWidth;
}
if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight)
{
// Dont use an object initializer here.
// Don't use an object initializer here.
// ReSharper disable once UseObjectOrCollectionInitializer
newImage = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
newImage.Tag = image.Tag;
@ -128,7 +167,7 @@ namespace ImageProcessor.Processors
// We want to use two different blending algorithms for enlargement/shrinking.
// Bicubic is better enlarging for whilst Bilinear is better for shrinking.
// http://www.codinghorror.com/blog/2007/07/better-image-resizing.html
if (image.Width < width && image.Height < height)
if (image.Width < destinationWidth && image.Height < destinationHeight)
{
// We are making it larger.
graphics.SmoothingMode = SmoothingMode.AntiAlias;
@ -140,11 +179,9 @@ namespace ImageProcessor.Processors
{
// We are making it smaller.
graphics.SmoothingMode = SmoothingMode.None;
// Contrary to everything I have read bicubic is producing the best results.
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.None;
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
}
// An unwanted border appears when using InterpolationMode.HighQualityBicubic to resize the image
@ -154,7 +191,8 @@ namespace ImageProcessor.Processors
using (ImageAttributes wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
Rectangle destRect = new Rectangle(0, 0, width, height);
graphics.Clear(backgroundColor);
Rectangle destRect = new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight);
graphics.DrawImage(image, destRect, 0, 0, sourceWidth, sourceHeight, GraphicsUnit.Pixel, wrapMode);
}

2
src/ImageProcessor/Processors/Rotate.cs

@ -24,7 +24,7 @@ namespace ImageProcessor.Processors
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"rotate=((?:3[0-5][0-9]|[12][0-9]{2}|[1-9][0-9]?)|angle-(?:3[0-5][0-9]|[12][0-9]{2}|[1-9][0-9]?)\|bgcolor-([0-9a-fA-F]{3}){1,2})", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"rotate=((?:3[0-5][0-9]|[12][0-9]{2}|[1-9][0-9]?)|angle-(?:3[0-5][0-9]|[12][0-9]{2}|[1-9][0-9]?)[\|,]bgcolor-([0-9a-fA-F]{3}){1,2})", RegexOptions.Compiled);
/// <summary>
/// The regular expression to search strings for the angle attribute.

5
src/ImageProcessor/Settings.StyleCop

@ -1,4 +1,9 @@
<StyleCopSettings Version="105">
<GlobalSettings>
<CollectionProperty Name="RecognizedWords">
<Value>behaviour</Value>
</CollectionProperty>
</GlobalSettings>
<Analyzers>
<Analyzer AnalyzerId="StyleCop.CSharp.DocumentationRules">
<AnalyzerSettings>

14
src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml

@ -16,6 +16,20 @@
<img src="/images/Penguins.jpg?crop=0-0-300-225" />
</div>
</div>
</section>
<section>
<div class="row">
<div class="column-6">
<h2>Padded</h2>
<img src="/images/Penguins.jpg?width=300&height=500" />
@* <h3>Foreign language test.</h3>
<img src="/images/udendørs.jpg?width=300" />*@
</div>
<div class="column-6">
@* <h2>Cropped </h2>
<img src="/images/Penguins.jpg?crop=0-0-300-225" />*@
</div>
</div>
</section>
<section>
<h2>Filter</h2>

Loading…
Cancel
Save