Browse Source

Update Halftone filter

Former-commit-id: 2da23c0ceef5289df89dbf62dda8afa50ad90159
Former-commit-id: 4a67bf4b96bde694297fc54e16e225ae47fdccf8
Former-commit-id: d1ca0e94e47d7e21de3690bbd22e4ee378a50679
pull/17/head
James South 11 years ago
parent
commit
aca07b089f
  1. 10
      src/ImageProcessor.Playground/Program.cs
  2. 2
      src/ImageProcessor.Playground/images/input/monster.png.REMOVED.git-id
  3. 97
      src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs
  4. 1
      src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs

10
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, "crop-base-300x200.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cmyk.png")); //FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cmyk.png"));
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif"); //IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".png"); IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".png", ".jpg", ".jpeg");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif"); //IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif");
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png"); //IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png");
foreach (FileInfo fileInfo in files) foreach (FileInfo fileInfo in files)
{ {
@ -83,7 +83,7 @@ namespace ImageProcessor.PlayGround
// ImageProcessor // ImageProcessor
using (MemoryStream inStream = new MemoryStream(photoBytes)) 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); Size size = new Size(500, 0);
//CropLayer cropLayer = new CropLayer(20, 20, 20, 20, ImageProcessor.Imaging.CropMode.Percentage); //CropLayer cropLayer = new CropLayer(20, 20, 20, 20, ImageProcessor.Imaging.CropMode.Percentage);
@ -109,14 +109,14 @@ namespace ImageProcessor.PlayGround
//.Format(new PngFormat()) //.Format(new PngFormat())
//.BackgroundColor(Color.Cyan) //.BackgroundColor(Color.Cyan)
//.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128) //.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.Max))
// .Resize(new ResizeLayer(size, ResizeMode.Stretch)) // .Resize(new ResizeLayer(size, ResizeMode.Stretch))
//.DetectEdges(new Laplacian3X3EdgeFilter(), true) //.DetectEdges(new Laplacian3X3EdgeFilter(), true)
//.DetectEdges(new LaplacianOfGaussianEdgeFilter()) //.DetectEdges(new LaplacianOfGaussianEdgeFilter())
//.GaussianBlur(new GaussianLayer(10, 11)) //.GaussianBlur(new GaussianLayer(10, 11))
//.EntropyCrop() //.EntropyCrop()
//.Halftone(false) .Halftone()
//.RotateBounded(150, false) //.RotateBounded(150, false)
//.Crop(cropLayer) //.Crop(cropLayer)
//.Rotate(140) //.Rotate(140)

2
src/ImageProcessor.Playground/images/input/monster.png.REMOVED.git-id

@ -1 +1 @@
4edf74a6857665c8efe2d3282c25907f5b20ca81 21ea506a4ea2691e653b6dc88e47a9876167231a

97
src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs

@ -160,6 +160,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic
public Bitmap ApplyFilter(Bitmap source) public Bitmap ApplyFilter(Bitmap source)
{ {
// TODO: Make this class implement an interface? // TODO: Make this class implement an interface?
Bitmap padded = null;
Bitmap cyan = null; Bitmap cyan = null;
Bitmap magenta = null; Bitmap magenta = null;
Bitmap yellow = null; Bitmap yellow = null;
@ -168,8 +169,26 @@ namespace ImageProcessor.Imaging.Filters.Artistic
try try
{ {
int width = source.Width; int sourceWidth = source.Width;
int height = source.Height; 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. // Calculate min and max widths/heights.
Rectangle rotatedBounds = this.GetBoundingRectangle(width, height); Rectangle rotatedBounds = this.GetBoundingRectangle(width, height);
@ -180,13 +199,13 @@ namespace ImageProcessor.Imaging.Filters.Artistic
Point center = Point.Empty; Point center = Point.Empty;
// Yellow oversaturates the output. // Yellow oversaturates the output.
const float YellowMultiplier = 4; int offset = this.distance;
float multiplier = YellowMultiplier * (float)Math.Sqrt(2); float yellowMultiplier = this.distance * 1.667f;
float multiplier = this.distance * 2.2f;
float max = this.distance; float max = this.distance * (float)Math.Sqrt(2);
// Bump up the keyline max so that black looks black. // 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. // Color sampled process colours from Wikipedia pages.
// Keyline brush is declared separately. // Keyline brush is declared separately.
@ -199,7 +218,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic
magenta = new Bitmap(width, height); magenta = new Bitmap(width, height);
yellow = new Bitmap(width, height); yellow = new Bitmap(width, height);
keyline = 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. // Ensure the correct resolution is set.
cyan.SetResolution(source.HorizontalResolution, source.VerticalResolution); cyan.SetResolution(source.HorizontalResolution, source.VerticalResolution);
@ -222,73 +241,79 @@ namespace ImageProcessor.Imaging.Filters.Artistic
graphicsYellow.PixelOffsetMode = PixelOffsetMode.HighQuality; graphicsYellow.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphicsKeyline.PixelOffsetMode = PixelOffsetMode.HighQuality; graphicsKeyline.PixelOffsetMode = PixelOffsetMode.HighQuality;
// Set up the canvas. graphicsCyan.SmoothingMode = SmoothingMode.AntiAlias;
graphicsCyan.Clear(Color.Transparent); graphicsMagenta.SmoothingMode = SmoothingMode.AntiAlias;
graphicsMagenta.Clear(Color.Transparent); graphicsYellow.SmoothingMode = SmoothingMode.AntiAlias;
graphicsYellow.Clear(Color.Transparent); graphicsKeyline.SmoothingMode = SmoothingMode.AntiAlias;
graphicsKeyline.Clear(Color.Transparent);
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 // This is too slow. The graphics object can't be called within a parallel
// loop so we have to do it old school. :( // 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; Color color;
CmykColor cmykColor; CmykColor cmykColor;
float brushWidth; float brushWidth;
int offsetX = x - (d >> 1);
int offsetY = y - (d >> 1);
// Cyan // 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 angledX = rotatedPoint.X;
int angledY = rotatedPoint.Y; int angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY))) if (rectangle.Contains(new Point(angledX, angledY)))
{ {
color = sourceBitmap.GetPixel(angledX, angledY); color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color; 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); graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth);
} }
// Magenta // Magenta
rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.magentaAngle, center); rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.magentaAngle, center);
angledX = rotatedPoint.X; angledX = rotatedPoint.X;
angledY = rotatedPoint.Y; angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY))) if (rectangle.Contains(new Point(angledX, angledY)))
{ {
color = sourceBitmap.GetPixel(angledX, angledY); color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color; 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); graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth);
} }
// Yellow // Yellow
rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.yellowAngle, center); rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.yellowAngle, center);
angledX = rotatedPoint.X; angledX = rotatedPoint.X;
angledY = rotatedPoint.Y; angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY))) if (rectangle.Contains(new Point(angledX, angledY)))
{ {
color = sourceBitmap.GetPixel(angledX, angledY); color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color; 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); graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth);
} }
// Keyline // Keyline
rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.keylineAngle, center); rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.keylineAngle, center);
angledX = rotatedPoint.X; angledX = rotatedPoint.X;
angledY = rotatedPoint.Y; angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY))) if (rectangle.Contains(new Point(angledX, angledY)))
{ {
color = sourceBitmap.GetPixel(angledX, angledY); color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color; 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. // Just using black is too dark.
Brush keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K)); 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)) using (FastBitmap destinationBitmap = new FastBitmap(newImage))
{ {
Parallel.For( Parallel.For(
0, offset,
height, height,
y => y =>
{ {
for (int x = 0; x < width; x++) for (int x = offset; x < width; x++)
{ {
// ReSharper disable AccessToDisposedClosure // ReSharper disable AccessToDisposedClosure
Color cyanPixel = cyanBitmap.GetPixel(x, y); Color cyanPixel = cyanBitmap.GetPixel(x, y);
@ -324,10 +349,14 @@ namespace ImageProcessor.Imaging.Filters.Artistic
Color yellowPixel = yellowBitmap.GetPixel(x, y); Color yellowPixel = yellowBitmap.GetPixel(x, y);
Color keylinePixel = keylineBitmap.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); 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 // ReSharper restore AccessToDisposedClosure
} }
@ -335,6 +364,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic
} }
} }
padded.Dispose();
cyan.Dispose(); cyan.Dispose();
magenta.Dispose(); magenta.Dispose();
yellow.Dispose(); yellow.Dispose();
@ -344,6 +374,11 @@ namespace ImageProcessor.Imaging.Filters.Artistic
} }
catch catch
{ {
if (padded != null)
{
padded.Dispose();
}
if (cyan != null) if (cyan != null)
{ {
cyan.Dispose(); cyan.Dispose();

1
src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs

@ -11,7 +11,6 @@
namespace ImageProcessor.Imaging.Filters.Photo namespace ImageProcessor.Imaging.Filters.Photo
{ {
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using ImageProcessor.Imaging.Filters.Artistic; using ImageProcessor.Imaging.Filters.Artistic;

Loading…
Cancel
Save