@ -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 Hsv Color. FromRgb ( R , G , B , A ) ;
return Color . ToHsv ( R , G , B , A ) ;
}
/// <inheritdoc/>
@ -308,6 +315,130 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Converts the given RGB color to it's HSV color equivalent.
/// </summary>
/// <param name="color">The color in the RGB color model.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
public static HsvColor ToHsv ( Color color )
{
return Color . ToHsv ( color . R , color . G , color . B , color . A ) ;
}
/// <summary>
/// Converts the given RGBA color component values to its HSV color equivalent.
/// </summary>
/// <param name="red">The red component in the RGB color model.</param>
/// <param name="green">The green component in the RGB color model.</param>
/// <param name="blue">The blue component in the RGB color model.</param>
/// <param name="alpha">The alpha component.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
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 / 2 5 5.0 ;
double g = green / 2 5 5.0 ;
double b = blue / 2 5 5.0 ;
double a = alpha / 2 5 5.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 = 6 0 * ( 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 = 1 2 0 + ( 6 0 * ( 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 = 2 4 0 + ( 6 0 * ( 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 + = 3 6 0.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 ) ;
}
/// <summary>
/// Indicates whether the values of two specified <see cref="Color"/> objects are equal.
/// </summary>