Browse Source

Experiments in CMYK haftoning

Needs cleanup


Former-commit-id: 88dd7a27cabb780526d1691d38a674210a174c7e
Former-commit-id: d8e3f0a7f19517e0b52f30d021fbacc7a77eb498
pull/17/head
James South 11 years ago
parent
commit
51edc8f606
  1. 133
      src/ImageProcessor.Playground/Program.cs
  2. 1
      src/ImageProcessor.Playground/images/input/Martin-Schoeller-Jack-Nicholson-Portrait.jpeg.REMOVED.git-id
  3. 1
      src/ImageProcessor.Playground/images/input/cat.jpg.REMOVED.git-id
  4. 1
      src/ImageProcessor.Playground/images/input/cmyk-test.png.REMOVED.git-id
  5. BIN
      src/ImageProcessor.Playground/images/input/cmyk.png
  6. 2
      src/ImageProcessor.Playground/images/input/mountain.jpg.REMOVED.git-id
  7. 49
      src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs
  8. 11
      src/ImageProcessor/ImageFactory.cs
  9. 4
      src/ImageProcessor/ImageProcessor.csproj
  10. 383
      src/ImageProcessor/Imaging/Colors/CmykColor.cs
  11. 68
      src/ImageProcessor/Imaging/Colors/ColorExtensions.cs
  12. 19
      src/ImageProcessor/Imaging/Colors/HSLAColor.cs
  13. 33
      src/ImageProcessor/Imaging/Colors/RGBAColor.cs
  14. 16
      src/ImageProcessor/Imaging/Colors/YCbCrColor.cs
  15. 2
      src/ImageProcessor/Imaging/FastBitmap.cs
  16. 346
      src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs
  17. 2
      src/ImageProcessor/Imaging/Helpers/Effects.cs
  18. 46
      src/ImageProcessor/Imaging/Helpers/ImageMaths.cs
  19. 4
      src/ImageProcessor/Imaging/Quantizers/Quantizer.cs
  20. 262
      src/ImageProcessor/Processors/Halftone - Copy.cs
  21. 181
      src/ImageProcessor/Processors/Halftone.cs
  22. 4
      src/ImageProcessor/Processors/Pixelate.cs

133
src/ImageProcessor.Playground/Program.cs

@ -50,75 +50,88 @@ 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, "bob_revolutionpro_wilderness_02_2013_72dpi_2000x2000.jpg"));
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif");
FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "ej.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<FileInfo> files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif");
// foreach (FileInfo fileInfo in files)
// {
byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName);
Console.WriteLine("Processing: " + fileInfo.Name);
//foreach (FileInfo fileInfo in files)
//{
//if (fileInfo.Name == "test5.jpg")
//{
// continue;
//}
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName);
Console.WriteLine("Processing: " + fileInfo.Name);
// ImageProcessor
using (MemoryStream inStream = new MemoryStream(photoBytes))
{
using (ImageFactory imageFactory = new ImageFactory(true))
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
// ImageProcessor
using (MemoryStream inStream = new MemoryStream(photoBytes))
{
Size size = new Size(844, 1017);
ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false);
//ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size)
//{
// ConvolutionType = ConvolutionType.Sobel
//};
// Load, resize, set the format and quality and save an image.
imageFactory.Load(inStream)
//.Overlay(new ImageLayer
// {
// Image = overlay,
// Opacity = 50
// })
//.Alpha(50)
//.BackgroundColor(Color.White)
//.Resize(new Size((int)(size.Width * 1.1), 0))
//.ContentAwareResize(layer)
//.Constrain(size)
//.Mask(mask)
//.Format(new PngFormat())
//.BackgroundColor(Color.Cyan)
//.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128)
//.Resize(size)
//.Resize(new ResizeLayer(size, ResizeMode.Max))
// .Resize(new ResizeLayer(size, ResizeMode.Stretch))
//.DetectEdges(new Laplacian3X3EdgeFilter(), true)
//.DetectEdges(new LaplacianOfGaussianEdgeFilter())
.EntropyCrop()
//.Filter(MatrixFilters.Invert)
//.Contrast(50)
//.Filter(MatrixFilters.Comic)
//.Flip()
//.Filter(MatrixFilters.HiSatch)
//.Pixelate(8)
//.Rotate(45)
//.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")));
stopwatch.Stop();
using (ImageFactory imageFactory = new ImageFactory(true))
{
Size size = new Size(844, 1017);
ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false);
//ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size)
//{
// ConvolutionType = ConvolutionType.Sobel
//};
// Load, resize, set the format and quality and save an image.
imageFactory.Load(inStream)
//.Overlay(new ImageLayer
// {
// Image = overlay,
// Opacity = 50
// })
//.Alpha(50)
//.BackgroundColor(Color.White)
//.Resize(new Size((int)(size.Width * 1.1), 0))
//.ContentAwareResize(layer)
//.Constrain(size)
//.Mask(mask)
//.Format(new PngFormat())
//.BackgroundColor(Color.Cyan)
//.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128)
//.Resize(size)
//.Resize(new ResizeLayer(size, ResizeMode.Max))
// .Resize(new ResizeLayer(size, ResizeMode.Stretch))
//.DetectEdges(new Laplacian3X3EdgeFilter(), true)
//.DetectEdges(new LaplacianOfGaussianEdgeFilter())
//.EntropyCrop()
.Halftone()
//.Filter(MatrixFilters.Invert)
//.Contrast(50)
//.Filter(MatrixFilters.Comic)
//.Flip()
//.Filter(MatrixFilters.HiSatch)
//.Pixelate(8)
//.Rotate(45)
//.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")));
stopwatch.Stop();
}
}
}
long peakWorkingSet64 = Process.GetCurrentProcess().PeakWorkingSet64;
float mB = peakWorkingSet64 / (float)1024 / 1024;
long peakWorkingSet64 = Process.GetCurrentProcess().PeakWorkingSet64;
float mB = peakWorkingSet64 / (float)1024 / 1024;
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(@"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.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms");
//}
Console.ReadLine();

