diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index 5a7daa6d12..9629324c8d 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -83,6 +83,9 @@
Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
Markup/%(RecursiveDir)%(FileName)%(Extension)
diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs
index 9f8588d400..84fde9bfc2 100644
--- a/src/Avalonia.Visuals/Media/Color.cs
+++ b/src/Avalonia.Visuals/Media/Color.cs
@@ -308,11 +308,23 @@ namespace Avalonia.Media
}
}
+ ///
+ /// Indicates whether the values of two specified objects are equal.
+ ///
+ /// The first object to compare.
+ /// The second object to compare.
+ /// True if left and right are equal; otherwise, false.
public static bool operator ==(Color left, Color right)
{
return left.Equals(right);
}
+ ///
+ /// Indicates whether the values of two specified objects are not equal.
+ ///
+ /// The first object to compare.
+ /// The second object to compare.
+ /// True if left and right are not equal; otherwise, false.
public static bool operator !=(Color left, Color right)
{
return !left.Equals(right);
diff --git a/src/Avalonia.Visuals/Media/HslColor.cs b/src/Avalonia.Visuals/Media/HslColor.cs
new file mode 100644
index 0000000000..21ab669a05
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/HslColor.cs
@@ -0,0 +1,322 @@
+// Color conversion portions of this source file are adapted from 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;
+using System.Text;
+using Avalonia.Utilities;
+
+namespace Avalonia.Media
+{
+ ///
+ /// Defines a color using the hue/saturation/lightness (HSL) model.
+ ///
+#if !BUILDTASK
+ public
+#endif
+ readonly struct HslColor : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The Alpha (transparency) component in the range from 0..1.
+ /// The Hue component in the range from 0..360.
+ /// Note that 360 is equivalent to 0 and will be adjusted automatically.
+ /// The Saturation component in the range from 0..1.
+ /// The Lightness component in the range from 0..1.
+ public HslColor(
+ double alpha,
+ double hue,
+ double saturation,
+ double lightness)
+ {
+ A = MathUtilities.Clamp(alpha, 0.0, 1.0);
+ H = MathUtilities.Clamp(hue, 0.0, 360.0);
+ S = MathUtilities.Clamp(saturation, 0.0, 1.0);
+ L = MathUtilities.Clamp(lightness, 0.0, 1.0);
+
+ // The maximum value of Hue is technically 360 minus epsilon (just below 360).
+ // This is because, in a color circle, 360 degrees is equivalent to 0 degrees.
+ // However, that is too tricky to work with in code and isn't as intuitive.
+ // Therefore, since 360 == 0, just wrap 360 if needed back to 0.
+ H = (H == 360.0 ? 0 : H);
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// This constructor exists only for internal use where performance is critical.
+ /// Whether or not the component values are in the correct ranges must be known.
+ ///
+ /// The Alpha (transparency) component in the range from 0..1.
+ /// The Hue component in the range from 0..360.
+ /// Note that 360 is equivalent to 0 and will be adjusted automatically.
+ /// The Saturation component in the range from 0..1.
+ /// The Lightness component in the range from 0..1.
+ /// Whether to clamp component values to their required ranges.
+ internal HslColor(
+ double alpha,
+ double hue,
+ double saturation,
+ double lightness,
+ bool clampValues)
+ {
+ if (clampValues)
+ {
+ A = MathUtilities.Clamp(alpha, 0.0, 1.0);
+ H = MathUtilities.Clamp(hue, 0.0, 360.0);
+ S = MathUtilities.Clamp(saturation, 0.0, 1.0);
+ L = MathUtilities.Clamp(lightness, 0.0, 1.0);
+
+ // See comments in constructor above
+ H = (H == 360.0 ? 0 : H);
+ }
+ else
+ {
+ A = alpha;
+ H = hue;
+ S = saturation;
+ L = lightness;
+ }
+ }
+
+ ///
+ /// Gets the Alpha (transparency) component in the range from 0..1.
+ ///
+ public double A { get; }
+
+ ///
+ /// Gets the Hue component in the range from 0..360.
+ /// Note that 360 is equivalent to 0 and will be adjusted automatically.
+ ///
+ public double H { get; }
+
+ ///
+ /// Gets the Saturation component in the range from 0..1.
+ ///
+ public double S { get; }
+
+ ///
+ /// Gets the Lightness component in the range from 0..1.
+ ///
+ public double L { get; }
+
+ ///
+ public bool Equals(HslColor other)
+ {
+ return other.A == A &&
+ other.H == H &&
+ other.S == S &&
+ other.L == L;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (obj is HslColor hslColor)
+ {
+ return Equals(hslColor);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Gets a hashcode for this object.
+ /// Hashcode is not guaranteed to be unique.
+ ///
+ /// The hashcode for this object.
+ public override int GetHashCode()
+ {
+ // Same algorithm as Color
+ // This is used instead of HashCode.Combine() due to .NET Standard 2.0 requirements
+ unchecked
+ {
+ int hashCode = A.GetHashCode();
+ hashCode = (hashCode * 397) ^ H.GetHashCode();
+ hashCode = (hashCode * 397) ^ S.GetHashCode();
+ hashCode = (hashCode * 397) ^ L.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ /// Returns the RGB color model equivalent of this HSL color.
+ ///
+ /// The RGB equivalent color.
+ public Color ToRgb()
+ {
+ // Use the by-component conversion method directly for performance
+ return HslColor.ToRgb(H, S, L, A);
+ }
+
+ ///
+ /// Creates a new from individual color component values.
+ ///
+ ///
+ /// This exists for symmetry with the struct; however, the
+ /// appropriate constructor should commonly be used instead.
+ ///
+ /// The Alpha (transparency) component in the range from 0..1.
+ /// The Hue component in the range from 0..360.
+ /// The Saturation component in the range from 0..1.
+ /// The Lightness component in the range from 0..1.
+ /// A new built from the individual color component values.
+ public static HslColor FromAhsl(double a, double h, double s, double l)
+ {
+ return new HslColor(a, h, s, l);
+ }
+
+ ///
+ /// Creates a new from individual color component values.
+ ///
+ ///
+ /// This exists for symmetry with the struct; however, the
+ /// appropriate constructor should commonly be used instead.
+ ///
+ /// The Hue component in the range from 0..360.
+ /// The Saturation component in the range from 0..1.
+ /// The Lightness component in the range from 0..1.
+ /// A new built from the individual color component values.
+ public static HslColor FromHsl(double h, double s, double l)
+ {
+ return new HslColor(1.0, h, s, l);
+ }
+
+ ///
+ /// Converts the given HSL color to it's RGB color equivalent.
+ ///
+ /// The color in the HSL color model.
+ /// A new RGB equivalent to the given HSLA values.
+ public static Color ToRgb(HslColor hslColor)
+ {
+ return HslColor.ToRgb(hslColor.H, hslColor.S, hslColor.L, hslColor.A);
+ }
+
+ ///
+ /// Converts the given HSLA color component values to it's 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.
+ /// 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 RGB equivalent to the given HSLA values.
+ public static Color ToRgb(
+ double hue,
+ double saturation,
+ double lightness,
+ double alpha = 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 to ensure input ranges and not throw exceptions.
+
+ // 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;
+
+ double chroma = (1 - Math.Abs((2 * lightness) - 1)) * saturation;
+ double h1 = hue / 60;
+ double x = chroma * (1 - Math.Abs((h1 % 2) - 1));
+ double m = lightness - (0.5 * chroma);
+ double r1, g1, b1;
+
+ if (h1 < 1)
+ {
+ r1 = chroma;
+ g1 = x;
+ b1 = 0;
+ }
+ else if (h1 < 2)
+ {
+ r1 = x;
+ g1 = chroma;
+ b1 = 0;
+ }
+ else if (h1 < 3)
+ {
+ r1 = 0;
+ g1 = chroma;
+ b1 = x;
+ }
+ else if (h1 < 4)
+ {
+ r1 = 0;
+ g1 = x;
+ b1 = chroma;
+ }
+ else if (h1 < 5)
+ {
+ r1 = x;
+ g1 = 0;
+ b1 = chroma;
+ }
+ else
+ {
+ r1 = chroma;
+ g1 = 0;
+ b1 = x;
+ }
+
+ return Color.FromArgb(
+ (byte)(255 * alpha),
+ (byte)(255 * (r1 + m)),
+ (byte)(255 * (g1 + m)),
+ (byte)(255 * (b1 + m)));
+ }
+
+ ///
+ /// Indicates whether the values of two specified objects are equal.
+ ///
+ /// The first object to compare.
+ /// The second object to compare.
+ /// True if left and right are equal; otherwise, false.
+ public static bool operator ==(HslColor left, HslColor right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Indicates whether the values of two specified objects are not equal.
+ ///
+ /// The first object to compare.
+ /// The second object to compare.
+ /// True if left and right are not equal; otherwise, false.
+ public static bool operator !=(HslColor left, HslColor right)
+ {
+ return !(left == right);
+ }
+
+ ///
+ /// Explicit conversion from an to a .
+ ///
+ /// The to convert.
+ public static explicit operator Color(HslColor hslColor)
+ {
+ return hslColor.ToRgb();
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/HsvColor.cs b/src/Avalonia.Visuals/Media/HsvColor.cs
index c58db9df00..6a27c39c9b 100644
--- a/src/Avalonia.Visuals/Media/HsvColor.cs
+++ b/src/Avalonia.Visuals/Media/HsvColor.cs
@@ -338,10 +338,10 @@ namespace Avalonia.Media
///
/// Converts the given HSVA color component values to it's 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.
- /// The value component in the HSV color model in the range from 0..1.
- /// The alpha component in the range from 0..1.
+ /// 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 RGB equivalent to the given HSVA values.
public static Color ToRgb(
double hue,
@@ -467,10 +467,10 @@ namespace Avalonia.Media
}
return Color.FromArgb(
- (byte)Math.Round(alpha * 255),
- (byte)Math.Round(r * 255),
- (byte)Math.Round(g * 255),
- (byte)Math.Round(b * 255));
+ (byte)(alpha * 255),
+ (byte)(r * 255),
+ (byte)(g * 255),
+ (byte)(b * 255));
}
///