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