@@ -637,12 +637,63 @@ def _set_image_for_button(self, button):
637637 path_large = path_regular .with_name (
638638 path_regular .name .replace ('.png' , '_large.png' ))
639639 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+
640657 # Use the high-resolution (48x48 px) icon if it exists and is needed
641658 with Image .open (path_large if (size > 24 and path_large .exists ())
642659 else path_regular ) as im :
643660 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' )
646697
647698 def _Button (self , text , image_file , toggle , command ):
648699 if not toggle :
0 commit comments