1
src/ImageProcessor.Playground/images/input/Martin-Schoeller-Jack-Nicholson-Portrait.jpeg.REMOVED.git-id

@ -0,0 +1 @@
ea3907f002c75115c976edb47c9a8ba28e76ad9a

1
src/ImageProcessor.Playground/images/input/cat.jpg.REMOVED.git-id

@ -0,0 +1 @@
c704af2c49aa45e35eab9e1b4839edbb7221cc7e

1
src/ImageProcessor.Playground/images/input/cmyk-test.png.REMOVED.git-id

@ -0,0 +1 @@
b6a165b747788ab89169b1b0ab7a66c1a67eeaee

BIN
src/ImageProcessor.Playground/images/input/cmyk.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

2
src/ImageProcessor.Playground/images/input/mountain.jpg.REMOVED.git-id

@ -1 +1 @@
66b1f9f5ef7ca628ca4a44de9188e11e4af17229
961e0a9abe5cb414d6a34f6c40f713d88a826910

49
src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs

@ -10,6 +10,7 @@
namespace ImageProcessor.UnitTests.Imaging
{
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
@ -61,6 +62,18 @@ namespace ImageProcessor.UnitTests.Imaging
first.Equals(second).Should().BeTrue("because the color structure should implement Equals()");
}
/// <summary>
/// Tests the <see cref="CmykColor"/> struct equality operators.
/// </summary>
[Test]
public void CmykColorImplementsEquals()
{
CmykColor first = CmykColor.FromColor(Color.White);
CmykColor second = CmykColor.FromColor(Color.White);
first.Equals(second).Should().BeTrue("because the color structure should implement Equals()");
}
/// <summary>
/// Test conversion to and from a <see cref="HslaColor"/>.
/// </summary>
@ -91,6 +104,40 @@ namespace ImageProcessor.UnitTests.Imaging
result.Should().Be(expected);
}
/// <summary>
/// Test conversion to and from a <see cref="CmykColor"/>.
/// </summary>
/// <param name="expected">
/// The expected output.
/// </param>
[Test]
[TestCase("#FFFFFF")]
[TestCase("#FEFFFE")]
[TestCase("#F0F8FF")]
[TestCase("#000000")]
[TestCase("#CCFF33")]
[TestCase("#00FF00")]
[TestCase("#FF00FF")]
[TestCase("#990000")]
[TestCase("#5C955C")]
[TestCase("#5C5C95")]
[TestCase("#3F3F66")]
[TestCase("#FFFFBB")]
[TestCase("#FF002B")]
[TestCase("#00ABFF")]
public void CmykColorShouldConvertToAndFromString(string expected)
{
Color color = ColorTranslator.FromHtml(expected);
CmykColor cmykColor = CmykColor.FromColor(color);
Debug.Print(cmykColor.ToString());
string result = ColorTranslator.ToHtml(cmykColor);
result.Should().Be(expected);
}
/// <summary>
/// Test conversion to and from a <see cref="RgbaColor"/>.
/// </summary>
@ -122,7 +169,7 @@ namespace ImageProcessor.UnitTests.Imaging
}
/// <summary>
/// Test conversion to and from a <see cref="RgbaColor"/>.
/// Test conversion to and from a <see cref="YCbCrColor"/>.
/// </summary>
/// <param name="expected">
/// The expected output.

11
src/ImageProcessor/ImageFactory.cs

@ -681,6 +681,17 @@ namespace ImageProcessor
return this;
}
public ImageFactory Halftone()
{
if (this.ShouldProcess)
{
Halftone halftone = new Halftone();
this.CurrentImageFormat.ApplyProcessor(halftone.ProcessImage, this);
}
return this;
}
/// <summary>
/// Applies the given image mask to the current image.
/// </summary>

4
src/ImageProcessor/ImageProcessor.csproj

@ -131,13 +131,16 @@
<Compile Include="Common\Extensions\DoubleExtensions.cs" />
<Compile Include="Configuration\NativeBinaryFactory.cs" />
<Compile Include="Configuration\NativeMethods.cs" />
<Compile Include="Imaging\Colors\CmykColor.cs" />
<Compile Include="Imaging\Colors\Color32.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Imaging\Colors\ColorExtensions.cs" />
<Compile Include="Imaging\Colors\HslaColor.cs" />
<Compile Include="Imaging\Colors\RgbaColor.cs" />
<Compile Include="Imaging\Colors\RgbaComponent.cs" />
<Compile Include="Imaging\Colors\YCbCrColor.cs" />
<Compile Include="Imaging\Filters\Artistic\HalftoneFilter.cs" />
<Compile Include="Imaging\Filters\Artistic\OilPaintingFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\ConvolutionFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\I2DEdgeFilter.cs" />
@ -223,6 +226,7 @@
<Compile Include="Processors\Brightness.cs" />
<Compile Include="Processors\Contrast.cs" />
<Compile Include="Processors\GaussianSharpen.cs" />
<Compile Include="Processors\Halftone.cs" />
<Compile Include="Processors\Hue.cs" />
<Compile Include="Processors\Mask.cs" />
<Compile Include="Processors\Meta.cs" />

