diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml
index b759720cf2..25fffabfd2 100644
--- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml
+++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml
@@ -103,7 +103,8 @@
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />-->
+ HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}"
+ Margin="0,2,0,0" />
diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs
index 50c2faacc0..3ee151389a 100644
--- a/src/Avalonia.Base/Media/Color.cs
+++ b/src/Avalonia.Base/Media/Color.cs
@@ -478,7 +478,6 @@ namespace Avalonia.Media
/// 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);
}
@@ -488,7 +487,6 @@ namespace Avalonia.Media
/// The HSV equivalent color.
public HsvColor ToHsv()
{
- // Don't use the HsvColor(Color) constructor to avoid an extra HsvColor
return Color.ToHsv(R, G, B, A);
}
@@ -517,21 +515,6 @@ namespace Avalonia.Media
}
}
- ///
- /// 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.
///
@@ -606,21 +589,6 @@ namespace Avalonia.Media
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)
- {
- // 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 their HSV color equivalent.
///
diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs
index 897c883875..84f2149367 100644
--- a/src/Avalonia.Base/Media/HslColor.cs
+++ b/src/Avalonia.Base/Media/HslColor.cs
@@ -90,7 +90,7 @@ namespace Avalonia.Media
/// The RGB color to convert to HSL.
public HslColor(Color color)
{
- var hsl = Color.ToHsl(color);
+ var hsl = color.ToHsl();
A = hsl.A;
H = hsl.H;
@@ -165,10 +165,18 @@ namespace Avalonia.Media
/// The RGB equivalent color.
public Color ToRgb()
{
- // Use the by-component conversion method directly for performance
return HslColor.ToRgb(H, S, L, A);
}
+ ///
+ /// Returns the HSV color model equivalent of this HSL color.
+ ///
+ /// The HSV equivalent color.
+ public HsvColor ToHsv()
+ {
+ return HslColor.ToHsv(H, S, L, A);
+ }
+
///
public override string ToString()
{
@@ -349,16 +357,6 @@ namespace Avalonia.Media
return new HslColor(1.0, h, s, l);
}
- ///
- /// 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.
- 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 their RGB color equivalent.
///
@@ -442,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)));
}
+ ///
+ /// Converts the given HSLA color component values to their HSV 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 equivalent to the given HSLA values.
+ 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);
+ }
+
///
/// Indicates whether the values of two specified objects are equal.
///
diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs
index df68252065..03949d32aa 100644
--- a/src/Avalonia.Base/Media/HsvColor.cs
+++ b/src/Avalonia.Base/Media/HsvColor.cs
@@ -90,7 +90,7 @@ namespace Avalonia.Media
/// The RGB color to convert to HSV.
public HsvColor(Color color)
{
- var hsv = Color.ToHsv(color);
+ var hsv = color.ToHsv();
A = hsv.A;
H = hsv.H;
@@ -195,10 +195,18 @@ namespace Avalonia.Media
/// The RGB equivalent color.
public Color ToRgb()
{
- // Use the by-component conversion method directly for performance
return HsvColor.ToRgb(H, S, V, A);
}
+ ///
+ /// Returns the HSL color model equivalent of this HSV color.
+ ///
+ /// The HSL equivalent color.
+ public HslColor ToHsl()
+ {
+ return HsvColor.ToHsl(H, S, V, A);
+ }
+
///
public override string ToString()
{
@@ -379,16 +387,6 @@ namespace Avalonia.Media
return new HsvColor(1.0, h, s, v);
}
- ///
- /// 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.
- public static Color ToRgb(HsvColor hsvColor)
- {
- return HsvColor.ToRgb(hsvColor.H, hsvColor.S, hsvColor.V, hsvColor.A);
- }
-
///
/// Converts the given HSVA color component values to their RGB color equivalent.
///
@@ -520,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));
}
+ ///
+ /// Converts the given HSVA color component values to their HSL 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.
+ /// A new equivalent to the given HSVA values.
+ 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);
+ }
+
///
/// Indicates whether the values of two specified objects are equal.
///
diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs
index e2a34a7f90..a065a7f826 100644
--- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs
+++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs
@@ -41,11 +41,19 @@ namespace Avalonia.Controls.Primitives
defaultBindingMode: BindingMode.TwoWay);
///
- /// Defines the property.
+ /// Defines the property.
///
- public static readonly StyledProperty IsAlphaMaxForcedProperty =
+ public static readonly StyledProperty IsAlphaVisibleProperty =
AvaloniaProperty.Register(
- nameof(IsAlphaMaxForced),
+ nameof(IsAlphaVisible),
+ false);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty IsPerceptiveProperty =
+ AvaloniaProperty.Register(
+ nameof(IsPerceptive),
true);
///
@@ -56,14 +64,6 @@ namespace Avalonia.Controls.Primitives
nameof(IsRoundingEnabled),
false);
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty IsSaturationValueMaxForcedProperty =
- AvaloniaProperty.Register(
- nameof(IsSaturationValueMaxForced),
- true);
-
///
/// Gets or sets the currently selected color in the RGB color model.
///
@@ -109,14 +109,41 @@ namespace Avalonia.Controls.Primitives
}
///
- /// Gets or sets a value indicating whether the alpha component is always forced to maximum for components
- /// other than .
- /// This ensures that the background is always visible and never transparent regardless of the actual color.
+ /// Gets or sets a value indicating whether the alpha component is visible and rendered.
+ /// When false, this ensures that the gradient is always visible and never transparent regardless of
+ /// the actual color. This property is ignored when the alpha component itself is being displayed.
+ ///
+ ///
+ /// Setting to false means the alpha component is always forced to maximum for components other than
+ /// during rendering. This doesn't change the value of the alpha component
+ /// in the color – it is only for display.
+ ///
+ public bool IsAlphaVisible
+ {
+ get => GetValue(IsAlphaVisibleProperty);
+ set => SetValue(IsAlphaVisibleProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the slider adapts rendering to improve user-perception
+ /// over exactness.
///
- public bool IsAlphaMaxForced
+ ///
+ /// When true in the HSVA color model, this ensures that the gradient is always visible and
+ /// never washed out regardless of the actual color. When true in the RGBA color model, this ensures
+ /// the gradient always appears as red, green or blue.
+ ///
+ /// For example, with Hue in the HSVA color model, the Saturation and Value components are always forced
+ /// to maximum values during rendering. In the RGBA color model, all components other than
+ /// are forced to minimum values during rendering.
+ ///
+ /// Note this property will only adjust components other than during rendering.
+ /// This also doesn't change the values of any components in the actual color – it is only for display.
+ ///
+ public bool IsPerceptive
{
- get => GetValue(IsAlphaMaxForcedProperty);
- set => SetValue(IsAlphaMaxForcedProperty, value);
+ get => GetValue(IsPerceptiveProperty);
+ set => SetValue(IsPerceptiveProperty, value);
}
///
@@ -131,16 +158,5 @@ namespace Avalonia.Controls.Primitives
get => GetValue(IsRoundingEnabledProperty);
set => SetValue(IsRoundingEnabledProperty, value);
}
-
- ///
- /// Gets or sets a value indicating whether the saturation and value components are always forced to maximum values
- /// when using the HSVA color model. Only component values other than will be changed.
- /// This ensures, for example, that the Hue background is always visible and never washed out regardless of the actual color.
- ///
- public bool IsSaturationValueMaxForced
- {
- get => GetValue(IsSaturationValueMaxForcedProperty);
- set => SetValue(IsSaturationValueMaxForcedProperty, value);
- }
}
}
diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
index ce47a797ec..a6d9ca459f 100644
--- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
+++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
@@ -52,8 +52,7 @@ namespace Avalonia.Controls.Primitives
// This means under a certain alpha threshold, neither a white or black selector thumb
// should be shown and instead the default slider thumb color should be used instead.
if (Color.A < 128 &&
- (IsAlphaMaxForced == false ||
- ColorComponent == ColorComponent.Alpha))
+ (IsAlphaVisible || ColorComponent == ColorComponent.Alpha))
{
PseudoClasses.Set(pcDarkSelector, false);
PseudoClasses.Set(pcLightSelector, false);
@@ -64,11 +63,11 @@ namespace Avalonia.Controls.Primitives
if (ColorModel == ColorModel.Hsva)
{
- perceivedColor = GetEquivalentBackgroundColor(HsvColor).ToRgb();
+ perceivedColor = GetPerceptiveBackgroundColor(HsvColor).ToRgb();
}
else
{
- perceivedColor = GetEquivalentBackgroundColor(Color);
+ perceivedColor = GetPerceptiveBackgroundColor(Color);
}
if (ColorHelper.GetRelativeLuminance(perceivedColor) <= 0.5)
@@ -108,7 +107,7 @@ namespace Avalonia.Controls.Primitives
{
// As a fallback, attempt to calculate using the overall control size
// This shouldn't happen as a track is a required template part of a slider
- // However, if it does, the spectrum will still be shown
+ // However, if it does, the spectrum gradient will still be shown
pixelWidth = Convert.ToInt32(Bounds.Width * scale);
pixelHeight = Convert.ToInt32(Bounds.Height * scale);
}
@@ -122,8 +121,8 @@ namespace Avalonia.Controls.Primitives
ColorModel,
ColorComponent,
HsvColor,
- IsAlphaMaxForced,
- IsSaturationValueMaxForced);
+ IsAlphaVisible,
+ IsPerceptive);
if (_backgroundBitmap != null)
{
@@ -316,40 +315,35 @@ namespace Avalonia.Controls.Primitives
///
/// The actual color to get the equivalent background color for.
/// The equivalent, perceived background color.
- private HsvColor GetEquivalentBackgroundColor(HsvColor hsvColor)
+ private HsvColor GetPerceptiveBackgroundColor(HsvColor hsvColor)
{
var component = ColorComponent;
- var isAlphaMaxForced = IsAlphaMaxForced;
- var isSaturationValueMaxForced = IsSaturationValueMaxForced;
+ var isAlphaVisible = IsAlphaVisible;
+ var isPerceptive = IsPerceptive;
- if (isAlphaMaxForced &&
+ if (isAlphaVisible == false &&
component != ColorComponent.Alpha)
{
hsvColor = new HsvColor(1.0, hsvColor.H, hsvColor.S, hsvColor.V);
}
- switch (component)
+ if (isPerceptive)
{
- case ColorComponent.Component1:
- return new HsvColor(
- hsvColor.A,
- hsvColor.H,
- isSaturationValueMaxForced ? 1.0 : hsvColor.S,
- isSaturationValueMaxForced ? 1.0 : hsvColor.V);
- case ColorComponent.Component2:
- return new HsvColor(
- hsvColor.A,
- hsvColor.H,
- hsvColor.S,
- isSaturationValueMaxForced ? 1.0 : hsvColor.V);
- case ColorComponent.Component3:
- return new HsvColor(
- hsvColor.A,
- hsvColor.H,
- isSaturationValueMaxForced ? 1.0 : hsvColor.S,
- hsvColor.V);
- default:
- return hsvColor;
+ switch (component)
+ {
+ case ColorComponent.Component1:
+ return new HsvColor(hsvColor.A, hsvColor.H, 1.0, 1.0);
+ case ColorComponent.Component2:
+ return new HsvColor(hsvColor.A, hsvColor.H, hsvColor.S, 1.0);
+ case ColorComponent.Component3:
+ return new HsvColor(hsvColor.A, hsvColor.H, 1.0, hsvColor.V);
+ default:
+ return hsvColor;
+ }
+ }
+ else
+ {
+ return hsvColor;
}
}
@@ -359,18 +353,36 @@ namespace Avalonia.Controls.Primitives
///
/// The actual color to get the equivalent background color for.
/// The equivalent, perceived background color.
- private Color GetEquivalentBackgroundColor(Color rgbColor)
+ private Color GetPerceptiveBackgroundColor(Color rgbColor)
{
var component = ColorComponent;
- var isAlphaMaxForced = IsAlphaMaxForced;
+ var isAlphaVisible = IsAlphaVisible;
+ var isPerceptive = IsPerceptive;
- if (isAlphaMaxForced &&
+ if (isAlphaVisible == false &&
component != ColorComponent.Alpha)
{
rgbColor = new Color(255, rgbColor.R, rgbColor.G, rgbColor.B);
}
- return rgbColor;
+ if (isPerceptive)
+ {
+ switch (component)
+ {
+ case ColorComponent.Component1:
+ return new Color(rgbColor.A, rgbColor.R, 0, 0);
+ case ColorComponent.Component2:
+ return new Color(rgbColor.A, 0, rgbColor.G, 0);
+ case ColorComponent.Component3:
+ return new Color(rgbColor.A, 0, 0, rgbColor.B);
+ default:
+ return rgbColor;
+ }
+ }
+ else
+ {
+ return rgbColor;
+ }
}
///
@@ -401,8 +413,8 @@ namespace Avalonia.Controls.Primitives
}
else if (change.Property == ColorComponentProperty ||
change.Property == ColorModelProperty ||
- change.Property == IsAlphaMaxForcedProperty ||
- change.Property == IsSaturationValueMaxForcedProperty)
+ change.Property == IsAlphaVisibleProperty ||
+ change.Property == IsPerceptiveProperty)
{
ignorePropertyChanged = true;
diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
index 9198a2f237..6683346eeb 100644
--- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
+++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
@@ -45,6 +45,7 @@ namespace Avalonia.Controls.Primitives
private bool _updatingColor = false;
private bool _updatingHsvColor = false;
+ private bool _coercedInitialColor = false;
private bool _isPointerPressed = false;
private bool _shouldShowLargeSelection = false;
private List _hsvValues = new List();
@@ -601,14 +602,102 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
+ /// Changes the currently selected color (always in HSV) and applies all necessary updates.
+ ///
+ ///
+ /// Some additional logic is applied in certain situations to coerce and sync color values.
+ /// Use this method instead of update the or directly.
+ ///
+ /// The new HSV color to change to.
private void UpdateColor(Hsv newHsv)
{
_updatingColor = true;
_updatingHsvColor = true;
- Rgb newRgb = newHsv.ToRgb();
double alpha = HsvColor.A;
+ // It is common for the ColorPicker (and therefore the Spectrum) to be initialized
+ // with a #00000000 color value in some use cases. This is usually used to indicate
+ // that no color has been selected by the user. Note that #00000000 is different than
+ // #00FFFFFF (Transparent).
+ //
+ // In this situation, the first time the user clicks on the spectrum the third
+ // component and alpha component will remain zero. This is because the spectrum only
+ // controls two components at any given time.
+ //
+ // This is very unintuitive from a user-standpoint as after the user clicks on the
+ // spectrum they must then increase the alpha and then the third component sliders
+ // to the desired value. In fact, until they increase these slider values no color
+ // will show at all since it is fully transparent and black. In almost all cases
+ // though the desired value is simply full color.
+ //
+ // To work around this usability issue with an initial #00000000 color, the selected
+ // color is coerced (only the first time) into a color with maximum third component
+ // value and maximum alpha. This can only happen once and only if those two components
+ // are already zero.
+ //
+ // Also note this is NOT currently done for #00FFFFFF (Transparent) but based on
+ // further usability study that case may need to be handled here as well. Right now
+ // Transparent is treated as a normal color value with the alpha intentionally set
+ // to zero so the alpha slider must still be adjusted after the spectrum.
+ if (!_coercedInitialColor &&
+ IsLoaded)
+ {
+ bool isAlphaComponentZero = (alpha == 0.0);
+ bool isThirdComponentZero = false;
+
+ switch (Components)
+ {
+ case ColorSpectrumComponents.HueValue:
+ case ColorSpectrumComponents.ValueHue:
+ isThirdComponentZero = (newHsv.S == 0.0);
+ break;
+
+ case ColorSpectrumComponents.HueSaturation:
+ case ColorSpectrumComponents.SaturationHue:
+ isThirdComponentZero = (newHsv.V == 0.0);
+ break;
+
+ case ColorSpectrumComponents.ValueSaturation:
+ case ColorSpectrumComponents.SaturationValue:
+ isThirdComponentZero = (newHsv.H == 0.0);
+ break;
+ }
+
+ if (isAlphaComponentZero && isThirdComponentZero)
+ {
+ alpha = 1.0;
+
+ switch (Components)
+ {
+ case ColorSpectrumComponents.HueValue:
+ case ColorSpectrumComponents.ValueHue:
+ newHsv.S = 1.0;
+ break;
+
+ case ColorSpectrumComponents.HueSaturation:
+ case ColorSpectrumComponents.SaturationHue:
+ newHsv.V = 1.0;
+ break;
+
+ case ColorSpectrumComponents.ValueSaturation:
+ case ColorSpectrumComponents.SaturationValue:
+ // Hue is mathematically NOT a special case; however, is one conceptually.
+ // It doesn't make sense to change the selected Hue value, so why is it set here?
+ // Setting to 360.0 is equivalent to the max set for other components and is
+ // internally wrapped back to 0.0 (since 360 degrees = 0 degrees).
+ // This means effectively there is no change to the hue component value.
+ newHsv.H = 360.0;
+ break;
+ }
+
+ _coercedInitialColor = true;
+ }
+ }
+
+ Rgb newRgb = newHsv.ToRgb();
+
SetCurrentValue(ColorProperty, newRgb.ToColor(alpha));
SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(alpha));
diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
index 532e87a9fc..701dab97f7 100644
--- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
+++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
@@ -504,6 +504,12 @@ namespace Avalonia.Controls
///
/// Gets or sets the index of the selected tab/panel/page (subview).
///
+ ///
+ /// When using the default control theme, this property is designed to be used with the
+ /// enum. The enum defines the
+ /// index values of each of the three standard tabs.
+ /// Use like `SelectedIndex = (int)ColorViewTab.Palette`.
+ ///
public int SelectedIndex
{
get => GetValue(SelectedIndexProperty);
diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
index dbd92d4ac5..c2332751af 100644
--- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
+++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
@@ -29,11 +29,10 @@ namespace Avalonia.Controls.Primitives
/// The color model being used: RGBA or HSVA.
/// The specific color component to sweep.
/// The base HSV color used for components not being changed.
- /// Fix the alpha component value to maximum during calculation.
- /// This will remove any alpha/transparency from the other component backgrounds.
- /// Fix the saturation and value components to maximum
- /// during calculation with the HSVA color model.
- /// This will ensure colors are always discernible regardless of saturation/value.
+ /// Whether the alpha component is visible and rendered in the bitmap.
+ /// This property is ignored when the alpha component itself is being rendered.
+ /// Whether the slider adapts rendering to improve user-perception over exactness.
+ /// This will ensure colors are always discernible.
/// A new bitmap representing a gradient of color component values.
public static async Task> CreateComponentBitmapAsync(
int width,
@@ -42,8 +41,8 @@ namespace Avalonia.Controls.Primitives
ColorModel colorModel,
ColorComponent component,
HsvColor baseHsvColor,
- bool isAlphaMaxForced,
- bool isSaturationValueMaxForced)
+ bool isAlphaVisible,
+ bool isPerceptive)
{
if (width == 0 || height == 0)
{
@@ -67,7 +66,7 @@ namespace Avalonia.Controls.Primitives
bgraPixelDataWidth = width * 4;
// Maximize alpha component value
- if (isAlphaMaxForced &&
+ if (isAlphaVisible == false &&
component != ColorComponent.Alpha)
{
baseHsvColor = new HsvColor(1.0, baseHsvColor.H, baseHsvColor.S, baseHsvColor.V);
@@ -79,22 +78,41 @@ namespace Avalonia.Controls.Primitives
baseRgbColor = baseHsvColor.ToRgb();
}
- // Maximize Saturation and Value components when in HSVA mode
- if (isSaturationValueMaxForced &&
- colorModel == ColorModel.Hsva &&
+ // Apply any perceptive adjustments to the color
+ if (isPerceptive &&
component != ColorComponent.Alpha)
{
- switch (component)
+ if (colorModel == ColorModel.Hsva)
{
- case ColorComponent.Component1:
- baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0);
- break;
- case ColorComponent.Component2:
- baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0);
- break;
- case ColorComponent.Component3:
- baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V);
- break;
+ // Maximize Saturation and Value components
+ switch (component)
+ {
+ case ColorComponent.Component1: // Hue
+ baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0);
+ break;
+ case ColorComponent.Component2: // Saturation
+ baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0);
+ break;
+ case ColorComponent.Component3: // Value
+ baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V);
+ break;
+ }
+ }
+ else
+ {
+ // Minimize component values other than the current one
+ switch (component)
+ {
+ case ColorComponent.Component1: // Red
+ baseRgbColor = new Color(baseRgbColor.A, baseRgbColor.R, 0, 0);
+ break;
+ case ColorComponent.Component2: // Green
+ baseRgbColor = new Color(baseRgbColor.A, 0, baseRgbColor.G, 0);
+ break;
+ case ColorComponent.Component3: // Blue
+ baseRgbColor = new Color(baseRgbColor.A, 0, 0, baseRgbColor.B);
+ break;
+ }
}
}
diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs b/src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs
index 8a425b9581..76f33b3d83 100644
--- a/src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs
+++ b/src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Controls.Primitives
/// Contains and allows modification of Hue, Saturation and Value components.
///
///
- /// The is a specialized struct optimized for permanence and memory:
+ /// The is a specialized struct optimized for performance and memory:
///
/// - This is not a read-only struct like and allows editing the fields
/// - Removes the alpha component unnecessary in core calculations
diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs b/src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs
index 72e3821c2b..ee81a22ecf 100644
--- a/src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs
+++ b/src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Controls.Primitives
/// Contains and allows modification of Red, Green and Blue components.
///
///
- /// The is a specialized struct optimized for permanence and memory:
+ /// The is a specialized struct optimized for performance and memory:
///
/// - This is not a read-only struct like and allows editing the fields
/// - Removes the alpha component unnecessary in core calculations
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
index b3c7cd9f9c..f3100a648a 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
@@ -113,8 +113,8 @@
-
+
@@ -502,6 +502,23 @@
+
+
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
index e05fa5a907..fabc5d0349 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
@@ -8,7 +8,9 @@
-
+
+
+
@@ -21,7 +23,6 @@
Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Width="{StaticResource ColorPreviewerAccentSectionWidth}"
ColumnDefinitions="*,*"
- Margin="0,0,-10,0"
VerticalAlignment="Center">
+ CornerRadius="{TemplateBinding CornerRadius}">
@@ -82,8 +80,7 @@
+ VerticalAlignment="Stretch">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
index acd2c7ff15..8793467b36 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
@@ -360,8 +360,8 @@
-
+
+
+
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
index ff4e1d93a8..d9ba8bb9d2 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
@@ -112,8 +112,8 @@
-
+
@@ -501,6 +501,23 @@
+
+
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
index a39dd91f52..9e123b2a1f 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
@@ -8,7 +8,9 @@
-
+
+
+
@@ -21,7 +23,6 @@
Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Width="{StaticResource ColorPreviewerAccentSectionWidth}"
ColumnDefinitions="*,*"
- Margin="0,0,-10,0"
VerticalAlignment="Center">
+ CornerRadius="{TemplateBinding CornerRadius}">
@@ -82,8 +80,7 @@
+ VerticalAlignment="Stretch">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
index a26d3179b5..d4f02933f2 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
@@ -322,8 +322,8 @@
-
+
+
+
+
diff --git a/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs b/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
index 36929d5e95..1ed3ea50b9 100644
--- a/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
+++ b/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[]
+ {
+ 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);
+ }
+ }
}
}