Skip to content

Commit bd8673c

Browse files
committed
Use light icons on dark themes for wx and gtk, too.
These can be tried e.g. by setting `GTK_THEME` to `Adwaita:dark`. I couldn't find how to get the foreground color in GTK3 so the check for dark themes is just looking at the luminance of the background, and the icon color is set to white. (Better approaches welcome...) I made the `_icon` API "consistent" across qt, wx, and gtk.
1 parent 5ab1352 commit bd8673c

File tree

4 files changed

+69
-14
lines changed

4 files changed

+69
-14
lines changed

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import colorsys
12
import functools
23
import logging
34
import os
45
from pathlib import Path
56
import sys
67

8+
import numpy as np
9+
import PIL
10+
711
import matplotlib as mpl
812
from matplotlib import backend_tools, cbook
913
from matplotlib._pylab_helpers import Gcf
@@ -13,6 +17,10 @@
1317
from matplotlib.figure import Figure
1418
from matplotlib.widgets import SubplotTool
1519

20+
try:
21+
from .backend_cairo import cairo
22+
except ImportError as err:
23+
raise ImportError('The GTK3 backends require cairo') from err
1624
try:
1725
import gi
1826
except ImportError as err:
@@ -464,14 +472,11 @@ def __init__(self, canvas, window):
464472
if text is None:
465473
self.insert(Gtk.SeparatorToolItem(), -1)
466474
continue
467-
image = Gtk.Image()
468-
image.set_from_file(
469-
str(cbook._get_data_path('images', image_file + '.png')))
470475
self._gtk_ids[text] = tbutton = (
471476
Gtk.ToggleToolButton() if callback in ['zoom', 'pan'] else
472477
Gtk.ToolButton())
473478
tbutton.set_label(text)
474-
tbutton.set_icon_widget(image)
479+
tbutton.set_icon_widget(self._icon(f"{image_file}.png"))
475480
self.insert(tbutton, -1)
476481
# Save the handler id, so that we can block it as needed.
477482
tbutton._signal_handler = tbutton.connect(
@@ -492,6 +497,27 @@ def __init__(self, canvas, window):
492497

493498
NavigationToolbar2.__init__(self, canvas)
494499

500+
def _icon(self, name):
501+
"""
502+
Construct a `.GtkImage` suitable for use as icon from an image file
503+
*name*, including the extension and relative to Matplotlib's "images"
504+
data directory.
505+
"""
506+
path = cbook._get_data_path("images", name)
507+
bg = self.get_style_context().get_background_color(
508+
Gtk.StateFlags.NORMAL)
509+
lum, _, _ = colorsys.rgb_to_yiq(bg.red, bg.green, bg.blue)
510+
if lum < .5:
511+
image = np.array(PIL.Image.open(path))
512+
image[(image[..., :3] == 0).all(axis=-1), :3] = (255, 255, 255)
513+
image = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(image)
514+
return Gtk.Image.new_from_surface(
515+
cairo.ImageSurface.create_for_data(
516+
image.ravel().data, cairo.FORMAT_ARGB32,
517+
image.shape[1], image.shape[0]))
518+
else:
519+
return Gtk.Image.new_from_file(str(path))
520+
495521
@cbook.deprecated("3.3")
496522
@property
497523
def ctx(self):
@@ -644,9 +670,8 @@ def add_toolitem(self, name, group, position, image_file, description,
644670
tbutton.set_label(name)
645671

646672
if image_file is not None:
647-
image = Gtk.Image()
648-
image.set_from_file(image_file)
649-
tbutton.set_icon_widget(image)
673+
tbutton.set_icon_widget(
674+
NavigationToolbar2GTK3._icon(self, image_file))
650675

651676
if position is None:
652677
position = -1

lib/matplotlib/backends/backend_gtk3agg.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import numpy as np
22

33
from .. import cbook
4-
try:
5-
from . import backend_cairo
6-
except ImportError as e:
7-
raise ImportError('backend Gtk3Agg requires cairo') from e
8-
from . import backend_agg, backend_gtk3
4+
# Importing backend_gtk3 before backend_cairo provides a proper error message
5+
# if cairo is missing.
6+
from . import backend_agg, backend_gtk3, backend_cairo
97
from .backend_cairo import cairo
108
from .backend_gtk3 import Gtk, _BackendGTK3
119
from matplotlib import transforms

lib/matplotlib/backends/backend_qt5.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,10 @@ def basedir(self):
688688
return str(cbook._get_data_path('images'))
689689

690690
def _icon(self, name):
691+
"""
692+
Construct a `.QIcon` from an image file *name*, including the extension
693+
and relative to Matplotlib's "images" data directory.
694+
"""
691695
if QtCore.qVersion() >= '5.':
692696
name = name.replace('.png', '_large.png')
693697
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))

