Skip to content

Commit 1b50814

Browse files
committed
Add support for High DPI displays to wxAgg backend
1 parent 5f785e3 commit 1b50814

File tree

2 files changed

+34
-32
lines changed

2 files changed

+34
-32
lines changed

lib/matplotlib/backends/backend_wx.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
import sys
1515
import weakref
1616

17-
import numpy as np
18-
import PIL.Image
19-
2017
import matplotlib as mpl
2118
from matplotlib.backend_bases import (
2219
_Backend, FigureCanvasBase, FigureManagerBase,
@@ -30,6 +27,7 @@
3027
from matplotlib.transforms import Affine2D
3128

3229
import wx
30+
import wx.svg
3331

3432
_log = logging.getLogger(__name__)
3533

@@ -473,10 +471,8 @@ def __init__(self, parent, id, figure=None):
473471
FigureCanvasBase.__init__(self, figure)
474472
w, h = map(math.ceil, self.figure.bbox.size)
475473
# Set preferred window size hint - helps the sizer, if one is connected
476-
wx.Panel.__init__(self, parent, id, size=wx.Size(w, h))
477-
# Create the drawing bitmap
478-
self.bitmap = wx.Bitmap(w, h)
479-
_log.debug("%s - __init__() - bitmap w:%d h:%d", type(self), w, h)
474+
wx.Panel.__init__(self, parent, id, size=parent.FromDIP(wx.Size(w, h)))
475+
self.bitmap = None
480476
self._isDrawn = False
481477
self._rubberband_rect = None
482478
self._rubberband_pen_black = wx.Pen('BLACK', 1, wx.PENSTYLE_SHORT_DASH)
@@ -524,6 +520,12 @@ def Copy_to_Clipboard(self, event=None):
524520
wx.TheClipboard.Flush()
525521
wx.TheClipboard.Close()
526522

523+
def _update_device_pixel_ratio(self, *args, **kwargs):
524+
# We need to be careful in cases with mixed resolution displays if
525+
# device_pixel_ratio changes.
526+
if self._set_device_pixel_ratio(self.GetDPIScaleFactor()):
527+
self.draw()
528+
527529
def draw_idle(self):
528530
# docstring inherited
529531
_log.debug("%s - draw_idle()", type(self))
@@ -631,7 +633,7 @@ def _on_size(self, event):
631633
In this application we attempt to resize to fit the window, so it
632634
is better to take the performance hit and redraw the whole window.
633635
"""
634-
636+
self._update_device_pixel_ratio()
635637
_log.debug("%s - _on_size()", type(self))
636638
sz = self.GetParent().GetSizer()
637639
if sz:
@@ -655,9 +657,10 @@ def _on_size(self, event):
655657
return # Empty figure
656658

657659
# Create a new, correctly sized bitmap
658-
self.bitmap = wx.Bitmap(self._width, self._height)
659-
660660
dpival = self.figure.dpi
661+
if not wx.Platform == '__WXMSW__':
662+
scale = self.GetDPIScaleFactor()
663+
dpival /= scale
661664
winch = self._width / dpival
662665
hinch = self._height / dpival
663666
self.figure.set_size_inches(winch, hinch, forward=False)
@@ -712,7 +715,11 @@ def _mpl_coords(self, pos=None):
712715
else:
713716
x, y = pos.X, pos.Y
714717
# flip y so y=0 is bottom of canvas
715-
return x, self.figure.bbox.height - y
718+
if not wx.Platform == '__WXMSW__':
719+
scale = self.GetDPIScaleFactor()
720+
return x*scale, self.figure.bbox.height - y*scale
721+
else:
722+
return x, self.figure.bbox.height - y
716723

717724
def _on_key_down(self, event):
718725
"""Capture key press."""
@@ -898,8 +905,8 @@ def __init__(self, num, fig, *, canvas_class):
898905
# On Windows, canvas sizing must occur after toolbar addition;
899906
# otherwise the toolbar further resizes the canvas.
900907
w, h = map(math.ceil, fig.bbox.size)
901-
self.canvas.SetInitialSize(wx.Size(w, h))
902-
self.canvas.SetMinSize((2, 2))
908+
self.canvas.SetInitialSize(self.FromDIP(wx.Size(w, h)))
909+
self.canvas.SetMinSize(self.FromDIP(wx.Size(2, 2)))
903910
self.canvas.SetFocus()
904911

905912
self.Fit()
@@ -1017,9 +1024,6 @@ def _set_frame_icon(frame):
10171024
class NavigationToolbar2Wx(NavigationToolbar2, wx.ToolBar):
10181025
def __init__(self, canvas, coordinates=True, *, style=wx.TB_BOTTOM):
10191026
wx.ToolBar.__init__(self, canvas.GetParent(), -1, style=style)
1020-
1021-
if 'wxMac' in wx.PlatformInfo:
1022-
self.SetToolBitmapSize((24, 24))
10231027
self.wx_ids = {}
10241028
for text, tooltip_text, image_file, callback in self.toolitems:
10251029
if text is None:
@@ -1028,7 +1032,7 @@ def __init__(self, canvas, coordinates=True, *, style=wx.TB_BOTTOM):
10281032
self.wx_ids[text] = (
10291033
self.AddTool(
10301034
-1,
1031-
bitmap=self._icon(f"{image_file}.png"),
1035+
bitmap=self._icon(f"{image_file}.svg"),
10321036
bmpDisabled=wx.NullBitmap,
10331037
label=text, shortHelp=tooltip_text,
10341038
kind=(wx.ITEM_CHECK if text in ["Pan", "Zoom"]
@@ -1054,9 +1058,7 @@ def _icon(name):
10541058
*name*, including the extension and relative to Matplotlib's "images"
10551059
data directory.
10561060
"""
1057-
pilimg = PIL.Image.open(cbook._get_data_path("images", name))
1058-
# ensure RGBA as wx BitMap expects RGBA format
1059-
image = np.array(pilimg.convert("RGBA"))
1061+
svg = cbook._get_data_path("images", name).read_bytes()
10601062
try:
10611063
dark = wx.SystemSettings.GetAppearance().IsDark()
10621064
except AttributeError: # wxpython < 4.1
@@ -1068,11 +1070,9 @@ def _icon(name):
10681070
fg_lum = (.299 * fg.red + .587 * fg.green + .114 * fg.blue) / 255
10691071
dark = fg_lum - bg_lum > .2
10701072
if dark:
1071-
fg = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
1072-
black_mask = (image[..., :3] == 0).all(axis=-1)
1073-
image[black_mask, :3] = (fg.Red(), fg.Green(), fg.Blue())
1074-
return wx.Bitmap.FromBufferRGBA(
1075-
image.shape[1], image.shape[0], image.tobytes())
1073+
svg = svg.replace(b'fill:black;', b'fill:white;')
1074+
toolbarIconSize = wx.ArtProvider().GetDIPSizeHint(wx.ART_TOOLBAR)
1075+
return wx.BitmapBundle.FromSVG(svg, toolbarIconSize)
10761076

10771077
def _update_buttons_checked(self):
10781078
if "Pan" in self.wx_ids:

lib/matplotlib/backends/backend_wxagg.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ def draw(self, drawDC=None):
1212
Render the figure using agg.
1313
"""
1414
FigureCanvasAgg.draw(self)
15-
self.bitmap = _rgba_to_wx_bitmap(self.get_renderer().buffer_rgba())
15+
self.bitmap = self._create_bitmap()
1616
self._isDrawn = True
1717
self.gui_repaint(drawDC=drawDC)
1818

1919
def blit(self, bbox=None):
2020
# docstring inherited
21-
bitmap = _rgba_to_wx_bitmap(self.get_renderer().buffer_rgba())
21+
bitmap = self._create_bitmap()
2222
if bbox is None:
2323
self.bitmap = bitmap
2424
else:
@@ -31,11 +31,13 @@ def blit(self, bbox=None):
3131
srcDC.SelectObject(wx.NullBitmap)
3232
self.gui_repaint()
3333

34-
35-
def _rgba_to_wx_bitmap(rgba):
36-
"""Convert an RGBA buffer to a wx.Bitmap."""
37-
h, w, _ = rgba.shape
38-
return wx.Bitmap.FromBufferRGBA(w, h, rgba)
34+
def _create_bitmap(self):
35+
"""Create a wx.Bitmap from the renderer RGBA buffer"""
36+
rgba = self.get_renderer().buffer_rgba()
37+
h, w, _ = rgba.shape
38+
bitmap = wx.Bitmap.FromBufferRGBA(w, h, rgba)
39+
bitmap.SetScaleFactor(self.GetDPIScaleFactor())
40+
return bitmap
3941

4042

4143
@_BackendWx.export

0 commit comments

Comments
 (0)