From 81e09be64e05398fe9ffeb7a042d129016d97259 Mon Sep 17 00:00:00 2001 From: James South Date: Sat, 11 Oct 2014 00:49:07 +0100 Subject: [PATCH] Finishing Mask plus BGColor fix Former-commit-id: 1f45b8cf79700436d535eb97857253222bacc574 Former-commit-id: 5bf2d026598facc3e4ce8b1f45e2d80d8e1175c1 --- src/ImageProcessor.Playground/Program.cs | 12 ++- .../images/input/mask.png | Bin 0 -> 3284 bytes src/ImageProcessor/ImageFactory.cs | 14 +++- .../Filters/Photo/ComicMatrixFilter.cs | 60 ++------------- src/ImageProcessor/Imaging/Helpers/Effects.cs | 71 +++++++++++++++++- .../Imaging/Helpers/ImageMaths.cs | 40 +++++++--- .../Processors/BackgroundColor.cs | 7 +- src/ImageProcessor/Processors/Filter.cs | 1 - src/ImageProcessor/Processors/Mask.cs | 41 +++++++--- 9 files changed, 159 insertions(+), 87 deletions(-) create mode 100644 src/ImageProcessor.Playground/images/input/mask.png diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index ec95d6f19..13dbc37f8 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -18,15 +18,19 @@ namespace ImageProcessor.PlayGround using System.Linq; using ImageProcessor; + using ImageProcessor.Configuration; using ImageProcessor.Imaging; using ImageProcessor.Imaging.Filters.EdgeDetection; using ImageProcessor.Imaging.Filters.Photo; + using ImageProcessor.Imaging.Formats; /// /// The program. /// public class Program { + protected static readonly IEnumerable formats = ImageProcessorBootstrapper.Instance.SupportedImageFormats; + /// /// The main routine. /// @@ -45,6 +49,7 @@ namespace ImageProcessor.PlayGround di.Create(); } + Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask.png")); IEnumerable files = GetFilesByExtensions(di, ".jpg"); //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); @@ -73,15 +78,16 @@ namespace ImageProcessor.PlayGround //.BackgroundColor(Color.White) //.Resize(new Size((int)(size.Width * 1.1), 0)) //.ContentAwareResize(layer) - .Constrain(size) - .Mask() + //.Constrain(size) + //.Mask(mask) + //.BackgroundColor(Color.HotPink) //.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80) //.Resize(layer) //.DetectEdges(new SobelEdgeFilter(), false) //.DetectEdges(new LaplacianOfGaussianEdgeFilter()) //.EntropyCrop() //.Filter(MatrixFilters.Invert) - //.Filter(MatrixFilters.Comic) + .Filter(MatrixFilters.Comic) //.Filter(MatrixFilters.HiSatch) //.Pixelate(8) //.GaussianSharpen(10) diff --git a/src/ImageProcessor.Playground/images/input/mask.png b/src/ImageProcessor.Playground/images/input/mask.png new file mode 100644 index 0000000000000000000000000000000000000000..f86272dc302b50135b7efcad7548f89e339abf86 GIT binary patch literal 3284 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 Invw4Q2YL@68UO$Q literal 0 HcmV?d00001 diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index 4137c4f30..2eb0e22fc 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -670,11 +670,21 @@ namespace ImageProcessor return this; } - public ImageFactory Mask() + /// + /// Applies the given image mask to the current image. If the mask is not the same size as the image + /// it will be centered against the image. + /// + /// + /// The image containing the mask to apply. + /// + /// + /// The current instance of the class. + /// + public ImageFactory Mask(Image imageMask) { if (this.ShouldProcess) { - Mask mask = new Mask(); + Mask mask = new Mask { DynamicParameter = imageMask }; this.CurrentImageFormat.ApplyProcessor(mask.ProcessImage, this); } diff --git a/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs b/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs index 4dc980ce0..1ad4f9b3d 100644 --- a/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs @@ -10,7 +10,6 @@ namespace ImageProcessor.Imaging.Filters.Photo { - using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; @@ -106,8 +105,8 @@ namespace ImageProcessor.Imaging.Filters.Photo } } - // Transfer the alpha channel from the mask to the high saturation image. - ApplyMask(patternBitmap, lowBitmap); + // Transfer the alpha channel from the mask to the low saturation image. + lowBitmap = Effects.ApplyMask(lowBitmap, patternBitmap); using (Graphics graphics = Graphics.FromImage(newImage)) { @@ -195,8 +194,8 @@ namespace ImageProcessor.Imaging.Filters.Photo using (Bitmap temp = filter.Process2DFilter(source)) { destination = new InvertMatrixFilter().TransformImage(temp, destination); - - // Darken it slightly + + // Darken it slightly to aid detection destination = Adjustments.Brightness(destination, -5); } @@ -222,56 +221,9 @@ namespace ImageProcessor.Imaging.Filters.Photo }); } + // Darken it again to average out the color. + destination = Adjustments.Brightness(destination, -5); return (Bitmap)destination; } - - /// - /// Applies a mask . - /// - /// - /// The source. - /// - /// - /// The destination. - /// - /// - /// Thrown if the two images are of different size. - /// - private static void ApplyMask(Image source, Image destination) - { - if (source.Size != destination.Size) - { - throw new ArgumentException(); - } - - using (FastBitmap sourceBitmap = new FastBitmap(source)) - { - using (FastBitmap destinationBitmap = new FastBitmap(destination)) - { - int width = source.Width; - int height = source.Height; - - Parallel.For( - 0, - height, - y => - { - for (int x = 0; x < width; x++) - { - // 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 - } - }); - } - } - } } } \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Helpers/Effects.cs b/src/ImageProcessor/Imaging/Helpers/Effects.cs index 3af918a21..e079f7e37 100644 --- a/src/ImageProcessor/Imaging/Helpers/Effects.cs +++ b/src/ImageProcessor/Imaging/Helpers/Effects.cs @@ -13,6 +13,7 @@ namespace ImageProcessor.Imaging.Helpers using System; using System.Drawing; using System.Drawing.Drawing2D; + using System.Threading.Tasks; /// /// Provides reusable effect methods to apply to images. @@ -69,8 +70,8 @@ namespace ImageProcessor.Imaging.Helpers } else { - centerColor = Color.FromArgb(0, baseColor.R, baseColor.G, baseColor.B); - edgeColor = Color.FromArgb(255, baseColor.R, baseColor.G, baseColor.B); + centerColor = Color.FromArgb(0, baseColor.R, baseColor.G, baseColor.B); + edgeColor = Color.FromArgb(255, baseColor.R, baseColor.G, baseColor.B); } brush.WrapMode = WrapMode.Tile; @@ -108,5 +109,71 @@ namespace ImageProcessor.Imaging.Helpers { return Vignette(source, baseColor, rectangle, true); } + + /// + /// Applies the given image mask to the source. + /// + /// + /// The source . + /// + /// + /// The mask . + /// + /// + /// Thrown if the two images are of different size. + /// + /// + /// The masked . + /// + public static Bitmap ApplyMask(Image source, Image mask) + { + if (mask.Size != source.Size) + { + throw new ArgumentException(); + } + + int width = mask.Width; + int height = mask.Height; + + Bitmap toMask = new Bitmap(source); + + // Loop through and replace the alpha channel + using (FastBitmap maskBitmap = new FastBitmap(mask)) + { + using (FastBitmap sourceBitmap = new FastBitmap(toMask)) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + // ReSharper disable AccessToDisposedClosure + Color maskColor = maskBitmap.GetPixel(x, y); + Color sourceColor = sourceBitmap.GetPixel(x, y); + + if (sourceColor.A != 0) + { + sourceBitmap.SetPixel(x, y, Color.FromArgb(maskColor.B, sourceColor.R, sourceColor.G, sourceColor.B)); + } + + // ReSharper restore AccessToDisposedClosure + } + }); + } + } + + // Ensure the background is cleared out on non alpha supporting formats. + Bitmap clear = new Bitmap(width, height); + using (Graphics graphics = Graphics.FromImage(clear)) + { + graphics.Clear(Color.Transparent); + graphics.DrawImage(toMask, 0, 0, width, height); + } + + toMask.Dispose(); + return clear; + } } } diff --git a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs index 1d2ac60b0..c801cb5e3 100644 --- a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs @@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Helpers /// /// Provides reusable mathematical methods to apply to images. /// - public class ImageMaths + public static class ImageMaths { /// /// Gets the bounding from the given points. @@ -29,13 +29,13 @@ namespace ImageProcessor.Imaging.Helpers /// /// The bounding . /// - public Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) + public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) { return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); } /// - /// Gets a centered within it's parent. + /// Gets a representing the child centered relative to the parent. /// /// /// The parent . @@ -46,17 +46,33 @@ namespace ImageProcessor.Imaging.Helpers /// /// The centered . /// - public Rectangle CenteredRectangle(Rectangle parent, Rectangle child) + public static RectangleF CenteredRectangle(Rectangle parent, Rectangle child) { - if (parent.Size.Width < child.Size.Width && parent.Size.Height < child.Size.Height) - { - return parent; - } - - int x = (parent.Width - child.Width) / 2; - int y = (parent.Height - child.Height) / 2; + float x = (parent.Width - child.Width) / 2.0F; + float y = (parent.Height - child.Height) / 2.0F; + int width = child.Width; + int height = child.Height; + return new RectangleF(x, y, width, height); + } - return new Rectangle(x, y, child.Width, child.Height); + /// + /// Returns the array of matching the bounds of the given rectangle. + /// + /// + /// The to return the points from. + /// + /// + /// The array. + /// + public static Point[] ToPoints(Rectangle rectangle) + { + return new[] + { + new Point(rectangle.Left, rectangle.Top), + new Point(rectangle.Right, rectangle.Top), + new Point(rectangle.Right, rectangle.Bottom), + new Point(rectangle.Left, rectangle.Bottom) + }; } } } diff --git a/src/ImageProcessor/Processors/BackgroundColor.cs b/src/ImageProcessor/Processors/BackgroundColor.cs index a41396f90..c00ce6d3e 100644 --- a/src/ImageProcessor/Processors/BackgroundColor.cs +++ b/src/ImageProcessor/Processors/BackgroundColor.cs @@ -55,8 +55,11 @@ namespace ImageProcessor.Processors try { + int width = image.Width; + int height = image.Height; + Color backgroundColor = this.DynamicParameter; - newImage = new Bitmap(image.Width, image.Height); + newImage = new Bitmap(width, height); // Make a graphics object from the empty bitmap. using (Graphics graphics = Graphics.FromImage(newImage)) @@ -65,7 +68,7 @@ namespace ImageProcessor.Processors graphics.Clear(backgroundColor); // Draw passed in image onto graphics object. - graphics.DrawImage(image, 0, 0); + graphics.DrawImage(image, 0, 0, width, height); } image.Dispose(); diff --git a/src/ImageProcessor/Processors/Filter.cs b/src/ImageProcessor/Processors/Filter.cs index 69ed65a73..a9197ce83 100644 --- a/src/ImageProcessor/Processors/Filter.cs +++ b/src/ImageProcessor/Processors/Filter.cs @@ -13,7 +13,6 @@ namespace ImageProcessor.Processors using System; using System.Collections.Generic; using System.Drawing; - using System.Drawing.Imaging; using ImageProcessor.Common.Exceptions; using ImageProcessor.Imaging.Filters.Photo; diff --git a/src/ImageProcessor/Processors/Mask.cs b/src/ImageProcessor/Processors/Mask.cs index 372be395e..7a8aee463 100644 --- a/src/ImageProcessor/Processors/Mask.cs +++ b/src/ImageProcessor/Processors/Mask.cs @@ -15,9 +15,11 @@ namespace ImageProcessor.Processors using System.Drawing; using ImageProcessor.Common.Exceptions; + using ImageProcessor.Imaging.Helpers; /// - /// Applies a mask to the given image. + /// Applies a mask to the given image. If the mask is not the same size as the image + /// it will be centered against the image. /// public class Mask : IGraphicsProcessor { @@ -60,29 +62,46 @@ namespace ImageProcessor.Processors public Image ProcessImage(ImageFactory factory) { Bitmap newImage = null; + Bitmap mask = null; + Bitmap maskResized = null; Image image = factory.Image; - Size original = image.Size; - Size smaller = new Size(image.Width / 2, image.Height / 2); - int x = (original.Width - smaller.Width) / 2; - int y = (original.Height - smaller.Height) / 2; - - int width = image.Width; - int height = image.Height; try { - newImage = new Bitmap(original.Width, image.Height); + int width = image.Width; + int height = image.Height; + mask = new Bitmap(this.DynamicParameter); + Rectangle parent = new Rectangle(0, 0, width, height); + Rectangle child = new Rectangle(0, 0, mask.Width, mask.Height); + RectangleF centered = ImageMaths.CenteredRectangle(parent, child); - using (Graphics graphics = Graphics.FromImage(newImage)) + // Resize the mask to the size of the input image so that we can apply it. + maskResized = new Bitmap(width, height); + using (Graphics graphics = Graphics.FromImage(maskResized)) { - graphics.DrawImage(image, x, y, smaller.Width, smaller.Height); + graphics.Clear(Color.Transparent); + graphics.DrawImage(mask, new PointF(centered.X, centered.Y)); } + newImage = Effects.ApplyMask(image, maskResized); + + mask.Dispose(); + maskResized.Dispose(); image.Dispose(); image = newImage; } catch (Exception ex) { + if (mask != null) + { + mask.Dispose(); + } + + if (maskResized != null) + { + maskResized.Dispose(); + } + if (newImage != null) { newImage.Dispose();