diff --git a/src/ImageSharp/Colors/Spaces/Cmyk.cs b/src/ImageSharp/Colors/Spaces/Cmyk.cs
index 488c36e8ed..d21ab0fcd1 100644
--- a/src/ImageSharp/Colors/Spaces/Cmyk.cs
+++ b/src/ImageSharp/Colors/Spaces/Cmyk.cs
@@ -32,9 +32,18 @@ namespace ImageSharp.Colors.Spaces
/// The yellow component.
/// The keyline black component.
public Cmyk(float c, float m, float y, float k)
+ : this(new Vector4(c, m, y, k))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the c, m, y, k components.
+ public Cmyk(Vector4 vector)
: this()
{
- this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), Vector4.Zero, Vector4.One);
+ this.backingVector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One);
}
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs
index 95582d2b36..87d48b2184 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs
@@ -85,6 +85,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToCieLab(xyzColor);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs
index 79fa53c82b..6224c91597 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs
@@ -72,6 +72,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToCieLch(xyzColor);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyy.cs
index 0b86109af8..8d27484056 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyy.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyy.cs
@@ -58,7 +58,7 @@ namespace ImageSharp.Colors.Spaces.Conversion
/// Converts a into a
///
/// The color to convert.
- /// The
+ /// The
public CieXyy ToCieXyy(Cmyk color)
{
Guard.NotNull(color, nameof(color));
@@ -68,6 +68,20 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToCieXyy(xyzColor);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs
index 7e171d42eb..f963c72f98 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs
@@ -85,6 +85,21 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToCieXyz(rgb);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ Rgb rgb = this.ToRgb(color);
+
+ return this.ToCieXyz(rgb);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Cmyk.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Cmyk.cs
index dc7e109271..3934eaa50a 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Cmyk.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Cmyk.cs
@@ -71,6 +71,20 @@ namespace ImageSharp.Colors.Spaces.Conversion
return CmykAndRgbConverter.Convert(rgb);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ Rgb rgb = this.ToRgb(color);
+
+ return CmykAndRgbConverter.Convert(rgb);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Hsl.cs
new file mode 100644
index 0000000000..53ba332e30
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Hsl.cs
@@ -0,0 +1,142 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces.Conversion
+{
+ using ImageSharp.Colors.Spaces;
+ using ImageSharp.Colors.Spaces.Conversion.Implementation.Hsl;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorSpaceConverter
+ {
+ private static readonly HslAndRgbConverter HslAndRgbConverter = new HslAndRgbConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ Rgb rgb = this.ToRgb(color);
+
+ return HslAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ Rgb rgb = this.ToRgb(color);
+
+ return HslAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ Rgb rgb = this.ToRgb(color);
+
+ return HslAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ return HslAndRgbConverter.Convert(color);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs
index 6d84423875..a00c19b741 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs
@@ -82,6 +82,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToHunterLab(xyzColor);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs
index d381634d2f..6ba6b6cd30 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs
@@ -87,6 +87,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToLinearRgb(rgb);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ Rgb rgb = this.ToRgb(color);
+ return this.ToLinearRgb(rgb);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs
index 202ecf4ddf..cdea60f870 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs
@@ -77,6 +77,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToLms(xyzColor);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs
index 2682b05aae..547a04771e 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs
@@ -82,6 +82,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return CmykAndRgbConverter.Convert(color);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return HslAndRgbConverter.Convert(color);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs
new file mode 100644
index 0000000000..d5cc2f6316
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs
@@ -0,0 +1,153 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Hsl
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.Colors.Spaces;
+
+ ///
+ /// Color converter between HSL and Rgb
+ /// See for formulas.
+ ///
+ internal class HslAndRgbConverter : IColorConversion, IColorConversion
+ {
+ ///
+ public Rgb Convert(Hsl input)
+ {
+ float rangedH = input.H / 360F;
+ float r = 0;
+ float g = 0;
+ float b = 0;
+ float s = input.S;
+ float l = input.L;
+
+ if (MathF.Abs(l) > Constants.Epsilon)
+ {
+ if (MathF.Abs(s) < Constants.Epsilon)
+ {
+ r = g = b = l;
+ }
+ else
+ {
+ float temp2 = (l < .5F) ? l * (1F + s) : l + s - (l * s);
+ float temp1 = (2F * l) - temp2;
+
+ r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F);
+ g = GetColorComponent(temp1, temp2, rangedH);
+ b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F);
+ }
+ }
+
+ return new Rgb(r, g, b);
+ }
+
+ ///
+ public Hsl Convert(Rgb input)
+ {
+ float r = input.R;
+ float g = input.G;
+ float b = input.B;
+
+ float max = MathF.Max(r, MathF.Max(g, b));
+ float min = MathF.Min(r, MathF.Min(g, b));
+ float chroma = max - min;
+ float h = 0F;
+ float s = 0F;
+ float l = (max + min) / 2F;
+
+ if (MathF.Abs(chroma) < Constants.Epsilon)
+ {
+ return new Hsl(0F, s, l);
+ }
+
+ if (MathF.Abs(r - max) < Constants.Epsilon)
+ {
+ h = (g - b) / chroma;
+ }
+ else if (MathF.Abs(g - max) < Constants.Epsilon)
+ {
+ h = 2F + ((b - r) / chroma);
+ }
+ else if (MathF.Abs(b - max) < Constants.Epsilon)
+ {
+ h = 4F + ((r - g) / chroma);
+ }
+
+ h *= 60F;
+ if (h < 0F)
+ {
+ h += 360F;
+ }
+
+ if (l <= .5F)
+ {
+ s = chroma / (max + min);
+ }
+ else
+ {
+ s = chroma / (2F - chroma);
+ }
+
+ return new Hsl(h, s, l);
+ }
+
+ ///
+ /// Gets the color component from the given values.
+ ///
+ /// The first value.
+ /// The second value.
+ /// The third value.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float GetColorComponent(float first, float second, float third)
+ {
+ third = MoveIntoRange(third);
+ if (third < 0.1666667F)
+ {
+ return first + ((second - first) * 6F * third);
+ }
+
+ if (third < .5F)
+ {
+ return second;
+ }
+
+ if (third < 0.6666667F)
+ {
+ return first + ((second - first) * (0.6666667F - third) * 6F);
+ }
+
+ return first;
+ }
+
+ ///
+ /// Moves the specific value within the acceptable range for
+ /// conversion.
+ /// Used for converting colors to this type.
+ ///
+ /// The value to shift.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float MoveIntoRange(float value)
+ {
+ if (value < 0F)
+ {
+ value += 1F;
+ }
+ else if (value > 1F)
+ {
+ value -= 1F;
+ }
+
+ return value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Hsl.cs b/src/ImageSharp/Colors/Spaces/Hsl.cs
new file mode 100644
index 0000000000..48948c7494
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Hsl.cs
@@ -0,0 +1,157 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents a Hsl (hue, saturation, lightness) color.
+ ///
+ public struct Hsl : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has H, S, and L values set to zero.
+ ///
+ public static readonly Hsl Empty = default(Hsl);
+
+ ///
+ /// Max range used for clamping
+ ///
+ private static readonly Vector3 VectorMax = new Vector3(360, 1, 1);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The h hue component.
+ /// The s saturation component.
+ /// The l value (lightness) component.
+ public Hsl(float h, float s, float l)
+ : this(new Vector3(h, s, l))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the h, s, l components.
+ public Hsl(Vector3 vector)
+ {
+ this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax);
+ }
+
+ ///
+ /// 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 lightness component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float L => this.backingVector.Z;
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector => this.backingVector;
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(Hsl left, Hsl right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(Hsl left, Hsl right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Hsl [ Empty ]";
+ }
+
+ return $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={this.L:#0.##} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is Hsl)
+ {
+ return this.Equals((Hsl)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(Hsl other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ public bool AlmostEquals(Hsl other, float precision)
+ {
+ Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs
index c82fc6d803..d3af7d43ea 100644
--- a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs
+++ b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs
@@ -27,6 +27,7 @@ namespace ImageSharp.Tests.Colors
Lms.Empty,
LinearRgb.Empty,
Rgb.Empty,
+ Hsl.Empty
};
public static readonly TheoryData