383
src/ImageProcessor/Imaging/Colors/CmykColor.cs

@ -0,0 +1,383 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CmykColor.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents an CMYK (cyan, magenta, yellow, keyline) color.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Colors
{
using System;
using System.Drawing;
using ImageProcessor.Common.Extensions;
/// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
/// </summary>
public struct CmykColor
{
/// <summary>
/// Represents a <see cref="CmykColor"/> that is null.
/// </summary>
public static readonly CmykColor Empty = new CmykColor();
/// <summary>
/// The cyan color component.
/// </summary>
private readonly float c;
/// <summary>
/// The magenta color component.
/// </summary>
private readonly float m;
/// <summary>
/// The yellow color component.
/// </summary>
private readonly float y;
/// <summary>
/// The keyline black color component.
/// </summary>
private readonly float k;
/// <summary>
/// Initializes a new instance of the <see cref="CmykColor"/> struct.
/// </summary>
/// <param name="cyan">
/// The cyan component.
/// </param>
/// <param name="magenta">
/// The magenta component.
/// </param>
/// <param name="yellow">
/// The yellow component.
/// </param>
/// <param name="keyline">
/// The keyline black component.
/// </param>
private CmykColor(float cyan, float magenta, float yellow, float keyline)
{
this.c = Clamp(cyan);
this.m = Clamp(magenta);
this.y = Clamp(yellow);
this.k = Clamp(keyline);
}
/// <summary>
/// Initializes a new instance of the <see cref="CmykColor"/> struct.
/// </summary>
/// <param name="color">
/// The <see cref="System.Drawing.Color"/> to initialize from.
/// </param>
private CmykColor(Color color)
{
CmykColor cmykColor = color;
this.c = cmykColor.c;
this.m = cmykColor.m;
this.y = cmykColor.y;
this.k = cmykColor.k;
}
/// <summary>
/// Gets the cyan component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// </summary>
public float C
{
get
{
return this.c;
}
}
/// <summary>
/// Gets the magenta component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// </summary>
public float M
{
get
{
return this.m;
}
}
/// <summary>
/// Gets the yellow component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// </summary>
public float Y
{
get
{
return this.y;
}
}
/// <summary>
/// Gets the keyline black component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// </summary>
public float K
{
get
{
return this.k;
}
}
/// <summary>
/// Creates a <see cref="CmykColor"/> structure from the four 32-bit CMYK
/// components (cyan, magenta, yellow, and keyline) values.
/// </summary>
/// <param name="cyan">
/// The cyan component.
/// </param>
/// <param name="magenta">
/// The magenta component.
/// </param>
/// <param name="yellow">
/// The yellow component.
/// </param>
/// <param name="keyline">
/// The keyline black component.
/// </param>
/// <returns>
/// The <see cref="CmykColor"/>.
/// </returns>
public static CmykColor FromCmykColor(float cyan, float magenta, float yellow, float keyline)
{
return new CmykColor(cyan, magenta, yellow, keyline);
}
/// <summary>
/// Creates a <see cref="CmykColor"/> structure from the specified <see cref="System.Drawing.Color"/> structure
/// </summary>
/// <param name="color">
/// The <see cref="System.Drawing.Color"/> from which to create the new <see cref="CmykColor"/>.
/// </param>
/// <returns>
/// The <see cref="CmykColor"/>.
/// </returns>
public static CmykColor FromColor(Color color)
{
return new CmykColor(color);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="System.Drawing.Color"/> to a
/// <see cref="CmykColor"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="System.Drawing.Color"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CmykColor"/>.
/// </returns>
public static implicit operator CmykColor(Color color)
{
float c = (255f - color.R) / 255;
float m = (255f - color.G) / 255;
float y = (255f - color.B) / 255;
float k = Math.Min(c, Math.Min(m, y));
if (Math.Abs(k - 1.0) <= .0001f)
{
return new CmykColor(0, 0, 0, 100);
}
c = ((c - k) / (1 - k)) * 100;
m = ((m - k) / (1 - k)) * 100;
y = ((y - k) / (1 - k)) * 100;
return new CmykColor(c, m, y, k * 100);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="RgbaColor"/> to a
/// <see cref="CmykColor"/>.
/// </summary>
/// <param name="rgbaColor">
/// The instance of <see cref="RgbaColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CmykColor"/>.
/// </returns>
public static implicit operator CmykColor(RgbaColor rgbaColor)
{
return FromColor(rgbaColor);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCrColor"/> to a
/// <see cref="HslaColor"/>.
/// </summary>
/// <param name="ycbcrColor">
/// The instance of <see cref="YCbCrColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="YCbCrColor"/>.
/// </returns>
public static implicit operator CmykColor(YCbCrColor ycbcrColor)
{
Color color = ycbcrColor;
return FromColor(color);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="CmykColor"/> to a
/// <see cref="System.Drawing.Color"/>.
/// </summary>
/// <param name="cmykColor">
/// The instance of <see cref="CmykColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="System.Drawing.Color"/>.
/// </returns>
public static implicit operator Color(CmykColor cmykColor)
{
int red = Convert.ToInt32((1 - (cmykColor.c / 100)) * (1 - (cmykColor.k / 100)) * 255.0);
int green = Convert.ToInt32((1 - (cmykColor.m / 100)) * (1 - (cmykColor.k / 100)) * 255.0);
int blue = Convert.ToInt32((1 - (cmykColor.y / 100)) * (1 - (cmykColor.k / 100)) * 255.0);
return Color.FromArgb(red.ToByte(), green.ToByte(), blue.ToByte());
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="CmykColor"/> to a
/// <see cref="System.Drawing.Color"/>.
/// </summary>
/// <param name="cmykColor">
/// The instance of <see cref="CmykColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="System.Drawing.Color"/>.
/// </returns>
public static implicit operator RgbaColor(CmykColor cmykColor)
{
int red = Convert.ToInt32((1 - (cmykColor.c / 100)) * (1 - (cmykColor.k / 100)) * 255.0);
int green = Convert.ToInt32((1 - (cmykColor.m / 100)) * (1 - (cmykColor.k / 100)) * 255.0);
int blue = Convert.ToInt32((1 - (cmykColor.y / 100)) * (1 - (cmykColor.k / 100)) * 255.0);
return RgbaColor.FromRgba(red.ToByte(), green.ToByte(), blue.ToByte());
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="CmykColor"/> to a
/// <see cref="YCbCrColor"/>.
/// </summary>
/// <param name="cmykColor">
/// The instance of <see cref="CmykColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="YCbCrColor"/>.
/// </returns>
public static implicit operator YCbCrColor(CmykColor cmykColor)
{
return YCbCrColor.FromColor(cmykColor);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="CmykColor"/> to a
/// <see cref="HslaColor"/>.
/// </summary>
/// <param name="cmykColor">
/// The instance of <see cref="CmykColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="HslaColor"/>.
/// </returns>
public static implicit operator HslaColor(CmykColor cmykColor)
{
return HslaColor.FromColor(cmykColor);
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
if (this.IsEmpty())
{
return "CmykColor [Empty]";
}
return string.Format("CmykColor [ C={0:#0.##}, M={1:#0.##}, Y={2:#0.##}, K={3:#0.##}]", this.C, this.M, this.Y, this.K);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
public override bool Equals(object obj)
{
if (obj is CmykColor)
{
Color thisColor = this;
Color otherColor = (CmykColor)obj;
return thisColor.Equals(otherColor);
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
Color thisColor = this;
return thisColor.GetHashCode();
}
/// <summary>
/// Checks the range of the given value to ensure that it remains within the acceptable boundaries.
/// </summary>
/// <param name="value">
/// The value to check.
/// </param>
/// <returns>
/// The sanitized <see cref="float"/>.
/// </returns>
private static float Clamp(float value)
{
if (value < 0.0)
{
value = 0.0f;
}
else if (value > 100)
{
value = 100f;
}
return value;
}
/// <summary>
/// Returns a value indicating whether the current instance is empty.
/// </summary>
/// <returns>
/// The true if this instance is empty; otherwise, false.
/// </returns>
private bool IsEmpty()
{
const float Epsilon = .0001f;
return Math.Abs(this.c - 0) <= Epsilon && Math.Abs(this.m - 0) <= Epsilon &&
Math.Abs(this.y - 0) <= Epsilon && Math.Abs(this.k - 0) <= Epsilon;
}
}
}

68
src/ImageProcessor/Imaging/Colors/ColorExtensions.cs

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ImageProcessor.Imaging.Colors
{
using System.Drawing;
using ImageProcessor.Common.Extensions;
internal static class ColorExtensions
{
public static Color Add(this Color color, params Color[] colors)
{
int red = color.A > 0 ? color.R : 0;
int green = color.A > 0 ? color.G : 0;
int blue = color.A > 0 ? color.B : 0;
int alpha = color.A;
int counter = 0;
foreach (Color addColor in colors)
{
if (addColor.A > 0)
{
counter += 1;
red += addColor.R;
green += addColor.G;
blue += addColor.B;
alpha += addColor.A;
}
}
counter = Math.Max(1, counter);
return Color.FromArgb((alpha / counter).ToByte(), (red / counter).ToByte(), (green / counter).ToByte(), (blue / counter).ToByte());
}
public static CmykColor AddAsCmykColor(this Color color, params Color[] colors)
{
CmykColor cmyk = color;
float c = color.A > 0 ? cmyk.C : 0;
float m = color.A > 0 ? cmyk.M : 0;
float y = color.A > 0 ? cmyk.Y : 0;
float k = color.A > 0 ? cmyk.K : 0;
foreach (Color addColor in colors)
{
if (addColor.A > 0)
{
CmykColor cmykAdd = addColor;
c += cmykAdd.C;
m += cmykAdd.M;
y += cmykAdd.Y;
k += cmykAdd.K;
}
}
//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);
}
}
}

19
src/ImageProcessor/Imaging/Colors/HSLAColor.cs

@ -336,6 +336,21 @@ namespace ImageProcessor.Imaging.Colors
return YCbCrColor.FromColor(hslaColor);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="HslaColor"/> to a
/// <see cref="CmykColor"/>.
/// </summary>
/// <param name="hslaColor">
/// The instance of <see cref="HslaColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CmykColor"/>.
/// </returns>
public static implicit operator CmykColor(HslaColor hslaColor)
{
return CmykColor.FromColor(hslaColor);
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
@ -346,10 +361,10 @@ namespace ImageProcessor.Imaging.Colors
{
if (this.IsEmpty())
{
return "HSLAColor [Empty]";
return "HslaColor [Empty]";
}
return string.Format("HSLAColor [ H={0:#0.##}, S={1:#0.##}, L={2:#0.##}, A={3:#0.##}]", this.H, this.S, this.L, this.A);
return string.Format("HslaColor [ H={0:#0.##}, S={1:#0.##}, L={2:#0.##}, A={3:#0.##}]", this.H, this.S, this.L, this.A);
}
/// <summary>

33
src/ImageProcessor/Imaging/Colors/RGBAColor.cs

@ -228,45 +228,60 @@ namespace ImageProcessor.Imaging.Colors
/// Allows the implicit conversion of an instance of <see cref="RgbaColor"/> to a
/// <see cref="System.Drawing.Color"/>.
/// </summary>
/// <param name="rgba">
/// <param name="rgbaColor">
/// The instance of <see cref="RgbaColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="System.Drawing.Color"/>.
/// </returns>
public static implicit operator Color(RgbaColor rgba)
public static implicit operator Color(RgbaColor rgbaColor)
{
return Color.FromArgb(rgba.A, rgba.R, rgba.G, rgba.B);
return Color.FromArgb(rgbaColor.A, rgbaColor.R, rgbaColor.G, rgbaColor.B);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="RgbaColor"/> to a
/// <see cref="HslaColor"/>.
/// </summary>
/// <param name="rgba">
/// <param name="rgbaColor">
/// The instance of <see cref="RgbaColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="HslaColor"/>.
/// </returns>
public static implicit operator HslaColor(RgbaColor rgba)
public static implicit operator HslaColor(RgbaColor rgbaColor)
{
return HslaColor.FromColor(rgba);
return HslaColor.FromColor(rgbaColor);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="RgbaColor"/> to a
/// <see cref="YCbCrColor"/>.
/// </summary>
/// <param name="rgba">
/// <param name="rgbaColor">
/// The instance of <see cref="RgbaColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="YCbCrColor"/>.
/// </returns>
public static implicit operator YCbCrColor(RgbaColor rgba)
public static implicit operator YCbCrColor(RgbaColor rgbaColor)
{
return YCbCrColor.FromColor(rgba);
return YCbCrColor.FromColor(rgbaColor);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="RgbaColor"/> to a
/// <see cref="CmykColor"/>.
/// </summary>
/// <param name="rgbaColor">
/// The instance of <see cref="RgbaColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CmykColor"/>.
/// </returns>
public static implicit operator CmykColor(RgbaColor rgbaColor)
{
return CmykColor.FromColor(rgbaColor);
}
/// <summary>

16
src/ImageProcessor/Imaging/Colors/YCbCrColor.cs

@ -224,6 +224,22 @@ namespace ImageProcessor.Imaging.Colors
return HslaColor.FromColor(ycbcrColor);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCrColor"/> to a
/// <see cref="CmykColor"/>.
/// </summary>
/// <param name="ycbcrColor">
/// The instance of <see cref="YCbCrColor"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CmykColor"/>.
/// </returns>
public static implicit operator CmykColor(YCbCrColor ycbcrColor)
{
return CmykColor.FromColor(ycbcrColor);
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>

2
src/ImageProcessor/Imaging/FastBitmap.cs

@ -288,7 +288,7 @@ namespace ImageProcessor.Imaging
}
// Lock the bitmap
this.bitmapData = this.bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
this.bitmapData = this.bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppPArgb);
// Set the value to the first scan line
this.pixelBase = (byte*)this.bitmapData.Scan0.ToPointer();

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

@ -0,0 +1,346 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HalftoneFilter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The halftone filter applies a classical CMYK filter to the given image.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Filters.Artistic
{
using System;
using System.Drawing;
using System.Threading.Tasks;
using ImageProcessor.Imaging.Colors;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// The halftone filter applies a classical CMYK filter to the given image.
/// </summary>
public class HalftoneFilter
{
/// <summary>
/// The angle of the cyan component in degrees.
/// </summary>
private float cyanAngle = 15f;
/// <summary>
/// The angle of the magenta component in degrees.
/// </summary>
private float magentaAngle = 75f;
/// <summary>
/// The angle of the yellow component in degrees.
/// </summary>
private float yellowAngle = 0f;
/// <summary>
/// The angle of the keyline component in degrees.
/// </summary>
private float keylineAngle = 45f;
/// <summary>
/// The distance between component points.
/// </summary>
private int distance = 4;
/// <summary>
/// Initializes a new instance of the <see cref="HalftoneFilter"/> class.
/// </summary>
public HalftoneFilter()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HalftoneFilter"/> class.
/// </summary>
/// <param name="distance">
/// The distance.
/// </param>
public HalftoneFilter(int distance)
{
this.distance = distance;
}
/// <summary>
/// Gets or sets the angle of the cyan component in degrees.
/// </summary>
public float CyanAngle
{
get
{
return this.cyanAngle;
}
set
{
this.cyanAngle = value;
}
}
/// <summary>
/// Gets or sets the angle of the magenta component in degrees.
/// </summary>
public float MagentaAngle
{
get
{
return this.magentaAngle;
}
set
{
this.magentaAngle = value;
}
}
/// <summary>
/// Gets or sets the angle of the yellow component in degrees.
/// </summary>
public float YellowAngle
{
get
{
return this.yellowAngle;
}
set
{
this.yellowAngle = value;
}
}
/// <summary>
/// Gets or sets the angle of the keyline black component in degrees.
/// </summary>
public float KeylineAngle
{
get
{
return this.keylineAngle;
}
set
{
this.keylineAngle = value;
}
}
/// <summary>
/// Gets or sets the distance between component points.
/// </summary>
public int Distance
{
get
{
return this.distance;
}
set
{
this.distance = value;
}
}
/// <summary>
/// Applies the filter. TODO: Make this class implement an interface?
/// </summary>
/// <param name="source">
/// The <see cref="Bitmap"/> to apply the filter to.
/// </param>
/// <returns>
/// The <see cref="Bitmap"/> with the filter applied.
/// </returns>
public Bitmap ApplyFilter(Bitmap source)
{
Bitmap cyan = null;
Bitmap magenta = null;
Bitmap yellow = null;
Bitmap keyline = null;
Bitmap newImage = null;
try
{
int width = source.Width;
int height = source.Height;
float multiplier = 4 * (float)Math.Sqrt(2);
float max = this.distance + (float)Math.Sqrt(2);
float keylineMax = max + 1;
// Cyan color sampled from Wikipedia page. Keyline brush is declared
// separately.
Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 153, 239));
Brush magentaBrush = Brushes.Magenta;
Brush yellowBrush = Brushes.Yellow;
// Create our images.
cyan = new Bitmap(width, height);
magenta = new Bitmap(width, height);
yellow = new Bitmap(width, height);
keyline = new Bitmap(width, height);
newImage = new Bitmap(width, height);
// Ensure the correct resolution is set.
cyan.SetResolution(source.HorizontalResolution, source.VerticalResolution);
magenta.SetResolution(source.HorizontalResolution, source.VerticalResolution);
yellow.SetResolution(source.HorizontalResolution, source.VerticalResolution);
keyline.SetResolution(source.HorizontalResolution, source.VerticalResolution);
newImage.SetResolution(source.HorizontalResolution, source.VerticalResolution);
// Check bounds against this.
Rectangle rectangle = new Rectangle(0, 0, width, height);
using (Graphics graphicsCyan = Graphics.FromImage(cyan))
using (Graphics graphicsMagenta = Graphics.FromImage(magenta))
using (Graphics graphicsYellow = Graphics.FromImage(yellow))
using (Graphics graphicsKeyline = Graphics.FromImage(keyline))
{
// Ensure cleared out.
graphicsCyan.Clear(Color.Transparent);
graphicsMagenta.Clear(Color.Transparent);
graphicsYellow.Clear(Color.Transparent);
graphicsKeyline.Clear(Color.Transparent);
// 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 x = -width * 2; x < width * 2; x += this.distance)
{
Color color;
CmykColor cmykColor;
float brushWidth;
// Cyan
Point rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.cyanAngle);
int angledX = rotatedPoint.X;
int angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.C / 255f) * multiplier));
graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth);
}
// Magenta
rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.magentaAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.M / 255f) * multiplier));
graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth);
}
// Yellow
rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.yellowAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.Y / 255f) * multiplier));
graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth);
}
// Keyline
rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.keylineAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = Math.Max(0, Math.Min(keylineMax, this.distance * (cmykColor.K / 255f) * multiplier));
// Just using black is too dark.
Brush keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K));
graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth);
}
}
}
}
// Set our white background.
using (Graphics graphics = Graphics.FromImage(newImage))
{
graphics.Clear(Color.White);
}
// Blend the colors now to mimic adaptive blending.
using (FastBitmap cyanBitmap = new FastBitmap(cyan))
using (FastBitmap magentaBitmap = new FastBitmap(magenta))
using (FastBitmap yellowBitmap = new FastBitmap(yellow))
using (FastBitmap keylineBitmap = new FastBitmap(keyline))
using (FastBitmap destinationBitmap = new FastBitmap(newImage))
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < width; x++)
{
// ReSharper disable AccessToDisposedClosure
Color cyanPixel = cyanBitmap.GetPixel(x, y);
Color magentaPixel = magentaBitmap.GetPixel(x, y);
Color yellowPixel = yellowBitmap.GetPixel(x, y);
Color keylinePixel = keylineBitmap.GetPixel(x, y);
CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel);
destinationBitmap.SetPixel(x, y, blended);
// ReSharper restore AccessToDisposedClosure
}
});
}
}
cyan.Dispose();
magenta.Dispose();
yellow.Dispose();
keyline.Dispose();
source.Dispose();
source = newImage;
}
catch
{
if (cyan != null)
{
cyan.Dispose();
}
if (magenta != null)
{
magenta.Dispose();
}
if (yellow != null)
{
yellow.Dispose();
}
if (keyline != null)
{
keyline.Dispose();
}
if (newImage != null)
{
newImage.Dispose();
}
}
return source;
}
}
}

