Browse Source

Add HSV color

Former-commit-id: 335da75d1e70ee70be838cd80524cc27d3d4d084
Former-commit-id: b9de3784cf6e4b64bb8d461564efbc48b1663b4c
Former-commit-id: caab867d4d031398f3029354978426aea46cc1bb
pull/17/head
James South 11 years ago
parent
commit
b68715af57
  1. 284
      src/ImageProcessor/Colors/Hsv.cs
  2. 40
      src/ImageProcessor/Colors/YCbCr.cs
  3. 3
      src/ImageProcessor/ImageProcessor.csproj
  4. 71
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

284
src/ImageProcessor/Colors/Hsv.cs

@ -0,0 +1,284 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Hsv.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
using System;
using System.ComponentModel;
/// <summary>
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
/// </summary>
public struct Hsv : IEquatable<Hsv>
{
/// <summary>
/// Represents a <see cref="Hsv"/> that has H, S, and V values set to zero.
/// </summary>
public static readonly Hsv Empty = new Hsv();
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// Initializes a new instance of the <see cref="Hsv"/> struct.
/// </summary>
/// <param name="h">The h hue component.</param>
/// <param name="s">The s saturation component.</param>
/// <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);
}
/// <summary>
/// Gets the H hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public float H { get; }
/// <summary>
/// Gets the S saturation component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// </summary>
public float S { get; }
/// <summary>
/// Gets the V value (brightness) component.
/// <remarks>A value ranging between 0 and 100.</remarks>
/// </summary>
public float V { get; }
/// <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;
/// <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.
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Hsv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Hsv left, Hsv right)
{
return left.Equals(right);
}
/// <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.
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Hsv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Hsv left, Hsv right)
{
return !left.Equals(right);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Bgra"/> to a
/// <see cref="Hsv"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Hsv"/>.
/// </returns>
public static implicit operator Hsv(Bgra color)
{
float max = Math.Max(color.R, Math.Max(color.G, color.B));
float min = Math.Min(color.R, Math.Min(color.G, color.B));
float delta = max - min;
if (Math.Abs(max) < Epsilon)
{
return new Hsv(0, 0, 0);
}
float h = 0.0F;
float s;
float v;
if (Math.Abs(delta) < Epsilon) { h = 0; }
else if (Math.Abs(color.R - max) < Epsilon) { h = (color.G - color.B) / delta; }
else if (Math.Abs(color.G - max) < Epsilon) { h = 2 + (color.B - color.R) / delta; }
else if (Math.Abs(color.B - max) < Epsilon) { h = 4 + (color.R - color.G) / delta; }
h *= 60;
if (h < 0.0) { h += 360; }
s = delta / max;
v = max / 255F;
return new Hsv(h, s, v);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a
/// <see cref="Bgra"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra"/>.
/// </returns>
public static implicit operator Bgra(Hsv color)
{
if (Math.Abs(color.S) < Epsilon)
{
byte component = (byte)(color.V * 255);
return new Bgra(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 = color.V * (1.0f - color.S);
float q = color.V * (1.0f - (color.S * f));
float t = color.V * (1.0f - (color.S * (1.0f - f)));
float r, g, b;
switch (i)
{
case 0:
r = color.V;
g = t;
b = p;
break;
case 1:
r = q;
g = color.V;
b = p;
break;
case 2:
r = p;
g = color.V;
b = t;
break;
case 3:
r = p;
g = q;
b = color.V;
break;
case 4:
r = t;
g = p;
b = color.V;
break;
default:
r = color.V;
g = p;
b = q;
break;
}
return new Bgra((byte)b, (byte)g, (byte)r);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
public override bool Equals(object obj)
{
if (obj is Hsv)
{
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 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>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.H.GetHashCode();
hashCode = (hashCode * 397) ^ this.S.GetHashCode();
hashCode = (hashCode * 397) ^ this.V.GetHashCode();
return hashCode;
}
}
/// <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>
public override string ToString()
{
if (this.IsEmpty)
{
return "Hsv [ Empty ]";
}
return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]";
}
/// <summary>
/// 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(Hsv other)
{
return this.H.Equals(other.H)
&& this.S.Equals(other.S)
&& this.V.Equals(other.V);
}
}
}

40
src/ImageProcessor/Colors/YCbCr.cs

@ -27,24 +27,6 @@ namespace ImageProcessor
/// </summary>
public static readonly YCbCr Empty = new YCbCr();
/// <summary>
/// Holds the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Y { get; }
/// <summary>
/// Holds the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Cb { get; }
/// <summary>
/// Holds the Cr chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Cr { get; }
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
@ -63,6 +45,24 @@ namespace ImageProcessor
this.Cr = cr.Clamp(0, 255);
}
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Y { get; }
/// <summary>
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Cb { get; }
/// <summary>
/// Gets the Cr chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public float Cr { get; }
/// <summary>
/// Gets a value indicating whether this <see cref="YCbCr"/> is empty.
/// </summary>
@ -203,10 +203,10 @@ namespace ImageProcessor
{
if (this.IsEmpty)
{
return "YCbCrColor [ Empty ]";
return "YCbCr [ Empty ]";
}
return $"YCbCrColor [ Y={this.Y:#0.##}, Cb={this.Cb:#0.##}, Cr={this.Cr:#0.##} ]";
return $"YCbCr [ Y={this.Y:#0.##}, Cb={this.Cb:#0.##}, Cr={this.Cr:#0.##} ]";
}
/// <summary>

3
src/ImageProcessor/ImageProcessor.csproj

@ -37,9 +37,10 @@
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<Folder Include="Filters\" />
<Folder Include="Filters\Transforms\" />
</ItemGroup>
<ItemGroup>
<Compile Include="Colors\Hsv.cs" />
<Compile Include="Colors\YCbCr.cs" />
<Compile Include="Common\Extensions\ByteExtensions.cs" />
<Compile Include="Common\Extensions\ComparableExtensions.cs" />

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

@ -23,42 +23,44 @@ namespace ImageProcessor.Tests
/// Tests the implicit conversion from <see cref="Bgra"/> to <see cref="YCbCr"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
public void ColorToYCbCrColor()
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void BgrToYCbCr()
{
// White
Bgra color = new Bgra(255, 255, 255, 255);
YCbCr yCbCrColor = color;
YCbCr yCbCr = color;
Assert.Equal(255, yCbCrColor.Y);
Assert.Equal(128, yCbCrColor.Cb);
Assert.Equal(128, yCbCrColor.Cr);
Assert.Equal(255, yCbCr.Y);
Assert.Equal(128, yCbCr.Cb);
Assert.Equal(128, yCbCr.Cr);
// Black
Bgra color2 = new Bgra(0, 0, 0, 255);
YCbCr yCbCrColor2 = color2;
Assert.Equal(0, yCbCrColor2.Y);
Assert.Equal(128, yCbCrColor2.Cb);
Assert.Equal(128, yCbCrColor2.Cr);
YCbCr yCbCr2 = color2;
Assert.Equal(0, yCbCr2.Y);
Assert.Equal(128, yCbCr2.Cb);
Assert.Equal(128, yCbCr2.Cr);
// Grey
Bgra color3 = new Bgra(128, 128, 128, 255);
YCbCr yCbCrColor3 = color3;
Assert.Equal(128, yCbCrColor3.Y);
Assert.Equal(128, yCbCrColor3.Cb);
Assert.Equal(128, yCbCrColor3.Cr);
YCbCr yCbCr3 = color3;
Assert.Equal(128, yCbCr3.Y);
Assert.Equal(128, yCbCr3.Cb);
Assert.Equal(128, yCbCr3.Cr);
}
/// <summary>
/// Tests the implicit conversion from <see cref="YCbCr"/> to <see cref="Bgra"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
public void YCbCrColorToColor()
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void YCbCrToBgr()
{
// White
YCbCr yCbCrColor = new YCbCr(255, 128, 128);
Bgra color = yCbCrColor;
YCbCr yCbCr = new YCbCr(255, 128, 128);
Bgra color = yCbCr;
Assert.Equal(255, color.B);
Assert.Equal(255, color.G);
@ -66,8 +68,8 @@ namespace ImageProcessor.Tests
Assert.Equal(255, color.A);
// Black
YCbCr yCbCrColor2 = new YCbCr(0, 128, 128);
Bgra color2 = yCbCrColor2;
YCbCr yCbCr2 = new YCbCr(0, 128, 128);
Bgra color2 = yCbCr2;
Assert.Equal(0, color2.B);
Assert.Equal(0, color2.G);
@ -75,13 +77,38 @@ namespace ImageProcessor.Tests
Assert.Equal(255, color2.A);
// Grey
YCbCr yCbCrColor3 = new YCbCr(128, 128, 128);
Bgra color3 = yCbCrColor3;
YCbCr yCbCr3 = new YCbCr(128, 128, 128);
Bgra color3 = yCbCr3;
Assert.Equal(128, color3.B);
Assert.Equal(128, color3.G);
Assert.Equal(128, color3.R);
Assert.Equal(255, color3.A);
}
/// <summary>
/// Tests the implicit conversion from <see cref="YCbCr"/> to <see cref="Bgra"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void BgrToHsv()
{
// White
Bgra color = new Bgra(255, 255, 255, 255);
Hsv hsv = color;
Assert.Equal(0, hsv.H);
Assert.Equal(0, hsv.S);
Assert.Equal(1, hsv.V);
// Dark moderate pink.
Bgra color2 = new Bgra(106, 64, 128, 255);
Hsv hsv2 = color2;
Assert.Equal(320.6, hsv2.H, 1);
Assert.Equal(50, hsv2.S, 1);
Assert.Equal(50.2, hsv2.V, 1);
}
}
}

Loading…
Cancel
Save