/ src / modules / cmdpal / Microsoft.CmdPal.UI / Converters / ContrastBrushConverter.cs
ContrastBrushConverter.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using Microsoft.UI;
  6  using Microsoft.UI.Xaml;
  7  using Microsoft.UI.Xaml.Data;
  8  using Microsoft.UI.Xaml.Media;
  9  using Windows.UI;
 10  
 11  namespace Microsoft.CmdPal.UI.Converters;
 12  
 13  /// <summary>
 14  /// Gets a color, either black or white, depending on the brightness of the supplied color.
 15  /// </summary>
 16  public sealed partial class ContrastBrushConverter : IValueConverter
 17  {
 18      /// <summary>
 19      /// Gets or sets the alpha channel threshold below which a default color is used instead of black/white.
 20      /// </summary>
 21      public byte AlphaThreshold { get; set; } = 128;
 22  
 23      /// <inheritdoc />
 24      public object Convert(
 25          object value,
 26          Type targetType,
 27          object parameter,
 28          string language)
 29      {
 30          Color comparisonColor;
 31          Color? defaultColor = null;
 32  
 33          // Get the changing color to compare against
 34          if (value is Color valueColor)
 35          {
 36              comparisonColor = valueColor;
 37          }
 38          else if (value is SolidColorBrush valueBrush)
 39          {
 40              comparisonColor = valueBrush.Color;
 41          }
 42          else
 43          {
 44              // Invalid color value provided
 45              return DependencyProperty.UnsetValue;
 46          }
 47  
 48          // Get the default color when transparency is high
 49          if (parameter is Color parameterColor)
 50          {
 51              defaultColor = parameterColor;
 52          }
 53          else if (parameter is SolidColorBrush parameterBrush)
 54          {
 55              defaultColor = parameterBrush.Color;
 56          }
 57  
 58          if (comparisonColor.A < AlphaThreshold &&
 59              defaultColor.HasValue)
 60          {
 61              // If the transparency is less than 50 %, just use the default brush
 62              // This can commonly be something like the TextControlForeground brush
 63              return new SolidColorBrush(defaultColor.Value);
 64          }
 65          else
 66          {
 67              // Chose a white/black brush based on contrast to the base color
 68              return UseLightContrastColor(comparisonColor)
 69                  ? new SolidColorBrush(Colors.White)
 70                  : new SolidColorBrush(Colors.Black);
 71          }
 72      }
 73  
 74      /// <inheritdoc />
 75      public object ConvertBack(
 76          object value,
 77          Type targetType,
 78          object parameter,
 79          string language)
 80      {
 81          return DependencyProperty.UnsetValue;
 82      }
 83  
 84      /// <summary>
 85      /// Determines whether a light or dark contrast color should be used with the given displayed color.
 86      /// </summary>
 87      /// <remarks>
 88      /// This code is using the WinUI algorithm.
 89      /// </remarks>
 90      private bool UseLightContrastColor(Color displayedColor)
 91      {
 92          // The selection ellipse should be light if and only if the chosen color
 93          // contrasts more with black than it does with white.
 94          // To find how much something contrasts with white, we use the equation
 95          // for relative luminance, which is given by
 96          //
 97          // L = 0.2126 * Rg + 0.7152 * Gg + 0.0722 * Bg
 98          //
 99          // where Xg = { X/3294 if X <= 10, (R/269 + 0.0513)^2.4 otherwise }
100          //
101          // If L is closer to 1, then the color is closer to white; if it is closer to 0,
102          // then the color is closer to black.  This is based on the fact that the human
103          // eye perceives green to be much brighter than red, which in turn is perceived to be
104          // brighter than blue.
105          //
106          // If the third dimension is value, then we won't be updating the spectrum's displayed colors,
107          // so in that case we should use a value of 1 when considering the backdrop
108          // for the selection ellipse.
109          var rg = displayedColor.R <= 10
110              ? displayedColor.R / 3294.0
111              : Math.Pow((displayedColor.R / 269.0) + 0.0513, 2.4);
112          var gg = displayedColor.G <= 10
113              ? displayedColor.G / 3294.0
114              : Math.Pow((displayedColor.G / 269.0) + 0.0513, 2.4);
115          var bg = displayedColor.B <= 10
116              ? displayedColor.B / 3294.0
117              : Math.Pow((displayedColor.B / 269.0) + 0.0513, 2.4);
118  
119          return (0.2126 * rg) + (0.7152 * gg) + (0.0722 * bg) <= 0.5;
120      }
121  }