Browse Source

Add HSL touch #260

Former-commit-id: 8596939ae841cee506be5327c7bba085d28c0453
Former-commit-id: 9e70e88253d02d7ffec313b8182c1d4ad2d3f6bc
Former-commit-id: 805a8600f0577b4572533d44841e8c7b9079fc82
af/merge-core
James Jackson-South 10 years ago
parent
commit
8a58cb23be
  1. 3
      README.md
  2. 119
      src/ImageProcessor/Colors/Color.cs
  3. 8
      src/ImageProcessor/Colors/ColorDefinitions.cs
  4. 229
      src/ImageProcessor/Colors/ColorTransforms.cs
  5. 0
      src/ImageProcessor/Colors/Colorspaces/Bgra32.cs
  6. 0
      src/ImageProcessor/Colors/Colorspaces/Cmyk.cs
  7. 207
      src/ImageProcessor/Colors/Colorspaces/Hsl.cs
  8. 6
      src/ImageProcessor/Colors/Colorspaces/Hsv.cs
  9. 0
      src/ImageProcessor/Colors/Colorspaces/YCbCr.cs
  10. 81
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

3
README.md

@ -52,8 +52,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
- [ ] CIE XYZ
- [x] CMYK
- [x] HSV
- [ ] HSLA
- [ ] RGBAW
- [x] HSL
- [x] YCbCr
- Basic shape primitives (Unfinished and could possible be updated by using Vector2, Vector3, etc)
- [x] Rectangle

119
src/ImageProcessor/Colors/Color.cs

