From c2db72794587277d807c573e51deff7b2a246f98 Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 11 Feb 2014 16:00:26 +0000 Subject: [PATCH] Added new percentile based cropping system Former-commit-id: c50657927da59f8acc381780e18e50750f511a3b --- .../Extensions/StringExtensions.cs | 31 ++++- src/ImageProcessor/ImageFactory.cs | 25 +++- src/ImageProcessor/ImageProcessor.csproj | 2 + src/ImageProcessor/Imaging/CropLayer.cs | 81 +++++++++++++ src/ImageProcessor/Imaging/CropMode.cs | 28 +++++ src/ImageProcessor/Processors/Crop.cs | 111 ++++++++++++++++-- src/ImageProcessor/Processors/Resize.cs | 1 + .../Views/Home/Index.cshtml | 28 +++-- 8 files changed, 281 insertions(+), 26 deletions(-) create mode 100644 src/ImageProcessor/Imaging/CropLayer.cs create mode 100644 src/ImageProcessor/Imaging/CropMode.cs diff --git a/src/ImageProcessor/Extensions/StringExtensions.cs b/src/ImageProcessor/Extensions/StringExtensions.cs index 40380a1aa..9377ea2ed 100644 --- a/src/ImageProcessor/Extensions/StringExtensions.cs +++ b/src/ImageProcessor/Extensions/StringExtensions.cs @@ -123,7 +123,7 @@ namespace ImageProcessor.Extensions throw new ArgumentNullException("expression"); } - Regex regex = new Regex(@"\d+", RegexOptions.Compiled); + Regex regex = new Regex(@"[\d+]+(?=[,|])|[\d+]+(?![,|])", RegexOptions.Compiled); MatchCollection matchCollection = regex.Matches(expression); @@ -139,6 +139,35 @@ namespace ImageProcessor.Extensions return matches; } + + /// + /// Creates an array of floats scraped from the String. + /// + /// The String instance that this method extends. + /// An array of floats scraped from the String. + public static float[] ToPositiveFloatArray(this string expression) + { + if (string.IsNullOrWhiteSpace(expression)) + { + throw new ArgumentNullException("expression"); + } + + Regex regex = new Regex(@"[\d+\.]+(?=[,|])|[\d+\.]+(?![,|])", RegexOptions.Compiled); + + MatchCollection matchCollection = regex.Matches(expression); + + // Get the collections. + int count = matchCollection.Count; + float[] matches = new float[count]; + + // Loop and parse the int values. + for (int i = 0; i < count; i++) + { + matches[i] = float.Parse(matchCollection[i].Value); + } + + return matches; + } #endregion #region Files and Paths diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 3b610f444..4dfc2e297 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -395,7 +395,30 @@ namespace ImageProcessor { if (this.ShouldProcess) { - Crop crop = new Crop { DynamicParameter = rectangle }; + CropLayer cropLayer = new CropLayer(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom, CropMode.Pixels); + + Crop crop = new Crop { DynamicParameter = cropLayer }; + + this.Image = crop.ProcessImage(this); + } + + return this; + } + + /// + /// Crops the current image to the given location and size. + /// + /// + /// The containing the coordinates and mode to crop the image with. + /// + /// + /// The current instance of the class. + /// + public ImageFactory Crop(CropLayer cropLayer) + { + if (this.ShouldProcess) + { + Crop crop = new Crop { DynamicParameter = cropLayer }; this.Image = crop.ProcessImage(this); } diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 5a5b7c98e..f75cf9ff9 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -66,6 +66,8 @@ + + diff --git a/src/ImageProcessor/Imaging/CropLayer.cs b/src/ImageProcessor/Imaging/CropLayer.cs new file mode 100644 index 000000000..b9b163fb2 --- /dev/null +++ b/src/ImageProcessor/Imaging/CropLayer.cs @@ -0,0 +1,81 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates the properties required to crop an image. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + using System; + + /// + /// Encapsulates the properties required to crop an image. + /// + public class CropLayer + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The left coordinate of the crop layer. + /// + /// + /// The top coordinate of the crop layer. + /// + /// + /// The right coordinate of the crop layer. + /// + /// + /// The bottom coordinate of the crop layer. + /// + /// + /// The . + /// + /// + /// If the is set to CropMode.Percentage then the four coordinates + /// become percentages to reduce from each edge. + /// + public CropLayer(float left, float top, float right, float bottom, CropMode cropMode = CropMode.Percentage) + { + if (left < 0 || top < 0 || right < 0 || bottom < 0) + { + throw new ArgumentOutOfRangeException(); + } + + this.Left = left; + this.Top = top; + this.Right = right; + this.Bottom = bottom; + this.CropMode = cropMode; + } + + /// + /// Gets or sets the left coordinate of the crop layer. + /// + public float Left { get; set; } + + /// + /// Gets or sets the top coordinate of the crop layer. + /// + public float Top { get; set; } + + /// + /// Gets or sets the right coordinate of the crop layer. + /// + public float Right { get; set; } + + /// + /// Gets or sets the bottom coordinate of the crop layer. + /// + public float Bottom { get; set; } + + /// + /// Gets or sets the . + /// + public CropMode CropMode { get; set; } + } +} diff --git a/src/ImageProcessor/Imaging/CropMode.cs b/src/ImageProcessor/Imaging/CropMode.cs new file mode 100644 index 000000000..a0b9fe0d2 --- /dev/null +++ b/src/ImageProcessor/Imaging/CropMode.cs @@ -0,0 +1,28 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Enumerated cop modes to apply to cropped images. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + /// + /// Enumerated cop modes to apply to cropped images. + /// + public enum CropMode + { + /// + /// Crops the image using the standard rectangle model of x, y, width, height. + /// + Pixels, + + /// + /// Crops the image using percentages model left, top, right, bottom. + /// + Percentage + } +} diff --git a/src/ImageProcessor/Processors/Crop.cs b/src/ImageProcessor/Processors/Crop.cs index 24123bb25..336885b80 100644 --- a/src/ImageProcessor/Processors/Crop.cs +++ b/src/ImageProcessor/Processors/Crop.cs @@ -15,9 +15,11 @@ namespace ImageProcessor.Processors using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; + using System.Text; using System.Text.RegularExpressions; using ImageProcessor.Extensions; + using ImageProcessor.Imaging; #endregion @@ -30,7 +32,17 @@ namespace ImageProcessor.Processors /// The regular expression to search strings for. /// /// - private static readonly Regex QueryRegex = new Regex(@"crop=\d+[,-]\d+[,-]\d+[,-]\d+", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"crop=\d+(.\d+)?[,-]\d+(.\d+)?[,-]\d+(.\d+)?[,-]\d+(.\d+)?|cropmode=(pixels|percent)", RegexOptions.Compiled); + + /// + /// The coordinate regex. + /// + private static readonly Regex CoordinateRegex = new Regex(@"crop=\d+(.\d+)?[,-]\d+(.\d+)?[,-]\d+(.\d+)?[,-]\d+(.\d+)?", RegexOptions.Compiled); + + /// + /// The mode regex. + /// + private static readonly Regex ModeRegex = new Regex(@"cropmode=(pixels|percent)", RegexOptions.Compiled); #region IGraphicsProcessor Members /// @@ -87,6 +99,9 @@ namespace ImageProcessor.Processors // Set the sort order to max to allow filtering. this.SortOrder = int.MaxValue; + // First merge the matches so we can parse . + StringBuilder stringBuilder = new StringBuilder(); + foreach (Match match in this.RegexPattern.Matches(queryString)) { if (match.Success) @@ -95,21 +110,26 @@ namespace ImageProcessor.Processors { // Set the index on the first instance only. this.SortOrder = match.Index; - int[] coordinates = match.Value.ToPositiveIntegerArray(); - - int x = coordinates[0]; - int y = coordinates[1]; - int width = coordinates[2]; - int height = coordinates[3]; - - Rectangle rectangle = new Rectangle(x, y, width, height); - this.DynamicParameter = rectangle; } + stringBuilder.Append(match.Value); + index += 1; } } + if (this.SortOrder < int.MaxValue) + { + // Match syntax + string toParse = stringBuilder.ToString(); + + float[] coordinates = this.ParseCoordinates(toParse); + CropMode cropMode = this.ParseMode(toParse); + + CropLayer cropLayer = new CropLayer(coordinates[0], coordinates[1], coordinates[2], coordinates[3], cropMode); + this.DynamicParameter = cropLayer; + } + return this.SortOrder; } @@ -129,10 +149,27 @@ namespace ImageProcessor.Processors Image image = factory.Image; try { - Rectangle rectangle = this.DynamicParameter; - int sourceWidth = image.Width; int sourceHeight = image.Height; + RectangleF rectangleF; + CropLayer cropLayer = this.DynamicParameter; + + if (cropLayer.CropMode == CropMode.Percentage) + { + // Work out the percentages. + float left = cropLayer.Left * sourceWidth; + float top = cropLayer.Top * sourceWidth; + float right = (sourceWidth - (cropLayer.Right * sourceWidth)) - left; + float bottom = (sourceHeight - (cropLayer.Bottom * sourceHeight)) - top; + + rectangleF = new RectangleF(left, top, right, bottom); + } + else + { + rectangleF = new RectangleF(cropLayer.Left, cropLayer.Top, cropLayer.Right, cropLayer.Bottom); + } + + Rectangle rectangle = Rectangle.Round(rectangleF); if (rectangle.X < sourceWidth && rectangle.Y < sourceHeight) { @@ -190,5 +227,55 @@ namespace ImageProcessor.Processors return image; } #endregion + + /// + /// Returns the correct for the given string. + /// + /// + /// The input string containing the value to parse. + /// + /// + /// The correct . + /// + private CropMode ParseMode(string input) + { + foreach (Match match in ModeRegex.Matches(input)) + { + // Split on = + string mode = match.Value.Split('=')[1]; + + switch (mode) + { + case "percent": + return CropMode.Percentage; + case "pixels": + return CropMode.Pixels; + } + } + + return CropMode.Pixels; + } + + + /// + /// Returns the correct for the given string. + /// + /// + /// The input string containing the value to parse. + /// + /// + /// The correct . + /// + private float[] ParseCoordinates(string input) + { + float[] floats = { }; + + foreach (Match match in CoordinateRegex.Matches(input)) + { + floats = match.Value.ToPositiveFloatArray(); + } + + return floats; + } } } diff --git a/src/ImageProcessor/Processors/Resize.cs b/src/ImageProcessor/Processors/Resize.cs index 9cb1fbc5a..7989b6bb8 100644 --- a/src/ImageProcessor/Processors/Resize.cs +++ b/src/ImageProcessor/Processors/Resize.cs @@ -59,6 +59,7 @@ namespace ImageProcessor.Processors /// The regular expression to search strings for the upscale attribute. /// private static readonly Regex UpscaleRegex = new Regex(@"upscale=false", RegexOptions.Compiled); + #region IGraphicsProcessor Members /// /// Gets the regular expression to search strings for. diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml b/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml index 0cd6e38b3..1ea44b27b 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml +++ b/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml @@ -14,7 +14,11 @@

Cropped

- + + +

Cropped Percent

+ +
@@ -185,17 +189,17 @@

Color Profiles

@*
-
-
-

CMYK original jpg

- -
-
-

sRGB original jpg

- -
-
-
*@ +
+
+

CMYK original jpg

+ +
+
+

sRGB original jpg

+ +
+
+ *@