Skip to content

Commit a96b4a1

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

File tree

2 files changed

+45
-32
lines changed

2 files changed

+45
-32
lines changed

lib/matplotlib/backends/backend_wx.py

Lines changed: 36 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)
@@ -512,6 +508,12 @@ def __init__(self, parent, id, figure=None):
512508
self.SetBackgroundStyle(wx.BG_STYLE_PAINT) # Reduce flicker.
513509
self.SetBackgroundColour(wx.WHITE)
514510

511+
if wx.Platform == '__WXMAC__':
512+
# Initial scaling. Other platforms handle this automatically
513+
dpiScale = self.GetDPIScaleFactor()
514+
self.SetInitialSize(self.GetSize()*(1/dpiScale))
515+
self._set_device_pixel_ratio(dpiScale)
516+
515517
def Copy_to_Clipboard(self, event=None):
516518
"""Copy bitmap of canvas to system clipboard."""
517519
bmp_obj = wx.BitmapDataObject()
@@ -524,6 +526,12 @@ def Copy_to_Clipboard(self, event=None):
524526
wx.TheClipboard.Flush()
525527
wx.TheClipboard.Close()
526528

529+
def _update_device_pixel_ratio(self, *args, **kwargs):
530+
# We need to be careful in cases with mixed resolution displays if
531+
# device_pixel_ratio changes.
532+
if self._set_device_pixel_ratio(self.GetDPIScaleFactor()):
533+
self.draw()
534+
527535
def draw_idle(self):
528536
# docstring inherited
529537
_log.debug("%s - draw_idle()", type(self))
@@ -631,7 +639,7 @@ def _on_size(self, event):
631639
In this application we attempt to resize to fit the window, so it
632640
is better to take the performance hit and redraw the whole window.
633641
"""
634-
642+
self._update_device_pixel_ratio()
635643
_log.debug("%s - _on_size()", type(self))
636644
sz = self.GetParent().GetSizer()
637645
if sz:
@@ -655,9 +663,10 @@ def _on_size(self, event):
655663
return # Empty figure
656664

657665
# Create a new, correctly sized bitmap
658-
self.bitmap = wx.Bitmap(self._width, self._height)
659-
660666
dpival = self.figure.dpi
667+
if not wx.Platform == '__WXMSW__':
668+
scale = self.GetDPIScaleFactor()
669+
dpival /= scale
661670
winch = self._width / dpival
662671
hinch = self._height / dpival
663672
self.figure.set_size_inches(winch, hinch, forward=False)
@@ -712,7 +721,11 @@ def _mpl_coords(self, pos=None):
712721
else:
713722
x, y = pos.X, pos.Y
714723
# flip y so y=0 is bottom of canvas
715-
return x, self.figure.bbox.height - y
724+
if not wx.Platform == '__WXMSW__':
725+
scale = self.GetDPIScaleFactor()
726+
return x*scale, self.figure.bbox.height - y*scale
727+
else:
728+
return x, self.figure.bbox.height - y
716729

717730
def _on_key_down(self, event):
718731
"""Capture key press."""
@@ -898,8 +911,8 @@ def __init__(self, num, fig, *, canvas_class):
898911
# On Windows, canvas sizing must occur after toolbar addition;
899912
# otherwise the toolbar further resizes the canvas.
900913
w, h = map(math.ceil, fig.bbox.size)
901-
self.canvas.SetInitialSize(wx.Size(w, h))
902-
self.canvas.SetMinSize((2, 2))
914+
self.canvas.SetInitialSize(self.FromDIP(wx.Size(w, h)))
915+
self.canvas.SetMinSize(self.FromDIP(wx.Size(2, 2)))
903916
self.canvas.SetFocus()
904917

905918
self.Fit()
@@ -1017,9 +1030,9 @@ def _set_frame_icon(frame):
10171030
class NavigationToolbar2Wx(NavigationToolbar2, wx.ToolBar):
10181031
def __init__(self, canvas, coordinates=True, *, style=wx.TB_BOTTOM):
10191032
wx.ToolBar.__init__(self, canvas.GetParent(), -1, style=style)
1033+
if wx.Platform == '__WXMAC__':
1034+
self.SetToolBitmapSize(self.GetToolBitmapSize()*self.GetDPIScaleFactor())
10201035

1021-
if 'wxMac' in wx.PlatformInfo:
1022-
self.SetToolBitmapSize((24, 24))
10231036
self.wx_ids = {}
10241037
for text, tooltip_text, image_file, callback in self.toolitems:
10251038
if text is None:
@@ -1028,7 +1041,7 @@ def __init__(self, canvas, coordinates=True, *, style=wx.TB_BOTTOM):
10281041
self.wx_ids[text] = (
10291042
self.AddTool(
10301043
-1,
1031-
bitmap=self._icon(f"{image_file}.png"),
1044+
bitmap=self._icon(f"{image_file}.svg"),
10321045
bmpDisabled=wx.NullBitmap,
10331046
label=text, shortHelp=tooltip_text,
10341047
kind=(wx.ITEM_CHECK if text in ["Pan", "Zoom"]
@@ -1054,9 +1067,7 @@ def _icon(name):
10541067
*name*, including the extension and relative to Matplotlib's "images"
10551068
data directory.
10561069
"""
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"))
1070+
svg = cbook._get_data_path("images", name).read_bytes()
10601071
try:
10611072
dark = wx.SystemSettings.GetAppearance().IsDark()
10621073
except AttributeError: # wxpython < 4.1
@@ -1068,11 +1079,9 @@ def _icon(name):
10681079
fg_lum = (.299 * fg.red + .587 * fg.green + .114 * fg.blue) / 255
10691080
dark = fg_lum - bg_lum > .2
10701081
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())
1082+
svg = svg.replace(b'fill:black;', b'fill:white;')
1083+
toolbarIconSize = wx.ArtProvider().GetDIPSizeHint(wx.ART_TOOLBAR)
1084+
return wx.BitmapBundle.FromSVG(svg, toolbarIconSize)
10761085

10771086
def _update_buttons_checked(self):
10781087
if "Pan" in self.wx_ids:
@@ -1123,7 +1132,9 @@ def save_figure(self, *args):
11231132

11241133
def draw_rubberband(self, event, x0, y0, x1, y1):
11251134
height = self.canvas.figure.bbox.height
1126-
self.canvas._rubberband_rect = (x0, height - y0, x1, height - y1)
1135+
sf = self.GetDPIScaleFactor()
1136+
self.canvas._rubberband_rect = (x0/sf, (height - y0)/sf,
1137+
x1/sf, (height - y1)/sf)
11271138
self.canvas.Refresh()
11281139

11291140
def remove_rubberband(self):

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)