mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Needs cleanup Former-commit-id: 88dd7a27cabb780526d1691d38a674210a174c7e Former-commit-id: d8e3f0a7f19517e0b52f30d021fbacc7a77eb498pull/17/head
22 changed files with 1487 additions and 81 deletions
@ -0,0 +1 @@ |
|||
ea3907f002c75115c976edb47c9a8ba28e76ad9a |
|||
@ -0,0 +1 @@ |
|||
c704af2c49aa45e35eab9e1b4839edbb7221cc7e |
|||
@ -0,0 +1 @@ |
|||
b6a165b747788ab89169b1b0ab7a66c1a67eeaee |
|||
|
After Width: | Height: | Size: 37 KiB |
@ -1 +1 @@ |
|||
66b1f9f5ef7ca628ca4a44de9188e11e4af17229 |
|||
961e0a9abe5cb414d6a34f6c40f713d88a826910 |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue