Browse Source

Color to CMYK conversion

Former-commit-id: be686be849ba8b99cecb3e9188c7d62cff015a2a
Former-commit-id: 832c449448d72e536b546493ff5a3f045f6b60f4
Former-commit-id: e73973e1db53ab8c7f626b7720d74922810fc674
pull/17/head
James Jackson-South 10 years ago
parent
commit
fe5735507b
  1. 18
      src/ImageProcessor/Colors/Color.cs
  2. 18
      src/ImageProcessor/Colors/Formats/Bgra32.cs
  3. 148
      src/ImageProcessor/Colors/Formats/Cmyk.cs
  4. 2
      src/ImageProcessor/Colors/Formats/Hsv.cs
  5. 92
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

18
src/ImageProcessor/Colors/Color.cs

@ -174,6 +174,24 @@ namespace ImageProcessor
return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Cmyk"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="cmykColor">
/// The instance of <see cref="Cmyk"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
public static implicit operator Color(Cmyk cmykColor)
{
float r = (1 - cmykColor.C) * (1 - cmykColor.K);
float g = (1 - cmykColor.M) * (1 - cmykColor.K);
float b = (1 - cmykColor.Y) * (1 - cmykColor.K);
return new Color(r, g, b);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a
/// <see cref="Color"/>.

18
src/ImageProcessor/Colors/Formats/Bgra32.cs

@ -212,24 +212,6 @@ namespace ImageProcessor
return new Bgra32(b, g, r, 255);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Cmyk"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="cmykColor">
/// The instance of <see cref="Cmyk"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
public static implicit operator Bgra32(Cmyk 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 new Bgra32(blue.ToByte(), green.ToByte(), red.ToByte());
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects. The result specifies whether the values
/// of the <see cref="Bgra32.B"/>, <see cref="Bgra32.G"/>, <see cref="Bgra32.R"/>, and <see cref="Bgra32.A"/>

148
src/ImageProcessor/Colors/Formats/Cmyk.cs

@ -7,6 +7,7 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
@ -19,60 +20,63 @@ namespace ImageProcessor
public static readonly Cmyk Empty = default(Cmyk);
/// <summary>
/// Gets the cyan color component.
/// The epsilon for comparing floating point numbers.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float C;
private const float Epsilon = 0.0001f;
/// <summary>
/// Gets the magenta color component.
/// The backing vector for SIMD support.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float M;
private Vector4 backingVector;
/// <summary>
/// Gets the yellow color component.
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float Y;
/// <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>
public Cmyk(float cyan, float magenta, float yellow, float keyline)
: this()
{
this.backingVector.X = Clamp(cyan);
this.backingVector.Y = Clamp(magenta);
this.backingVector.Z = Clamp(yellow);
this.backingVector.W = Clamp(keyline);
}
/// <summary>
/// Gets the keyline black color component.
/// Gets the cyan color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float K;
public float C => this.backingVector.X;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// Gets the magenta color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
private const float Epsilon = 0.0001f;
public float M => this.backingVector.Y;
/// <summary>
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// Gets the yellow color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </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>
public Cmyk(float cyan, float magenta, float yellow, float keyline)
{
this.C = Clamp(cyan);
this.M = Clamp(magenta);
this.Y = Clamp(yellow);
this.K = Clamp(keyline);
}
public float Y => this.backingVector.Z;
/// <summary>
/// Gets the keyline black color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float K => this.backingVector.W;
/// <summary>
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.C) < Epsilon
&& Math.Abs(this.M) < Epsilon
&& Math.Abs(this.Y) < Epsilon
&& Math.Abs(this.K) < Epsilon;
public bool IsEmpty => this.backingVector.Equals(default(Vector4));
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Bgra32"/> to a
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Cmyk"/>.
/// </summary>
/// <param name="color">
@ -81,30 +85,30 @@ namespace ImageProcessor
/// <returns>
/// An instance of <see cref="Cmyk"/>.
/// </returns>
public static implicit operator Cmyk(Bgra32 color)
public static implicit operator Cmyk(Color color)
{
float c = (255f - color.R) / 255;
float m = (255f - color.G) / 255;
float y = (255f - color.B) / 255;
color = color.Limited;
float c = 1f - color.R;
float m = 1f - color.G;
float y = 1f - color.B;
float k = Math.Min(c, Math.Min(m, y));
if (Math.Abs(k - 1.0) <= Epsilon)
if (Math.Abs(k - 1.0f) <= Epsilon)
{
return new Cmyk(0, 0, 0, 100);
return new Cmyk(0, 0, 0, 1);
}
c = ((c - k) / (1 - k)) * 100;
m = ((m - k) / (1 - k)) * 100;
y = ((y - k) / (1 - k)) * 100;
c = (c - k) / (1 - k);
m = (m - k) / (1 - k);
y = (y - k) / (1 - k);
return new Cmyk(c, m, y, k * 100);
return new Cmyk(c, m, y, k);
}
/// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values
/// of the <see cref="Cmyk.C"/>, <see cref="Cmyk.M"/>, <see cref="Cmyk.Y"/>, and <see cref="Cmyk.K"/>
/// properties of the two <see cref="Cmyk"/> objects are equal.
/// Compares two <see cref="Cmyk"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand.
@ -121,9 +125,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values
/// of the <see cref="Cmyk.C"/>, <see cref="Cmyk.M"/>, <see cref="Cmyk.Y"/>, and <see cref="Cmyk.K"/>
/// properties of the two <see cref="Cmyk"/> objects are unequal.
/// Compares two <see cref="Cmyk"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand.
@ -152,39 +154,19 @@ namespace ImageProcessor
{
Cmyk color = (Cmyk)obj;
return Math.Abs(this.C - color.C) < Epsilon
&& Math.Abs(this.M - color.M) < Epsilon
&& Math.Abs(this.Y - color.Y) < Epsilon
&& Math.Abs(this.K - color.K) < Epsilon;
return this.backingVector == color.backingVector;
}
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>
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.C.GetHashCode();
hashCode = (hashCode * 397) ^ this.M.GetHashCode();
hashCode = (hashCode * 397) ^ this.Y.GetHashCode();
hashCode = (hashCode * 397) ^ this.K.GetHashCode();
return hashCode;
}
return GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
@ -195,19 +177,10 @@ namespace ImageProcessor
return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
/// <inheritdoc/>
public bool Equals(Cmyk other)
{
return Math.Abs(this.C - other.C) < Epsilon
&& Math.Abs(this.M - other.M) < Epsilon
&& Math.Abs(this.Y - other.Y) < Epsilon
&& Math.Abs(this.K - other.Y) < Epsilon;
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
@ -223,5 +196,16 @@ namespace ImageProcessor
{
return value.Clamp(0, 100);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Cmyk"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(Cmyk color) => color.backingVector.GetHashCode();
}
}