2
src/ImageProcessor/Imaging/Helpers/Effects.cs

@ -43,7 +43,7 @@ namespace ImageProcessor.Imaging.Helpers
{
using (Graphics graphics = Graphics.FromImage(source))
{
Rectangle bounds = rectangle.HasValue ? rectangle.Value : new Rectangle(0, 0, source.Width, source.Height);
Rectangle bounds = rectangle ?? new Rectangle(0, 0, source.Width, source.Height);
Rectangle ellipsebounds = bounds;
// Increase the rectangle size by the difference between the rectangle dimensions and sqrt(2)/2 * the rectangle dimensions.

46
src/ImageProcessor/Imaging/Helpers/ImageMaths.cs

@ -151,7 +151,7 @@ namespace ImageProcessor.Imaging.Helpers
bottomRight.X = getMaxX(fastBitmap) + 1;
}
return ImageMaths.GetBoundingRectangle(topLeft, bottomRight);
return GetBoundingRectangle(topLeft, bottomRight);
}
/// <summary>
@ -194,5 +194,49 @@ namespace ImageProcessor.Imaging.Helpers
new Point(rectangle.Left, rectangle.Bottom)
};
}
/// <summary>
/// Returns the given degrees converted to radians.
/// </summary>
/// <param name="angleInDegrees">
/// The angle in degrees.
/// </param>
/// <returns>
/// The <see cref="double"/> representing the degree as radians.
/// </returns>
public static double DegreesToRadians(double angleInDegrees)
{
return angleInDegrees * (Math.PI / 180);
}
/// <summary>
/// Rotates one point around another
/// <see href="http://stackoverflow.com/questions/13695317/rotate-a-point-around-another-point"/>
/// </summary>
/// <param name="pointToRotate">The point to rotate.</param>
/// <param name="angleInDegrees">The rotation angle in degrees.</param>
/// <param name="centerPoint">The centre point of rotation. If not set the point will equal
/// <see cref="Point.Empty"/>
/// </param>
/// <returns>Rotated point</returns>
public static Point RotatePoint(Point pointToRotate, double angleInDegrees, Point? centerPoint = null)
{
Point center = centerPoint ?? Point.Empty;
double angleInRadians = DegreesToRadians(angleInDegrees);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
((cosTheta * (pointToRotate.X - center.X)) -
((sinTheta * (pointToRotate.Y - center.Y)) + center.X)),
Y =
(int)
((sinTheta * (pointToRotate.X - center.X)) +
((cosTheta * (pointToRotate.Y - center.Y)) + center.Y))
};
}
}
}

