Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1023939

Browse files
committed
Move pixel ratio handling into FigureCanvasBase.
This is already implemented in two backends (Qt5 and nbAgg), and I plan to implement it in TkAgg, so it's better to remove the repetition.
1 parent 09ab2c2 commit 1023939

File tree

7 files changed

+72
-64
lines changed

7 files changed

+72
-64
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,10 @@ def __init__(self, figure):
17231723
self.toolbar = None # NavigationToolbar2 will set me
17241724
self._is_idle_drawing = False
17251725

1726+
# We don't want to scale up the figure DPI more than once.
1727+
figure._original_dpi = figure.dpi
1728+
self._pixel_ratio = 1
1729+
17261730
@property
17271731
def callbacks(self):
17281732
return self.figure._canvas_callbacks
@@ -2040,12 +2044,45 @@ def draw_idle(self, *args, **kwargs):
20402044
with self._idle_draw_cntx():
20412045
self.draw(*args, **kwargs)
20422046

2047+
@property
2048+
def pixel_ratio(self):
2049+
"""
2050+
The ratio of logical to physical pixels used for the canvas.
2051+
2052+
Subclasses that support High DPI screens can set this property to
2053+
indicate that said ratio is different. The canvas itself will be
2054+
created at the physical size, while the display and any events will use
2055+
the logical size (and such implementations should ensure that events do
2056+
so.)
2057+
2058+
By default, this is 1, meaning physical and logical pixels are the same
2059+
size.
2060+
"""
2061+
return self._pixel_ratio
2062+
2063+
@pixel_ratio.setter
2064+
def pixel_ratio(self, ratio):
2065+
# In cases with mixed resolution displays, we need to be careful if the
2066+
# pixel_ratio changes - in this case we need to resize the canvas
2067+
# accordingly. Some backends provide events that indicate a change in
2068+
# DPI, but those that don't will update this before drawing.
2069+
if self._pixel_ratio != ratio:
2070+
dpi = ratio * self.figure._original_dpi
2071+
self.figure._set_dpi(dpi, forward=False)
2072+
self._pixel_ratio = ratio
2073+
20432074
def get_width_height(self):
20442075
"""
2045-
Return the figure width and height in points or pixels
2046-
(depending on the backend), truncated to integers.
2076+
Return the figure width and height in integral points or pixels.
2077+
2078+
Returns
2079+
-------
2080+
width, height : int
2081+
The size of the figure, in points or pixels, depending on the
2082+
backend.
20472083
"""
2048-
return int(self.figure.bbox.width), int(self.figure.bbox.height)
2084+
return tuple(int(size / self.pixel_ratio)
2085+
for size in self.figure.bbox.max)
20492086

20502087
@classmethod
20512088
def get_supported_filetypes(cls):

lib/matplotlib/backends/backend_qt5.py

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,6 @@ def __init__(self, figure):
221221
_create_qApp()
222222
super().__init__(figure=figure)
223223

224-
# We don't want to scale up the figure DPI more than once.
225-
# Note, we don't handle a signal for changing DPI yet.
226-
figure._original_dpi = figure.dpi
227-
self._update_figure_dpi()
228-
# In cases with mixed resolution displays, we need to be careful if the
229-
# dpi_ratio changes - in this case we need to resize the canvas
230-
# accordingly.
231-
self._dpi_ratio_prev = self._dpi_ratio
232-
233224
self._draw_pending = False
234225
self._is_drawing = False
235226
self._draw_rect_callback = lambda painter: None
@@ -241,21 +232,13 @@ def __init__(self, figure):
241232
palette = QtGui.QPalette(QtCore.Qt.white)
242233
self.setPalette(palette)
243234

244-
def _update_figure_dpi(self):
245-
dpi = self._dpi_ratio * self.figure._original_dpi
246-
self.figure._set_dpi(dpi, forward=False)
247-
248-
@property
249-
def _dpi_ratio(self):
250-
return _devicePixelRatioF(self)
251-
252235
def _update_pixel_ratio(self):
253236
# We need to be careful in cases with mixed resolution displays if
254-
# dpi_ratio changes.
255-
if self._dpi_ratio != self._dpi_ratio_prev:
237+
# pixel_ratio changes.
238+
current_pixel_ratio = _devicePixelRatioF(self)
239+
if self.pixel_ratio != current_pixel_ratio:
256240
# We need to update the figure DPI.
257-
self._update_figure_dpi()
258-
self._dpi_ratio_prev = self._dpi_ratio
241+
self.pixel_ratio = current_pixel_ratio
259242
# The easiest way to resize the canvas is to emit a resizeEvent
260243
# since we implement all the logic for resizing the canvas for
261244
# that event.
@@ -278,10 +261,6 @@ def showEvent(self, event):
278261
window.screenChanged.connect(self._update_screen)
279262
self._update_screen(window.screen())
280263

