Skip to content

Commit de11026

Browse files
authored
Merge pull request #26710 from jmoraleda/wx-hidpi
Add support for High DPI displays to wxAgg backend
2 parents ae73ddb + 6c1991d commit de11026

File tree

2 files changed

+51
-34
lines changed

2 files changed

+51
-34
lines changed

lib/matplotlib/backends/backend_wx.py

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,20 @@
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,
2320
GraphicsContextBase, MouseButton, NavigationToolbar2, RendererBase,
2421
TimerBase, ToolContainerBase, cursors,
2522
CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
2623

27-
from matplotlib import _api, cbook, backend_tools
24+
from matplotlib import _api, cbook, backend_tools, _c_internal_utils
2825
from matplotlib._pylab_helpers import Gcf
2926
from matplotlib.path import Path
3027
from matplotlib.transforms import Affine2D
3128

3229
import wx
30+
import wx.svg
3331

3432
_log = logging.getLogger(__name__)
3533

@@ -45,6 +43,8 @@ def _create_wxapp():
4543
wxapp = wx.App(False)
4644
wxapp.SetExitOnFrameDelete(True)
4745
cbook._setup_new_guiapp()
46+
# Set per-process DPI awareness. This is a NoOp except in MSW
47+
_c_internal_utils.Win32_SetProcessDpiAwareness_max()
4848
return wxapp
4949

5050

@@ -471,12 +471,12 @@ def __init__(self, parent, id, figure=None):
471471
"""
472472

473473
FigureCanvasBase.__init__(self, figure)
474-
w, h = map(math.ceil, self.figure.bbox.size)
474+
size = wx.Size(*map(math.ceil, self.figure.bbox.size))
475+
if wx.Platform != '__WXMSW__':
476+
size = parent.FromDIP(size)
475477
# 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)
478+
wx.Panel.__init__(self, parent, id, size=size)
479+
self.bitmap = None
480480
self._isDrawn = False
481481
self._rubberband_rect = None
482482
self._rubberband_pen_black = wx.Pen('BLACK', 1, wx.PENSTYLE_SHORT_DASH)
@@ -512,6 +512,12 @@ def __init__(self, parent, id, figure=None):
512512
self.SetBackgroundStyle(wx.BG_STYLE_PAINT) # Reduce flicker.
513513
self.SetBackgroundColour(wx.WHITE)
514514

515+
if wx.Platform == '__WXMAC__':
516+
# Initial scaling. Other platforms handle this automatically
517+
dpiScale = self.GetDPIScaleFactor()
518+
self.SetInitialSize(self.GetSize()*(1/dpiScale))
519+
self._set_device_pixel_ratio(dpiScale)
520+
515521
def Copy_to_Clipboard(self, event=None):
516522
"""Copy bitmap of canvas to system clipboard."""
517523
bmp_obj = wx.BitmapDataObject()
@@ -524,6 +530,12 @@ def Copy_to_Clipboard(self, event=None):
524530
wx.TheClipboard.Flush()
525531
wx.TheClipboard.Close()
526532

533+
def _update_device_pixel_ratio(self, *args, **kwargs):
534+
# We need to be careful in cases with mixed resolution displays if
535+
# device_pixel_ratio changes.
536+
if self._set_device_pixel_ratio(self.GetDPIScaleFactor()):
537+
self.draw()
538+
527539
def draw_idle(self):
528540
# docstring inherited
529541
_log.debug("%s - draw_idle()", type(self))
@@ -631,7 +643,7 @@ def _on_size(self, event):
631643
In this application we attempt to resize to fit the window, so it
632644
is better to take the performance hit and redraw the whole window.
633645
"""
634-
646+
self._update_device_pixel_ratio()
635647
_log.debug("%s - _on_size()", type(self))
636648
sz = self.GetParent().GetSizer()
637649
if sz:
@@ -655,9 +667,10 @@ def _on_size(self, event):
655667
return # Empty figure
656668