@ -228,125 +228,6 @@ namespace ImageProcessor
}
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
public static implicit operator Color(Bgra32 color)
{
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="Cmyk"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="cmykColor">The instance of <see cref="Cmyk"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCr"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="YCbCr"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
public static implicit operator Color(YCbCr color)
{
float y = color.Y;
float cb = color.Cb - 128;
float cr = color.Cr - 128;
float r = (float)(y + (1.402 * cr)) / 255f;
float g = (float)(y - (0.34414 * cb) - (0.71414 * cr)) / 255f;
float b = (float)(y + (1.772 * cb)) / 255f;
return new Color(r, g, b);
}
/// <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>

8
src/ImageProcessor/Colors/ColorDefinitions.cs

@ -5,6 +5,14 @@
namespace ImageProcessor
{
/// <summary>
/// Represents a four-component color using red, green, blue, and alpha data.
/// Each component is stored in premultiplied format multiplied by the alpha component.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public partial struct Color
{
/// <summary>

229
src/ImageProcessor/Colors/ColorTransforms.cs

@ -0,0 +1,229 @@
// <copyright file="ColorTransforms.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
using System;
/// <summary>
/// Represents a four-component color using red, green, blue, and alpha data.
/// Each component is stored in premultiplied format multiplied by the alpha component.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public partial struct Color
{
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
public static implicit operator Color(Bgra32 color)
{
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="Cmyk"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="cmykColor">The instance of <see cref="Cmyk"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCr"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="YCbCr"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
public static implicit operator Color(YCbCr color)
{
float y = color.Y;
float cb = color.Cb - 128;
float cr = color.Cr - 128;
float r = (float)(y + (1.402 * cr)) / 255f;
float g = (float)(y - (0.34414 * cb) - (0.71414 * cr)) / 255f;
float b = (float)(y + (1.772 * cb)) / 255f;
return new Color(r, g, b);
}
/// <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>
/// Allows the implicit conversion of an instance of <see cref="Hsl"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Hsl"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
public static implicit operator Color(Hsl color)
{
float rangedH = color.H / 360f;
float r = 0;
float g = 0;
float b = 0;
float s = color.S;
float l = color.L;
if (Math.Abs(l) > Epsilon)
{
if (Math.Abs(s) < Epsilon)
{
r = g = b = l;
}
else
{
float temp2 = (l < 0.5f) ? l * (1f + s) : l + s - (l * s);
float temp1 = (2f * l) - temp2;
r = GetColorComponent(temp1, temp2, rangedH + (1 / 3f));
g = GetColorComponent(temp1, temp2, rangedH);
b = GetColorComponent(temp1, temp2, rangedH - (1 / 3f));
}
}
return new Color(r, g, b);
}
/// <summary>
/// Gets the color component from the given values.
/// </summary>
/// <param name="first">The first value.</param>
/// <param name="second">The second value.</param>
/// <param name="third">The third value.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float GetColorComponent(float first, float second, float third)
{
third = MoveIntoRange(third);
if (third < 1.0 / 6.0)
{
return first + ((second - first) * 6.0f * third);
}
if (third < 0.5)
{
return second;
}
if (third < 2.0 / 3.0)
{
return first + ((second - first) * ((2.0f / 3.0f) - third) * 6.0f);
}
return first;
}
/// <summary>
/// Moves the specific value within the acceptable range for
/// conversion.
/// <remarks>Used for converting <see cref="Hsl"/> colors to this type.</remarks>
/// </summary>
/// <param name="value">The value to shift.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float MoveIntoRange(float value)
{
if (value < 0.0)
{
value += 1.0f;
}
else if (value > 1.0)
{
value -= 1.0f;
}
return value;
}
}
}

0
src/ImageProcessor/Colors/Formats/Bgra32.cs → src/ImageProcessor/Colors/Colorspaces/Bgra32.cs

0
src/ImageProcessor/Colors/Formats/Cmyk.cs → src/ImageProcessor/Colors/Colorspaces/Cmyk.cs

207
src/ImageProcessor/Colors/Colorspaces/Hsl.cs

@ -0,0 +1,207 @@
// <copyright file="Hsl.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents a Hsl (hue, saturation, lightness) color.
/// </summary>
public struct Hsl : IEquatable<Hsl>
{
/// <summary>
/// Represents a <see cref="Hsl"/> that has H, S, and L values set to zero.
/// </summary>
public static readonly Hsl Empty = default(Hsl);
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Hsl"/> struct.
/// </summary>
/// <param name="h">The h hue component.</param>
/// <param name="s">The s saturation component.</param>
/// <param name="l">The l value (lightness) component.</param>
public Hsl(float h, float s, float l)
{
this.backingVector.X = h.Clamp(0, 360);
this.backingVector.Y = s.Clamp(0, 1);
this.backingVector.Z = l.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 lightness component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float L => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="Hsl"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Hsl"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Hsl"/>.
/// </returns>
public static implicit operator Hsl(Color color)
{
color = Color.ToNonPremultiplied(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));
float chroma = max - min;
float h = 0;
float s = 0;
float l = (max + min) / 2;
if (Math.Abs(chroma) < Epsilon)
{
return new Hsl(0, s, l);
}
if (Math.Abs(r - max) < Epsilon)
{
h = (g - b) / chroma;
}
else if (Math.Abs(g - max) < Epsilon)
{
h = 2 + ((b - r) / chroma);
}
else if (Math.Abs(b - max) < Epsilon)
{
h = 4 + ((r - g) / chroma);
}
h *= 60;
if (h < 0.0)
{
h += 360;
}
if (l <= .5f)
{
s = chroma / (max + min);
}
else {
s = chroma / (2 - chroma);
}
return new Hsl(h, s, l);
}
/// <summary>
/// Compares two <see cref="Hsl"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsl"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Hsl"/> 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 ==(Hsl left, Hsl right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Hsl"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsl"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Hsl"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Hsl left, Hsl right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Hsl)
{
Hsl color = (Hsl)obj;
return this.backingVector == color.backingVector;
}
return false;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Hsl [ Empty ]";
}
return $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={this.L:#0.##} ]";
}
/// <inheritdoc/>
public bool Equals(Hsl other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsl"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(Hsl color) => color.backingVector.GetHashCode();
}
}

6
src/ImageProcessor/Colors/Formats/Hsv.cs → src/ImageProcessor/Colors/Colorspaces/Hsv.cs

@ -93,11 +93,7 @@ namespace ImageProcessor
return new Hsv(0, s, v);
}
if (Math.Abs(chroma) < Epsilon)
{
h = 0;
}
else if (Math.Abs(r - max) < Epsilon)
if (Math.Abs(r - max) < Epsilon)
{
h = (g - b) / chroma;
}

0
src/ImageProcessor/Colors/Formats/YCbCr.cs → src/ImageProcessor/Colors/Colorspaces/YCbCr.cs

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

@ -172,6 +172,87 @@ namespace ImageProcessor.Tests
}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Hsl"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void ColorToHsl()
{
// Black
Color b = new Color(0, 0, 0);
Hsl h = b;
Assert.Equal(0, h.H, 1);
Assert.Equal(0, h.S, 1);
Assert.Equal(0, h.L, 1);
// White
Color color = new Color(1, 1, 1);
Hsl hsl = color;
Assert.Equal(0f, hsl.H, 1);
Assert.Equal(0f, hsl.S, 1);
Assert.Equal(1f, hsl.L, 1);
// Dark moderate pink.
Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f);
Hsl hsl2 = color2;
Assert.Equal(320.6f, hsl2.H, 1);
Assert.Equal(0.33f, hsl2.S, 1);
Assert.Equal(0.376f, hsl2.L, 2);
// Ochre.
Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f);
Hsl hsl3 = color3;
Assert.Equal(30f, hsl3.H, 1);
Assert.Equal(0.714f, hsl3.S, 3);
Assert.Equal(0.467f, hsl3.L, 3);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Hsl"/> to <see cref="Color"/>.
/// </summary>
[Fact]
public void HslToColor()
{
// Dark moderate pink.
Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f);
Color color = hsl;
Assert.Equal(color.B, 106 / 255f, 1);
Assert.Equal(color.G, 64 / 255f, 1);
Assert.Equal(color.R, 128 / 255f, 1);
// Ochre
Hsl hsl2 = new Hsl(30, 0.714f, 0.467f);
Color color2 = hsl2;
Assert.Equal(color2.B, 34 / 255f, 1);
Assert.Equal(color2.G, 119 / 255f, 1);
Assert.Equal(color2.R, 204 / 255f, 1);
// White
Hsl hsl3 = new Hsl(0, 0, 1);
Color color3 = hsl3;
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++)
{
Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
Hsl hsl4 = color4;
Assert.Equal(color4, (Color)hsl4);
}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Cmyk"/>.
/// </summary>

Loading…
Cancel
Save