diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index 27fefc8cf..5c1b259d8 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -63,9 +63,9 @@ namespace ImageProcessor.PlayGround ////FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "crop-base-300x200.jpg")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cmyk.png")); //IEnumerable files = GetFilesByExtensions(di, ".gif"); - //IEnumerable files = GetFilesByExtensions(di, ".png"); + IEnumerable files = GetFilesByExtensions(di, ".png", ".jpg", ".jpeg"); //IEnumerable files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif"); - IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png"); + //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png"); foreach (FileInfo fileInfo in files) { @@ -83,7 +83,7 @@ namespace ImageProcessor.PlayGround // ImageProcessor using (MemoryStream inStream = new MemoryStream(photoBytes)) { - using (ImageFactory imageFactory = new ImageFactory(true, true)) + using (ImageFactory imageFactory = new ImageFactory(true, false)) { Size size = new Size(500, 0); //CropLayer cropLayer = new CropLayer(20, 20, 20, 20, ImageProcessor.Imaging.CropMode.Percentage); @@ -109,14 +109,14 @@ 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) //.DetectEdges(new LaplacianOfGaussianEdgeFilter()) //.GaussianBlur(new GaussianLayer(10, 11)) //.EntropyCrop() - //.Halftone(false) + .Halftone() //.RotateBounded(150, false) //.Crop(cropLayer) //.Rotate(140) diff --git a/src/ImageProcessor.Playground/images/input/monster.png.REMOVED.git-id b/src/ImageProcessor.Playground/images/input/monster.png.REMOVED.git-id index c13b65e9e..91c7b556f 100644 --- a/src/ImageProcessor.Playground/images/input/monster.png.REMOVED.git-id +++ b/src/ImageProcessor.Playground/images/input/monster.png.REMOVED.git-id @@ -1 +1 @@ -4edf74a6857665c8efe2d3282c25907f5b20ca81 \ No newline at end of file +21ea506a4ea2691e653b6dc88e47a9876167231a \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs b/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs index d56df76ad..2009b87f3 100644 --- a/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs @@ -160,6 +160,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic public Bitmap ApplyFilter(Bitmap source) { // TODO: Make this class implement an interface? + Bitmap padded = null; Bitmap cyan = null; Bitmap magenta = null; Bitmap yellow = null; @@ -168,8 +169,26 @@ namespace ImageProcessor.Imaging.Filters.Artistic try { - int width = source.Width; - int height = source.Height; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int width = source.Width + this.distance; + int height = source.Height + this.distance; + + // Draw a slightly larger image, flipping the top/left pixels to prevent + // jagged edge of output. + padded = new Bitmap(width, height); + padded.SetResolution(source.HorizontalResolution, source.VerticalResolution); + using (Graphics graphicsPadded = Graphics.FromImage(padded)) + { + graphicsPadded.Clear(Color.White); + Rectangle destinationRectangle = new Rectangle(0, 0, sourceWidth + this.distance, source.Height + this.distance); + using (TextureBrush tb = new TextureBrush(source)) + { + tb.WrapMode = WrapMode.TileFlipXY; + tb.TranslateTransform(this.distance, this.distance); + graphicsPadded.FillRectangle(tb, destinationRectangle); + } + } // Calculate min and max widths/heights. Rectangle rotatedBounds = this.GetBoundingRectangle(width, height); @@ -180,13 +199,13 @@ namespace ImageProcessor.Imaging.Filters.Artistic Point center = Point.Empty; // Yellow oversaturates the output. - const float YellowMultiplier = 4; - float multiplier = YellowMultiplier * (float)Math.Sqrt(2); - - float max = this.distance; + int offset = this.distance; + float yellowMultiplier = this.distance * 1.667f; + float multiplier = this.distance * 2.2f; + float max = this.distance * (float)Math.Sqrt(2); // Bump up the keyline max so that black looks black. - float keylineMax = max + ((float)Math.Sqrt(2) * 1.44f); + float keylineMax = max * (float)Math.Sqrt(2); // Color sampled process colours from Wikipedia pages. // Keyline brush is declared separately. @@ -199,7 +218,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic magenta = new Bitmap(width, height); yellow = new Bitmap(width, height); keyline = new Bitmap(width, height); - newImage = new Bitmap(width, height); + newImage = new Bitmap(sourceWidth, sourceHeight); // Ensure the correct resolution is set. cyan.SetResolution(source.HorizontalResolution, source.VerticalResolution); @@ -222,73 +241,79 @@ namespace ImageProcessor.Imaging.Filters.Artistic 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); + graphicsCyan.SmoothingMode = SmoothingMode.AntiAlias; + graphicsMagenta.SmoothingMode = SmoothingMode.AntiAlias; + graphicsYellow.SmoothingMode = SmoothingMode.AntiAlias; + graphicsKeyline.SmoothingMode = SmoothingMode.AntiAlias; - int d = this.distance; + graphicsCyan.CompositingQuality = CompositingQuality.HighQuality; + graphicsMagenta.CompositingQuality = CompositingQuality.HighQuality; + graphicsYellow.CompositingQuality = CompositingQuality.HighQuality; + graphicsKeyline.CompositingQuality = CompositingQuality.HighQuality; + + // Set up the canvas. + graphicsCyan.Clear(Color.White); + graphicsMagenta.Clear(Color.White); + graphicsYellow.Clear(Color.White); + graphicsKeyline.Clear(Color.White); // 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)) + using (FastBitmap sourceBitmap = new FastBitmap(padded)) { - for (int y = minY; y < maxY; y += d) + for (int y = minY; y < maxY; y += offset) { - for (int x = minX; x < maxX; x += d) + for (int x = minX; x < maxX; x += offset) { Color color; CmykColor cmykColor; float brushWidth; - int offsetX = x - (d >> 1); - int offsetY = y - (d >> 1); // Cyan - Point rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.cyanAngle, center); + 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))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; - brushWidth = ImageMaths.Clamp(d * (cmykColor.C / 255f) * multiplier, 0, max); + brushWidth = Math.Min((cmykColor.C / 100f) * multiplier, max); graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth); } // Magenta - rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.magentaAngle, center); + rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.magentaAngle, 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.M / 255f) * multiplier, 0, max); + brushWidth = Math.Min((cmykColor.M / 100f) * multiplier, max); graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth); } // Yellow - rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.yellowAngle, center); + 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) * YellowMultiplier, 0, max); + brushWidth = Math.Min((cmykColor.Y / 100f) * yellowMultiplier, max); graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth); } // Keyline - rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.keylineAngle, center); + rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.keylineAngle, 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.K / 255f) * multiplier, 0, keylineMax); + brushWidth = Math.Min((cmykColor.K / 100f) * multiplier, keylineMax); // Just using black is too dark. Brush keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K)); @@ -312,11 +337,11 @@ namespace ImageProcessor.Imaging.Filters.Artistic using (FastBitmap destinationBitmap = new FastBitmap(newImage)) { Parallel.For( - 0, + offset, height, y => { - for (int x = 0; x < width; x++) + for (int x = offset; x < width; x++) { // ReSharper disable AccessToDisposedClosure Color cyanPixel = cyanBitmap.GetPixel(x, y); @@ -324,10 +349,14 @@ namespace ImageProcessor.Imaging.Filters.Artistic Color yellowPixel = yellowBitmap.GetPixel(x, y); Color keylinePixel = keylineBitmap.GetPixel(x, y); + // Negate the offset. + int xBack = x - offset; + int yBack = y - offset; + CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel); - if (rectangle.Contains(new Point(x, y))) + if (rectangle.Contains(new Point(xBack, yBack))) { - destinationBitmap.SetPixel(x, y, blended); + destinationBitmap.SetPixel(xBack, yBack, blended); } // ReSharper restore AccessToDisposedClosure } @@ -335,6 +364,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic } } + padded.Dispose(); cyan.Dispose(); magenta.Dispose(); yellow.Dispose(); @@ -344,6 +374,11 @@ namespace ImageProcessor.Imaging.Filters.Artistic } catch { + if (padded != null) + { + padded.Dispose(); + } + if (cyan != null) { cyan.Dispose(); diff --git a/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs b/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs index a8f87fb06..d9c635940 100644 --- a/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs @@ -11,7 +11,6 @@ namespace ImageProcessor.Imaging.Filters.Photo { using System.Drawing; - using System.Drawing.Drawing2D; using System.Drawing.Imaging; using ImageProcessor.Imaging.Filters.Artistic;