4
src/ImageProcessor/Imaging/Quantizers/Quantizer.cs

@ -62,7 +62,7 @@ namespace ImageProcessor.Imaging.Quantizers
Rectangle bounds = new Rectangle(0, 0, width, height);
// First off take a 32bpp copy of the image
Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
copy.SetResolution(source.HorizontalResolution, source.VerticalResolution);
// And construct an 8bpp version
@ -85,7 +85,7 @@ namespace ImageProcessor.Imaging.Quantizers
try
{
// Get the source image bits and lock into memory
sourceData = copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
sourceData = copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through

262
src/ImageProcessor/Processors/Halftone - Copy.cs

@ -0,0 +1,262 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Halftone.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Processors
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading.Tasks;
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Common.Extensions;
using ImageProcessor.Imaging;
using ImageProcessor.Imaging.Colors;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// The halftone.
/// </summary>
class Halftone : IGraphicsProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="Halftone"/> class.
/// </summary>
public Halftone()
{
this.Settings = new Dictionary<string, string>();
}
/// <summary>
/// Gets or sets the dynamic parameter.
/// </summary>
public dynamic DynamicParameter
{
get;
set;
}
/// <summary>
/// Gets or sets any additional settings required by the processor.
/// </summary>
public Dictionary<string, string> Settings
{
get;
set;
}
/// <summary>
/// The process image.
/// </summary>
/// <param name="factory">
/// The factory.
/// </param>
/// <returns>
/// The <see cref="Image"/>.
/// </returns>
/// <exception cref="ImageProcessingException">
/// </exception>
public Image ProcessImage(ImageFactory factory)
{
Bitmap cyan = null;
Bitmap magenta = null;
Bitmap yellow = null;
Bitmap keyline = null;
Bitmap newImage = null;
Image image = factory.Image;
try
{
int width = image.Width;
int height = image.Height;
// Angles taken from Wikipedia page.
float cyanAngle = 15f;
float magentaAngle = 75f;
float yellowAngle = 0f;
float keylineAngle = 45f;
int diameter = 4;
float multiplier = 4 * (float)Math.Sqrt(2);
// Cyan color sampled from Wikipedia page.
Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 153, 239));
Brush magentaBrush = Brushes.Magenta;
Brush yellowBrush = Brushes.Yellow;
Brush keylineBrush;
// Create our images.
cyan = new Bitmap(width, height);
magenta = new Bitmap(width, height);
yellow = new Bitmap(width, height);
keyline = new Bitmap(width, height);
newImage = new Bitmap(width, height);
// Ensure the correct resolution is set.
cyan.SetResolution(image.HorizontalResolution, image.VerticalResolution);
magenta.SetResolution(image.HorizontalResolution, image.VerticalResolution);
yellow.SetResolution(image.HorizontalResolution, image.VerticalResolution);
keyline.SetResolution(image.HorizontalResolution, image.VerticalResolution);
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
// Check bounds against this.
Rectangle rectangle = new Rectangle(0, 0, width, height);
using (Graphics graphicsCyan = Graphics.FromImage(cyan))
using (Graphics graphicsMagenta = Graphics.FromImage(magenta))
using (Graphics graphicsYellow = Graphics.FromImage(yellow))
using (Graphics graphicsKeyline = Graphics.FromImage(keyline))
{
// Ensure cleared out.
graphicsCyan.Clear(Color.Transparent);
graphicsMagenta.Clear(Color.Transparent);
graphicsYellow.Clear(Color.Transparent);
graphicsKeyline.Clear(Color.Transparent);
// 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(image))
{
for (int y = -height * 2; y < height * 2; y += diameter)
{
for (int x = -width * 2; x < width * 2; x += diameter)
{
Color color;
CmykColor cmykColor;
float brushWidth;
// Cyan
Point rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), cyanAngle);
int angledX = rotatedPoint.X;
int angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.C / 255f) * multiplier;
graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth);
}
// Magenta
rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), magentaAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.M / 255f) * multiplier;
graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth);
}
// Yellow
rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), yellowAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.Y / 255f) * multiplier;
graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth);
}
// Keyline
rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), keylineAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.K / 255f) * multiplier;
// Just using blck is too dark.
keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K));
graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth);
}
}
}
}
// Set our white background.
using (Graphics graphics = Graphics.FromImage(newImage))
{
graphics.Clear(Color.White);
}
// Blend the colors now to mimic adaptive blending.
using (FastBitmap cyanBitmap = new FastBitmap(cyan))
using (FastBitmap magentaBitmap = new FastBitmap(magenta))
using (FastBitmap yellowBitmap = new FastBitmap(yellow))
using (FastBitmap keylineBitmap = new FastBitmap(keyline))
using (FastBitmap destinationBitmap = new FastBitmap(newImage))
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < width; x++)
{
// ReSharper disable AccessToDisposedClosure
Color cyanPixel = cyanBitmap.GetPixel(x, y);
Color magentaPixel = magentaBitmap.GetPixel(x, y);
Color yellowPixel = yellowBitmap.GetPixel(x, y);
Color keylinePixel = keylineBitmap.GetPixel(x, y);
CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel);
destinationBitmap.SetPixel(x, y, blended);
// ReSharper restore AccessToDisposedClosure
}
});
}
}
cyan.Dispose();
magenta.Dispose();
yellow.Dispose();
keyline.Dispose();
image.Dispose();
image = newImage;
}
catch (Exception ex)
{
if (cyan != null)
{
cyan.Dispose();
}
if (magenta != null)
{
magenta.Dispose();
}
if (yellow != null)
{
yellow.Dispose();
}
if (keyline != null)
{
keyline.Dispose();
}
if (newImage != null)
{
newImage.Dispose();
}
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
}
return image;
}
}
}

