diff --git a/src/ImageProcessor/Colors/Bgra.cs b/src/ImageProcessor/Colors/Bgra.cs
index db814cf6b..eb9bc7e26 100644
--- a/src/ImageProcessor/Colors/Bgra.cs
+++ b/src/ImageProcessor/Colors/Bgra.cs
@@ -273,6 +273,24 @@ namespace ImageProcessor
return new Bgra(b, g, r, 255);
}
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ ///
+ /// The instance of to convert.
+ ///
+ ///
+ /// An instance of .
+ ///
+ public static implicit operator Bgra(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 Bgra(blue.ToByte(), green.ToByte(), red.ToByte());
+ }
+
///
/// Compares two objects. The result specifies whether the values
/// of the , , , and
diff --git a/src/ImageProcessor/Colors/Cmyk.cs b/src/ImageProcessor/Colors/Cmyk.cs
new file mode 100644
index 000000000..42fd96ba0
--- /dev/null
+++ b/src/ImageProcessor/Colors/Cmyk.cs
@@ -0,0 +1,227 @@
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor
+{
+ using System;
+ using System.ComponentModel;
+
+ ///
+ /// Represents an CMYK (cyan, magenta, yellow, keyline) color.
+ ///
+ public struct Cmyk : IEquatable
+ {
+ ///
+ /// Represents a that has C, M, Y, and K values set to zero.
+ ///
+ public static readonly Cmyk Empty = default(Cmyk);
+
+ ///
+ /// Gets the cyan color component.
+ ///
+ /// A value ranging between 0 and 100.
+ public readonly float C;
+
+ ///
+ /// Gets the magenta color component.
+ ///
+ /// A value ranging between 0 and 100.
+ public readonly float M;
+
+ ///
+ /// Gets the yellow color component.
+ ///
+ /// A value ranging between 0 and 100.
+ public readonly float Y;
+
+ ///
+ /// Gets the keyline black color component.
+ ///
+ /// A value ranging between 0 and 100.
+ public readonly float K;
+
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.0001f;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The cyan component.
+ /// The magenta component.
+ /// The yellow component.
+ /// The keyline black component.
+ 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);
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [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;
+
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ ///
+ /// The instance of to convert.
+ ///
+ ///
+ /// An instance of .
+ ///
+ public static implicit operator Cmyk(Bgra 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) <= Epsilon)
+ {
+ return new Cmyk(0, 0, 0, 100);
+ }
+
+ c = ((c - k) / (1 - k)) * 100;
+ m = ((m - k) / (1 - k)) * 100;
+ y = ((y - k) / (1 - k)) * 100;
+
+ return new Cmyk(c, m, y, k * 100);
+ }
+
+ ///
+ /// Compares two objects. The result specifies whether the values
+ /// of the , , , and
+ /// properties of the two objects are equal.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(Cmyk left, Cmyk right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects. The result specifies whether the values
+ /// of the , , , and
+ /// properties of the two objects are unequal.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(Cmyk left, Cmyk right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ /// Indicates whether this instance and a specified object are equal.
+ ///
+ ///
+ /// true if and this instance are the same type and represent the same value; otherwise, false.
+ ///
+ /// Another object to compare to.
+ public override bool Equals(object obj)
+ {
+ if (obj is Cmyk)
+ {
+ 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 false;
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Returns the fully qualified type name of this instance.
+ ///
+ ///
+ /// A containing a fully qualified type name.
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Cmyk [Empty]";
+ }
+
+ return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]";
+ }
+
+ ///
+ /// Indicates whether the current object is equal to another object of the same type.
+ ///
+ ///
+ /// True if the current object is equal to the parameter; otherwise, false.
+ ///
+ /// An object to compare with this object.
+ 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;
+ }
+
+ ///
+ /// Checks the range of the given value to ensure that it remains within the acceptable boundaries.
+ ///
+ ///
+ /// The value to check.
+ ///
+ ///
+ /// The sanitized .
+ ///
+ private static float Clamp(float value)
+ {
+ return value.Clamp(0, 100);
+ }
+ }
+}
diff --git a/src/ImageProcessor/Colors/Hsv.cs b/src/ImageProcessor/Colors/Hsv.cs
index 352556fae..94248d969 100644
--- a/src/ImageProcessor/Colors/Hsv.cs
+++ b/src/ImageProcessor/Colors/Hsv.cs
@@ -149,7 +149,7 @@ namespace ImageProcessor
/// The on the right side of the operand.
///
///
- /// True if the current left is equal to the parameter; otherwise, false.
+ /// True if the current left is unequal to the parameter; otherwise, false.
///
public static bool operator !=(Hsv left, Hsv right)
{
diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj
index 1207c97ff..7843b88bf 100644
--- a/src/ImageProcessor/ImageProcessor.csproj
+++ b/src/ImageProcessor/ImageProcessor.csproj
@@ -40,6 +40,7 @@
+
diff --git a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
index a5d584cc8..7b6de0e0d 100644
--- a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
+++ b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
@@ -160,12 +160,94 @@ namespace ImageProcessor.Tests
// Check others.
Random random = new Random(0);
- for (var i = 0; i < 1000; i++)
+ for (int i = 0; i < 1000; i++)
{
Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
Hsv hsb4 = bgra4;
Assert.Equal(bgra4, (Bgra)hsb4);
}
}
+
+ ///
+ /// Tests the implicit conversion from to .
+ ///
+ [Fact]
+ [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
+ Justification = "Reviewed. Suppression is OK here.")]
+ public void BgrToCmyk()
+ {
+ // White
+ Bgra color = new Bgra(255, 255, 255, 255);
+ Cmyk cmyk = color;
+
+ Assert.Equal(0, cmyk.C);
+ Assert.Equal(0, cmyk.M);
+ Assert.Equal(0, cmyk.Y);
+ Assert.Equal(0, cmyk.K);
+
+ // Black
+ Bgra color2 = new Bgra(0, 0, 0, 255);
+ Cmyk cmyk2 = color2;
+ Assert.Equal(0, cmyk2.C);
+ Assert.Equal(0, cmyk2.M);
+ Assert.Equal(0, cmyk2.Y);
+ Assert.Equal(100, cmyk2.K);
+
+ // Grey
+ Bgra color3 = new Bgra(128, 128, 128, 255);
+ 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.
+
+ // Cyan
+ Bgra color4 = new Bgra(255, 255, 0, 255);
+ Cmyk cmyk4 = color4;
+ Assert.Equal(100, cmyk4.C);
+ Assert.Equal(0, cmyk4.M);
+ Assert.Equal(0, cmyk4.Y);
+ Assert.Equal(0, cmyk4.K);
+ }
+
+ ///
+ /// Tests the implicit conversion from to .
+ ///
+ [Fact]
+ public void CmykToBgr()
+ {
+ // Dark moderate pink.
+ Cmyk cmyk = new Cmyk(49.8f, 74.9f, 58.4f, 0);
+ Bgra bgra = cmyk;
+
+ Assert.Equal(bgra.B, 106);
+ Assert.Equal(bgra.G, 64);
+ Assert.Equal(bgra.R, 128);
+
+ // Ochre
+ Cmyk cmyk2 = new Cmyk(20, 53.3f, 86.7f, 0);
+ Bgra bgra2 = cmyk2;
+
+ Assert.Equal(bgra2.B, 34);
+ Assert.Equal(bgra2.G, 119);
+ Assert.Equal(bgra2.R, 204);
+
+ // White
+ Cmyk cmyk3 = new Cmyk(0, 0, 0, 0);
+ Bgra bgra3 = cmyk3;
+
+ Assert.Equal(bgra3.B, 255);
+ Assert.Equal(bgra3.G, 255);
+ Assert.Equal(bgra3.R, 255);
+
+ // Check others.
+ Random random = new Random(0);
+ for (int i = 0; i < 1000; i++)
+ {
+ Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
+ Cmyk cmyk4 = bgra4;
+ Assert.Equal(bgra4, (Bgra)cmyk4);
+ }
+ }
}
}