2
src/ImageProcessor/Colors/Formats/Hsv.cs

@ -122,7 +122,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects for equality
/// Compares two <see cref="Hsv"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand.

92
tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

@ -17,7 +17,11 @@ namespace ImageProcessor.Tests
/// <summary>
/// Test conversion between the various color structs.
/// </summary>
/// </summary>
/// <remarks>
/// Output values have been compared with <see cref="http://colormine.org/color-converter"/>
/// and <see cref="http://www.colorhexa.com/"/> for accuracy.
/// </remarks>
public class ColorConversionTests
{
/// <summary>
@ -163,90 +167,90 @@ namespace ImageProcessor.Tests
for (int i = 0; i < 1000; i++)
{
Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Hsv hsb4 = color4;
Assert.Equal(color4, (Color)hsb4);
Hsv hsv4 = color4;
Assert.Equal(color4, (Color)hsv4);
}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Bgra32"/> to <see cref="Cmyk"/>.
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Cmyk"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void BgrToCmyk()
public void ColorToCmyk()
{
// White
Bgra32 color = new Bgra32(255, 255, 255, 255);
Color color = new Color(1, 1, 1);
Cmyk cmyk = color;
Assert.Equal(0, cmyk.C);
Assert.Equal(0, cmyk.M);
Assert.Equal(0, cmyk.Y);
Assert.Equal(0, cmyk.K);
Assert.Equal(0, cmyk.C, 1);
Assert.Equal(0, cmyk.M, 1);
Assert.Equal(0, cmyk.Y, 1);
Assert.Equal(0, cmyk.K, 1);
// Black
Bgra32 color2 = new Bgra32(0, 0, 0, 255);
Color color2 = new Color(0, 0, 0);
Cmyk cmyk2 = color2;
Assert.Equal(0, cmyk2.C);
Assert.Equal(0, cmyk2.M);
Assert.Equal(0, cmyk2.Y);
Assert.Equal(100, cmyk2.K);
Assert.Equal(0, cmyk2.C, 1);
Assert.Equal(0, cmyk2.M, 1);
Assert.Equal(0, cmyk2.Y, 1);
Assert.Equal(1, cmyk2.K, 1);
// Grey
Bgra32 color3 = new Bgra32(128, 128, 128, 255);
Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f);
Cmyk cmyk3 = color3;
Assert.Equal(0, cmyk3.C);
Assert.Equal(0, cmyk3.M);
Assert.Equal(0, cmyk3.Y);
Assert.Equal(49.8, cmyk3.K, 1); // Checked with other tools.
Assert.Equal(0f, cmyk3.C, 1);
Assert.Equal(0f, cmyk3.M, 1);
Assert.Equal(0f, cmyk3.Y, 1);
Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters.
// Cyan
Bgra32 color4 = new Bgra32(255, 255, 0, 255);
Color color4 = new Color(0, 1, 1);
Cmyk cmyk4 = color4;
Assert.Equal(100, cmyk4.C);
Assert.Equal(0, cmyk4.M);
Assert.Equal(0, cmyk4.Y);
Assert.Equal(0, cmyk4.K);
Assert.Equal(1, cmyk4.C, 1);
Assert.Equal(0f, cmyk4.M, 1);
Assert.Equal(0f, cmyk4.Y, 1);
Assert.Equal(0f, cmyk4.K, 1);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Hsv"/> to <see cref="Bgra32"/>.
/// Tests the implicit conversion from <see cref="Cmyk"/> to <see cref="Color"/>.
/// </summary>
[Fact]
public void CmykToBgr()
public void CmykToColor()
{
// Dark moderate pink.
Cmyk cmyk = new Cmyk(49.8f, 74.9f, 58.4f, 0);
Bgra32 bgra32 = cmyk;
Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f);
Color color = cmyk;
Assert.Equal(bgra32.B, 106);
Assert.Equal(bgra32.G, 64);
Assert.Equal(bgra32.R, 128);
Assert.Equal(color.R, 128 / 255f, 1);
Assert.Equal(color.G, 64 / 255f, 1);
Assert.Equal(color.B, 106 / 255f, 1);
// Ochre
Cmyk cmyk2 = new Cmyk(20, 53.3f, 86.7f, 0);
Bgra32 bgra2 = cmyk2;
Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f);
Color color2 = cmyk2;
Assert.Equal(bgra2.B, 34);
Assert.Equal(bgra2.G, 119);
Assert.Equal(bgra2.R, 204);
Assert.Equal(color2.R, 204 / 255f, 1);
Assert.Equal(color2.G, 119 / 255f, 1);
Assert.Equal(color2.B, 34 / 255f, 1);
// White
Cmyk cmyk3 = new Cmyk(0, 0, 0, 0);
Bgra32 bgra3 = cmyk3;
Color color3 = cmyk3;
Assert.Equal(bgra3.B, 255);
Assert.Equal(bgra3.G, 255);
Assert.Equal(bgra3.R, 255);
Assert.Equal(color3.R, 1f, 1);
Assert.Equal(color3.G, 1f, 1);
Assert.Equal(color3.B, 1f, 1);
// Check others.
Random random = new Random(0);
for (int i = 0; i < 1000; i++)
{
Bgra32 bgra4 = new Bgra32((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
Cmyk cmyk4 = bgra4;
Assert.Equal(bgra4, (Bgra32)cmyk4);
Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Cmyk cmyk4 = color4;
Assert.Equal(color4, (Color)cmyk4);
}
}
}

Loading…
Cancel
Save