diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs
index 84fde9bfc2..107bfc7a8e 100644
--- a/src/Avalonia.Visuals/Media/Color.cs
+++ b/src/Avalonia.Visuals/Media/Color.cs
@@ -1,3 +1,10 @@
+// Color conversion portions of this source file are adapted from the WinUI project.
+// (https://github.com/microsoft/microsoft-ui-xaml)
+// and the Windows Community Toolkit project.
+// (https://github.com/CommunityToolkit/WindowsCommunityToolkit)
+//
+// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
+
using System;
using System.Globalization;
#if !BUILDTASK
@@ -280,7 +287,7 @@ namespace Avalonia.Media
{
// Use the by-component conversion method directly for performance
// Don't use the HsvColor(Color) constructor to avoid an extra HsvColor
- return HsvColor.FromRgb(R, G, B, A);
+ return Color.ToHsv(R, G, B, A);
}
///
@@ -308,6 +315,130 @@ namespace Avalonia.Media
}
}
+ ///
+ /// Converts the given RGB color to it's 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);
+ }
+
+ ///
+ /// Converts the given RGBA color component values to its 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.
+ /// A new equivalent to the given RGBA values.
+ public static HsvColor ToHsv(
+ byte red,
+ byte green,
+ byte blue,
+ byte alpha = 0xFF)
+ {
+ // 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;
+
+ double max = r >= g ? (r >= b ? r : b) : (g >= b ? g : b);
+ double min = r <= g ? (r <= b ? r : b) : (g <= b ? g : b);
+
+ // The value, a number between 0 and 1, is the largest of R, G, and B (divided by 255).
+ // Conceptually speaking, it represents how much color is present.
+ // If at least one of R, G, B is 255, then there exists as much color as there can be.
+ // If RGB = (0, 0, 0), then there exists no color at all - a value of zero corresponds
+ // to black (i.e., the absence of any color).
+ value = max;
+
+ // The "chroma" of the color is a value directly proportional to the extent to which
+ // the color diverges from greyscale. If, for example, we have RGB = (255, 255, 0),
+ // then the chroma is maximized - this is a pure yellow, no gray of any kind.
+ // On the other hand, if we have RGB = (128, 128, 128), then the chroma being zero
+ // implies that this color is pure greyscale, with no actual hue to be found.
+ var chroma = max - min;
+
+ // If the chrome is zero, then hue is technically undefined - a greyscale color
+ // has no hue. For the sake of convenience, we'll just set hue to zero, since
+ // it will be unused in this circumstance. Since the color is purely gray,
+ // saturation is also equal to zero - you can think of saturation as basically
+ // a measure of hue intensity, such that no hue at all corresponds to a
+ // nonexistent intensity.
+ if (chroma == 0)
+ {
+ hue = 0.0;
+ saturation = 0.0;
+ }
+ else
+ {
+ // In this block, hue is properly defined, so we'll extract both hue
+ // and saturation information from the RGB color.
+
+ // Hue can be thought of as a cyclical thing, between 0 degrees and 360 degrees.
+ // A hue of 0 degrees is red; 120 degrees is green; 240 degrees is blue; and 360 is back to red.
+ // Every other hue is somewhere between either red and green, green and blue, and blue and red,
+ // so every other hue can be thought of as an angle on this color wheel.
+ // These if/else statements determines where on this color wheel our color lies.
+ if (r == max)
+ {
+ // If the red channel is the most pronounced channel, then we exist
+ // somewhere between (-60, 60) on the color wheel - i.e., the section around 0 degrees
+ // where red dominates. We figure out where in that section we are exactly
+ // by considering whether the green or the blue channel is greater - by subtracting green from blue,
+ // then if green is greater, we'll nudge ourselves closer to 60, whereas if blue is greater, then
+ // we'll nudge ourselves closer to -60. We then divide by chroma (which will actually make the result larger,
+ // since chroma is a value between 0 and 1) to normalize the value to ensure that we get the right hue
+ // even if we're very close to greyscale.
+ hue = 60 * (g - b) / chroma;
+ }
+ else if (g == max)
+ {
+ // We do the exact same for the case where the green channel is the most pronounced channel,
+ // only this time we want to see if we should tilt towards the blue direction or the red direction.
+ // We add 120 to center our value in the green third of the color wheel.
+ hue = 120 + (60 * (b - r) / chroma);
+ }
+ else // blue == max
+ {
+ // And we also do the exact same for the case where the blue channel is the most pronounced channel,
+ // only this time we want to see if we should tilt towards the red direction or the green direction.
+ // We add 240 to center our value in the blue third of the color wheel.
+ hue = 240 + (60 * (r - g) / chroma);
+ }
+
+ // Since we want to work within the range [0, 360), we'll add 360 to any value less than zero -
+ // this will bump red values from within -60 to -1 to 300 to 359. The hue is the same at both values.
+ if (hue < 0.0)
+ {
+ hue += 360.0;
+ }
+
+ // The saturation, our final HSV axis, can be thought of as a value between 0 and 1 indicating how intense our color is.
+ // To find it, we divide the chroma - the distance between the minimum and the maximum RGB channels - by the maximum channel (i.e., the value).
+ // This effectively normalizes the chroma - if the maximum is 0.5 and the minimum is 0, the saturation will be (0.5 - 0) / 0.5 = 1,
+ // meaning that although this color is not as bright as it can be, the dark color is as intense as it possibly could be.
+ // If, on the other hand, the maximum is 0.5 and the minimum is 0.25, then the saturation will be (0.5 - 0.25) / 0.5 = 0.5,
+ // meaning that this color is partially washed out.
+ // A saturation value of 0 corresponds to a greyscale color, one in which the color is *completely* washed out and there is no actual hue.
+ saturation = chroma / value;
+ }
+
+ return new HsvColor(a, hue, saturation, value, false);
+ }
+
///
/// Indicates whether the values of two specified objects are equal.
///
diff --git a/src/Avalonia.Visuals/Media/HsvColor.cs b/src/Avalonia.Visuals/Media/HsvColor.cs
index 6a27c39c9b..fcfd6a7040 100644
--- a/src/Avalonia.Visuals/Media/HsvColor.cs
+++ b/src/Avalonia.Visuals/Media/HsvColor.cs
@@ -1,6 +1,6 @@
-// Color conversion portions of this source file are adapted from the WinUI project.
-// (https://github.com/microsoft/microsoft-ui-xaml)
-//
+// Color conversion portions of this source file are adapted from the WinUI project.
+// (https://github.com/microsoft/microsoft-ui-xaml)
+//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
@@ -89,7 +89,7 @@ namespace Avalonia.Media
/// The RGB color to convert to HSV.
public HsvColor(Color color)
{
- var hsv = HsvColor.FromRgb(color);
+ var hsv = Color.ToHsv(color);
A = hsv.A;
H = hsv.H;
@@ -473,130 +473,6 @@ namespace Avalonia.Media
(byte)(b * 255));
}
- ///
- /// Converts the given RGB color to it's HSV color equivalent.
- ///
- /// The color in the RGB color model.
- /// A new equivalent to the given RGBA values.
- public static HsvColor FromRgb(Color color)
- {
- return HsvColor.FromRgb(color.R, color.G, color.B, color.A);
- }
-
- ///
- /// Converts the given RGBA color component values to its 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.
- /// A new equivalent to the given RGBA values.
- public static HsvColor FromRgb(
- byte red,
- byte green,
- byte blue,
- byte alpha = 0xFF)
- {
- // 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;
-
- double max = r >= g ? (r >= b ? r : b) : (g >= b ? g : b);
- double min = r <= g ? (r <= b ? r : b) : (g <= b ? g : b);
-
- // The value, a number between 0 and 1, is the largest of R, G, and B (divided by 255).
- // Conceptually speaking, it represents how much color is present.
- // If at least one of R, G, B is 255, then there exists as much color as there can be.
- // If RGB = (0, 0, 0), then there exists no color at all - a value of zero corresponds
- // to black (i.e., the absence of any color).
- value = max;
-
- // The "chroma" of the color is a value directly proportional to the extent to which
- // the color diverges from greyscale. If, for example, we have RGB = (255, 255, 0),
- // then the chroma is maximized - this is a pure yellow, no gray of any kind.
- // On the other hand, if we have RGB = (128, 128, 128), then the chroma being zero
- // implies that this color is pure greyscale, with no actual hue to be found.
- var chroma = max - min;
-
- // If the chrome is zero, then hue is technically undefined - a greyscale color
- // has no hue. For the sake of convenience, we'll just set hue to zero, since
- // it will be unused in this circumstance. Since the color is purely gray,
- // saturation is also equal to zero - you can think of saturation as basically
- // a measure of hue intensity, such that no hue at all corresponds to a
- // nonexistent intensity.
- if (chroma == 0)
- {
- hue = 0.0;
- saturation = 0.0;
- }
- else
- {
- // In this block, hue is properly defined, so we'll extract both hue
- // and saturation information from the RGB color.
-
- // Hue can be thought of as a cyclical thing, between 0 degrees and 360 degrees.
- // A hue of 0 degrees is red; 120 degrees is green; 240 degrees is blue; and 360 is back to red.
- // Every other hue is somewhere between either red and green, green and blue, and blue and red,
- // so every other hue can be thought of as an angle on this color wheel.
- // These if/else statements determines where on this color wheel our color lies.
- if (r == max)
- {
- // If the red channel is the most pronounced channel, then we exist
- // somewhere between (-60, 60) on the color wheel - i.e., the section around 0 degrees
- // where red dominates. We figure out where in that section we are exactly
- // by considering whether the green or the blue channel is greater - by subtracting green from blue,
- // then if green is greater, we'll nudge ourselves closer to 60, whereas if blue is greater, then
- // we'll nudge ourselves closer to -60. We then divide by chroma (which will actually make the result larger,
- // since chroma is a value between 0 and 1) to normalize the value to ensure that we get the right hue
- // even if we're very close to greyscale.
- hue = 60 * (g - b) / chroma;
- }
- else if (g == max)
- {
- // We do the exact same for the case where the green channel is the most pronounced channel,
- // only this time we want to see if we should tilt towards the blue direction or the red direction.
- // We add 120 to center our value in the green third of the color wheel.
- hue = 120 + (60 * (b - r) / chroma);
- }
- else // blue == max
- {
- // And we also do the exact same for the case where the blue channel is the most pronounced channel,
- // only this time we want to see if we should tilt towards the red direction or the green direction.
- // We add 240 to center our value in the blue third of the color wheel.
- hue = 240 + (60 * (r - g) / chroma);
- }
-
- // Since we want to work within the range [0, 360), we'll add 360 to any value less than zero -
- // this will bump red values from within -60 to -1 to 300 to 359. The hue is the same at both values.
- if (hue < 0.0)
- {
- hue += 360.0;
- }
-
- // The saturation, our final HSV axis, can be thought of as a value between 0 and 1 indicating how intense our color is.
- // To find it, we divide the chroma - the distance between the minimum and the maximum RGB channels - by the maximum channel (i.e., the value).
- // This effectively normalizes the chroma - if the maximum is 0.5 and the minimum is 0, the saturation will be (0.5 - 0) / 0.5 = 1,
- // meaning that although this color is not as bright as it can be, the dark color is as intense as it possibly could be.
- // If, on the other hand, the maximum is 0.5 and the minimum is 0.25, then the saturation will be (0.5 - 0.25) / 0.5 = 0.5,
- // meaning that this color is partially washed out.
- // A saturation value of 0 corresponds to a greyscale color, one in which the color is *completely* washed out and there is no actual hue.
- saturation = chroma / value;
- }
-
- return new HsvColor(a, hue, saturation, value, false);
- }
-
///
/// Indicates whether the values of two specified objects are equal.
///