diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index 4e70b66c1..295e72a1e 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -50,7 +50,7 @@ namespace ImageProcessor.PlayGround // Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask.png")); // Image overlay = Image.FromFile(Path.Combine(resolvedPath, "imageprocessor.128.png")); - FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "test5.jpg")); + FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "bob_revolutionpro_wilderness_02_2013_72dpi_2000x2000.jpg")); IEnumerable files = GetFilesByExtensions(di, ".gif"); //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); @@ -91,11 +91,11 @@ namespace ImageProcessor.PlayGround //.BackgroundColor(Color.Cyan) //.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128) //.Resize(size) - .Resize(new ResizeLayer(size, ResizeMode.Max)) + //.Resize(new ResizeLayer(size, ResizeMode.Max)) // .Resize(new ResizeLayer(size, ResizeMode.Stretch)) - //.DetectEdges(new SobelEdgeFilter(), true) + //.DetectEdges(new Laplacian3X3EdgeFilter(), true) //.DetectEdges(new LaplacianOfGaussianEdgeFilter()) - //.EntropyCrop() + .EntropyCrop() //.Filter(MatrixFilters.Invert) //.Contrast(50) //.Filter(MatrixFilters.Comic) diff --git a/src/ImageProcessor.Playground/images/input/bob_revolutionpro_wilderness_02_2013_72dpi_2000x2000.jpg.REMOVED.git-id b/src/ImageProcessor.Playground/images/input/bob_revolutionpro_wilderness_02_2013_72dpi_2000x2000.jpg.REMOVED.git-id new file mode 100644 index 000000000..6a0fdbe79 --- /dev/null +++ b/src/ImageProcessor.Playground/images/input/bob_revolutionpro_wilderness_02_2013_72dpi_2000x2000.jpg.REMOVED.git-id @@ -0,0 +1 @@ +cfa62bc3b77a7efb16c25218c1381da5d4f36db7 \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs b/src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs index e8ffa61e7..e0f1178b7 100644 --- a/src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs +++ b/src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs @@ -1,21 +1,14 @@ // -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) James South. -// Licensed under the Apache License, Version 2.0. +// // Licensed under the Apache License, Version 2.0. // -// -// Performs binary threshold filtering against a given greyscale image. -// // -------------------------------------------------------------------------------------------------------------------- - 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. /// @@ -24,7 +17,7 @@ namespace ImageProcessor.Imaging.Filters.Binarization /// /// The threshold value. /// - private byte threshold = 10; + private byte threshold; /// /// Initializes a new instance of the class. @@ -32,7 +25,7 @@ namespace ImageProcessor.Imaging.Filters.Binarization /// /// The threshold. /// - public BinaryThreshold(byte threshold) + public BinaryThreshold(byte threshold = 10) { this.threshold = threshold; } @@ -56,8 +49,12 @@ namespace ImageProcessor.Imaging.Filters.Binarization /// /// Processes the given bitmap to apply the threshold. /// - /// The image to process. - /// A processed bitmap. + /// + /// The image to process. + /// + /// + /// A processed bitmap. + /// public Bitmap ProcessFilter(Bitmap source) { int width = source.Width; @@ -66,8 +63,8 @@ namespace ImageProcessor.Imaging.Filters.Binarization using (FastBitmap sourceBitmap = new FastBitmap(source)) { Parallel.For( - 0, - height, + 0, + height, y => { for (int x = 0; x < width; x++) @@ -75,6 +72,7 @@ namespace ImageProcessor.Imaging.Filters.Binarization // ReSharper disable AccessToDisposedClosure Color color = sourceBitmap.GetPixel(x, y); sourceBitmap.SetPixel(x, y, color.B >= this.threshold ? Color.White : Color.Black); + // ReSharper restore AccessToDisposedClosure } }); diff --git a/src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs b/src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs index 750f072d2..f974f6b9d 100644 --- a/src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs @@ -12,6 +12,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection { using System; using System.Drawing; + using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Threading.Tasks; @@ -57,29 +58,42 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection { int width = source.Width; int height = source.Height; + int maxWidth = width + 1; + int maxHeight = height + 1; + int bufferedWidth = width + 2; + int bufferedHeight = height + 2; Bitmap destination = new Bitmap(width, height); - Bitmap input = new Bitmap(width, height); + Bitmap input = new Bitmap(bufferedWidth, bufferedHeight); destination.SetResolution(source.HorizontalResolution, source.VerticalResolution); input.SetResolution(source.HorizontalResolution, source.VerticalResolution); using (Graphics graphics = Graphics.FromImage(input)) { + // Fixes an issue with transparency not converting properly. + graphics.Clear(Color.Transparent); + + Rectangle destinationRectangle = new Rectangle(0, 0, bufferedWidth, bufferedHeight); Rectangle rectangle = new Rectangle(0, 0, width, height); - if (this.greyscale) + + // If it's greyscale apply a colormatrix to the image. + using (ImageAttributes attributes = new ImageAttributes()) { - // If it's greyscale apply a colormatrix to the image. - using (ImageAttributes attributes = new ImageAttributes()) + if (this.greyscale) { attributes.SetColorMatrix(ColorMatrixes.GreyScale); - graphics.DrawImage(source, rectangle, 0, 0, width, height, GraphicsUnit.Pixel, attributes); } - } - else - { - // Fixes an issue with transparency not converting properly. - graphics.Clear(Color.Transparent); - graphics.DrawImage(source, rectangle); + + // We use a trick here to detect right to the edges of the image. + // flip/tile the image with a pixel in excess in each direction to duplicate pixels. + // Later on we draw pixels without that excess. + using (TextureBrush tb = new TextureBrush(source, rectangle, attributes)) + { + tb.WrapMode = WrapMode.TileFlipXY; + tb.TranslateTransform(1, 1); + + graphics.FillRectangle(tb, destinationRectangle); + } } } @@ -97,10 +111,10 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection // Loop through the pixels. Parallel.For( 0, - height, + bufferedHeight, y => { - for (int x = 0; x < width; x++) + for (int x = 0; x < bufferedWidth; x++) { double rX = 0; double gX = 0; @@ -119,7 +133,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection } // Outwith the current bounds so break. - if (offsetY >= height) + if (offsetY >= bufferedHeight) { break; } @@ -135,7 +149,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection continue; } - if (offsetX < width) + if (offsetX < bufferedWidth) { // ReSharper disable once AccessToDisposedClosure Color currentColor = sourceBitmap.GetPixel(offsetX, offsetY); @@ -158,8 +172,11 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection byte blue = bX.ToByte(); Color newColor = Color.FromArgb(red, green, blue); - // ReSharper disable once AccessToDisposedClosure - destinationBitmap.SetPixel(x, y, newColor); + if (y > 0 && x > 0 && y < maxHeight && x < maxWidth) + { + // ReSharper disable once AccessToDisposedClosure + destinationBitmap.SetPixel(x - 1, y - 1, newColor); + } } }); } @@ -171,17 +188,6 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection input.Dispose(); } - // Draw a black rectangle around the area to ensure that the first row/column in covered. - 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; } @@ -194,29 +200,42 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection { int width = source.Width; int height = source.Height; + int maxWidth = width + 1; + int maxHeight = height + 1; + int bufferedWidth = width + 2; + int bufferedHeight = height + 2; Bitmap destination = new Bitmap(width, height); - Bitmap input = new Bitmap(width, height); + Bitmap input = new Bitmap(bufferedWidth, bufferedHeight); destination.SetResolution(source.HorizontalResolution, source.VerticalResolution); input.SetResolution(source.HorizontalResolution, source.VerticalResolution); using (Graphics graphics = Graphics.FromImage(input)) { + // Fixes an issue with transparency not converting properly. + graphics.Clear(Color.Transparent); + + Rectangle destinationRectangle = new Rectangle(0, 0, bufferedWidth, bufferedHeight); Rectangle rectangle = new Rectangle(0, 0, width, height); - if (this.greyscale) + + // If it's greyscale apply a colormatrix to the image. + using (ImageAttributes attributes = new ImageAttributes()) { - // If it's greyscale apply a colormatrix to the image. - using (ImageAttributes attributes = new ImageAttributes()) + if (this.greyscale) { attributes.SetColorMatrix(ColorMatrixes.GreyScale); - graphics.DrawImage(source, rectangle, 0, 0, width, height, GraphicsUnit.Pixel, attributes); } - } - else - { - // Fixes an issue with transparency not converting properly. - graphics.Clear(Color.Transparent); - graphics.DrawImage(source, rectangle); + + // We use a trick here to detect right to the edges of the image. + // flip/tile the image with a pixel in excess in each direction to duplicate pixels. + // Later on we draw pixels without that excess. + using (TextureBrush tb = new TextureBrush(source, rectangle, attributes)) + { + tb.WrapMode = WrapMode.TileFlipXY; + tb.TranslateTransform(1, 1); + + graphics.FillRectangle(tb, destinationRectangle); + } } } @@ -235,10 +254,10 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection // Loop through the pixels. Parallel.For( 0, - height, + bufferedHeight, y => { - for (int x = 0; x < width; x++) + for (int x = 0; x < bufferedWidth; x++) { double rX = 0; double rY = 0; @@ -260,7 +279,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection } // Outwith the current bounds so break. - if (offsetY >= height) + if (offsetY >= bufferedHeight) { break; } @@ -276,7 +295,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection continue; } - if (offsetX < width) + if (offsetX < bufferedWidth) { // ReSharper disable once AccessToDisposedClosure Color currentColor = sourceBitmap.GetPixel(offsetX, offsetY); @@ -302,8 +321,11 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection byte blue = Math.Sqrt((bX * bX) + (bY * bY)).ToByte(); Color newColor = Color.FromArgb(red, green, blue); - // ReSharper disable once AccessToDisposedClosure - destinationBitmap.SetPixel(x, y, newColor); + if (y > 0 && x > 0 && y < maxHeight && x < maxWidth) + { + // ReSharper disable once AccessToDisposedClosure + destinationBitmap.SetPixel(x - 1, y - 1, newColor); + } } }); } @@ -315,17 +337,6 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection input.Dispose(); } - // Draw a black rectangle around the area to ensure that the first row/column in covered. - 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; } }