Browse Source

Color to CMYK conversion

Former-commit-id: 19ef3f0b5766df8f02ad5257cf952cbcff38552d
Former-commit-id: 0ed3ad845f74a410f94de38b7cd9e751c3d1baaa
Former-commit-id: 30ee7d2e2fe873ba3e7700db51010f4578d66c30
af/merge-core
James Jackson-South 11 years ago
parent
commit
1bdf766e50
  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); 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> /// <summary>
/// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a /// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a
/// <see cref="Color"/>. /// <see cref="Color"/>.

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

@ -212,24 +212,6 @@ namespace ImageProcessor
return new Bgra32(b, g, r, 255); 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> /// <summary>
/// Compares two <see cref="Bgra32"/> objects. The result specifies whether the values /// 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"/> /// 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;
using System.ComponentModel; using System.ComponentModel;
using System.Numerics;
/// <summary> /// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color. /// Represents an CMYK (cyan, magenta, yellow, keyline) color.
@ -19,60 +20,63 @@ namespace ImageProcessor
public static readonly Cmyk Empty = default(Cmyk); public static readonly Cmyk Empty = default(Cmyk);
/// <summary> /// <summary>
/// Gets the cyan color component. /// The epsilon for comparing floating point numbers.
/// </summary> /// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks> private const float Epsilon = 0.0001f;
public readonly float C;
/// <summary> /// <summary>
/// Gets the magenta color component. /// The backing vector for SIMD support.
/// </summary> /// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks> private Vector4 backingVector;
public readonly float M;
/// <summary> /// <summary>
/// Gets the yellow color component. /// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// </summary> /// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks> /// <param name="cyan">The cyan component.</param>
public readonly float Y; /// <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> /// <summary>
/// Gets the keyline black color component. /// Gets the cyan color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary> /// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks> public float C => this.backingVector.X;
public readonly float K;
/// <summary> /// <summary>
/// The epsilon for comparing floating point numbers. /// Gets the magenta color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary> /// </summary>
private const float Epsilon = 0.0001f; public float M => this.backingVector.Y;
/// <summary> /// <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> /// </summary>
/// <param name="cyan">The cyan component.</param> public float Y => this.backingVector.Z;
/// <param name="magenta">The magenta component.</param>
/// <param name="yellow">The yellow component.</param> /// <summary>
/// <param name="keyline">The keyline black component.</param> /// Gets the keyline black color component.
public Cmyk(float cyan, float magenta, float yellow, float keyline) /// <remarks>A value ranging between 0 and 1.</remarks>
{ /// </summary>
this.C = Clamp(cyan); public float K => this.backingVector.W;
this.M = Clamp(magenta);
this.Y = Clamp(yellow);
this.K = Clamp(keyline);
}
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty. /// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.C) < Epsilon public bool IsEmpty => this.backingVector.Equals(default(Vector4));
&& Math.Abs(this.M) < Epsilon
&& Math.Abs(this.Y) < Epsilon
&& Math.Abs(this.K) < Epsilon;
/// <summary> /// <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"/>. /// <see cref="Cmyk"/>.
/// </summary> /// </summary>
/// <param name="color"> /// <param name="color">
@ -81,30 +85,30 @@ namespace ImageProcessor
/// <returns> /// <returns>
/// An instance of <see cref="Cmyk"/>. /// An instance of <see cref="Cmyk"/>.
/// </returns> /// </returns>
public static implicit operator Cmyk(Bgra32 color) public static implicit operator Cmyk(Color color)
{ {
float c = (255f - color.R) / 255; color = color.Limited;
float m = (255f - color.G) / 255;
float y = (255f - color.B) / 255; float c = 1f - color.R;
float m = 1f - color.G;
float y = 1f - color.B;
float k = Math.Min(c, Math.Min(m, y)); 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; c = (c - k) / (1 - k);
m = ((m - k) / (1 - k)) * 100; m = (m - k) / (1 - k);
y = ((y - k) / (1 - k)) * 100; y = (y - k) / (1 - k);
return new Cmyk(c, m, y, k * 100); return new Cmyk(c, m, y, k);
} }
/// <summary> /// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values /// Compares two <see cref="Cmyk"/> objects for equality.
/// 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.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand. /// The <see cref="Cmyk"/> on the left side of the operand.
@ -121,9 +125,7 @@ namespace ImageProcessor
} }
/// <summary> /// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values /// Compares two <see cref="Cmyk"/> objects for inequality
/// 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.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand. /// The <see cref="Cmyk"/> on the left side of the operand.
@ -152,39 +154,19 @@ namespace ImageProcessor
{ {
Cmyk color = (Cmyk)obj; Cmyk color = (Cmyk)obj;
return Math.Abs(this.C - color.C) < Epsilon return this.backingVector == color.backingVector;
&& Math.Abs(this.M - color.M) < Epsilon
&& Math.Abs(this.Y - color.Y) < Epsilon
&& Math.Abs(this.K - color.K) < Epsilon;
} }
return false; return false;
} }
/// <summary> /// <inheritdoc/>
/// 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() public override int GetHashCode()
{ {
unchecked return GetHashCode(this);
{
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;
}
} }
/// <summary> /// <inheritdoc/>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
public override string ToString() public override string ToString()
{ {
if (this.IsEmpty) 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.##}]"; return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]";
} }
/// <summary> /// <inheritdoc/>
/// 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>
public bool Equals(Cmyk other) public bool Equals(Cmyk other)
{ {
return Math.Abs(this.C - other.C) < Epsilon return this.backingVector.Equals(other.backingVector);
&& Math.Abs(this.M - other.M) < Epsilon
&& Math.Abs(this.Y - other.Y) < Epsilon
&& Math.Abs(this.K - other.Y) < Epsilon;
} }
/// <summary> /// <summary>
@ -223,5 +196,16 @@ namespace ImageProcessor
{ {
return value.Clamp(0, 100); 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> /// <summary>
/// Compares two <see cref="Hsv"/> objects for equality /// Compares two <see cref="Hsv"/> objects for equality.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand. /// 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> /// <summary>
/// Test conversion between the various color structs. /// 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 public class ColorConversionTests
{ {
/// <summary> /// <summary>
@ -163,90 +167,90 @@ namespace ImageProcessor.Tests
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {
Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1)); Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Hsv hsb4 = color4; Hsv hsv4 = color4;
Assert.Equal(color4, (Color)hsb4); Assert.Equal(color4, (Color)hsv4);
} }
} }
/// <summary> /// <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> /// </summary>
[Fact] [Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")] Justification = "Reviewed. Suppression is OK here.")]
public void BgrToCmyk() public void ColorToCmyk()
{ {
// White // White
Bgra32 color = new Bgra32(255, 255, 255, 255); Color color = new Color(1, 1, 1);
Cmyk cmyk = color; Cmyk cmyk = color;
Assert.Equal(0, cmyk.C); Assert.Equal(0, cmyk.C, 1);
Assert.Equal(0, cmyk.M); Assert.Equal(0, cmyk.M, 1);
Assert.Equal(0, cmyk.Y); Assert.Equal(0, cmyk.Y, 1);
Assert.Equal(0, cmyk.K); Assert.Equal(0, cmyk.K, 1);
// Black // Black
Bgra32 color2 = new Bgra32(0, 0, 0, 255); Color color2 = new Color(0, 0, 0);
Cmyk cmyk2 = color2; Cmyk cmyk2 = color2;
Assert.Equal(0, cmyk2.C); Assert.Equal(0, cmyk2.C, 1);
Assert.Equal(0, cmyk2.M); Assert.Equal(0, cmyk2.M, 1);
Assert.Equal(0, cmyk2.Y); Assert.Equal(0, cmyk2.Y, 1);
Assert.Equal(100, cmyk2.K); Assert.Equal(1, cmyk2.K, 1);
// Grey // Grey
Bgra32 color3 = new Bgra32(128, 128, 128, 255); Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f);
Cmyk cmyk3 = color3; Cmyk cmyk3 = color3;
Assert.Equal(0, cmyk3.C); Assert.Equal(0f, cmyk3.C, 1);
Assert.Equal(0, cmyk3.M); Assert.Equal(0f, cmyk3.M, 1);
Assert.Equal(0, cmyk3.Y); Assert.Equal(0f, cmyk3.Y, 1);
Assert.Equal(49.8, cmyk3.K, 1); // Checked with other tools. Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters.
// Cyan // Cyan
Bgra32 color4 = new Bgra32(255, 255, 0, 255); Color color4 = new Color(0, 1, 1);
Cmyk cmyk4 = color4; Cmyk cmyk4 = color4;
Assert.Equal(100, cmyk4.C); Assert.Equal(1, cmyk4.C, 1);
Assert.Equal(0, cmyk4.M); Assert.Equal(0f, cmyk4.M, 1);
Assert.Equal(0, cmyk4.Y); Assert.Equal(0f, cmyk4.Y, 1);
Assert.Equal(0, cmyk4.K); Assert.Equal(0f, cmyk4.K, 1);
} }
/// <summary> /// <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> /// </summary>
[Fact] [Fact]
public void CmykToBgr() public void CmykToColor()
{ {
// Dark moderate pink. // Dark moderate pink.
Cmyk cmyk = new Cmyk(49.8f, 74.9f, 58.4f, 0); Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f);
Bgra32 bgra32 = cmyk; Color color = cmyk;
Assert.Equal(bgra32.B, 106); Assert.Equal(color.R, 128 / 255f, 1);
Assert.Equal(bgra32.G, 64); Assert.Equal(color.G, 64 / 255f, 1);
Assert.Equal(bgra32.R, 128); Assert.Equal(color.B, 106 / 255f, 1);
// Ochre // Ochre
Cmyk cmyk2 = new Cmyk(20, 53.3f, 86.7f, 0); Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f);
Bgra32 bgra2 = cmyk2; Color color2 = cmyk2;
Assert.Equal(bgra2.B, 34); Assert.Equal(color2.R, 204 / 255f, 1);
Assert.Equal(bgra2.G, 119); Assert.Equal(color2.G, 119 / 255f, 1);
Assert.Equal(bgra2.R, 204); Assert.Equal(color2.B, 34 / 255f, 1);
// White // White
Cmyk cmyk3 = new Cmyk(0, 0, 0, 0); Cmyk cmyk3 = new Cmyk(0, 0, 0, 0);
Bgra32 bgra3 = cmyk3; Color color3 = cmyk3;
Assert.Equal(bgra3.B, 255); Assert.Equal(color3.R, 1f, 1);
Assert.Equal(bgra3.G, 255); Assert.Equal(color3.G, 1f, 1);
Assert.Equal(bgra3.R, 255); Assert.Equal(color3.B, 1f, 1);
// Check others. // Check others.
Random random = new Random(0); Random random = new Random(0);
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {
Bgra32 bgra4 = new Bgra32((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255)); Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Cmyk cmyk4 = bgra4; Cmyk cmyk4 = color4;
Assert.Equal(bgra4, (Bgra32)cmyk4); Assert.Equal(color4, (Color)cmyk4);
} }
} }
} }

Loading…
Cancel
Save