@@ -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,68 @@ 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
+ _selfUpdate = true ;
79
+ AssignColor ( d , contrastingColor ) ;
80
+ _selfUpdate = false ;
81
+ }
82
+
83
+ private static SolidColorBrush ? FindBrush ( DependencyObject d , out DependencyProperty ? dp )
84
+ {
85
+ ( SolidColorBrush ? brush , dp ) = d switch
86
+ {
87
+ SolidColorBrush b => ( b , SolidColorBrush . ColorProperty ) ,
88
+ TextBlock t => ( t . Foreground as SolidColorBrush , TextBlock . ForegroundProperty ) ,
89
+ Control c => ( c . Foreground as SolidColorBrush , Control . ForegroundProperty ) ,
90
+ _ => ( null , null ) ,
91
+ } ;
92
+
93
+ return brush ;
94
+ }
95
+
96
+ private static void AssignColor ( DependencyObject d , Color color )
97
+ {
66
98
switch ( d )
67
99
{
68
100
case SolidColorBrush b :
69
- b . Color = contrastingColor ;
101
+ b . Color = color ;
70
102
break ;
71
103
case TextBlock t :
72
- t . Foreground = new SolidColorBrush ( contrastingColor ) ;
104
+ t . Foreground = new SolidColorBrush ( color ) ;
73
105
break ;
74
106
case Control c :
75
- c . Foreground = new SolidColorBrush ( contrastingColor ) ;
107
+ c . Foreground = new SolidColorBrush ( color ) ;
76
108
break ;
77
109
}
78
110
}
79
111
80
- private static float CalculatePerceivedLuminance ( Color color )
112
+ private static double CalculateWCAGContrastRatio ( Color color1 , Color color2 )
81
113
{
114
+ // Using the formula for contrast ratio
115
+ // Source WCAG guidelines: https://www.w3.org/TR/WCAG20/#contrast-ratiodef
116
+
117
+ // Calculate perceived luminance for both colors
118
+ double luminance1 = CalculatePerceivedLuminance ( color1 ) ;
119
+ double luminance2 = CalculatePerceivedLuminance ( color2 ) ;
120
+
121
+ // Determine lighter and darker luminance
122
+ double lighter = Math . Max ( luminance1 , luminance2 ) ;
123
+ double darker = Math . Min ( luminance1 , luminance2 ) ;
124
+
125
+ // Calculate contrast ratio
126
+ return ( lighter + 0.05f ) / ( darker + 0.05f ) ;
127
+ }
128
+
129
+ private static double CalculatePerceivedLuminance ( Color color ) =>
82
130
// Using the formula for perceived luminance
83
131
// 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
- }
132
+ ( 0.299f * color . R + 0.587f * color . G + 0.114f * color . B ) / 255f ;
86
133
}
0 commit comments