lib/matplotlib/backends/backend_wx.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import sys
1414
import weakref
1515

16+
import numpy as np
17+
import PIL
18+
1619
import matplotlib as mpl
1720
from matplotlib.backend_bases import (
1821
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
@@ -1114,7 +1117,7 @@ def __init__(self, canvas):
11141117
self.wx_ids[text] = (
11151118
self.AddTool(
11161119
-1,
1117-
bitmap=_load_bitmap(f"{image_file}.png"),
1120+
bitmap=self._icon(f"{image_file}.png"),
11181121
bmpDisabled=wx.NullBitmap,
11191122
label=text, shortHelp=text, longHelp=tooltip_text,
11201123
kind=(wx.ITEM_CHECK if text in ["Pan", "Zoom"]
@@ -1146,6 +1149,31 @@ def __init__(self, canvas):
11461149
zoomStartX = cbook._deprecate_privatize_attribute("3.3")
11471150
zoomStartY = cbook._deprecate_privatize_attribute("3.3")
11481151

1152+
@staticmethod
1153+
def _icon(name):
1154+
"""
1155+
Construct a `wx.Bitmap` suitable for use as icon from an image file
1156+
*name*, including the extension and relative to Matplotlib's "images"
1157+
data directory.
1158+
"""
1159+
image = np.array(PIL.Image.open(cbook._get_data_path("images", name)))
1160+
try:
1161+
dark = wx.SystemSettings.GetAppearance().IsDark()
1162+
except AttributeError: # wxpython < 4.1
1163+
# copied from wx's IsUsingDarkBackground / GetLuminance.
1164+
bg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
1165+
fg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
1166+
# See wx.Colour.GetLuminance.
1167+
bg_lum = (.299 * bg.red + .587 * bg.green + .114 * bg.blue) / 255
1168+
fg_lum = (.299 * fg.red + .587 * fg.green + .114 * fg.blue) / 255
1169+
dark = fg_lum - bg_lum > .2
1170+
if dark:
1171+
fg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
1172+
image[(image[..., :3] == 0).all(axis=-1), :3] = (
1173+
fg.Red(), fg.Green(), fg.Blue())
1174+
return wx.Bitmap.FromBufferRGBA(
1175+
image.shape[1], image.shape[0], image.tobytes())
1176+
11491177
def get_canvas(self, frame, fig):
11501178
return type(self.canvas)(frame, -1, fig)
11511179

@@ -1375,7 +1403,7 @@ def add_toolitem(self, name, group, position, image_file, description,
13751403
start = self._get_tool_pos(sep) + 1
13761404
idx = start + position
13771405
if image_file:
1378-
bmp = _load_bitmap(image_file)
1406+
bmp = NavigationToolbar2Wx._icon(image_file)
13791407
kind = wx.ITEM_NORMAL if not toggle else wx.ITEM_CHECK
13801408
tool = self.InsertTool(idx, -1, name, bmp, wx.NullBitmap, kind,
13811409
description or "")

0 commit comments

Comments
 (0)