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 }