From b47aa8e2cffcbf3e5594960ab11449acd69e6860 Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 30 Sep 2014 17:35:54 +0100 Subject: [PATCH] Adding detect edges to web and moar unit tests Former-commit-id: a73c74f036b0137963147d593d1d0118c7d867d1 --- .../ImageFactoryUnitTests.cs | 138 ++++++++++++++- .../Configuration/Resources/processing.config | 1 + .../ImageProcessor.Web.csproj | 1 + .../Processors/DetectEdges.cs | 166 ++++++++++++++++++ src/ImageProcessor.Web/Processors/Filter.cs | 6 +- src/ImageProcessor.Web/Processors/Resize.cs | 39 ++-- src/ImageProcessor.sln | 2 +- src/ImageProcessor/ImageProcessor.csproj | 6 +- .../Filters/EdgeDetection/I2DEdgeFilter.cs | 2 +- ...3x3Filter.cs => Laplacian3X3EdgeFilter.cs} | 4 +- ...5X5Filter.cs => Laplacian5X5EdgeFilter.cs} | 4 +- ...er.cs => LaplacianOfGaussianEdgeFilter.cs} | 4 +- src/ImageProcessorConsole/Program.cs | 0 .../config/imageprocessor/processing.config | 1 + 14 files changed, 338 insertions(+), 36 deletions(-) create mode 100644 src/ImageProcessor.Web/Processors/DetectEdges.cs rename src/ImageProcessor/Imaging/Filters/EdgeDetection/{Laplacian3x3Filter.cs => Laplacian3X3EdgeFilter.cs} (90%) rename src/ImageProcessor/Imaging/Filters/EdgeDetection/{Laplacian5X5Filter.cs => Laplacian5X5EdgeFilter.cs} (90%) rename src/ImageProcessor/Imaging/Filters/EdgeDetection/{LaplacianOfGaussianFilter.cs => LaplacianOfGaussianEdgeFilter.cs} (90%) create mode 100644 src/ImageProcessorConsole/Program.cs diff --git a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index dd7d00563..87cd3d51d 100644 --- a/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/src/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -13,6 +13,7 @@ namespace ImageProcessor.UnitTests using System.Linq; using ImageProcessor.Imaging; + using ImageProcessor.Imaging.Filters.EdgeDetection; using ImageProcessor.Imaging.Filters.Photo; using NUnit.Framework; @@ -246,9 +247,9 @@ namespace ImageProcessor.UnitTests Image original = (Image)imageFactory.Image.Clone(); imageFactory.Watermark(new TextLayer { - FontFamily = new FontFamily("Arial"), - FontSize = 10, - Position = new Point(10, 10), + FontFamily = new FontFamily("Arial"), + FontSize = 10, + Position = new Point(10, 10), Text = "Lorem ipsum dolor" }); Assert.AreNotEqual(original, imageFactory.Image); @@ -546,6 +547,137 @@ namespace ImageProcessor.UnitTests } } + /// + /// Tests that the images hue has been altered. + /// + [Test] + public void TestHue() + { + foreach (FileInfo file in this.ListInputFiles()) + { + using (ImageFactory imageFactory = new ImageFactory()) + { + imageFactory.Load(file.FullName); + Image original = (Image)imageFactory.Image.Clone(); + imageFactory.Hue(90); + Assert.AreNotEqual(original, imageFactory.Image); + + imageFactory.Reset(); + + imageFactory.Hue(116, true); + Assert.AreNotEqual(original, imageFactory.Image); + } + } + } + + /// + /// Tests that the image has been pixelated. + /// + [Test] + public void TestPixelate() + { + foreach (FileInfo file in this.ListInputFiles()) + { + using (ImageFactory imageFactory = new ImageFactory()) + { + imageFactory.Load(file.FullName); + Image original = (Image)imageFactory.Image.Clone(); + imageFactory.Pixelate(8); + Assert.AreNotEqual(original, imageFactory.Image); + } + } + } + + /// + /// Tests that the images quality has been set. + /// + [Test] + public void TestQuality() + { + foreach (FileInfo file in this.ListInputFiles()) + { + using (ImageFactory imageFactory = new ImageFactory()) + { + imageFactory.Load(file.FullName); + int original = imageFactory.CurrentImageFormat.Quality; + imageFactory.Quality(69); + int updated = imageFactory.CurrentImageFormat.Quality; + + Assert.AreNotEqual(original, updated); + } + } + } + + /// + /// Tests that the image has had a color replaced. + /// + [Test] + public void TestReplaceColor() + { + foreach (FileInfo file in this.ListInputFiles()) + { + using (ImageFactory imageFactory = new ImageFactory()) + { + imageFactory.Load(file.FullName); + Image original = (Image)imageFactory.Image.Clone(); + imageFactory.ReplaceColor(Color.White, Color.Black, 90); + Assert.AreNotEqual(original, imageFactory.Image); + } + } + } + + /// + /// Tests that the various edge detection algorithms are applied. + /// + [Test] + public void TestEdgeDetection() + { + foreach (FileInfo file in this.ListInputFiles()) + { + using (ImageFactory imageFactory = new ImageFactory()) + { + imageFactory.Load(file.FullName); + Image original = (Image)imageFactory.Image.Clone(); + + imageFactory.DetectEdges(new KayyaliEdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + + imageFactory.DetectEdges(new KirschEdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + + imageFactory.DetectEdges(new Laplacian3X3EdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + + imageFactory.DetectEdges(new Laplacian5X5EdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + + imageFactory.DetectEdges(new LaplacianOfGaussianEdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + + imageFactory.DetectEdges(new PrewittEdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + + imageFactory.DetectEdges(new RobertsCrossEdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + + imageFactory.DetectEdges(new ScharrEdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + + imageFactory.DetectEdges(new SobelEdgeFilter()); + Assert.AreNotEqual(original, imageFactory.Image); + imageFactory.Reset(); + } + } + } + /// /// Gets the files matching the given extensions. /// diff --git a/src/ImageProcessor.Web/Configuration/Resources/processing.config b/src/ImageProcessor.Web/Configuration/Resources/processing.config index ce00ddffa..bbb28f6e8 100644 --- a/src/ImageProcessor.Web/Configuration/Resources/processing.config +++ b/src/ImageProcessor.Web/Configuration/Resources/processing.config @@ -7,6 +7,7 @@ + diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 05b2299b1..e98b1ad50 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -46,6 +46,7 @@ + diff --git a/src/ImageProcessor.Web/Processors/DetectEdges.cs b/src/ImageProcessor.Web/Processors/DetectEdges.cs new file mode 100644 index 000000000..3a65730ad --- /dev/null +++ b/src/ImageProcessor.Web/Processors/DetectEdges.cs @@ -0,0 +1,166 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Produces an image with the detected edges highlighted. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Processors +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Text.RegularExpressions; + using System.Web.Compilation; + + using ImageProcessor.Common.Extensions; + using ImageProcessor.Imaging.Filters.EdgeDetection; + using ImageProcessor.Processors; + + /// + /// Produces an image with the detected edges highlighted. + /// + public class DetectEdges : IWebGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = BuildRegex(); + + /// + /// The regular expression to search strings for the greyscale attribute. + /// + private static readonly Regex GreyscaleRegex = new Regex(@"greyscale=false", RegexOptions.Compiled); + + /// + /// The edge detectors. + /// + private static Dictionary detectors; + + /// + /// Initializes a new instance of the class. + /// + public DetectEdges() + { + this.Processor = new ImageProcessor.Processors.DetectEdges(); + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder { get; private set; } + + /// + /// Gets the processor. + /// + /// + /// The processor. + /// + public IGraphicsProcessor Processor { get; private set; } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + + // First merge the matches so we can parse . + StringBuilder stringBuilder = new StringBuilder(); + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + stringBuilder.Append(queryString); + } + + index += 1; + } + } + + if (this.SortOrder < int.MaxValue) + { + // Match syntax + string toParse = stringBuilder.ToString(); + IEdgeFilter filter = this.ParseFilter(toParse); + bool greyscale = !GreyscaleRegex.IsMatch(toParse); + + this.Processor.DynamicParameter = new Tuple(filter, greyscale); + } + + return this.SortOrder; + } + + /// + /// Builds a regular expression from the type, this allows extensibility. + /// + /// + /// The to match matrix filters. + /// + private static Regex BuildRegex() + { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.Append("detectedges=("); + + Type type = typeof(IEdgeFilter); + + // Build a list of native IEdgeFilter instances. + detectors = BuildManager.GetReferencedAssemblies() + .Cast() + .SelectMany(s => s.GetLoadableTypes()) + .Where(t => type.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract) + .ToDictionary(t => t.Name.ToLowerInvariant().Replace("edgefilter", string.Empty), Activator.CreateInstance); + + stringBuilder.Append(string.Join("|", detectors.Keys.ToList())); + + stringBuilder.Append(")"); + + return new Regex(stringBuilder.ToString(), RegexOptions.IgnoreCase); + } + + /// + /// Parses the input string to return the correct . + /// + /// + /// The identifier. + /// + /// + /// The . + /// + private IEdgeFilter ParseFilter(string identifier) + { + return (IEdgeFilter)detectors[this.RegexPattern.Match(identifier).Value.Split('=')[1]]; + } + } +} diff --git a/src/ImageProcessor.Web/Processors/Filter.cs b/src/ImageProcessor.Web/Processors/Filter.cs index 533bf2e1d..f9c8fec7c 100644 --- a/src/ImageProcessor.Web/Processors/Filter.cs +++ b/src/ImageProcessor.Web/Processors/Filter.cs @@ -51,11 +51,7 @@ namespace ImageProcessor.Web.Processors /// /// Gets the order in which this processor is to be used in a chain. /// - public int SortOrder - { - get; - private set; - } + public int SortOrder { get; private set; } /// /// Gets the processor. diff --git a/src/ImageProcessor.Web/Processors/Resize.cs b/src/ImageProcessor.Web/Processors/Resize.cs index ca980b232..207cd0b98 100644 --- a/src/ImageProcessor.Web/Processors/Resize.cs +++ b/src/ImageProcessor.Web/Processors/Resize.cs @@ -128,24 +128,29 @@ namespace ImageProcessor.Web.Processors } } - // Match syntax - string toParse = stringBuilder.ToString(); - - Size size = this.ParseSize(toParse); - ResizeLayer resizeLayer = new ResizeLayer(size) + if (this.SortOrder < int.MaxValue) { - ResizeMode = this.ParseMode(toParse), - AnchorPosition = this.ParsePosition(toParse), - Upscale = !UpscaleRegex.IsMatch(toParse), - CenterCoordinates = this.ParseCoordinates(toParse), - }; - - this.Processor.DynamicParameter = resizeLayer; - - // Correctly parse any restrictions. - string restrictions; - this.Processor.Settings.TryGetValue("RestrictTo", out restrictions); - ((ImageProcessor.Processors.Resize)this.Processor).RestrictedSizes = this.ParseRestrictions(restrictions); + // Match syntax + string toParse = stringBuilder.ToString(); + + Size size = this.ParseSize(toParse); + ResizeLayer resizeLayer = new ResizeLayer(size) + { + ResizeMode = this.ParseMode(toParse), + AnchorPosition = this.ParsePosition(toParse), + Upscale = !UpscaleRegex.IsMatch(toParse), + CenterCoordinates = this.ParseCoordinates(toParse), + }; + + this.Processor.DynamicParameter = resizeLayer; + + // Correctly parse any restrictions. + string restrictions; + this.Processor.Settings.TryGetValue("RestrictTo", out restrictions); + ((ImageProcessor.Processors.Resize)this.Processor).RestrictedSizes = this.ParseRestrictions( + restrictions); + } + return this.SortOrder; } diff --git a/src/ImageProcessor.sln b/src/ImageProcessor.sln index 335de55aa..52fa46752 100644 --- a/src/ImageProcessor.sln +++ b/src/ImageProcessor.sln @@ -22,7 +22,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web", "Image EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Plugins.WebP", "Plugins\ImageProcessor\ImageProcessor.Plugins.WebP\ImageProcessor.Plugins.WebP.csproj", "{2CF69699-959A-44DC-A281-4E2596C25043}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Playground", "ImageProcessor.Playground\ImageProcessor.Playground.csproj", "{7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Playground", "ImageProcessorConsole\ImageProcessor.Playground.csproj", "{7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web.UnitTests", "ImageProcessor.Web.UnitTests\ImageProcessor.Web.UnitTests.csproj", "{961340C8-8C93-401D-A0A2-FF9EC61E5260}" EndProject diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index dd4fa8015..f234473a6 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -81,9 +81,9 @@ - - - + + + diff --git a/src/ImageProcessor/Imaging/Filters/EdgeDetection/I2DEdgeFilter.cs b/src/ImageProcessor/Imaging/Filters/EdgeDetection/I2DEdgeFilter.cs index e6568c19f..7e3fb9be8 100644 --- a/src/ImageProcessor/Imaging/Filters/EdgeDetection/I2DEdgeFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/EdgeDetection/I2DEdgeFilter.cs @@ -1,5 +1,5 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // diff --git a/src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian3x3Filter.cs b/src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian3X3EdgeFilter.cs similarity index 90% rename from src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian3x3Filter.cs rename to src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian3X3EdgeFilter.cs index e4c694496..ccaa3d35e 100644 --- a/src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian3x3Filter.cs +++ b/src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian3X3EdgeFilter.cs @@ -1,5 +1,5 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // @@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection /// The Laplacian 3 x 3 operator filter. /// /// - public class Laplacian3X3Filter : IEdgeFilter + public class Laplacian3X3EdgeFilter : IEdgeFilter { /// /// Gets the horizontal gradient operator. diff --git a/src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian5X5Filter.cs b/src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian5X5EdgeFilter.cs similarity index 90% rename from src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian5X5Filter.cs rename to src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian5X5EdgeFilter.cs index 97bd90cf8..b25abbac7 100644 --- a/src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian5X5Filter.cs +++ b/src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian5X5EdgeFilter.cs @@ -1,5 +1,5 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // @@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection /// The Laplacian 5 x 5 operator filter. /// /// - public class Laplacian5X5Filter : IEdgeFilter + public class Laplacian5X5EdgeFilter : IEdgeFilter { /// /// Gets the horizontal gradient operator. diff --git a/src/ImageProcessor/Imaging/Filters/EdgeDetection/LaplacianOfGaussianFilter.cs b/src/ImageProcessor/Imaging/Filters/EdgeDetection/LaplacianOfGaussianEdgeFilter.cs similarity index 90% rename from src/ImageProcessor/Imaging/Filters/EdgeDetection/LaplacianOfGaussianFilter.cs rename to src/ImageProcessor/Imaging/Filters/EdgeDetection/LaplacianOfGaussianEdgeFilter.cs index b2c4cdd2d..947796a38 100644 --- a/src/ImageProcessor/Imaging/Filters/EdgeDetection/LaplacianOfGaussianFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/EdgeDetection/LaplacianOfGaussianEdgeFilter.cs @@ -1,5 +1,5 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // @@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection /// The Laplacian of Gaussian operator filter. /// /// - public class LaplacianOfGaussianFilter : IEdgeFilter + public class LaplacianOfGaussianEdgeFilter : IEdgeFilter { /// /// Gets the horizontal gradient operator. diff --git a/src/ImageProcessorConsole/Program.cs b/src/ImageProcessorConsole/Program.cs new file mode 100644 index 000000000..e69de29bb diff --git a/src/TestWebsites/MVC/config/imageprocessor/processing.config b/src/TestWebsites/MVC/config/imageprocessor/processing.config index 2dc2d374e..33c9a5c65 100644 --- a/src/TestWebsites/MVC/config/imageprocessor/processing.config +++ b/src/TestWebsites/MVC/config/imageprocessor/processing.config @@ -8,6 +8,7 @@ +