281-
def get_width_height(self):
282-
w, h = FigureCanvasBase.get_width_height(self)
283-
return int(w / self._dpi_ratio), int(h / self._dpi_ratio)
284-
285264
def enterEvent(self, event):
286265
try:
287266
x, y = self.mouseEventCoords(event.pos())
@@ -304,11 +283,11 @@ def mouseEventCoords(self, pos):
304283
305284
Also, the origin is different and needs to be corrected.
306285
"""
307-
dpi_ratio = self._dpi_ratio
286+
pixel_ratio = self.pixel_ratio
308287
x = pos.x()
309288
# flip y so y=0 is bottom of canvas
310-
y = self.figure.bbox.height / dpi_ratio - pos.y()
311-
return x * dpi_ratio, y * dpi_ratio
289+
y = self.figure.bbox.height / pixel_ratio - pos.y()
290+
return x * pixel_ratio, y * pixel_ratio
312291

313292
def mousePressEvent(self, event):
314293
x, y = self.mouseEventCoords(event.pos())
@@ -369,8 +348,8 @@ def keyReleaseEvent(self, event):
369348
FigureCanvasBase.key_release_event(self, key, guiEvent=event)
370349

371350
def resizeEvent(self, event):
372-
w = event.size().width() * self._dpi_ratio
373-
h = event.size().height() * self._dpi_ratio
351+
w = event.size().width() * self.pixel_ratio
352+
h = event.size().height() * self.pixel_ratio
374353
dpival = self.figure.dpi
375354
winch = w / dpival
376355
hinch = h / dpival
@@ -468,7 +447,7 @@ def blit(self, bbox=None):
468447
if bbox is None and self.figure:
469448
bbox = self.figure.bbox # Blit the entire canvas if bbox is None.
470449
# repaint uses logical pixels, not physical pixels like the renderer.
471-
l, b, w, h = [int(pt / self._dpi_ratio) for pt in bbox.bounds]
450+
l, b, w, h = [int(pt / self.pixel_ratio) for pt in bbox.bounds]
472451
t = b + h
473452
self.repaint(l, self.rect().height() - t, w, h)
474453

@@ -489,11 +468,11 @@ def drawRectangle(self, rect):
489468
# Draw the zoom rectangle to the QPainter. _draw_rect_callback needs
490469
# to be called at the end of paintEvent.
491470
if rect is not None:
492-
x0, y0, w, h = [int(pt / self._dpi_ratio) for pt in rect]
471+
x0, y0, w, h = [int(pt / self.pixel_ratio) for pt in rect]
493472
x1 = x0 + w
494473
y1 = y0 + h
495474
def _draw_rect_callback(painter):
496-
pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio)
475+
pen = QtGui.QPen(QtCore.Qt.black, 1 / self.pixel_ratio)
497476
pen.setDashPattern([3, 3])
498477
for color, offset in [
499478
(QtCore.Qt.black, 0), (QtCore.Qt.white, 3)]:

lib/matplotlib/backends/backend_qt5agg.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def paintEvent(self, event):
4242
# scale rect dimensions using the screen dpi ratio to get
4343
# correct values for the Figure coordinates (rather than
4444
# QT5's coords)
45-
width = rect.width() * self._dpi_ratio
46-
height = rect.height() * self._dpi_ratio
45+
width = rect.width() * self.pixel_ratio
46+
height = rect.height() * self.pixel_ratio
4747
left, top = self.mouseEventCoords(rect.topLeft())
4848
# shift the "top" by the height of the image to get the
4949
# correct corner for our coordinate system
@@ -61,7 +61,7 @@ def paintEvent(self, event):
6161

6262
qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0],
6363
QtGui.QImage.Format_ARGB32_Premultiplied)
64-
_setDevicePixelRatio(qimage, self._dpi_ratio)
64+
_setDevicePixelRatio(qimage, self.pixel_ratio)
6565
# set origin using original QT coordinates
6666
origin = QtCore.QPoint(rect.left(), rect.top())
6767
painter.drawImage(origin, qimage)

lib/matplotlib/backends/backend_qt5cairo.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ def draw(self):
1717
super().draw()
1818

1919
def paintEvent(self, event):
20-
dpi_ratio = self._dpi_ratio
21-
width = int(dpi_ratio * self.width())
22-
height = int(dpi_ratio * self.height())
20+
width = int(self.pixel_ratio * self.width())
21+
height = int(self.pixel_ratio * self.height())
2322
if (width, height) != self._renderer.get_canvas_width_height():
2423
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
2524
self._renderer.set_ctx_from_surface(surface)
@@ -32,7 +31,7 @@ def paintEvent(self, event):
3231
# QImage under PySide on Python 3.
3332
if QT_API == 'PySide':
3433
ctypes.c_long.from_address(id(buf)).value = 1
35-
_setDevicePixelRatio(qimage, dpi_ratio)
34+
_setDevicePixelRatio(qimage, self.pixel_ratio)
3635
painter = QtGui.QPainter(self)
3736
painter.eraseRect(event.rect())
3837
painter.drawImage(0, 0, qimage)

lib/matplotlib/backends/backend_webagg_core.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,6 @@ def __init__(self, *args, **kwargs):
172172
# to the connected clients.
173173
self._current_image_mode = 'full'
174174

175-
# Store the DPI ratio of the browser. This is the scaling that
176-
# occurs automatically for all images on a HiDPI display.
177-
self._dpi_ratio = 1
178-
179175
def show(self):
180176
# show the figure window
181177
from matplotlib.pyplot import show
@@ -346,7 +342,7 @@ def handle_refresh(self, event):
346342

347343
def handle_resize(self, event):
348344
x, y = event.get('width', 800), event.get('height', 800)
349-
x, y = int(x) * self._dpi_ratio, int(y) * self._dpi_ratio
345+
x, y = int(x) * self.pixel_ratio, int(y) * self.pixel_ratio
350346
fig = self.figure
351347
# An attempt at approximating the figure size in pixels.
352348
fig.set_size_inches(x / fig.dpi, y / fig.dpi, forward=False)
@@ -361,14 +357,10 @@ def handle_send_image_mode(self, event):
361357
# The client requests notification of what the current image mode is.
362358
self.send_event('image_mode', mode=self._current_image_mode)
363359

364-
def handle_set_dpi_ratio(self, event):
365-
dpi_ratio = event.get('dpi_ratio', 1)
366-
if dpi_ratio != self._dpi_ratio:
367-
# We don't want to scale up the figure dpi more than once.
368-
if not hasattr(self.figure, '_original_dpi'):
369-
self.figure._original_dpi = self.figure.dpi
370-
self.figure.dpi = dpi_ratio * self.figure._original_dpi
371-
self._dpi_ratio = dpi_ratio
360+
def handle_set_pixel_ratio(self, event):
361+
pixel_ratio = event.get('pixel_ratio', 1)
362+
if pixel_ratio != self.pixel_ratio:
363+
self.pixel_ratio = pixel_ratio
372364
self._force_full = True
373365
self.draw_idle()
374366

@@ -462,7 +454,7 @@ def _get_toolbar(self, canvas):
462454
def resize(self, w, h, forward=True):
463455
self._send_event(
464456
'resize',
465-
size=(w / self.canvas._dpi_ratio, h / self.canvas._dpi_ratio),
457+
size=(w / self.canvas.pixel_ratio, h / self.canvas.pixel_ratio),
466458
forward=forward)
467459

468460
def set_window_title(self, title):

lib/matplotlib/backends/web_backend/js/mpl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ mpl.figure = function (figure_id, websocket, ondownload, parent_element) {
6363
fig.send_message('supports_binary', { value: fig.supports_binary });
6464
fig.send_message('send_image_mode', {});
6565
if (fig.ratio !== 1) {
66-
fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });
66+
fig.send_message('set_pixel_ratio', { pixel_ratio: fig.ratio });
6767
}
6868
fig.send_message('refresh', {});
6969
};

lib/matplotlib/tests/test_backend_qt.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def on_key_press(event):
161161
def test_pixel_ratio_change():
162162
"""
163163
Make sure that if the pixel ratio changes, the figure dpi changes but the
164-
widget remains the same physical size.
164+
widget remains the same logical size.
165165
"""
166166

167167
prop = 'matplotlib.backends.backend_qt5.FigureCanvasQT.devicePixelRatioF'
@@ -174,8 +174,6 @@ def test_pixel_ratio_change():
174174

175175
def set_pixel_ratio(ratio):
176176
p.return_value = ratio
177-
# Make sure the mocking worked
178-
assert qt_canvas._dpi_ratio == ratio
179177

180178
# The value here doesn't matter, as we can't mock the C++ QScreen
181179
# object, but can override the functional wrapper around it.
@@ -186,6 +184,9 @@ def set_pixel_ratio(ratio):
186184
qt_canvas.draw()
187185
qt_canvas.flush_events()
188186

187+
# Make sure the mocking worked
188+
assert qt_canvas.pixel_ratio == ratio
189+
189190
qt_canvas.manager.show()
190191
size = qt_canvas.size()
191192
screen = qt_canvas.window().windowHandle().screen()
@@ -196,7 +197,7 @@ def set_pixel_ratio(ratio):
196197
assert qt_canvas.renderer.width == 1800
197198
assert qt_canvas.renderer.height == 720
198199

199-
# The actual widget size and figure physical size don't change
200+
# The actual widget size and figure logical size don't change.
200201
assert size.width() == 600
201202
assert size.height() == 240
202203
assert qt_canvas.get_width_height() == (600, 240)
@@ -209,7 +210,7 @@ def set_pixel_ratio(ratio):
209210
assert qt_canvas.renderer.width == 1200
210211
assert qt_canvas.renderer.height == 480
211212

212-
# The actual widget size and figure physical size don't change
213+
# The actual widget size and figure logical size don't change.
213214
assert size.width() == 600
214215
assert size.height() == 240
215216
assert qt_canvas.get_width_height() == (600, 240)
@@ -222,7 +223,7 @@ def set_pixel_ratio(ratio):
222223
assert qt_canvas.renderer.width == 900
223224
assert qt_canvas.renderer.height == 360
224225

225-
# The actual widget size and figure physical size don't change
226+
# The actual widget size and figure logical size don't change.
226227
assert size.width() == 600
227228
assert size.height() == 240
228229
assert qt_canvas.get_width_height() == (600, 240)

0 commit comments

Comments
 (0)