@@ -649,12 +649,63 @@ def _set_image_for_button(self, button):
649
649
path_large = path_regular .with_name (
650
650
path_regular .name .replace ('.png' , '_large.png' ))
651
651
size = button .winfo_pixels ('18p' )
652
+
653
+ # Nested functions because ToolbarTk calls _Button.
654
+ def _get_color (color_name ):
655
+ # `winfo_rgb` returns an (r, g, b) tuple in the range 0-65535
656
+ return button .winfo_rgb (button .cget (color_name ))
657
+
658
+ def _is_dark (color ):
659
+ if isinstance (color , str ):
660
+ color = _get_color (color )
661
+ return max (color ) < 65535 / 2
662
+
663
+ def _recolor_icon (image , color ):
664
+ image_data = np .asarray (image ).copy ()
665
+ black_mask = (image_data [..., :3 ] == 0 ).all (axis = - 1 )
666
+ image_data [black_mask , :3 ] = color
667
+ return Image .fromarray (image_data , mode = "RGBA" )
668
+
652
669
# Use the high-resolution (48x48 px) icon if it exists and is needed
653
670
with Image .open (path_large if (size > 24 and path_large .exists ())
654
671
else path_regular ) as im :
655
672
image = ImageTk .PhotoImage (im .resize ((size , size )), master = self )
656
- button .configure (image = image , height = '18p' , width = '18p' )
657
- button ._ntimage = image # Prevent garbage collection.
673
+ button ._ntimage = image
674
+
675
+ # create a version of the icon with the button's text color
676
+ foreground = (255 / 65535 ) * np .array (
677
+ button .winfo_rgb (button .cget ("foreground" )))
678
+ im_alt = _recolor_icon (im , foreground )
679
+ image_alt = ImageTk .PhotoImage (
680
+ im_alt .resize ((size , size )), master = self )
681
+ button ._ntimage_alt = image_alt
682
+
683
+ if _is_dark ("background" ):
684
+ button .configure (image = image_alt )
685
+ else :
686
+ button .configure (image = image )
687
+ # Checkbuttons may switch the background to `selectcolor` in the
688
+ # checked state, so check separately which image it needs to use in
689
+ # that state to still ensure enough contrast with the background.
690
+ if (
691
+ isinstance (button , tk .Checkbutton )
692
+ and button .cget ("selectcolor" ) != ""
693
+ ):
694
+ if self ._windowingsystem != "x11" :
695
+ selectcolor = "selectcolor"
696
+ else :
697
+ # On X11, selectcolor isn't used directly for indicator-less
698
+ # buttons. See `::tk::CheckEnter` in the Tk button.tcl source
699
+ # code for details.
700
+ r1 , g1 , b1 = _get_color ("selectcolor" )
701
+ r2 , g2 , b2 = _get_color ("activebackground" )
702
+ selectcolor = ((r1 + r2 )/ 2 , (g1 + g2 )/ 2 , (b1 + b2 )/ 2 )
703
+ if _is_dark (selectcolor ):
704
+ button .configure (selectimage = image_alt )
705
+ else :
706
+ button .configure (selectimage = image )
707
+
708
+ button .configure (height = '18p' , width = '18p' )
658
709
659
710
def _Button (self , text , image_file , toggle , command ):
660
711
if not toggle :
0 commit comments