From 3282bff5bb7a3f5f201b6add294dfeed03a9fc70 Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 7 Oct 2014 22:51:29 +0100 Subject: [PATCH] More speed improvements Former-commit-id: a7968b480695de2d5a30350d4b1e8f6a11d0f176 Former-commit-id: a9710c1bb24a748f17f21a9de45538aae448ff56 --- .../images/input/circle3.png | Bin 0 -> 5322 bytes src/ImageProcessor/Imaging/Convolution.cs | 158 +++++++++--------- src/ImageProcessor/Imaging/FastBitmap.cs | 6 +- .../Filters/Binarization/BinaryThreshold.cs | 25 ++- .../EdgeDetection/ConvolutionFilter.cs | 111 ++++++------ src/ImageProcessor/Processors/EntropyCrop.cs | 6 +- 6 files changed, 165 insertions(+), 141 deletions(-) create mode 100644 src/ImageProcessor.Playground/images/input/circle3.png diff --git a/src/ImageProcessor.Playground/images/input/circle3.png b/src/ImageProcessor.Playground/images/input/circle3.png new file mode 100644 index 0000000000000000000000000000000000000000..4bd2341656c34f69a45b0240836963087e9673e0 GIT binary patch literal 5322 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 literal 0 HcmV?d00001 diff --git a/src/ImageProcessor/Imaging/Convolution.cs b/src/ImageProcessor/Imaging/Convolution.cs index 8e9495637..36e57df41 100644 --- a/src/ImageProcessor/Imaging/Convolution.cs +++ b/src/ImageProcessor/Imaging/Convolution.cs @@ -12,6 +12,7 @@ namespace ImageProcessor.Imaging { using System; using System.Drawing; + using System.Threading.Tasks; using ImageProcessor.Common.Extensions; @@ -274,101 +275,106 @@ namespace ImageProcessor.Imaging int threshold = this.Threshold; // For each line - for (int y = 0; y < height; y++) - { - // For each pixel - for (int x = 0; x < width; x++) + Parallel.For( + 0, + height, + y => { - // The number of kernel elements taken into account - int processedKernelSize; - - // Colour sums - double blue; - double alpha; - double divider; - double green; - double red = green = blue = alpha = divider = processedKernelSize = 0; - - // For each kernel row - for (int i = 0; i < kernelLength; i++) + // For each pixel + for (int x = 0; x < width; x++) { - int ir = i - radius; - int offsetY = y + ir; - - // Skip the current row - if (offsetY < 0) + // The number of kernel elements taken into account + int processedKernelSize; + + // Colour sums + double blue; + double alpha; + double divider; + double green; + double red = green = blue = alpha = divider = processedKernelSize = 0; + + // For each kernel row + for (int i = 0; i < kernelLength; i++) { - continue; - } + int ir = i - radius; + int offsetY = y + ir; - // Outwith the current bounds so break. - if (offsetY >= height) - { - break; - } - - // For each kernel column - for (int j = 0; j < kernelLength; j++) - { - int jr = j - 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 = sourceFastBitmap.GetPixel(offsetX, offsetY); - double k = kernel[i, j]; - divider += k; - - red += k * color.R; - green += k * color.G; - blue += k * color.B; - alpha += k * color.A; + break; + } - processedKernelSize++; + // For each kernel column + for (int j = 0; j < kernelLength; j++) + { + int jr = j - radius; + int offsetX = x + jr; + + // Skip the column + if (offsetX < 0) + { + continue; + } + + if (offsetX < width) + { + // ReSharper disable once AccessToDisposedClosure + Color color = sourceFastBitmap.GetPixel(offsetX, offsetY); + double k = kernel[i, j]; + divider += k; + + red += k * color.R; + green += k * color.G; + blue += k * color.B; + alpha += k * color.A; + + processedKernelSize++; + } } } - } - // Check to see if all kernel elements were processed - if (processedKernelSize == kernelSize) - { - // All kernel elements are processed; we are not on the edge. - divider = this.Divider; - } - else - { - // We are on an edge; do we need to use dynamic divider or not? - if (!this.UseDynamicDividerForEdges) + // Check to see if all kernel elements were processed + if (processedKernelSize == kernelSize) { - // Apply the set divider. + // All kernel elements are processed; we are not on the edge. divider = this.Divider; } - } + else + { + // We are on an edge; do we need to use dynamic divider or not? + if (!this.UseDynamicDividerForEdges) + { + // Apply the set divider. + divider = this.Divider; + } + } - // Check and apply the divider - if ((long)divider != 0) - { - red /= divider; - green /= divider; - blue /= divider; - alpha /= divider; - } + // Check and apply the divider + if ((long)divider != 0) + { + red /= divider; + green /= divider; + blue /= divider; + alpha /= divider; + } - // Add any applicable threshold. - red += threshold; - green += threshold; - blue += threshold; - alpha += threshold; + // Add any applicable threshold. + red += threshold; + green += threshold; + blue += threshold; + alpha += threshold; - destinationFastBitmap.SetPixel(x, y, Color.FromArgb(alpha.ToByte(), red.ToByte(), green.ToByte(), blue.ToByte())); - } - } + // ReSharper disable once AccessToDisposedClosure + destinationFastBitmap.SetPixel(x, y, Color.FromArgb(alpha.ToByte(), red.ToByte(), green.ToByte(), blue.ToByte())); + } + }); } } diff --git a/src/ImageProcessor/Imaging/FastBitmap.cs b/src/ImageProcessor/Imaging/FastBitmap.cs index aea18d326..8b5fd8715 100644 --- a/src/ImageProcessor/Imaging/FastBitmap.cs +++ b/src/ImageProcessor/Imaging/FastBitmap.cs @@ -156,6 +156,7 @@ namespace ImageProcessor.Imaging /// The at the given pixel. public Color GetPixel(int x, int y) { +#if DEBUG if ((x < 0) || (x >= this.width)) { throw new ArgumentOutOfRangeException("x", "Value cannot be less than zero or greater than the bitmap width."); @@ -165,7 +166,7 @@ namespace ImageProcessor.Imaging { throw new ArgumentOutOfRangeException("y", "Value cannot be less than zero or greater than the bitmap height."); } - +#endif PixelData* data = this[x, y]; return Color.FromArgb(data->A, data->R, data->G, data->B); } @@ -181,6 +182,7 @@ namespace ImageProcessor.Imaging /// public void SetPixel(int x, int y, Color color) { +#if DEBUG if ((x < 0) || (x >= this.width)) { throw new ArgumentOutOfRangeException("x", "Value cannot be less than zero or greater than the bitmap width."); @@ -190,7 +192,7 @@ namespace ImageProcessor.Imaging { throw new ArgumentOutOfRangeException("y", "Value cannot be less than zero or greater than the bitmap height."); } - +#endif PixelData* data = this[x, y]; data->R = color.R; data->G = color.G; diff --git a/src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs b/src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs index 96478497f..e8ffa61e7 100644 --- a/src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs +++ b/src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs @@ -11,6 +11,10 @@ namespace ImageProcessor.Imaging.Filters.Binarization { using System.Drawing; + using System.Drawing.Imaging; + using System.Threading.Tasks; + + using ImageProcessor.Imaging.Filters.Photo; /// /// Performs binary threshold filtering against a given greyscale image. @@ -20,7 +24,7 @@ namespace ImageProcessor.Imaging.Filters.Binarization /// /// The threshold value. /// - private byte threshold = 128; + private byte threshold = 10; /// /// Initializes a new instance of the class. @@ -61,14 +65,19 @@ namespace ImageProcessor.Imaging.Filters.Binarization using (FastBitmap sourceBitmap = new FastBitmap(source)) { - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) + Parallel.For( + 0, + height, + y => { - Color color = sourceBitmap.GetPixel(x, y); - sourceBitmap.SetPixel(x, y, color.B >= this.threshold ? Color.White : Color.Black); - } - } + for (int x = 0; x < width; x++) + { + // ReSharper disable AccessToDisposedClosure + Color color = sourceBitmap.GetPixel(x, y); + sourceBitmap.SetPixel(x, y, color.B >= this.threshold ? Color.White : Color.Black); + // ReSharper restore AccessToDisposedClosure + } + }); } return source; diff --git a/src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs b/src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs index a5ec3c24a..450262c55 100644 --- a/src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs @@ -49,7 +49,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection } /// - /// Processes the given bitmap to apply the current instances . + /// Processes the given bitmap to apply the current instance of . /// /// The image to process. /// A processed bitmap. @@ -184,7 +184,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection } /// - /// Processes the given bitmap to apply the current instances . + /// Processes the given bitmap to apply the current instance of . /// /// The image to process. /// A processed bitmap. @@ -229,74 +229,79 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection using (FastBitmap destinationBitmap = new FastBitmap(destination)) { // Loop through the pixels. - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) + Parallel.For( + 0, + height, + y => { - double rX = 0; - double rY = 0; - double gX = 0; - double gY = 0; - double bX = 0; - double bY = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelLength; fy++) + for (int x = 0; x < width; x++) { - int fyr = fy - radius; - int offsetY = y + fyr; - - // Skip the current row - if (offsetY < 0) - { - continue; - } - - // Outwith the current bounds so break. - if (offsetY >= height) - { - break; - } + double rX = 0; + double rY = 0; + double gX = 0; + double gY = 0; + double bX = 0; + double bY = 0; - for (int fx = 0; fx < kernelLength; fx++) + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelLength; fy++) { - int fxr = fx - radius; - int offsetX = x + fxr; + int fyr = fy - radius; + int offsetY = y + fyr; - // 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) + { + break; + } + + for (int fx = 0; fx < kernelLength; fx++) { - Color currentColor = sourceBitmap.GetPixel(offsetX, offsetY); - double r = currentColor.R; - double g = currentColor.G; - double b = currentColor.B; + int fxr = fx - radius; + int offsetX = x + fxr; - rX += horizontalFilter[fy, fx] * r; - rY += verticalFilter[fy, fx] * r; + // Skip the column + if (offsetX < 0) + { + continue; + } - gX += horizontalFilter[fy, fx] * g; - gY += verticalFilter[fy, fx] * g; + if (offsetX < width) + { + // ReSharper disable once AccessToDisposedClosure + Color currentColor = sourceBitmap.GetPixel(offsetX, offsetY); + double r = currentColor.R; + double g = currentColor.G; + double b = currentColor.B; - bX += horizontalFilter[fy, fx] * b; - bY += verticalFilter[fy, fx] * b; + rX += horizontalFilter[fy, fx] * r; + rY += verticalFilter[fy, fx] * r; + + gX += horizontalFilter[fy, fx] * g; + gY += verticalFilter[fy, fx] * g; + + bX += horizontalFilter[fy, fx] * b; + bY += verticalFilter[fy, fx] * b; + } } } - } - // Apply the equation and sanitize. - byte red = Math.Sqrt((rX * rX) + (rY * rY)).ToByte(); - byte green = Math.Sqrt((gX * gX) + (gY * gY)).ToByte(); - byte blue = Math.Sqrt((bX * bX) + (bY * bY)).ToByte(); + // Apply the equation and sanitize. + byte red = Math.Sqrt((rX * rX) + (rY * rY)).ToByte(); + byte green = Math.Sqrt((gX * gX) + (gY * gY)).ToByte(); + byte blue = Math.Sqrt((bX * bX) + (bY * bY)).ToByte(); - Color newColor = Color.FromArgb(red, green, blue); - destinationBitmap.SetPixel(x, y, newColor); - } - } + Color newColor = Color.FromArgb(red, green, blue); + // ReSharper disable once AccessToDisposedClosure + destinationBitmap.SetPixel(x, y, newColor); + } + }); } } } diff --git a/src/ImageProcessor/Processors/EntropyCrop.cs b/src/ImageProcessor/Processors/EntropyCrop.cs index f8e6a3199..c14709e48 100644 --- a/src/ImageProcessor/Processors/EntropyCrop.cs +++ b/src/ImageProcessor/Processors/EntropyCrop.cs @@ -11,6 +11,7 @@ namespace ImageProcessor.Processors using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; + using System.IO; using ImageProcessor.Common.Exceptions; using ImageProcessor.Imaging; @@ -59,9 +60,10 @@ namespace ImageProcessor.Processors try { - grey = new ConvolutionFilter(new SobelEdgeFilter(), true).ProcessFilter((Bitmap)image); + // Detect the edges then strip out middle shades. + grey = new ConvolutionFilter(new SobelEdgeFilter(), true).Process2DFilter((Bitmap)image); grey = new BinaryThreshold(threshold).ProcessFilter(grey); - + Rectangle rectangle = this.FindBoundingBox(grey, 0); newImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb);