From 3aaa22c05fdf01525b1626b23bfbf487791e5832 Mon Sep 17 00:00:00 2001 From: James South Date: Thu, 25 Sep 2014 23:35:01 +0100 Subject: [PATCH] First attempt at energy cropping Former-commit-id: dff2b626bdbc8ef13e4802bae1980d47123aade9 --- src/ImageProcessor/ImageFactory.cs | 11 + src/ImageProcessor/ImageProcessor.csproj | 3 + .../Imaging/Binarization/BinaryThreshold.cs | 78 ++++++ src/ImageProcessor/Imaging/FastBitmap.cs | 26 -- src/ImageProcessor/Imaging/PixelData.cs | 38 +++ src/ImageProcessor/Processors/AutoCrop.cs | 222 ++++++++++++++++++ src/ImageProcessorConsole/Program.cs | 7 +- .../input/monster-whitebg.png2.REMOVED.git-id | 1 + .../images/input/pixel.png3 | Bin 0 -> 166 bytes 9 files changed, 357 insertions(+), 29 deletions(-) create mode 100644 src/ImageProcessor/Imaging/Binarization/BinaryThreshold.cs create mode 100644 src/ImageProcessor/Imaging/PixelData.cs create mode 100644 src/ImageProcessor/Processors/AutoCrop.cs create mode 100644 src/ImageProcessorConsole/images/input/monster-whitebg.png2.REMOVED.git-id create mode 100644 src/ImageProcessorConsole/images/input/pixel.png3 diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 496906c0a..ef57fc739 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -288,6 +288,17 @@ namespace ImageProcessor return this; } + public ImageFactory AutoCrop(byte threshold = 128) + { + if (this.ShouldProcess) + { + AutoCrop autoCrop = new AutoCrop() { DynamicParameter = threshold }; + this.CurrentImageFormat.ApplyProcessor(autoCrop.ProcessImage, this); + } + + return this; + } + /// /// Performs auto-rotation to ensure that EXIF defined rotation is reflected in /// the final image. diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index a2d74b754..ddd218e08 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -110,6 +110,7 @@ + @@ -129,8 +130,10 @@ + + diff --git a/src/ImageProcessor/Imaging/Binarization/BinaryThreshold.cs b/src/ImageProcessor/Imaging/Binarization/BinaryThreshold.cs new file mode 100644 index 000000000..2cac1ce6d --- /dev/null +++ b/src/ImageProcessor/Imaging/Binarization/BinaryThreshold.cs @@ -0,0 +1,78 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Performs binary threshold filtering against a given greyscale image. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging.Binarization +{ + using System.Drawing; + + /// + /// Performs binary threshold filtering against a given greyscale image. + /// + public class BinaryThreshold + { + /// + /// The threshold value. + /// + private byte threshold = 128; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The threshold. + /// + public BinaryThreshold(byte threshold) + { + this.threshold = threshold; + } + + /// + /// Gets or sets the threshold. + /// + public byte Threshold + { + get + { + return this.threshold; + } + + set + { + this.threshold = value; + } + } + + /// + /// Processes the given bitmap to apply the threshold. + /// + /// The image to process. + /// A processed bitmap. + public Bitmap ProcessFilter(Bitmap source) + { + int width = source.Width; + int height = source.Height; + + using (FastBitmap fastBitmap = 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); + } + } + } + + return source; + } + } +} diff --git a/src/ImageProcessor/Imaging/FastBitmap.cs b/src/ImageProcessor/Imaging/FastBitmap.cs index 97af88356..539f941c2 100644 --- a/src/ImageProcessor/Imaging/FastBitmap.cs +++ b/src/ImageProcessor/Imaging/FastBitmap.cs @@ -270,31 +270,5 @@ namespace ImageProcessor.Imaging this.bitmapData = null; this.pixelBuffer = null; } - - /// - /// The pixel data. - /// - private struct PixelData - { - /// - /// The blue component. - /// - public byte B; - - /// - /// The green component. - /// - public byte G; - - /// - /// The red component. - /// - public byte R; - - /// - /// The alpha component. - /// - public byte A; - } } } diff --git a/src/ImageProcessor/Imaging/PixelData.cs b/src/ImageProcessor/Imaging/PixelData.cs new file mode 100644 index 000000000..b899ab5b2 --- /dev/null +++ b/src/ImageProcessor/Imaging/PixelData.cs @@ -0,0 +1,38 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Contains the component parts that make up a single pixel. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + /// + /// Contains the component parts that make up a single pixel. + /// + public struct PixelData + { + /// + /// The blue component. + /// + public byte B; + + /// + /// The green component. + /// + public byte G; + + /// + /// The red component. + /// + public byte R; + + /// + /// The alpha component. + /// + public byte A; + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Processors/AutoCrop.cs b/src/ImageProcessor/Processors/AutoCrop.cs new file mode 100644 index 000000000..cecd5fe83 --- /dev/null +++ b/src/ImageProcessor/Processors/AutoCrop.cs @@ -0,0 +1,222 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + using System; + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Imaging; + + using ImageProcessor.Common.Exceptions; + using ImageProcessor.Imaging; + using ImageProcessor.Imaging.Binarization; + using ImageProcessor.Imaging.EdgeDetection; + + /// + /// The auto crop. + /// + public class AutoCrop : IGraphicsProcessor + { + /// + /// Initializes a new instance of the class. + /// + public AutoCrop() + { + this.Settings = new Dictionary(); + } + + /// + /// Gets or sets the dynamic parameter. + /// + public dynamic DynamicParameter { get; set; } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings { get; set; } + + /// + /// Processes the image. + /// + /// + /// The current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + Bitmap newImage = null; + Bitmap grey = null; + Image image = factory.Image; + byte threshold = this.DynamicParameter; + + 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); + + // 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); + + 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); + } + } + + // Reassign the image. + //grey.Dispose(); + image.Dispose(); + image = newImage; + } + catch (Exception ex) + { + if (grey != null) + { + grey.Dispose(); + } + + if (newImage != null) + { + newImage.Dispose(); + } + + throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); + } + + 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) + { + 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; + + 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) + { + if (x < minX) + minX = x; + if (x > maxX) + maxX = x; + if (y < minY) + minY = y; + if (y > maxY) + maxY = y; + } + } + } + } + + // check + if ((minX == width) && (minY == height) && (maxX == 0) && (maxY == 0)) + { + minX = minY = 0; + } + + return new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1); + } + + + 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; + + using (FastBitmap fastBitmap = new FastBitmap(bitmap)) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (fastBitmap.GetPixel(x, y).B != indexToRemove) + { + if (x < startX) + { + startX = x; + } + + if (x > stopX) + { + stopX = x; + } + + if (y < startY) + { + startY = y; + } + + if (y > stopY) + { + stopY = y; + } + } + } + } + } + + // check + if ((startX == width) && (startY == height) && (stopX == 0) && (stopY == 0)) + { + startX = startY = 0; + } + + return new Rectangle(startX, startY, stopX - startX + 1, stopY - startY + 1); + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorConsole/Program.cs b/src/ImageProcessorConsole/Program.cs index 07243551b..0b2489362 100644 --- a/src/ImageProcessorConsole/Program.cs +++ b/src/ImageProcessorConsole/Program.cs @@ -46,7 +46,7 @@ namespace ImageProcessorConsole di.Create(); } - IEnumerable files = GetFilesByExtensions(di, ".png"); + IEnumerable files = GetFilesByExtensions(di, ".png2"); //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); foreach (FileInfo fileInfo in files) @@ -76,8 +76,9 @@ namespace ImageProcessorConsole //.ContentAwareResize(layer) //.Constrain(size) //.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80) - .Resize(layer) - .DetectEdges(new KirschEdgeFilter()) + //.Resize(layer) + //.DetectEdges(new KirschEdgeFilter()) + .AutoCrop() //.Filter(MatrixFilters.Comic) //.Filter(MatrixFilters.HiSatch) //.Pixelate(8) diff --git a/src/ImageProcessorConsole/images/input/monster-whitebg.png2.REMOVED.git-id b/src/ImageProcessorConsole/images/input/monster-whitebg.png2.REMOVED.git-id new file mode 100644 index 000000000..22d1aa140 --- /dev/null +++ b/src/ImageProcessorConsole/images/input/monster-whitebg.png2.REMOVED.git-id @@ -0,0 +1 @@ +71ccd68b75f913237cdd2047d5f5d0b39d9b16ae \ No newline at end of file diff --git a/src/ImageProcessorConsole/images/input/pixel.png3 b/src/ImageProcessorConsole/images/input/pixel.png3 new file mode 100644 index 0000000000000000000000000000000000000000..01ba8148e4021f1c16296edd49a51974309ac531 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1SD^IDZKzvjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL5ULAh?3y^w370~qEv>0#LT=By}Z;C1rt33 zJ>#Bd(*uBN#64XcLnOkJfABLah%qzIVEOzX2;>>kj*7qZ*2>!oRKnot>gTe~DWM4f Dy