657669
# Create a new, correctly sized bitmap
658-
self.bitmap = wx.Bitmap(self._width, self._height)
659-
660670
dpival = self.figure.dpi
671+
if not wx.Platform == '__WXMSW__':
672+
scale = self.GetDPIScaleFactor()
673+
dpival /= scale
661674
winch = self._width / dpival
662675
hinch = self._height / dpival
663676
self.figure.set_size_inches(winch, hinch, forward=False)
@@ -712,7 +725,11 @@ def _mpl_coords(self, pos=None):
712725
else:
713726
x, y = pos.X, pos.Y
714727
# flip y so y=0 is bottom of canvas
715-
return x, self.figure.bbox.height - y
728+
if not wx.Platform == '__WXMSW__':
729+
scale = self.GetDPIScaleFactor()
730+
return x*scale, self.figure.bbox.height - y*scale
731+
else:
732+
return x, self.figure.bbox.height - y
716733

717734
def _on_key_down(self, event):
718735
"""Capture key press."""
@@ -898,8 +915,8 @@ def __init__(self, num, fig, *, canvas_class):
898915
# On Windows, canvas sizing must occur after toolbar addition;
899916
# otherwise the toolbar further resizes the canvas.
900917
w, h = map(math.ceil, fig.bbox.size)
901-
self.canvas.SetInitialSize(wx.Size(w, h))
902-
self.canvas.SetMinSize((2, 2))
918+
self.canvas.SetInitialSize(self.FromDIP(wx.Size(w, h)))
919+
self.canvas.SetMinSize(self.FromDIP(wx.Size(2, 2)))
903920
self.canvas.SetFocus()
904921

905922
self.Fit()
@@ -1017,9 +1034,9 @@ def _set_frame_icon(frame):
10171034
class NavigationToolbar2Wx(NavigationToolbar2, wx.ToolBar):
10181035
def __init__(self, canvas, coordinates=True, *, style=wx.TB_BOTTOM):
10191036
wx.ToolBar.__init__(self, canvas.GetParent(), -1, style=style)
1037+
if wx.Platform == '__WXMAC__':
1038+
self.SetToolBitmapSize(self.GetToolBitmapSize()*self.GetDPIScaleFactor())
10201039

1021-
if 'wxMac' in wx.PlatformInfo:
1022-
self.SetToolBitmapSize((24, 24))
10231040
self.wx_ids = {}
10241041
for text, tooltip_text, image_file, callback in self.toolitems:
10251042
if text is None:
@@ -1028,7 +1045,7 @@ def __init__(self, canvas, coordinates=True, *, style=wx.TB_BOTTOM):
10281045
self.wx_ids[text] = (
10291046
self.AddTool(
10301047
-1,
1031-
bitmap=self._icon(f"{image_file}.png"),
1048+
bitmap=self._icon(f"{image_file}.svg"),
10321049
bmpDisabled=wx.NullBitmap,
10331050
label=text, shortHelp=tooltip_text,
10341051
kind=(wx.ITEM_CHECK if text in ["Pan", "Zoom"]
@@ -1054,9 +1071,7 @@ def _icon(name):
10541071
*name*, including the extension and relative to Matplotlib's "images"
10551072
data directory.
10561073
"""
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"))
1074+
svg = cbook._get_data_path("images", name).read_bytes()
10601075
try:
10611076
dark = wx.SystemSettings.GetAppearance().IsDark()
10621077
except AttributeError: # wxpython < 4.1
@@ -1068,11 +1083,9 @@ def _icon(name):
10681083
fg_lum = (.299 * fg.red + .587 * fg.green + .114 * fg.blue) / 255
10691084
dark = fg_lum - bg_lum > .2
10701085
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())
1086+
svg = svg.replace(b'fill:black;', b'fill:white;')
1087+
toolbarIconSize = wx.ArtProvider().GetDIPSizeHint(wx.ART_TOOLBAR)
1088+
return wx.BitmapBundle.FromSVG(svg, toolbarIconSize)
10761089

10771090
def _update_buttons_checked(self):
10781091
if "Pan" in self.wx_ids:
@@ -1123,7 +1136,9 @@ def save_figure(self, *args):
11231136

11241137
def draw_rubberband(self, event, x0, y0, x1, y1):
11251138
height = self.canvas.figure.bbox.height
1126-
self.canvas._rubberband_rect = (x0, height - y0, x1, height - y1)
1139+
sf = 1 if wx.Platform == '__WXMSW__' else self.GetDPIScaleFactor()
1140+
self.canvas._rubberband_rect = (x0/sf, (height - y0)/sf,
1141+
x1/sf, (height - y1)/sf)
11271142
self.canvas.Refresh()
11281143

11291144
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy