diff --git a/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs b/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs index b3c4770851..43669cec10 100644 --- a/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs @@ -1,4 +1,4 @@ -// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. @@ -11,6 +11,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic { using System; + using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Threading.Tasks; @@ -168,14 +169,22 @@ 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; + // Calculate min and max widths/heights. + Rectangle rotatedBounds = this.GetBoundingRectangle(width, height); + int minY = -(rotatedBounds.Height + height); + int maxY = rotatedBounds.Height + height; + int minX = -(rotatedBounds.Width + width); + int maxX = rotatedBounds.Width + width; + Point center = Point.Empty; + + // Yellow oversaturates the output. + const float YellowMultiplier = 4; + float multiplier = YellowMultiplier * (float)Math.Sqrt(2); - float multiplier = 4 * (float)Math.Sqrt(2); - float max = this.distance + ((float)Math.Sqrt(2) / 2); - float keylineMax = this.distance + (float)Math.Sqrt(2) + ((float)Math.Sqrt(2) / 2); + float max = this.distance; + + // Bump up the keyline max so that black looks black. + float keylineMax = max + ((float)Math.Sqrt(2) * 1.5f); // Color sampled process colours from Wikipedia pages. // Keyline brush is declared separately. @@ -223,16 +232,16 @@ namespace ImageProcessor.Imaging.Filters.Artistic // loop so we have to do it old school. :( using (FastBitmap sourceBitmap = new FastBitmap(source)) { - for (int y = minHeight; y < maxHeight; y += d) + for (int y = minY; y < maxY; y += d) { - for (int x = minWidth; x < maxWidth; x += d) + for (int x = minX; x < maxX; x += d) { Color color; CmykColor cmykColor; float brushWidth; // Cyan - Point rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.cyanAngle); + Point rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.cyanAngle, center); int angledX = rotatedPoint.X; int angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) @@ -244,7 +253,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic } // Magenta - rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.magentaAngle); + rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.magentaAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) @@ -256,19 +265,19 @@ namespace ImageProcessor.Imaging.Filters.Artistic } // Yellow - rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.yellowAngle); + rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.yellowAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; - brushWidth = ImageMaths.Clamp(d * (cmykColor.Y / 255f) * multiplier, 0, max); + brushWidth = ImageMaths.Clamp(d * (cmykColor.Y / 255f) * YellowMultiplier, 0, max); graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth); } // Keyline - rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.keylineAngle); + rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.keylineAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) @@ -357,16 +366,46 @@ namespace ImageProcessor.Imaging.Filters.Artistic return source; } - private void SetGraphicsSettings(ref Graphics graphics) + /// + /// Gets the bounding rectangle of the image based on the rotating angles. + /// + /// + /// The width of the image. + /// + /// + /// The height of the image. + /// + /// + /// The . + /// + private Rectangle GetBoundingRectangle(int width, int height) { - // 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); + int maxWidth = 0; + int maxHeight = 0; + List angles = new List { this.CyanAngle, this.MagentaAngle, this.YellowAngle, this.KeylineAngle }; + + foreach (float angle in angles) + { + double radians = ImageMaths.DegreesToRadians(angle); + double radiansSin = Math.Sin(radians); + double radiansCos = Math.Cos(radians); + double width1 = (height * radiansSin) + (width * radiansCos); + double height1 = (width * radiansSin) + (height * radiansCos); + + // Find dimensions in the other direction + radiansSin = Math.Sin(-radians); + radiansCos = Math.Cos(-radians); + double width2 = (height * radiansSin) + (width * radiansCos); + double height2 = (width * radiansSin) + (height * radiansCos); + + int maxW = Math.Max(maxWidth, Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2)))); + int maxH = Math.Max(maxHeight, Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2)))); + + maxHeight = maxH; + maxWidth = maxW; + } + + return new Rectangle(0, 0, maxWidth, maxHeight); } } }