diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index c290f2748..937909347 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -50,23 +50,23 @@ 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, "ej.jpg")); + //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "2008.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "new-york.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "mountain.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "Arc-de-Triomphe-France.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "Martin-Schoeller-Jack-Nicholson-Portrait.jpeg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "crop-base-300x200.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cmyk.png")); - //IEnumerable files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif"); - + //IEnumerable files = GetFilesByExtensions(di, ".gif"); + IEnumerable files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif"); //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); - //foreach (FileInfo fileInfo in files) - //{ - //if (fileInfo.Name == "test5.jpg") - //{ - // continue; - //} + foreach (FileInfo fileInfo in files) + { + if (fileInfo.Name == "test5.jpg") + { + continue; + } byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName); Console.WriteLine("Processing: " + fileInfo.Name); @@ -79,8 +79,8 @@ namespace ImageProcessor.PlayGround { using (ImageFactory imageFactory = new ImageFactory(true)) { - Size size = new Size(844, 1017); - ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false); + Size size = new Size(1024, 0); + //ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false); //ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size) //{ @@ -102,7 +102,7 @@ namespace ImageProcessor.PlayGround //.Format(new PngFormat()) //.BackgroundColor(Color.Cyan) //.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128) - //.Resize(size) + .Resize(size) //.Resize(new ResizeLayer(size, ResizeMode.Max)) // .Resize(new ResizeLayer(size, ResizeMode.Stretch)) //.DetectEdges(new Laplacian3X3EdgeFilter(), true) @@ -119,8 +119,8 @@ namespace ImageProcessor.PlayGround //.GaussianSharpen(10) //.Format(new PngFormat() { IsIndexed = true }) //.Format(new PngFormat() { IsIndexed = true }) - //.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); - .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".png"))); + .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); + //.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".png"))); stopwatch.Stop(); } @@ -132,7 +132,7 @@ namespace ImageProcessor.PlayGround Console.WriteLine(@"Completed {0} in {1:s\.fff} secs {2}Peak memory usage was {3} bytes or {4} Mb.", fileInfo.Name, stopwatch.Elapsed, Environment.NewLine, peakWorkingSet64.ToString("#,#"), mB); //Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms"); - //} + } Console.ReadLine(); } diff --git a/src/ImageProcessor/Common/Extensions/DoubleExtensions.cs b/src/ImageProcessor/Common/Extensions/DoubleExtensions.cs index 789058179..28fbeb217 100644 --- a/src/ImageProcessor/Common/Extensions/DoubleExtensions.cs +++ b/src/ImageProcessor/Common/Extensions/DoubleExtensions.cs @@ -12,6 +12,8 @@ namespace ImageProcessor.Common.Extensions { using System; + using ImageProcessor.Imaging.Helpers; + /// /// Encapsulates a series of time saving extension methods to the class. /// @@ -24,15 +26,15 @@ namespace ImageProcessor.Common.Extensions /// those restricted ranges. /// /// - /// + /// /// The to convert. /// /// /// The . /// - public static byte ToByte(this double d) + public static byte ToByte(this double value) { - return Convert.ToByte(Math.Max(0.0d, Math.Min(255d, d))); + return Convert.ToByte(ImageMaths.Clamp(value, 0, 255)); } } } diff --git a/src/ImageProcessor/Common/Extensions/IntegerExtensions.cs b/src/ImageProcessor/Common/Extensions/IntegerExtensions.cs index e04798c0b..467c46dcf 100644 --- a/src/ImageProcessor/Common/Extensions/IntegerExtensions.cs +++ b/src/ImageProcessor/Common/Extensions/IntegerExtensions.cs @@ -10,8 +10,11 @@ namespace ImageProcessor.Common.Extensions { + using System; using System.Globalization; + using ImageProcessor.Imaging.Helpers; + /// /// Encapsulates a series of time saving extension methods to the class. /// @@ -24,27 +27,27 @@ namespace ImageProcessor.Common.Extensions /// those restricted ranges. /// /// - /// + /// /// The to convert. /// /// /// The . /// - public static byte ToByte(this int integer) + public static byte ToByte(this int value) { - return ((double)integer).ToByte(); + return Convert.ToByte(ImageMaths.Clamp(value, 0, 255)); } /// /// Converts the string representation of a number in a specified culture-specific format to its /// 32-bit signed integer equivalent using invariant culture. /// - /// The integer. - /// A string containing a number to convert. - /// A 32-bit signed integer equivalent to the number specified in s. - public static int ParseInvariant(this int integer, string s) + /// The integer. + /// A string containing a number to convert. + /// A 32-bit signed integer equivalent to the number specified in toParse. + public static int ParseInvariant(this int value, string toParse) { - return int.Parse(s, CultureInfo.InvariantCulture); + return int.Parse(toParse, CultureInfo.InvariantCulture); } } } diff --git a/src/ImageProcessor/Imaging/Colors/CmykColor.cs b/src/ImageProcessor/Imaging/Colors/CmykColor.cs index 9ec02cd41..4efeb6819 100644 --- a/src/ImageProcessor/Imaging/Colors/CmykColor.cs +++ b/src/ImageProcessor/Imaging/Colors/CmykColor.cs @@ -14,6 +14,7 @@ namespace ImageProcessor.Imaging.Colors using System.Drawing; using ImageProcessor.Common.Extensions; + using ImageProcessor.Imaging.Helpers; /// /// Represents an CMYK (cyan, magenta, yellow, keyline) color. @@ -355,16 +356,7 @@ namespace ImageProcessor.Imaging.Colors /// private static float Clamp(float value) { - if (value < 0.0) - { - value = 0.0f; - } - else if (value > 100) - { - value = 100f; - } - - return value; + return ImageMaths.Clamp(value, 0, 100); } /// diff --git a/src/ImageProcessor/Imaging/Colors/ColorExtensions.cs b/src/ImageProcessor/Imaging/Colors/ColorExtensions.cs index f0f36efa0..3d06f8c07 100644 --- a/src/ImageProcessor/Imaging/Colors/ColorExtensions.cs +++ b/src/ImageProcessor/Imaging/Colors/ColorExtensions.cs @@ -1,17 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Provides extensions for manipulating colors. +// +// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Imaging.Colors { + using System; using System.Drawing; using ImageProcessor.Common.Extensions; + /// + /// Provides extensions for manipulating colors. + /// internal static class ColorExtensions { + /// + /// Adds colors together using the RGBA color format. + /// + /// + /// The color to add to. + /// + /// + /// The colors to add to the initial one. + /// + /// + /// The combined . + /// public static Color Add(this Color color, params Color[] colors) { int red = color.A > 0 ? color.R : 0; @@ -37,6 +57,18 @@ namespace ImageProcessor.Imaging.Colors return Color.FromArgb((alpha / counter).ToByte(), (red / counter).ToByte(), (green / counter).ToByte(), (blue / counter).ToByte()); } + /// + /// Adds colors together using the CMYK color format. + /// + /// + /// The color to add to. + /// + /// + /// The colors to add to the initial one. + /// + /// + /// The combined . + /// public static CmykColor AddAsCmykColor(this Color color, params Color[] colors) { CmykColor cmyk = color; @@ -57,11 +89,6 @@ namespace ImageProcessor.Imaging.Colors } } - //c = Math.Max(0.0f, Math.Min(100f, c)); - //m = Math.Max(0.0f, Math.Min(100f, m)); - //y = Math.Max(0.0f, Math.Min(100f, y)); - //k = Math.Max(0.0f, Math.Min(100f, k)); - return CmykColor.FromCmykColor(c, m, y, k); } } diff --git a/src/ImageProcessor/Imaging/Colors/HSLAColor.cs b/src/ImageProcessor/Imaging/Colors/HSLAColor.cs index 079b54b84..9958721db 100644 --- a/src/ImageProcessor/Imaging/Colors/HSLAColor.cs +++ b/src/ImageProcessor/Imaging/Colors/HSLAColor.cs @@ -10,6 +10,8 @@ namespace ImageProcessor.Imaging.Colors using System; using System.Drawing; + using ImageProcessor.Imaging.Helpers; + /// /// Represents an HSLA (hue, saturation, luminosity, alpha) color. /// Adapted from @@ -493,16 +495,7 @@ namespace ImageProcessor.Imaging.Colors /// private static float Clamp(float value) { - if (value < 0.0) - { - value = 0.0f; - } - else if (value > 1.0) - { - value = 1.0f; - } - - return value; + return ImageMaths.Clamp(value, 0, 1); } /// diff --git a/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs b/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs index e8f881f09..7cb2c236f 100644 --- a/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs +++ b/src/ImageProcessor/Imaging/Colors/YCbCrColor.cs @@ -14,6 +14,8 @@ namespace ImageProcessor.Imaging.Colors using System; using System.Drawing; + using ImageProcessor.Imaging.Helpers; + /// /// Represents an YCbCr (luminance, chroma, chroma) color conforming to the ITU-R BT.601 standard used in digital imaging systems. /// @@ -48,9 +50,9 @@ namespace ImageProcessor.Imaging.Colors /// The v chroma component. private YCbCrColor(float y, float cb, float cr) { - this.y = Math.Max(0f, Math.Min(255f, y)); - this.cb = Math.Max(-255f, Math.Min(255f, cb)); - this.cr = Math.Max(-255f, Math.Min(255f, cr)); + this.y = ImageMaths.Clamp(y, 0, 255); + this.cb = ImageMaths.Clamp(cb, -255, 255); + this.cr = ImageMaths.Clamp(cr, -255, 255); } /// diff --git a/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs b/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs index 3b4f93f03..7e7bda4bf 100644 --- a/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs @@ -12,6 +12,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic { using System; using System.Drawing; + using System.Drawing.Drawing2D; using System.Threading.Tasks; using ImageProcessor.Imaging.Colors; @@ -167,9 +168,14 @@ namespace ImageProcessor.Imaging.Filters.Artistic int width = source.Width; int height = source.Height; + int minHeight = -height * 2; + int maxHeight = height * 2; + int minWidth = -width * 2; + int maxWidth = width * 2; + float multiplier = 4 * (float)Math.Sqrt(2); - float max = this.distance + (float)Math.Sqrt(2); - float keylineMax = max + 1; + float max = this.distance + ((float)Math.Sqrt(2) / 2); + float keylineMax = this.distance + (float)Math.Sqrt(2) + ((float)Math.Sqrt(2) / 2); // Cyan color sampled from Wikipedia page. Keyline brush is declared // separately. @@ -199,19 +205,27 @@ namespace ImageProcessor.Imaging.Filters.Artistic using (Graphics graphicsYellow = Graphics.FromImage(yellow)) using (Graphics graphicsKeyline = Graphics.FromImage(keyline)) { - // Ensure cleared out. + // Set the quality properties. + graphicsCyan.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphicsMagenta.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphicsYellow.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphicsKeyline.PixelOffsetMode = PixelOffsetMode.HighQuality; + + // Set up the canvas. graphicsCyan.Clear(Color.Transparent); graphicsMagenta.Clear(Color.Transparent); graphicsYellow.Clear(Color.Transparent); graphicsKeyline.Clear(Color.Transparent); + int d = this.distance; + // This is too slow. The graphics object can't be called within a parallel // loop so we have to do it old school. :( using (FastBitmap sourceBitmap = new FastBitmap(source)) { - for (int y = -height * 2; y < height * 2; y += this.distance) + for (int y = minHeight; y < maxHeight; y += d) { - for (int x = -width * 2; x < width * 2; x += this.distance) + for (int x = minWidth; x < maxWidth; x += d) { Color color; CmykColor cmykColor; @@ -225,7 +239,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; - brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.C / 255f) * multiplier)); + brushWidth = ImageMaths.Clamp(d * (cmykColor.C / 255f) * multiplier, 0, max); graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth); } @@ -237,7 +251,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; - brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.M / 255f) * multiplier)); + brushWidth = ImageMaths.Clamp(d * (cmykColor.M / 255f) * multiplier, 0, max); graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth); } @@ -249,7 +263,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; - brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.Y / 255f) * multiplier)); + brushWidth = ImageMaths.Clamp(d * (cmykColor.Y / 255f) * multiplier, 0, max); graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth); } @@ -261,7 +275,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; - brushWidth = Math.Max(0, Math.Min(keylineMax, this.distance * (cmykColor.K / 255f) * multiplier)); + brushWidth = ImageMaths.Clamp(d * (cmykColor.K / 255f) * multiplier, 0, keylineMax); // Just using black is too dark. Brush keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K)); @@ -342,5 +356,17 @@ namespace ImageProcessor.Imaging.Filters.Artistic return source; } + + private void SetGraphicsSettings(ref Graphics graphics) + { + // Set the quality properties. + graphics.SmoothingMode = graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + + // Set up the canvas. + graphics.Clear(Color.Transparent); + } } } diff --git a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs index 266082718..7863e8ec9 100644 --- a/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Imaging/Helpers/ImageMaths.cs @@ -20,6 +20,39 @@ namespace ImageProcessor.Imaging.Helpers /// public static class ImageMaths { + /// + /// Restricts a value to be within a specified range. + /// + /// + /// The The value to clamp. + /// + /// + /// The minimum value. If value is less than min, min will be returned. + /// + /// + /// The maximum value. If value is greater than max, max will be returned. + /// + /// + /// The to clamp. + /// + /// + /// The representing the clamped value. + /// + public static T Clamp(T value, T min, T max) where T : IComparable + { + if (value.CompareTo(min) < 0) + { + return min; + } + + if (value.CompareTo(max) > 0) + { + return max; + } + + return value; + } + /// /// Gets the bounding from the given points. /// diff --git a/src/ImageProcessor/Processors/Halftone.cs b/src/ImageProcessor/Processors/Halftone.cs index d1b149b68..1dea5b68c 100644 --- a/src/ImageProcessor/Processors/Halftone.cs +++ b/src/ImageProcessor/Processors/Halftone.cs @@ -70,7 +70,7 @@ namespace ImageProcessor.Processors int width = image.Width; int height = image.Height; Bitmap newImage = null; - Bitmap edgeBitmap = null; + //Bitmap edgeBitmap = null; try { HalftoneFilter filter = new HalftoneFilter(5); @@ -79,34 +79,34 @@ namespace ImageProcessor.Processors newImage = filter.ApplyFilter(newImage); // Draw the edges. - edgeBitmap = new Bitmap(width, height); - edgeBitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution); - edgeBitmap = Trace(image, edgeBitmap, 120); + //edgeBitmap = new Bitmap(width, height); + //edgeBitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution); + //edgeBitmap = Trace(image, edgeBitmap, 120); using (Graphics graphics = Graphics.FromImage(newImage)) { // Overlay the image. - graphics.DrawImage(edgeBitmap, 0, 0); + //graphics.DrawImage(edgeBitmap, 0, 0); Rectangle rectangle = new Rectangle(0, 0, width, height); // Draw an edge around the image. - using (Pen blackPen = new Pen(Color.Black)) - { - blackPen.Width = 4; - graphics.DrawRectangle(blackPen, rectangle); - } + //using (Pen blackPen = new Pen(Color.Black)) + //{ + // blackPen.Width = 4; + // graphics.DrawRectangle(blackPen, rectangle); + //} } - edgeBitmap.Dispose(); + //edgeBitmap.Dispose(); image.Dispose(); image = newImage; } catch (Exception ex) { - if (edgeBitmap != null) - { - edgeBitmap.Dispose(); - } + //if (edgeBitmap != null) + //{ + // edgeBitmap.Dispose(); + //} if (newImage != null) {