From 1ea30b53ff31f866d2e1cd74f30dd8676ebd3a55 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 9 Apr 2022 00:43:34 -0400 Subject: [PATCH] Add Color.ToHsl() conversions --- src/Avalonia.Visuals/Media/Color.cs | 155 ++++++++++++++++++++++--- src/Avalonia.Visuals/Media/HslColor.cs | 18 ++- src/Avalonia.Visuals/Media/HsvColor.cs | 4 +- 3 files changed, 158 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs index 107bfc7a8e..da55147f8a 100644 --- a/src/Avalonia.Visuals/Media/Color.cs +++ b/src/Avalonia.Visuals/Media/Color.cs @@ -21,6 +21,8 @@ namespace Avalonia.Media #endif readonly struct Color : IEquatable { + private const double byteToDouble = 1.0 / 255; + static Color() { #if !BUILDTASK @@ -279,13 +281,22 @@ namespace Avalonia.Media return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B; } + /// + /// Returns the HSL color model equivalent of this RGB color. + /// + /// 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); + } + /// /// Returns the HSV color model equivalent of this RGB color. /// /// The HSV equivalent color. public HsvColor ToHsv() { - // Use the by-component conversion method directly for performance // Don't use the HsvColor(Color) constructor to avoid an extra HsvColor return Color.ToHsv(R, G, B, A); } @@ -316,40 +327,154 @@ namespace Avalonia.Media } /// - /// Converts the given RGB color to it's HSV color equivalent. + /// Converts the given RGB color to its HSL color equivalent. + /// + /// The color in the RGB color model. + /// A new equivalent to the given RGBA values. + public static HslColor ToHsl(Color color) + { + // Normalize RGBA components into the 0..1 range + return Color.ToHsl( + (byteToDouble * color.R), + (byteToDouble * color.G), + (byteToDouble * color.B), + (byteToDouble * color.A)); + } + + /// + /// Converts the given RGBA color component values to their HSL color equivalent. + /// + /// The Red component in the RGB color model. + /// The Green component in the RGB color model. + /// The Blue component in the RGB color model. + /// The Alpha component. + /// A new equivalent to the given RGBA values. + public static HslColor ToHsl( + byte red, + byte green, + byte blue, + byte alpha = 0xFF) + { + // Normalize RGBA components into the 0..1 range + return Color.ToHsl( + (byteToDouble * red), + (byteToDouble * green), + (byteToDouble * blue), + (byteToDouble * alpha)); + } + + /// + /// Converts the given RGBA color component values to their HSL color equivalent. + /// + /// + /// Warning: No bounds checks or clamping is done on the input component values. + /// This method is for internal-use only and the caller must ensure bounds. + /// + /// The Red component in the RGB color model within the range 0..1. + /// The Green component in the RGB color model within the range 0..1. + /// The Blue component in the RGB color model within the range 0..1. + /// The Alpha component in the RGB color model within the range 0..1. + /// A new equivalent to the given RGBA values. + internal static HslColor ToHsl( + double r, + double g, + double b, + double a = 1.0) + { + // Note: Conversion code is originally based on ColorHelper in the Windows Community Toolkit (licensed MIT) + // https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp/Helpers/ColorHelper.cs + // It has been modified. + + var max = Math.Max(Math.Max(r, g), b); + var min = Math.Min(Math.Min(r, g), b); + var chroma = max - min; + double h1; + + if (chroma == 0) + { + h1 = 0; + } + else if (max == r) + { + // The % operator doesn't do proper modulo on negative + // numbers, so we'll add 6 before using it + h1 = (((g - b) / chroma) + 6) % 6; + } + else if (max == g) + { + h1 = 2 + ((b - r) / chroma); + } + else + { + h1 = 4 + ((r - g) / chroma); + } + + double lightness = 0.5 * (max + min); + double saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs((2 * lightness) - 1)); + + return new HslColor(a, 60 * h1, saturation, lightness, clampValues: false); + } + + /// + /// Converts the given RGB color to its HSV color equivalent. /// /// The color in the RGB color model. /// A new equivalent to the given RGBA values. public static HsvColor ToHsv(Color color) { - return Color.ToHsv(color.R, color.G, color.B, color.A); + // Normalize RGBA components into the 0..1 range + return Color.ToHsv( + (byteToDouble * color.R), + (byteToDouble * color.G), + (byteToDouble * color.B), + (byteToDouble * color.A)); } /// - /// Converts the given RGBA color component values to its HSV color equivalent. + /// Converts the given RGBA color component values to their HSV color equivalent. /// - /// The red component in the RGB color model. - /// The green component in the RGB color model. - /// The blue component in the RGB color model. - /// The alpha component. + /// The Red component in the RGB color model. + /// The Green component in the RGB color model. + /// The Blue component in the RGB color model. + /// The Alpha component. /// A new equivalent to the given RGBA values. public static HsvColor ToHsv( byte red, byte green, byte blue, byte alpha = 0xFF) + { + // Normalize RGBA components into the 0..1 range + return Color.ToHsv( + (byteToDouble * red), + (byteToDouble * green), + (byteToDouble * blue), + (byteToDouble * alpha)); + } + + /// + /// Converts the given RGBA color component values to their HSV color equivalent. + /// + /// + /// Warning: No bounds checks or clamping is done on the input component values. + /// This method is for internal-use only and the caller must ensure bounds. + /// + /// The Red component in the RGB color model within the range 0..1. + /// The Green component in the RGB color model within the range 0..1. + /// The Blue component in the RGB color model within the range 0..1. + /// The Alpha component in the RGB color model within the range 0..1. + /// A new equivalent to the given RGBA values. + internal static HsvColor ToHsv( + double r, + double g, + double b, + double a = 1.0) { // Note: Conversion code is originally based on the C++ in WinUI (licensed MIT) // https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/Common/ColorConversion.cpp // This was used because it is the best documented and likely most optimized for performance // Alpha support was added - // Normalize RGBA components into the 0..1 range used by this algorithm - double r = red / 255.0; - double g = green / 255.0; - double b = blue / 255.0; - double a = alpha / 255.0; - double hue; double saturation; double value; @@ -436,7 +561,7 @@ namespace Avalonia.Media saturation = chroma / value; } - return new HsvColor(a, hue, saturation, value, false); + return new HsvColor(a, hue, saturation, value, clampValues: false); } /// diff --git a/src/Avalonia.Visuals/Media/HslColor.cs b/src/Avalonia.Visuals/Media/HslColor.cs index 21ab669a05..af4a9e20f4 100644 --- a/src/Avalonia.Visuals/Media/HslColor.cs +++ b/src/Avalonia.Visuals/Media/HslColor.cs @@ -83,6 +83,20 @@ namespace Avalonia.Media } } + /// + /// Initializes a new instance of the struct. + /// + /// The RGB color to convert to HSL. + public HslColor(Color color) + { + var hsl = Color.ToHsl(color); + + A = hsl.A; + H = hsl.H; + S = hsl.S; + L = hsl.L; + } + /// /// Gets the Alpha (transparency) component in the range from 0..1. /// @@ -189,7 +203,7 @@ namespace Avalonia.Media } /// - /// Converts the given HSL color to it's RGB color equivalent. + /// Converts the given HSL color to its RGB color equivalent. /// /// The color in the HSL color model. /// A new RGB equivalent to the given HSLA values. @@ -199,7 +213,7 @@ namespace Avalonia.Media } /// - /// Converts the given HSLA color component values to it's RGB color equivalent. + /// Converts the given HSLA color component values to their RGB 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. diff --git a/src/Avalonia.Visuals/Media/HsvColor.cs b/src/Avalonia.Visuals/Media/HsvColor.cs index fcfd6a7040..42333f7049 100644 --- a/src/Avalonia.Visuals/Media/HsvColor.cs +++ b/src/Avalonia.Visuals/Media/HsvColor.cs @@ -326,7 +326,7 @@ namespace Avalonia.Media } /// - /// Converts the given HSV color to it's RGB color equivalent. + /// Converts the given HSV color to its RGB color equivalent. /// /// The color in the HSV color model. /// A new RGB equivalent to the given HSVA values. @@ -336,7 +336,7 @@ namespace Avalonia.Media } /// - /// Converts the given HSVA color component values to it's RGB color equivalent. + /// Converts the given HSVA color component values to their RGB 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.