From 0cffee7da2f2d2ef8ddd0270eabc98e1026191c2 Mon Sep 17 00:00:00 2001 From: James South Date: Wed, 8 Oct 2014 12:42:52 +0100 Subject: [PATCH] Faster pixelate and oilpaint Former-commit-id: 14e4fa2fdef88ce05c76ee4a0446536310ea6eff Former-commit-id: 09ccde9cc33ffeae0de019e3310507cabbb01009 --- src/ImageProcessor.Playground/Program.cs | 4 +- .../Common/Extensions/EnumerableExtensions.cs | 93 +++++++++++++++ src/ImageProcessor/ImageProcessor.csproj | 1 + .../Filters/Artistic/OilPaintingFilter.cs | 110 +++++++++--------- .../Filters/Photo/ComicMatrixFilter.cs | 29 +++-- src/ImageProcessor/Processors/Pixelate.cs | 55 +++++---- 6 files changed, 204 insertions(+), 88 deletions(-) create mode 100644 src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index 5a4269aab..d34732b34 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -77,12 +77,12 @@ namespace ImageProcessor.PlayGround //.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80) //.Resize(layer) //.DetectEdges(new KirschEdgeFilter()) - .DetectEdges(new LaplacianOfGaussianEdgeFilter()) + //.DetectEdges(new LaplacianOfGaussianEdgeFilter()) //.EntropyCrop() //.Filter(MatrixFilters.Comic) //.Filter(MatrixFilters.Comic) //.Filter(MatrixFilters.HiSatch) - //.Pixelate(8) + .Pixelate(8) //.GaussianSharpen(10) .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); diff --git a/src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs b/src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs new file mode 100644 index 000000000..302ceaf78 --- /dev/null +++ b/src/ImageProcessor/Common/Extensions/EnumerableExtensions.cs @@ -0,0 +1,93 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates a series of time saving extension methods to the interface. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Common.Extensions +{ + using System; + using System.Collections.Generic; + + /// + /// Encapsulates a series of time saving extension methods to the interface. + /// + public static class EnumerableExtensions + { + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// The end index, exclusive. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, int toExclusive, int step) + { + // Borrowed from Enumerable.Range + long num = (fromInclusive + toExclusive) - 1L; + if ((toExclusive < 0) || (num > 0x7fffffffL)) + { + throw new ArgumentOutOfRangeException("toExclusive"); + } + + return RangeIterator(fromInclusive, i => i < toExclusive, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) + { + return RangeIterator(fromInclusive, toDelegate, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) + { + int i = fromInclusive; + while (toDelegate(i)) + { + yield return i; + i += step; + } + } + } +} diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index c5f4a8146..5cb599083 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -125,6 +125,7 @@ + diff --git a/src/ImageProcessor/Imaging/Filters/Artistic/OilPaintingFilter.cs b/src/ImageProcessor/Imaging/Filters/Artistic/OilPaintingFilter.cs index d659f6203..2213c043a 100644 --- a/src/ImageProcessor/Imaging/Filters/Artistic/OilPaintingFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/Artistic/OilPaintingFilter.cs @@ -12,6 +12,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic { using System; using System.Drawing; + using System.Threading.Tasks; using ImageProcessor.Common.Extensions; @@ -104,76 +105,81 @@ namespace ImageProcessor.Imaging.Filters.Artistic { using (FastBitmap destinationBitmap = new FastBitmap(destination)) { - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) + Parallel.For( + 0, + height, + y => { - int maxIntensity = 0; - int maxIndex = 0; - int[] intensityBin = new int[this.levels]; - int[] blueBin = new int[this.levels]; - int[] greenBin = new int[this.levels]; - int[] redBin = new int[this.levels]; - - for (int i = 0; i <= radius; i++) + for (int x = 0; x < width; x++) { - int ir = i - radius; - int offsetY = y + ir; - - // Skip the current row - if (offsetY < 0) + int maxIntensity = 0; + int maxIndex = 0; + int[] intensityBin = new int[this.levels]; + int[] blueBin = new int[this.levels]; + int[] greenBin = new int[this.levels]; + int[] redBin = new int[this.levels]; + + for (int i = 0; i <= radius; i++) { - continue; - } - - // Outwith the current bounds so break. - if (offsetY >= height) - { - break; - } + int ir = i - radius; + int offsetY = y + ir; - for (int fx = 0; fx <= radius; fx++) - { - int jr = fx - radius; - int offsetX = x + jr; - - // Skip the column - if (offsetX < 0) + // Skip the current row + if (offsetY < 0) { continue; } - if (offsetX < width) + // Outwith the current bounds so break. + if (offsetY >= height) { - Color color = sourceBitmap.GetPixel(offsetX, offsetY); - - byte sourceBlue = color.B; - byte sourceGreen = color.G; - byte sourceRed = color.R; + break; + } - int currentIntensity = (int)Math.Round(((sourceBlue + sourceGreen + sourceRed) / 3.0 * (this.levels - 1)) / 255.0); + for (int fx = 0; fx <= radius; fx++) + { + int jr = fx - radius; + int offsetX = x + jr; - intensityBin[currentIntensity] += 1; - blueBin[currentIntensity] += sourceBlue; - greenBin[currentIntensity] += sourceGreen; - redBin[currentIntensity] += sourceRed; + // Skip the column + if (offsetX < 0) + { + continue; + } - if (intensityBin[currentIntensity] > maxIntensity) + if (offsetX < width) { - maxIntensity = intensityBin[currentIntensity]; - maxIndex = currentIntensity; + // ReSharper disable once AccessToDisposedClosure + Color color = sourceBitmap.GetPixel(offsetX, offsetY); + + byte sourceBlue = color.B; + byte sourceGreen = color.G; + byte sourceRed = color.R; + + int currentIntensity = (int)Math.Round(((sourceBlue + sourceGreen + sourceRed) / 3.0 * (this.levels - 1)) / 255.0); + + intensityBin[currentIntensity] += 1; + blueBin[currentIntensity] += sourceBlue; + greenBin[currentIntensity] += sourceGreen; + redBin[currentIntensity] += sourceRed; + + if (intensityBin[currentIntensity] > maxIntensity) + { + maxIntensity = intensityBin[currentIntensity]; + maxIndex = currentIntensity; + } } } } - } - byte blue = Math.Abs(blueBin[maxIndex] / maxIntensity).ToByte(); - byte green = Math.Abs(greenBin[maxIndex] / maxIntensity).ToByte(); - byte red = Math.Abs(redBin[maxIndex] / maxIntensity).ToByte(); + byte blue = Math.Abs(blueBin[maxIndex] / maxIntensity).ToByte(); + byte green = Math.Abs(greenBin[maxIndex] / maxIntensity).ToByte(); + byte red = Math.Abs(redBin[maxIndex] / maxIntensity).ToByte(); - destinationBitmap.SetPixel(x, y, Color.FromArgb(red, green, blue)); - } - } + // ReSharper disable once AccessToDisposedClosure + destinationBitmap.SetPixel(x, y, Color.FromArgb(red, green, blue)); + } + }); } } diff --git a/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs b/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs index 0f5bcad66..ac9d488af 100644 --- a/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs @@ -15,6 +15,7 @@ namespace ImageProcessor.Imaging.Filters.Photo using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Runtime.InteropServices; + using System.Threading.Tasks; using ImageProcessor.Common.Extensions; using ImageProcessor.Imaging.Filters.Artistic; @@ -61,7 +62,7 @@ namespace ImageProcessor.Imaging.Filters.Photo // Apply a oil painting filter to the image. highBitmap = new OilPaintingFilter(3, 5).ApplyFilter((Bitmap)image); - + // Draw the edges. edgeBitmap = DrawEdges((Bitmap)image, 120); @@ -383,19 +384,25 @@ namespace ImageProcessor.Imaging.Filters.Photo int width = source.Width; int height = source.Height; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) + Parallel.For( + 0, + height, + y => { - Color sourceColor = sourceBitmap.GetPixel(x, y); - Color destinationColor = destinationBitmap.GetPixel(x, y); - - if (destinationColor.A != 0) + for (int x = 0; x < width; x++) { - destinationBitmap.SetPixel(x, y, Color.FromArgb(sourceColor.B, destinationColor.R, destinationColor.G, destinationColor.B)); + // ReSharper disable AccessToDisposedClosure + Color sourceColor = sourceBitmap.GetPixel(x, y); + Color destinationColor = destinationBitmap.GetPixel(x, y); + + if (destinationColor.A != 0) + { + destinationBitmap.SetPixel(x, y, Color.FromArgb(sourceColor.B, destinationColor.R, destinationColor.G, destinationColor.B)); + } + + // ReSharper restore AccessToDisposedClosure } - } - } + }); } } } diff --git a/src/ImageProcessor/Processors/Pixelate.cs b/src/ImageProcessor/Processors/Pixelate.cs index 437388eb0..7724f9b3f 100644 --- a/src/ImageProcessor/Processors/Pixelate.cs +++ b/src/ImageProcessor/Processors/Pixelate.cs @@ -13,8 +13,10 @@ namespace ImageProcessor.Processors using System; using System.Collections.Generic; using System.Drawing; + using System.Threading.Tasks; using ImageProcessor.Common.Exceptions; + using ImageProcessor.Common.Extensions; using ImageProcessor.Imaging; /// @@ -82,37 +84,44 @@ namespace ImageProcessor.Processors using (FastBitmap fastBitmap = new FastBitmap(newImage)) { - for (int j = y; j < y + height && j < maxHeight; j += size) - { - for (int i = x; i < x + width && i < maxWidth; i += size) - { - int offsetX = offset; - int offsetY = offset; + // Get the range of on the y-plane to choose from. + IEnumerable range = EnumerableExtensions.SteppedRange(y, i => i < y + height && i < maxHeight, size); - // Make sure that the offset is within the boundary of the image. - while (j + offsetY >= maxHeight) + Parallel.ForEach( + range, + j => + { + for (int i = x; i < x + width && i < maxWidth; i += size) { - offsetY--; - } + int offsetX = offset; + int offsetY = offset; - while (i + offsetX >= maxWidth) - { - offsetX--; - } + // Make sure that the offset is within the boundary of the image. + while (j + offsetY >= maxHeight) + { + offsetY--; + } - // Get the pixel color in the centre of the soon to be pixelated area. - Color pixel = fastBitmap.GetPixel(i + offsetX, j + offsetY); + while (i + offsetX >= maxWidth) + { + offsetX--; + } - // For each pixel in the pixelate size, set it to the centre color. - for (int l = j; l < j + size && l < maxHeight; l++) - { - for (int k = i; k < i + size && k < maxWidth; k++) + // Get the pixel color in the centre of the soon to be pixelated area. + // ReSharper disable AccessToDisposedClosure + Color pixel = fastBitmap.GetPixel(i + offsetX, j + offsetY); + + // For each pixel in the pixelate size, set it to the centre color. + for (int l = j; l < j + size && l < maxHeight; l++) { - fastBitmap.SetPixel(k, l, pixel); + for (int k = i; k < i + size && k < maxWidth; k++) + { + fastBitmap.SetPixel(k, l, pixel); + } } + // ReSharper restore AccessToDisposedClosure } - } - } + }); } image.Dispose();