@@ -21,32 +21,43 @@ public partial class ContrastAnalyzer
2121 typeof ( ContrastAnalyzer ) ,
2222 new PropertyMetadata ( Colors . Transparent , OnOpponentChanged ) ) ;
2323
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+
2437 /// <summary>
2538 /// Get the opponent color to compare against.
2639 /// </summary>
2740 /// <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 ) ;
3242
3343 /// <summary>
3444 /// Set the opponent color to compare against.
3545 /// </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 ) ;
4057
4158 private static void OnOpponentChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e )
4259 {
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 ) ;
5061
5162 // Could not find a brush to modify.
5263 if ( brush is null )
@@ -55,32 +66,66 @@ private static void OnOpponentChanged(DependencyObject d, DependencyPropertyChan
5566 Color @base = brush . Color ;
5667 Color opponent = ( Color ) e . NewValue ;
5768
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 ;
6072
6173 // In the meantime, adjust by percieved luminance regardless
6274 var luminance = CalculatePerceivedLuminance ( opponent ) ;
6375 var contrastingColor = luminance < 0.5f ? Colors . White : Colors . Black ;
6476
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+ {
6696 switch ( d )
6797 {
6898 case SolidColorBrush b :
69- b . Color = contrastingColor ;
99+ b . Color = color ;
70100 break ;
71101 case TextBlock t :
72- t . Foreground = new SolidColorBrush ( contrastingColor ) ;
102+ t . Foreground = new SolidColorBrush ( color ) ;
73103 break ;
74104 case Control c :
75- c . Foreground = new SolidColorBrush ( contrastingColor ) ;
105+ c . Foreground = new SolidColorBrush ( color ) ;
76106 break ;
77107 }
78108 }
79109
80- private static float CalculatePerceivedLuminance ( Color color )
110+ private static double CalculateWCAGContrastRatio ( Color color1 , Color color2 )
81111 {
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 ) =>
82128 // Using the formula for perceived luminance
83129 // 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 ;
86131}
0 commit comments