Browse Source

Add direct conversions for HsvColor.ToHsl() and HslColor.ToHsv()

pull/11264/head
robloo 3 years ago
parent
commit
fac0a045d4
  1. 2
      src/Avalonia.Base/Media/Color.cs
  2. 66
      src/Avalonia.Base/Media/HslColor.cs
  3. 66
      src/Avalonia.Base/Media/HsvColor.cs
  4. 29
      tests/Avalonia.Base.UnitTests/Media/ColorTests.cs

2
src/Avalonia.Base/Media/Color.cs

@ -478,7 +478,6 @@ namespace Avalonia.Media
/// <returns>The HSL equivalent color.</returns>
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
/// <returns>The HSV equivalent color.</returns>
public HsvColor ToHsv()
{
// Don't use the HsvColor(Color) constructor to avoid an extra HsvColor
return Color.ToHsv(R, G, B, A);
}

66
src/Avalonia.Base/Media/HslColor.cs

@ -165,10 +165,18 @@ namespace Avalonia.Media
/// <returns>The RGB equivalent color.</returns>
public Color ToRgb()
{
// Use the by-component conversion method directly for performance
return HslColor.ToRgb(H, S, L, A);
}
/// <summary>
/// Returns the HSV color model equivalent of this HSL color.
/// </summary>
/// <returns>The HSV equivalent color.</returns>
public HsvColor ToHsv()
{
return HslColor.ToHsv(H, S, L, A);
}
/// <inheritdoc/>
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)));
}
/// <summary>
/// Converts the given HSLA color component values to their HSV color equivalent.
/// </summary>
/// <param name="hue">The Hue component in the HSL color model in the range from 0..360.</param>
/// <param name="saturation">The Saturation component in the HSL color model in the range from 0..1.</param>
/// <param name="lightness">The Lightness component in the HSL color model in the range from 0..1.</param>
/// <param name="alpha">The Alpha component in the range from 0..1.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given HSLA values.</returns>
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);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HslColor"/> objects are equal.
/// </summary>

66
src/Avalonia.Base/Media/HsvColor.cs

@ -195,10 +195,18 @@ namespace Avalonia.Media
/// <returns>The RGB equivalent color.</returns>
public Color ToRgb()
{
// Use the by-component conversion method directly for performance
return HsvColor.ToRgb(H, S, V, A);
}
/// <summary>
/// Returns the HSL color model equivalent of this HSV color.
/// </summary>
/// <returns>The HSL equivalent color.</returns>
public HslColor ToHsl()
{
return HsvColor.ToHsl(H, S, V, A);
}
/// <inheritdoc/>
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));
}
/// <summary>
/// Converts the given HSVA color component values to their HSL color equivalent.
/// </summary>
/// <param name="hue">The Hue component in the HSV color model in the range from 0..360.</param>
/// <param name="saturation">The Saturation component in the HSV color model in the range from 0..1.</param>
/// <param name="value">The Value component in the HSV color model in the range from 0..1.</param>
/// <param name="alpha">The Alpha component in the range from 0..1.</param>
/// <returns>A new <see cref="HslColor"/> equivalent to the given HSVA values.</returns>
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);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HsvColor"/> objects are equal.
/// </summary>

29
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<HsvColor, HslColor>[]
{
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);
}
}
}
}

Loading…
Cancel
Save