Browse Source

Color to HSV conversion

Former-commit-id: 6227131bb3135663590c4790097f4391738f6f81
Former-commit-id: 24fd283f6ffd6fa5b78e9b7ab37432d4a69ee9d8
Former-commit-id: dcf087a8c090c99170c4fa3806a13aad8dd69c5c
pull/17/head
James Jackson-South 10 years ago
parent
commit
6c1bcf5957
  1. 102
      src/ImageProcessor/Colors/Color.cs
  2. 77
      src/ImageProcessor/Colors/Formats/Bgra32.cs
  3. 124
      src/ImageProcessor/Colors/Formats/Hsv.cs
  4. 76
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

102
src/ImageProcessor/Colors/Color.cs

@ -23,11 +23,30 @@ namespace ImageProcessor
/// </summary>
public static readonly Color Empty = default(Color);
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct with the alpha component set to 1.
/// </summary>
/// <param name="r">The red component of this <see cref="Color"/>.</param>
/// <param name="g">The green component of this <see cref="Color"/>.</param>
/// <param name="b">The blue component of this <see cref="Color"/>.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
@ -55,12 +74,6 @@ namespace ImageProcessor
this.backingVector = vector;
}
/// <summary>
/// Gets a value indicating whether this <see cref="Color"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector4));
/// <summary>
/// Gets or sets the red component of the color.
/// </summary>
@ -125,6 +138,12 @@ namespace ImageProcessor
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="Color"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector4));
/// <summary>
/// Gets this color with the component values clamped from 0 to 1.
/// </summary>
@ -155,6 +174,77 @@ 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="Hsv"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Computes the product of multiplying a color by a given factor.
/// </summary>

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

@ -65,11 +65,6 @@ namespace ImageProcessor
[FieldOffset(0)]
public readonly int BGRA;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32"/> struct.
/// </summary>
@ -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());
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
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));
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCr"/> to a
/// <see cref="Bgra32"/>.

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

@ -7,6 +7,7 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// 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);
/// <summary>
/// Gets the H hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public readonly float H;
/// <summary>
/// Gets the S saturation component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// </summary>
public readonly float S;
/// <summary>
/// Gets the V value (brightness) component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// The epsilon for comparing floating point numbers.
/// </summary>
public readonly float V;
private const float Epsilon = 0.0001f;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// The backing vector for SIMD support.
/// </summary>
private const float Epsilon = 0.0001f;
private Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Hsv"/> struct.
@ -49,34 +37,49 @@ namespace ImageProcessor
/// <param name="v">The v value (brightness) component.</param>
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);
}
/// <summary>
/// Gets the hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public float H => this.backingVector.X;
/// <summary>
/// Gets the saturation component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float S => this.backingVector.Y;
/// <summary>
/// Gets the value (brightness) component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float V => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="Hsv"/> is empty.
/// </summary>
[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));
/// <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="Hsv"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra32"/> to convert.
/// </param>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Hsv"/>.
/// </returns>
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);
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects. The result specifies whether the values
/// of the <see cref="Hsv.H"/>, <see cref="Hsv.S"/>, and <see cref="Hsv.V"/>
/// properties of the two <see cref="Hsv"/> objects are equal.
/// Compares two <see cref="Hsv"/> objects for equality
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand.
@ -138,9 +139,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects. The result specifies whether the values
/// of the <see cref="Hsv.H"/>, <see cref="Hsv.S"/>, and <see cref="Hsv.V"/>
/// properties of the two <see cref="Hsv"/> objects are unequal.
/// Compares two <see cref="Hsv"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> 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;
}
/// <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.H.GetHashCode();
hashCode = (hashCode * 397) ^ this.S.GetHashCode();
hashCode = (hashCode * 397) ^ this.V.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)
@ -210,18 +191,21 @@ namespace ImageProcessor
return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]";
}
/// <inheritdoc/>
public bool Equals(Hsv other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to return the hash code for.
/// </param>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
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();
}
}

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

@ -88,83 +88,83 @@ namespace ImageProcessor.Tests
}
/// <summary>
/// Tests the implicit conversion from <see cref="Bgra32"/> to <see cref="Hsv"/>.
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Hsv"/>.
/// </summary>
[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);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Hsv"/> to <see cref="Bgra32"/>.
/// Tests the implicit conversion from <see cref="Hsv"/> to <see cref="Color"/>.
/// </summary>
[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);
}
}

Loading…
Cancel
Save