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 000000000..f86272dc3 Binary files /dev/null and b/src/ImageProcessor.Playground/images/input/mask.png differ 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();