181
src/ImageProcessor/Processors/Halftone.cs

@ -0,0 +1,181 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Halftone.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The halftone processor applies a classical CMYK halftone to the given image.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Processors
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading.Tasks;
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Imaging;
using ImageProcessor.Imaging.Filters.Artistic;
using ImageProcessor.Imaging.Filters.EdgeDetection;
using ImageProcessor.Imaging.Filters.Photo;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// The halftone processor applies a classical CMYK halftone to the given image.
/// </summary>
public class Halftone : IGraphicsProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="Halftone"/> class.
/// </summary>
public Halftone()
{
this.Settings = new Dictionary<string, string>();
}
/// <summary>
/// Gets or sets the dynamic parameter.
/// </summary>
public dynamic DynamicParameter
{
get;
set;
}
/// <summary>
/// Gets or sets any additional settings required by the processor.
/// </summary>
public Dictionary<string, string> Settings
{
get;
set;
}
/// <summary>
/// Processes the image.
/// </summary>
/// <param name="factory">
/// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class containing
/// the image to process.
/// </param>
/// <returns>
/// The processed image from the current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
/// </returns>
public Image ProcessImage(ImageFactory factory)
{
Image image = factory.Image;
int width = image.Width;
int height = image.Height;
Bitmap newImage = null;
Bitmap edgeBitmap = null;
try
{
HalftoneFilter filter = new HalftoneFilter(5);
newImage = new Bitmap(image);
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
newImage = filter.ApplyFilter(newImage);
// Draw the edges.
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);
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);
}
}
edgeBitmap.Dispose();
image.Dispose();
image = newImage;
}
catch (Exception ex)
{
if (edgeBitmap != null)
{
edgeBitmap.Dispose();
}
if (newImage != null)
{
newImage.Dispose();
}
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
}
return image;
}
/// <summary>
/// Traces the edges of a given <see cref="Image"/>.
/// TODO: Move this to another class.
/// </summary>
/// <param name="source">
/// The source <see cref="Image"/>.
/// </param>
/// <param name="destination">
/// The destination <see cref="Image"/>.
/// </param>
/// <param name="threshold">
/// The threshold (between 0 and 255).
/// </param>
/// <returns>
/// The a new instance of <see cref="Bitmap"/> traced.
/// </returns>
private static Bitmap Trace(Image source, Image destination, byte threshold = 0)
{
int width = source.Width;
int height = source.Height;
// Grab the edges converting to greyscale, and invert the colors.
ConvolutionFilter filter = new ConvolutionFilter(new SobelEdgeFilter(), true);
using (Bitmap temp = filter.Process2DFilter(source))
{
destination = new InvertMatrixFilter().TransformImage(temp, destination);
// Darken it slightly to aid detection
destination = Adjustments.Brightness(destination, -5);
}
// Loop through and replace any colors more white than the threshold
// with a transparent one.
using (FastBitmap destinationBitmap = new FastBitmap(destination))
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < width; x++)
{
// ReSharper disable AccessToDisposedClosure
Color color = destinationBitmap.GetPixel(x, y);
if (color.B >= threshold)
{
destinationBitmap.SetPixel(x, y, Color.Transparent);
}
// ReSharper restore AccessToDisposedClosure
}
});
}
// Darken it again to average out the color.
destination = Adjustments.Brightness(destination, -5);
return (Bitmap)destination;
}
}
}

4
src/ImageProcessor/Processors/Pixelate.cs

@ -69,9 +69,7 @@ namespace ImageProcessor.Processors
{
Tuple<int, Rectangle?> parameters = this.DynamicParameter;
int size = parameters.Item1;
Rectangle rectangle = parameters.Item2.HasValue
? parameters.Item2.Value
: new Rectangle(0, 0, image.Width, image.Height);
Rectangle rectangle = parameters.Item2 ?? new Rectangle(0, 0, image.Width, image.Height);
int x = rectangle.X;
int y = rectangle.Y;
int offset = size / 2;

Loading…
Cancel
Save