@@ -21,32 +21,43 @@ public partial class ContrastAnalyzer
21
21
typeof ( ContrastAnalyzer ) ,
22
22
new PropertyMetadata ( Colors . Transparent , OnOpponentChanged ) ) ;
23
23
24
+ /// <summary>
25
+ /// An attached property that defines the minimum acceptable contrast ratio against the opponent color.
26
+ /// </summary>
27
+ /// <remarks>
28
+ /// Range: 1 to 21 (inclusive). Default is 21 (maximum contrast).
29
+ /// </remarks>
30
+ public static readonly DependencyProperty MinRatioProperty =
31
+ DependencyProperty . RegisterAttached (
32
+ "MinRatio" ,
33
+ typeof ( double ) ,
34
+ typeof ( ContrastAnalyzer ) ,
35
+ new PropertyMetadata ( 21d ) ) ;
36
+
24
37
/// <summary>
25
38
/// Get the opponent color to compare against.
26
39
/// </summary>
27
40
/// <returns>The opponent color.</returns>
28
- public static Color GetOpponent ( DependencyObject obj )
29
- {
30
- return ( Color ) obj . GetValue ( OpponentProperty ) ;
31
- }
41
+ public static Color GetOpponent ( DependencyObject obj ) => ( Color ) obj . GetValue ( OpponentProperty ) ;
32
42
33
43
/// <summary>
34
44
/// Set the opponent color to compare against.
35
45
/// </summary>
36
- public static void SetOpponent ( DependencyObject obj , Color value )
37
- {
38
- obj . SetValue ( OpponentProperty , value ) ;
39
- }
46
+ public static void SetOpponent ( DependencyObject obj , Color value ) => obj . SetValue ( OpponentProperty , value ) ;
47
+
48
+ /// <summary>
49
+ /// Get the minimum acceptable contrast ratio against the opponent color.
50
+ /// </summary>
51
+ public static double GetMinRatio ( DependencyObject obj ) => ( double ) obj . GetValue ( MinRatioProperty ) ;
52
+
53
+ /// <summary>
54
+ /// Set the minimum acceptable contrast ratio against the opponent color.
55
+ /// </summary>
56
+ public static void SetMinRatio ( DependencyObject obj , double value ) => obj . SetValue ( MinRatioProperty , value ) ;
40
57
41
58
private static void OnOpponentChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e )
42
59
{
43
- SolidColorBrush ? brush = d switch
44
- {
45
- SolidColorBrush b => b ,
46
- TextBlock t => t . Foreground as SolidColorBrush ,
47
- Control c => c . Foreground as SolidColorBrush ,
48
- _ => null ,
49
- } ;
60
+ var brush = FindBrush ( d , out var dp ) ;
50
61
51
62
// Could not find a brush to modify.
52
63
if ( brush is null )
@@ -55,32 +66,66 @@ private static void OnOpponentChanged(DependencyObject d, DependencyPropertyChan
55
66
Color @base = brush . Color ;
56
67
Color opponent = ( Color ) e . NewValue ;
57
68
58
- // TODO: Cache original color
59
- // TODO: Adjust only if contrast is insufficient
69
+ // If the colors already meet the minimum ratio, no adjustment is needed
70
+ if ( CalculateWCAGContrastRatio ( @base , opponent ) >= GetMinRatio ( d ) )
71
+ return ;
60
72
61
73
// In the meantime, adjust by percieved luminance regardless
62
74
var luminance = CalculatePerceivedLuminance ( opponent ) ;
63
75
var contrastingColor = luminance < 0.5f ? Colors . White : Colors . Black ;
64
76
65
- // Assign back
77
+ // Assign color
78
+ AssignColor ( d , contrastingColor ) ;
79
+ }
80
+
81
+ private static SolidColorBrush ? FindBrush ( DependencyObject d , out DependencyProperty ? dp )
82
+ {
83
+ ( SolidColorBrush ? brush , dp ) = d switch
84
+ {
85
+ SolidColorBrush b => ( b , SolidColorBrush . ColorProperty ) ,
86
+ TextBlock t => ( t . Foreground as SolidColorBrush , TextBlock . ForegroundProperty ) ,
87
+ Control c => ( c . Foreground as SolidColorBrush , Control . ForegroundProperty ) ,
88
+ _ => ( null , null ) ,
89
+ } ;
90
+
91
+ return brush ;
92
+ }
93
+
94
+ private static void AssignColor ( DependencyObject d , Color color )
95
+ {
66
96
switch ( d )
67
97
{
68
98
case SolidColorBrush b :
69
- b . Color = contrastingColor ;
99
+ b . Color = color ;
70
100
break ;
71
101
case TextBlock t :
72
- t . Foreground = new SolidColorBrush ( contrastingColor ) ;
102
+ t . Foreground = new SolidColorBrush ( color ) ;
73
103
break ;
74
104
case Control c :
75
- c . Foreground = new SolidColorBrush ( contrastingColor ) ;
105
+ c . Foreground = new SolidColorBrush ( color ) ;
76
106
break ;
77
107
}
78
108
}
79
109
80
- private static float CalculatePerceivedLuminance ( Color color )
110
+ private static double CalculateWCAGContrastRatio ( Color color1 , Color color2 )
81
111
{
112
+ // Using the formula for contrast ratio
113
+ // Source WCAG guidelines: https://www.w3.org/TR/WCAG20/#contrast-ratiodef
114
+
115
+ // Calculate perceived luminance for both colors
116
+ double luminance1 = CalculatePerceivedLuminance ( color1 ) ;
117
+ double luminance2 = CalculatePerceivedLuminance ( color2 ) ;
118
+
119
+ // Determine lighter and darker luminance
120
+ double lighter = Math . Max ( luminance1 , luminance2 ) ;
121
+ double darker = Math . Min ( luminance1 , luminance2 ) ;
122
+
123
+ // Calculate contrast ratio
124
+ return ( lighter + 0.05f ) / ( darker + 0.05f ) ;
125
+ }
126
+
127
+ private static double CalculatePerceivedLuminance ( Color color ) =>
82
128
// Using the formula for perceived luminance
83
129
// Source WCAG guidelines: https://www.w3.org/TR/AERT/#color-contrast
84
- return ( 0.299f * color . R + 0.587f * color . G + 0.114f * color . B ) / 255f ;
85
- }
130
+ ( 0.299f * color . R + 0.587f * color . G + 0.114f * color . B ) / 255f ;
86
131
}
0 commit comments