From 96e50d342425be1ec2bdeda343fcff058661d586 Mon Sep 17 00:00:00 2001 From: James South Date: Fri, 26 Sep 2014 15:10:02 +0100 Subject: [PATCH] Entropy crop a go go TODO: Clean up. Former-commit-id: 168831022aeb9d1db112f6c57d3fb05ff5956366 --- src/ImageProcessor/ImageFactory.cs | 4 +- .../Imaging/Binarization/BinaryThreshold.cs | 9 +- .../EdgeDetection/ConvolutionFilter.cs | 10 + src/ImageProcessor/Processors/AutoCrop.cs | 173 +++++++----------- src/ImageProcessorConsole/Program.cs | 2 +- .../input/tower - Copy.jpg2.REMOVED.git-id | 1 + .../output/monster-whitebg.png.REMOVED.git-id | 1 + 7 files changed, 90 insertions(+), 110 deletions(-) create mode 100644 src/ImageProcessorConsole/images/input/tower - Copy.jpg2.REMOVED.git-id create mode 100644 src/ImageProcessorConsole/images/output/monster-whitebg.png.REMOVED.git-id diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index ef57fc739..5c9e0efa7 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -288,11 +288,11 @@ namespace ImageProcessor return this; } - public ImageFactory AutoCrop(byte threshold = 128) + public ImageFactory EntropyCrop(byte threshold = 128) { if (this.ShouldProcess) { - AutoCrop autoCrop = new AutoCrop() { DynamicParameter = threshold }; + EntropyCrop autoCrop = new EntropyCrop() { DynamicParameter = threshold }; this.CurrentImageFormat.ApplyProcessor(autoCrop.ProcessImage, this); } diff --git a/src/ImageProcessor/Imaging/Binarization/BinaryThreshold.cs b/src/ImageProcessor/Imaging/Binarization/BinaryThreshold.cs index 2cac1ce6d..f0b1271b7 100644 --- a/src/ImageProcessor/Imaging/Binarization/BinaryThreshold.cs +++ b/src/ImageProcessor/Imaging/Binarization/BinaryThreshold.cs @@ -11,6 +11,7 @@ namespace ImageProcessor.Imaging.Binarization { using System.Drawing; + using System.Drawing.Imaging; /// /// Performs binary threshold filtering against a given greyscale image. @@ -59,15 +60,15 @@ namespace ImageProcessor.Imaging.Binarization int width = source.Width; int height = source.Height; - using (FastBitmap fastBitmap = new FastBitmap(source)) + using (FastBitmap sourceBitmap = new FastBitmap(source)) { + for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - Color color = fastBitmap.GetPixel(x, y); - - fastBitmap.SetPixel(x, y, color.B >= this.threshold ? Color.White : Color.Black); + Color color = sourceBitmap.GetPixel(x, y); + sourceBitmap.SetPixel(x, y, color.B >= this.threshold ? Color.White : Color.Black); } } } diff --git a/src/ImageProcessor/Imaging/EdgeDetection/ConvolutionFilter.cs b/src/ImageProcessor/Imaging/EdgeDetection/ConvolutionFilter.cs index b6c20ea72..97d5b8542 100644 --- a/src/ImageProcessor/Imaging/EdgeDetection/ConvolutionFilter.cs +++ b/src/ImageProcessor/Imaging/EdgeDetection/ConvolutionFilter.cs @@ -170,6 +170,16 @@ namespace ImageProcessor.Imaging.EdgeDetection input.Dispose(); } + using (Graphics graphics = Graphics.FromImage(destination)) + { + // Draw an edge around the image. + using (Pen blackPen = new Pen(Color.Black)) + { + blackPen.Width = 4; + graphics.DrawRectangle(blackPen, new Rectangle(0, 0, destination.Width, destination.Height)); + } + } + return destination; } } diff --git a/src/ImageProcessor/Processors/AutoCrop.cs b/src/ImageProcessor/Processors/AutoCrop.cs index cecd5fe83..18d2de354 100644 --- a/src/ImageProcessor/Processors/AutoCrop.cs +++ b/src/ImageProcessor/Processors/AutoCrop.cs @@ -20,12 +20,12 @@ namespace ImageProcessor.Processors /// /// The auto crop. /// - public class AutoCrop : IGraphicsProcessor + public class EntropyCrop : IGraphicsProcessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public AutoCrop() + public EntropyCrop() { this.Settings = new Dictionary(); } @@ -60,38 +60,25 @@ namespace ImageProcessor.Processors try { grey = new ConvolutionFilter(new SobelEdgeFilter(), true).ProcessFilter((Bitmap)image); - grey = new BinaryThreshold(threshold).ProcessFilter(grey);//.Clone(new Rectangle(0, 0, grey.Width, grey.Height), PixelFormat.Format8bppIndexed); + grey = new BinaryThreshold(threshold).ProcessFilter(grey); - // lock source bitmap data - //BitmapData data = grey.LockBits(new Rectangle(0, 0, grey.Width, grey.Height), ImageLockMode.ReadOnly, grey.PixelFormat); - //Rectangle rectangle = this.FindBoxExactgreyscale(data, 0); - //grey.UnlockBits(data); - - Rectangle rectangle = FindBox(grey, 0); + Rectangle rectangle = this.FindBox(grey, 0); newImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb); using (Graphics graphics = Graphics.FromImage(newImage)) { - // An unwanted border appears when using InterpolationMode.HighQualityBicubic to resize the image - // as the algorithm appears to be pulling averaging detail from surrounding pixels beyond the edge - // of the image. Using the ImageAttributes class to specify that the pixels beyond are simply mirror - // images of the pixels within solves this problem. - using (ImageAttributes wrapMode = new ImageAttributes()) - { - graphics.DrawImage( - image, - new Rectangle(0, 0, rectangle.Width, rectangle.Height), - rectangle.X, - rectangle.Y, - rectangle.Width, - rectangle.Height, - GraphicsUnit.Pixel, - wrapMode); - } + graphics.DrawImage( + image, + new Rectangle(0, 0, rectangle.Width, rectangle.Height), + rectangle.X, + rectangle.Y, + rectangle.Width, + rectangle.Height, + GraphicsUnit.Pixel); } // Reassign the image. - //grey.Dispose(); + grey.Dispose(); image.Dispose(); image = newImage; } @@ -113,107 +100,87 @@ namespace ImageProcessor.Processors return image; } - /// - /// Returns a bounding box that only excludes the specified color. - /// Only works on 8-bit images. - /// - /// - /// The palette index to remove. - /// - private Rectangle FindBoxExactgreyscale(BitmapData sourceData, byte indexToRemove) + private Rectangle FindBox(Bitmap bitmap, byte componentToRemove) { - if (sourceData.PixelFormat != PixelFormat.Format8bppIndexed) throw new ArgumentOutOfRangeException("FindBoxExact only operates on 8-bit greyscale images"); - // get source image size - int width = sourceData.Width; - int height = sourceData.Height; - int offset = sourceData.Stride - width; - - int minX = width; - int minY = height; - int maxX = 0; - int maxY = 0; - - // find rectangle which contains something except color to remove - unsafe - { - byte* src = (byte*)sourceData.Scan0; + int width = bitmap.Width; + int height = bitmap.Height; + int startX; + int startY; + int stopX; + int stopY; + Func getMinY = fastBitmap => + { for (int y = 0; y < height; y++) { - if (y > 0) src += offset; //Don't adjust for offset until after first row for (int x = 0; x < width; x++) { - if (x > 0 || y > 0) src++; //Don't increment until after the first pixel. - if (*src != indexToRemove) + Color c = fastBitmap.GetPixel(x, y); + + if (fastBitmap.GetPixel(x, y).B != componentToRemove) { - if (x < minX) - minX = x; - if (x > maxX) - maxX = x; - if (y < minY) - minY = y; - if (y > maxY) - maxY = y; + return y; } } } - } - // check - if ((minX == width) && (minY == height) && (maxX == 0) && (maxY == 0)) + return 0; + }; + + Func getMaxY = fastBitmap => { - minX = minY = 0; - } + for (int y = height - 1; y > -1; y--) + { + for (int x = 0; x < width; x++) + { + if (fastBitmap.GetPixel(x, y).B != componentToRemove) + { + return y; + } + } + } - return new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1); - } + return height; + }; + Func getMinX = fastBitmap => + { + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (fastBitmap.GetPixel(x, y).B != componentToRemove) + { + return x; + } + } + } - private Rectangle FindBox(Bitmap bitmap, byte indexToRemove) - { - int width = bitmap.Width; - int height = bitmap.Height; - int startX = width; - int startY = height; - int stopX = 0; - int stopY = 0; + return 0; + }; - using (FastBitmap fastBitmap = new FastBitmap(bitmap)) + Func getMaxX = fastBitmap => { - for (int y = 0; y < height; y++) + for (int x = width - 1; x > -1; x--) { - for (int x = 0; x < width; x++) + for (int y = 0; y < height; y++) { - if (fastBitmap.GetPixel(x, y).B != indexToRemove) + if (fastBitmap.GetPixel(x, y).B != componentToRemove) { - if (x < startX) - { - startX = x; - } - - if (x > stopX) - { - stopX = x; - } - - if (y < startY) - { - startY = y; - } - - if (y > stopY) - { - stopY = y; - } + return x; } } } - } - // check - if ((startX == width) && (startY == height) && (stopX == 0) && (stopY == 0)) + return height; + }; + + using (FastBitmap fastBitmap = new FastBitmap(bitmap)) { - startX = startY = 0; + startY = getMinY(fastBitmap); + stopY = getMaxY(fastBitmap); + startX = getMinX(fastBitmap); + stopX = getMaxX(fastBitmap); } return new Rectangle(startX, startY, stopX - startX + 1, stopY - startY + 1); diff --git a/src/ImageProcessorConsole/Program.cs b/src/ImageProcessorConsole/Program.cs index 0b2489362..2e0a6e89c 100644 --- a/src/ImageProcessorConsole/Program.cs +++ b/src/ImageProcessorConsole/Program.cs @@ -78,7 +78,7 @@ namespace ImageProcessorConsole //.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80) //.Resize(layer) //.DetectEdges(new KirschEdgeFilter()) - .AutoCrop() + .EntropyCrop() //.Filter(MatrixFilters.Comic) //.Filter(MatrixFilters.HiSatch) //.Pixelate(8) diff --git a/src/ImageProcessorConsole/images/input/tower - Copy.jpg2.REMOVED.git-id b/src/ImageProcessorConsole/images/input/tower - Copy.jpg2.REMOVED.git-id new file mode 100644 index 000000000..d45f646a2 --- /dev/null +++ b/src/ImageProcessorConsole/images/input/tower - Copy.jpg2.REMOVED.git-id @@ -0,0 +1 @@ +98c3f8fce4fdd9eebafe12431c8e014fd39d243f \ No newline at end of file diff --git a/src/ImageProcessorConsole/images/output/monster-whitebg.png.REMOVED.git-id b/src/ImageProcessorConsole/images/output/monster-whitebg.png.REMOVED.git-id new file mode 100644 index 000000000..51a0019e9 --- /dev/null +++ b/src/ImageProcessorConsole/images/output/monster-whitebg.png.REMOVED.git-id @@ -0,0 +1 @@ +e40fa501c49374e25d137627084dfa993931205f \ No newline at end of file