diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs index b8b554804..1cd19a231 100644 --- a/src/ImageProcessor/Colors/Color.cs +++ b/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); } + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + 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); + } + /// /// Allows the implicit conversion of an instance of to a /// . diff --git a/src/ImageProcessor/Colors/Formats/Bgra32.cs b/src/ImageProcessor/Colors/Formats/Bgra32.cs index f9b10069e..465e7d377 100644 --- a/src/ImageProcessor/Colors/Formats/Bgra32.cs +++ b/src/ImageProcessor/Colors/Formats/Bgra32.cs @@ -212,24 +212,6 @@ namespace ImageProcessor return new Bgra32(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 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()); - } - /// /// Compares two objects. The result specifies whether the values /// of the , , , and diff --git a/src/ImageProcessor/Colors/Formats/Cmyk.cs b/src/ImageProcessor/Colors/Formats/Cmyk.cs index 377d35ecc..3425c6ada 100644 --- a/src/ImageProcessor/Colors/Formats/Cmyk.cs +++ b/src/ImageProcessor/Colors/Formats/Cmyk.cs @@ -7,6 +7,7 @@ namespace ImageProcessor { using System; using System.ComponentModel; + using System.Numerics; /// /// Represents an CMYK (cyan, magenta, yellow, keyline) color. @@ -19,60 +20,63 @@ namespace ImageProcessor public static readonly Cmyk Empty = default(Cmyk); /// - /// Gets the cyan color component. + /// The epsilon for comparing floating point numbers. /// - /// A value ranging between 0 and 100. - public readonly float C; + private const float Epsilon = 0.0001f; /// - /// Gets the magenta color component. + /// The backing vector for SIMD support. /// - /// A value ranging between 0 and 100. - public readonly float M; + private Vector4 backingVector; /// - /// Gets the yellow color component. + /// Initializes a new instance of the struct. /// - /// A value ranging between 0 and 100. - public readonly float Y; + /// The cyan component. + /// The magenta component. + /// The yellow component. + /// The keyline black component. + 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); + } /// - /// Gets the keyline black color component. + /// Gets the cyan color component. + /// A value ranging between 0 and 1. /// - /// A value ranging between 0 and 100. - public readonly float K; + public float C => this.backingVector.X; /// - /// The epsilon for comparing floating point numbers. + /// Gets the magenta color component. + /// A value ranging between 0 and 1. /// - private const float Epsilon = 0.0001f; + public float M => this.backingVector.Y; /// - /// Initializes a new instance of the struct. + /// Gets the yellow color component. + /// A value ranging between 0 and 1. /// - /// 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); - } + public float Y => this.backingVector.Z; + + /// + /// Gets the keyline black color component. + /// A value ranging between 0 and 1. + /// + public float K => this.backingVector.W; /// /// 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; + public bool IsEmpty => this.backingVector.Equals(default(Vector4)); /// - /// Allows the implicit conversion of an instance of to a + /// Allows the implicit conversion of an instance of to a /// . /// /// @@ -81,30 +85,30 @@ namespace ImageProcessor /// /// An instance of . /// - 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); } /// - /// Compares two objects. The result specifies whether the values - /// of the , , , and - /// properties of the two objects are equal. + /// Compares two objects for equality. /// /// /// The on the left side of the operand. @@ -121,9 +125,7 @@ namespace ImageProcessor } /// - /// Compares two objects. The result specifies whether the values - /// of the , , , and - /// properties of the two objects are unequal. + /// Compares two objects for inequality /// /// /// The 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; } - /// - /// 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; - } + return GetHashCode(this); } - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// + /// 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.##}]"; } - /// - /// 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; + return this.backingVector.Equals(other.backingVector); } /// @@ -223,5 +196,16 @@ namespace ImageProcessor { return value.Clamp(0, 100); } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Cmyk color) => color.backingVector.GetHashCode(); } } diff --git a/src/ImageProcessor/Colors/Formats/Hsv.cs b/src/ImageProcessor/Colors/Formats/Hsv.cs index 32aedc96e..018c9bd18 100644 --- a/src/ImageProcessor/Colors/Formats/Hsv.cs +++ b/src/ImageProcessor/Colors/Formats/Hsv.cs @@ -122,7 +122,7 @@ namespace ImageProcessor } /// - /// Compares two objects for equality + /// Compares two objects for equality. /// /// /// The on the left side of the operand. diff --git a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs index f8087d4d4..ecf7f1267 100644 --- a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs +++ b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs @@ -17,7 +17,11 @@ namespace ImageProcessor.Tests /// /// Test conversion between the various color structs. - /// + /// + /// + /// Output values have been compared with + /// and for accuracy. + /// public class ColorConversionTests { /// @@ -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); } } /// - /// Tests the implicit conversion from to . + /// Tests the implicit conversion from to . /// [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); } /// - /// Tests the implicit conversion from to . + /// Tests the implicit conversion from to . /// [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); } } }