diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs index 4b13fb1dd..b8b554804 100644 --- a/src/ImageProcessor/Colors/Color.cs +++ b/src/ImageProcessor/Colors/Color.cs @@ -23,11 +23,30 @@ namespace ImageProcessor /// public static readonly Color Empty = default(Color); + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.0001f; + /// /// The backing vector for SIMD support. /// private Vector4 backingVector; + /// + /// Initializes a new instance of the struct with the alpha component set to 1. + /// + /// The red component of this . + /// The green component of this . + /// The blue component of this . + public Color(float r, float g, float b) + : this(r, g, b, 1) + { + this.backingVector.X = r; + this.backingVector.Y = g; + this.backingVector.Z = b; + } + /// /// Initializes a new instance of the struct. /// @@ -55,12 +74,6 @@ namespace ImageProcessor this.backingVector = vector; } - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.backingVector.Equals(default(Vector4)); - /// /// Gets or sets the red component of the color. /// @@ -125,6 +138,12 @@ namespace ImageProcessor } } + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.backingVector.Equals(default(Vector4)); + /// /// Gets this color with the component values clamped from 0 to 1. /// @@ -155,6 +174,77 @@ 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(Hsv color) + { + float s = color.S; + float v = color.V; + + if (Math.Abs(s) < Epsilon) + { + return new Color(v, v, v, 1); + } + + float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60; + int i = (int)Math.Truncate(h); + float f = h - i; + + float p = v * (1.0f - s); + float q = v * (1.0f - (s * f)); + float t = v * (1.0f - (s * (1.0f - f))); + + float r, g, b; + switch (i) + { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: + r = v; + g = p; + b = q; + break; + } + + return new Color(r, g, b); + } + /// /// Computes the product of multiplying a color by a given factor. /// diff --git a/src/ImageProcessor/Colors/Formats/Bgra32.cs b/src/ImageProcessor/Colors/Formats/Bgra32.cs index 01e2e664f..f9b10069e 100644 --- a/src/ImageProcessor/Colors/Formats/Bgra32.cs +++ b/src/ImageProcessor/Colors/Formats/Bgra32.cs @@ -65,11 +65,6 @@ namespace ImageProcessor [FieldOffset(0)] public readonly int BGRA; - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.0001f; - /// /// Initializes a new instance of the struct. /// @@ -194,78 +189,6 @@ namespace ImageProcessor return new Bgra32((255f * color.B).ToByte(), (255f * color.G).ToByte(), (255f * color.R).ToByte(), (255f * color.A).ToByte()); } - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - public static implicit operator Bgra32(Hsv color) - { - float s = color.S / 100; - float v = color.V / 100; - - if (Math.Abs(s) < Epsilon) - { - byte component = (byte)(v * 255); - return new Bgra32(component, component, component, 255); - } - - float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60; - int i = (int)Math.Truncate(h); - float f = h - i; - - float p = v * (1.0f - s); - float q = v * (1.0f - (s * f)); - float t = v * (1.0f - (s * (1.0f - f))); - - float r, g, b; - switch (i) - { - case 0: - r = v; - g = t; - b = p; - break; - - case 1: - r = q; - g = v; - b = p; - break; - - case 2: - r = p; - g = v; - b = t; - break; - - case 3: - r = p; - g = q; - b = v; - break; - - case 4: - r = t; - g = p; - b = v; - break; - - default: - r = v; - g = p; - b = q; - break; - } - - return new Bgra32((byte)Math.Round(b * 255), (byte)Math.Round(g * 255), (byte)Math.Round(r * 255)); - } - /// /// Allows the implicit conversion of an instance of to a /// . diff --git a/src/ImageProcessor/Colors/Formats/Hsv.cs b/src/ImageProcessor/Colors/Formats/Hsv.cs index 942ab8fb7..32aedc96e 100644 --- a/src/ImageProcessor/Colors/Formats/Hsv.cs +++ b/src/ImageProcessor/Colors/Formats/Hsv.cs @@ -7,6 +7,7 @@ namespace ImageProcessor { using System; using System.ComponentModel; + using System.Numerics; /// /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). @@ -19,27 +20,14 @@ namespace ImageProcessor public static readonly Hsv Empty = default(Hsv); /// - /// Gets the H hue component. - /// A value ranging between 0 and 360. - /// - public readonly float H; - - /// - /// Gets the S saturation component. - /// A value ranging between 0 and 100. - /// - public readonly float S; - - /// - /// Gets the V value (brightness) component. - /// A value ranging between 0 and 100. + /// The epsilon for comparing floating point numbers. /// - public readonly float V; + private const float Epsilon = 0.0001f; /// - /// The epsilon for comparing floating point numbers. + /// The backing vector for SIMD support. /// - private const float Epsilon = 0.0001f; + private Vector3 backingVector; /// /// Initializes a new instance of the struct. @@ -49,34 +37,49 @@ namespace ImageProcessor /// The v value (brightness) component. public Hsv(float h, float s, float v) { - this.H = h.Clamp(0, 360); - this.S = s.Clamp(0, 100); - this.V = v.Clamp(0, 100); + this.backingVector.X = h.Clamp(0, 360); + this.backingVector.Y = s.Clamp(0, 1); + this.backingVector.Z = v.Clamp(0, 1); } + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public float H => this.backingVector.X; + + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public float S => this.backingVector.Y; + + /// + /// Gets the value (brightness) component. + /// A value ranging between 0 and 1. + /// + public float V => this.backingVector.Z; + /// /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => Math.Abs(this.H) < Epsilon - && Math.Abs(this.S) < Epsilon - && Math.Abs(this.V) < Epsilon; + public bool IsEmpty => this.backingVector.Equals(default(Vector3)); /// - /// Allows the implicit conversion of an instance of to a + /// Allows the implicit conversion of an instance of to a /// . /// - /// - /// The instance of to convert. - /// + /// The instance of to convert. /// /// An instance of . /// - public static implicit operator Hsv(Bgra32 color) + public static implicit operator Hsv(Color color) { - float r = color.R / 255f; - float g = color.G / 255f; - float b = color.B / 255f; + color = color.Limited; + float r = color.R; + float g = color.G; + float b = color.B; float max = Math.Max(r, Math.Max(g, b)); float min = Math.Min(r, Math.Min(g, b)); @@ -87,7 +90,7 @@ namespace ImageProcessor if (Math.Abs(chroma) < Epsilon) { - return new Hsv(0, s * 100, v * 100); + return new Hsv(0, s, v); } if (Math.Abs(chroma) < Epsilon) @@ -115,13 +118,11 @@ namespace ImageProcessor s = chroma / v; - return new Hsv(h, s * 100, v * 100); + return new Hsv(h, s, v); } /// - /// 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. @@ -138,9 +139,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. @@ -169,37 +168,19 @@ namespace ImageProcessor { Hsv color = (Hsv)obj; - return Math.Abs(this.H - color.H) < Epsilon - && Math.Abs(this.S - color.S) < Epsilon - && Math.Abs(this.V - color.V) < 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.H.GetHashCode(); - hashCode = (hashCode * 397) ^ this.S.GetHashCode(); - hashCode = (hashCode * 397) ^ this.V.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) @@ -210,18 +191,21 @@ namespace ImageProcessor return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]"; } + /// + public bool Equals(Hsv other) + { + return this.backingVector.Equals(other.backingVector); + } + /// - /// Indicates whether the current object is equal to another object of the same type. + /// Returns the hash code for this instance. /// + /// + /// The instance of to return the hash code for. + /// /// - /// True if the current object is equal to the parameter; otherwise, false. + /// A 32-bit signed integer that is the hash code for this instance. /// - /// An object to compare with this object. - public bool Equals(Hsv other) - { - return Math.Abs(this.H - other.H) < Epsilon - && Math.Abs(this.S - other.S) < Epsilon - && Math.Abs(this.V - other.V) < Epsilon; - } + private static int GetHashCode(Hsv color) => color.backingVector.GetHashCode(); } } diff --git a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs index 2a822df19..f8087d4d4 100644 --- a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs +++ b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs @@ -88,83 +88,83 @@ namespace ImageProcessor.Tests } /// - /// 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 BgrToHsv() + public void ColorToHsv() { // Black - Bgra32 b = new Bgra32(0, 0, 0, 255); + Color b = new Color(0, 0, 0); Hsv h = b; - Assert.Equal(0, h.H); - Assert.Equal(0, h.S); - Assert.Equal(0, h.V); + Assert.Equal(0, h.H, 1); + Assert.Equal(0, h.S, 1); + Assert.Equal(0, h.V, 1); // White - Bgra32 color = new Bgra32(255, 255, 255, 255); + Color color = new Color(1, 1, 1); Hsv hsv = color; - Assert.Equal(0, hsv.H); - Assert.Equal(0, hsv.S); - Assert.Equal(100, hsv.V); + Assert.Equal(0f, hsv.H, 1); + Assert.Equal(0f, hsv.S, 1); + Assert.Equal(1f, hsv.V, 1); // Dark moderate pink. - Bgra32 color2 = new Bgra32(106, 64, 128, 255); + Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f); Hsv hsv2 = color2; - Assert.Equal(320.6, hsv2.H, 1); - Assert.Equal(50, hsv2.S, 1); - Assert.Equal(50.2, hsv2.V, 1); + Assert.Equal(320.6f, hsv2.H, 1); + Assert.Equal(0.5f, hsv2.S, 1); + Assert.Equal(0.502f, hsv2.V, 2); // Ochre. - Bgra32 color3 = new Bgra32(34, 119, 204, 255); + Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f); Hsv hsv3 = color3; - Assert.Equal(30, hsv3.H, 1); - Assert.Equal(83.3, hsv3.S, 1); - Assert.Equal(80, hsv3.V, 1); + Assert.Equal(30f, hsv3.H, 1); + Assert.Equal(0.833f, hsv3.S, 3); + Assert.Equal(0.8f, hsv3.V, 1); } /// - /// Tests the implicit conversion from to . + /// Tests the implicit conversion from to . /// [Fact] - public void HsvToBgr() + public void HsvToColor() { // Dark moderate pink. - Hsv hsv = new Hsv(320.6f, 50, 50.2f); - Bgra32 bgra32 = hsv; + Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f); + Color color = hsv; - Assert.Equal(bgra32.B, 106); - Assert.Equal(bgra32.G, 64); - Assert.Equal(bgra32.R, 128); + Assert.Equal(color.B, 106 / 255f, 1); + Assert.Equal(color.G, 64 / 255f, 1); + Assert.Equal(color.R, 128 / 255f, 1); // Ochre - Hsv hsv2 = new Hsv(30, 83.3f, 80); - Bgra32 bgra2 = hsv2; + Hsv hsv2 = new Hsv(30, 0.833f, 0.8f); + Color color2 = hsv2; - Assert.Equal(bgra2.B, 34); - Assert.Equal(bgra2.G, 119); - Assert.Equal(bgra2.R, 204); + Assert.Equal(color2.B, 34 / 255f, 1); + Assert.Equal(color2.G, 119 / 255f, 1); + Assert.Equal(color2.R, 204 / 255f, 1); // White - Hsv hsv3 = new Hsv(0, 0, 100); - Bgra32 bgra3 = hsv3; + Hsv hsv3 = new Hsv(0, 0, 1); + Color color3 = hsv3; - Assert.Equal(bgra3.B, 255); - Assert.Equal(bgra3.G, 255); - Assert.Equal(bgra3.R, 255); + Assert.Equal(color3.B, 1, 1); + Assert.Equal(color3.G, 1, 1); + Assert.Equal(color3.R, 1, 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)); - Hsv hsb4 = bgra4; - Assert.Equal(bgra4, (Bgra32)hsb4); + Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1)); + Hsv hsb4 = color4; + Assert.Equal(color4, (Color)hsb4); } }