diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs
index 7d74b4d602..3ee151389a 100644
--- a/src/Avalonia.Base/Media/Color.cs
+++ b/src/Avalonia.Base/Media/Color.cs
@@ -478,7 +478,6 @@ namespace Avalonia.Media
/// The HSL equivalent color.
public HslColor ToHsl()
{
- // Don't use the HslColor(Color) constructor to avoid an extra HslColor
return Color.ToHsl(R, G, B, A);
}
@@ -488,7 +487,6 @@ namespace Avalonia.Media
/// The HSV equivalent color.
public HsvColor ToHsv()
{
- // Don't use the HsvColor(Color) constructor to avoid an extra HsvColor
return Color.ToHsv(R, G, B, A);
}
diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs
index 624cc88ad4..84f2149367 100644
--- a/src/Avalonia.Base/Media/HslColor.cs
+++ b/src/Avalonia.Base/Media/HslColor.cs
@@ -165,10 +165,18 @@ namespace Avalonia.Media
/// The RGB equivalent color.
public Color ToRgb()
{
- // Use the by-component conversion method directly for performance
return HslColor.ToRgb(H, S, L, A);
}
+ ///
+ /// Returns the HSV color model equivalent of this HSL color.
+ ///
+ /// The HSV equivalent color.
+ public HsvColor ToHsv()
+ {
+ return HslColor.ToHsv(H, S, L, A);
+ }
+
///
public override string ToString()
{
@@ -432,13 +440,67 @@ namespace Avalonia.Media
b1 = x;
}
- return Color.FromArgb(
+ return new Color(
(byte)Math.Round(255 * alpha),
(byte)Math.Round(255 * (r1 + m)),
(byte)Math.Round(255 * (g1 + m)),
(byte)Math.Round(255 * (b1 + m)));
}
+ ///
+ /// Converts the given HSLA color component values to their HSV color equivalent.
+ ///
+ /// The Hue component in the HSL color model in the range from 0..360.
+ /// The Saturation component in the HSL color model in the range from 0..1.
+ /// The Lightness component in the HSL color model in the range from 0..1.
+ /// The Alpha component in the range from 0..1.
+ /// A new equivalent to the given HSLA values.
+ public static HsvColor ToHsv(
+ double hue,
+ double saturation,
+ double lightness,
+ double alpha = 1.0)
+ {
+ // We want the hue to be between 0 and 359,
+ // so we first ensure that that's the case.
+ while (hue >= 360.0)
+ {
+ hue -= 360.0;
+ }
+
+ while (hue < 0.0)
+ {
+ hue += 360.0;
+ }
+
+ // We similarly clamp saturation, lightness and alpha between 0 and 1.
+ saturation = saturation < 0.0 ? 0.0 : saturation;
+ saturation = saturation > 1.0 ? 1.0 : saturation;
+
+ lightness = lightness < 0.0 ? 0.0 : lightness;
+ lightness = lightness > 1.0 ? 1.0 : lightness;
+
+ alpha = alpha < 0.0 ? 0.0 : alpha;
+ alpha = alpha > 1.0 ? 1.0 : alpha;
+
+ // The conversion algorithm is from the below link
+ // https://en.wikipedia.org/wiki/HSL_and_HSV#Interconversion
+
+ double s;
+ double v = lightness + (saturation * Math.Min(lightness, 1.0 - lightness));
+
+ if (v <= 0)
+ {
+ s = 0;
+ }
+ else
+ {
+ s = 2.0 * (1.0 - (lightness / v));
+ }
+
+ return new HsvColor(alpha, hue, s, v);
+ }
+
///
/// Indicates whether the values of two specified objects are equal.
///
diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs
index 3c6336c445..03949d32aa 100644
--- a/src/Avalonia.Base/Media/HsvColor.cs
+++ b/src/Avalonia.Base/Media/HsvColor.cs
@@ -195,10 +195,18 @@ namespace Avalonia.Media
/// The RGB equivalent color.
public Color ToRgb()
{
- // Use the by-component conversion method directly for performance
return HsvColor.ToRgb(H, S, V, A);
}
+ ///
+ /// Returns the HSL color model equivalent of this HSV color.
+ ///
+ /// The HSL equivalent color.
+ public HslColor ToHsl()
+ {
+ return HsvColor.ToHsl(H, S, V, A);
+ }
+
///
public override string ToString()
{
@@ -510,13 +518,67 @@ namespace Avalonia.Media
break;
}
- return Color.FromArgb(
+ return new Color(
(byte)Math.Round(alpha * 255),
(byte)Math.Round(r * 255),
(byte)Math.Round(g * 255),
(byte)Math.Round(b * 255));
}
+ ///
+ /// Converts the given HSVA color component values to their HSL color equivalent.
+ ///
+ /// The Hue component in the HSV color model in the range from 0..360.
+ /// The Saturation component in the HSV color model in the range from 0..1.
+ /// The Value component in the HSV color model in the range from 0..1.
+ /// The Alpha component in the range from 0..1.
+ /// A new equivalent to the given HSVA values.
+ public static HslColor ToHsl(
+ double hue,
+ double saturation,
+ double value,
+ double alpha = 1.0)
+ {
+ // We want the hue to be between 0 and 359,
+ // so we first ensure that that's the case.
+ while (hue >= 360.0)
+ {
+ hue -= 360.0;
+ }
+
+ while (hue < 0.0)
+ {
+ hue += 360.0;
+ }
+
+ // We similarly clamp saturation, value and alpha between 0 and 1.
+ saturation = saturation < 0.0 ? 0.0 : saturation;
+ saturation = saturation > 1.0 ? 1.0 : saturation;
+
+ value = value < 0.0 ? 0.0 : value;
+ value = value > 1.0 ? 1.0 : value;
+
+ alpha = alpha < 0.0 ? 0.0 : alpha;
+ alpha = alpha > 1.0 ? 1.0 : alpha;
+
+ // The conversion algorithm is from the below link
+ // https://en.wikipedia.org/wiki/HSL_and_HSV#Interconversion
+
+ double s;
+ double l = value * (1.0 - (saturation / 2.0));
+
+ if (l <= 0 || l >= 1)
+ {
+ s = 0.0;
+ }
+ else
+ {
+ s = (value - l) / Math.Min(l, 1.0 - l);
+ }
+
+ return new HslColor(alpha, hue, s, l);
+ }
+
///
/// Indicates whether the values of two specified objects are equal.
///
diff --git a/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs b/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
index 36929d5e95..1ed3ea50b9 100644
--- a/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
@@ -335,5 +335,34 @@ namespace Avalonia.Base.UnitTests.Media
Assert.True(dataPoint.Item2 == parsedColor);
}
}
+
+ [Fact]
+ public void Hsv_To_From_Hsl_Conversion()
+ {
+ // Note that conversion of values more representative of actual colors is not done due to rounding error
+ // It would be necessary to introduce a different equality comparison that accounts for rounding differences in values
+ // This is a result of the math in the conversion itself
+ // RGB doesn't have this problem because it uses whole numbers
+ var data = new Tuple[]
+ {
+ Tuple.Create(new HsvColor(1.0, 0.0, 0.0, 0.0), new HslColor(1.0, 0.0, 0.0, 0.0)),
+ Tuple.Create(new HsvColor(1.0, 359.0, 1.0, 1.0), new HslColor(1.0, 359.0, 1.0, 0.5)),
+
+ Tuple.Create(new HsvColor(1.0, 128.0, 0.0, 0.0), new HslColor(1.0, 128.0, 0.0, 0.0)),
+ Tuple.Create(new HsvColor(1.0, 128.0, 0.0, 1.0), new HslColor(1.0, 128.0, 0.0, 1.0)),
+ Tuple.Create(new HsvColor(1.0, 128.0, 1.0, 1.0), new HslColor(1.0, 128.0, 1.0, 0.5)),
+
+ Tuple.Create(new HsvColor(0.23, 0.5, 1.0, 1.0), new HslColor(0.23, 0.5, 1.0, 0.5)),
+ };
+
+ foreach (var dataPoint in data)
+ {
+ var convertedHsl = dataPoint.Item1.ToHsl();
+ var convertedHsv = dataPoint.Item2.ToHsv();
+
+ Assert.Equal(convertedHsv, dataPoint.Item1);
+ Assert.Equal(convertedHsl, dataPoint.Item2);
+ }
+ }
}
}