@@ -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