Skip to content

Commit c205b60

Browse files
committed
Support fractional HiDpi scaling with Qt bakcends
1 parent f73f336 commit c205b60

File tree

5 files changed

+67
-21
lines changed

5 files changed

+67
-21
lines changed

lib/matplotlib/backends/backend_qt5.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
from matplotlib.backend_managers import ToolManager
1818

1919
from .qt_compat import (
20-
QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API)
20+
QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API,
21+
_devicePixelRatioF, _setDevicePixelRatioF)
2122

2223
backend_version = __version__
2324

@@ -260,12 +261,7 @@ def _update_figure_dpi(self):
260261

261262
@property
262263
def _dpi_ratio(self):
263-
# Not available on Qt4 or some older Qt5.
264-
try:
265-
# self.devicePixelRatio() returns 0 in rare cases
266-
return self.devicePixelRatio() or 1
267-
except AttributeError:
268-
return 1
264+
return _devicePixelRatioF(self)
269265

270266
def _update_dpi(self):
271267
# As described in __init__ above, we need to be careful in cases with
@@ -683,8 +679,7 @@ def _icon(self, name, color=None):
683679
if is_pyqt5():
684680
name = name.replace('.png', '_large.png')
685681
pm = QtGui.QPixmap(os.path.join(self.basedir, name))
686-
if hasattr(pm, 'setDevicePixelRatio'):
687-
pm.setDevicePixelRatio(self.canvas._dpi_ratio)
682+
_setDevicePixelRatioF(pm, self.canvas._dpi_ratio)
688683
if color is not None:
689684
mask = pm.createMaskFromColor(QtGui.QColor('black'),
690685
QtCore.Qt.MaskOutColor)
@@ -957,8 +952,7 @@ def _add_to_group(self, group, name, button, position):
957952

958953
def _icon(self, name):
959954
pm = QtGui.QPixmap(name)
960-
if hasattr(pm, 'setDevicePixelRatio'):
961-
pm.setDevicePixelRatio(self.toolmanager.canvas._dpi_ratio)
955+
_setDevicePixelRatioF(pm, self.toolmanager.canvas._dpi_ratio)
962956
return QtGui.QIcon(pm)
963957

964958
def toggle_toolitem(self, name, toggled):

lib/matplotlib/backends/backend_qt5agg.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .backend_qt5 import (
1212
QtCore, QtGui, QtWidgets, _BackendQT5, FigureCanvasQT, FigureManagerQT,
1313
NavigationToolbar2QT, backend_version)
14-
from .qt_compat import QT_API
14+
from .qt_compat import QT_API, _setDevicePixelRatioF
1515

1616

1717
class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT):
@@ -63,9 +63,7 @@ def paintEvent(self, event):
6363

6464
qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0],
6565
QtGui.QImage.Format_ARGB32_Premultiplied)
66-
if hasattr(qimage, 'setDevicePixelRatio'):
67-
# Not available on Qt4 or some older Qt5.
68-
qimage.setDevicePixelRatio(self._dpi_ratio)
66+
_setDevicePixelRatioF(qimage, self._dpi_ratio)
6967
# set origin using original QT coordinates
7068
origin = QtCore.QPoint(rect.left(), rect.top())
7169
painter.drawImage(origin, qimage)

lib/matplotlib/backends/backend_qt5cairo.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo
44
from .backend_qt5 import QtCore, QtGui, _BackendQT5, FigureCanvasQT
5-
from .qt_compat import QT_API
5+
from .qt_compat import QT_API, _setDevicePixelRatioF
66

77

88
class FigureCanvasQTCairo(FigureCanvasQT, FigureCanvasCairo):
@@ -19,8 +19,8 @@ def draw(self):
1919
def paintEvent(self, event):
2020
self._update_dpi()
2121
dpi_ratio = self._dpi_ratio
22-
width = dpi_ratio * self.width()
23-
height = dpi_ratio * self.height()
22+
width = int(dpi_ratio * self.width())
23+
height = int(dpi_ratio * self.height())
2424
if (width, height) != self._renderer.get_canvas_width_height():
2525
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
2626
self._renderer.set_ctx_from_surface(surface)
@@ -33,9 +33,7 @@ def paintEvent(self, event):
3333
# QImage under PySide on Python 3.
3434
if QT_API == 'PySide':
3535
ctypes.c_long.from_address(id(buf)).value = 1
36-
if hasattr(qimage, 'setDevicePixelRatio'):
37-
# Not available on Qt4 or some older Qt5.
38-
qimage.setDevicePixelRatio(dpi_ratio)
36+
_setDevicePixelRatioF(qimage, dpi_ratio)
3937
painter = QtGui.QPainter(self)
4038
painter.eraseRect(event.rect())
4139
painter.drawImage(0, 0, qimage)

lib/matplotlib/backends/qt_compat.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,36 @@ def is_pyqt5():
164164
ETS = dict(pyqt=(QT_API_PYQTv2, 4), pyside=(QT_API_PYSIDE, 4),
165165
pyqt5=(QT_API_PYQT5, 5), pyside2=(QT_API_PYSIDE2, 5))
166166
QT_RC_MAJOR_VERSION = 5 if is_pyqt5() else 4
167+
168+
169+
def _devicePixelRatioF(obj):
170+
"""
171+
Return obj.devicePixelRatioF() with graceful fallback for older Qt.
172+
173+
This can be replaced by the direct call when we require Qt>=5.6.
174+
"""
175+
try:
176+
# Not available on Qt<5.6
177+
return obj.devicePixelRatioF() or 1
178+
except AttributeError:
179+
pass
180+
try:
181+
# Not available on Qt4 or some older Qt5.
182+
# self.devicePixelRatio() returns 0 in rare cases
183+
return obj.devicePixelRatio() or 1
184+
except AttributeError:
185+
return 1
186+
187+
188+
def _setDevicePixelRatioF(obj, val):
189+
"""
190+
Call obj.setDevicePixelRatioF(val) with graceful fallback for older Qt.
191+
192+
This can be replaced by the direct call when we require Qt>=5.6.
193+
"""
194+
if hasattr(obj, 'setDevicePixelRatioF'):
195+
# Not available on Qt<5.6
196+
obj.setDevicePixelRatioF(val)
197+
if hasattr(obj, 'setDevicePixelRatio'):
198+
# Not available on Qt4 or some older Qt5.
199+
obj.setDevicePixelRatio(val)

lib/matplotlib/tests/test_backend_qt.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,29 @@ def test_dpi_ratio_change():
253253
assert qt_canvas.get_width_height() == (600, 240)
254254
assert (fig.get_size_inches() == (5, 2)).all()
255255

256+
p.return_value = 1.5
257+
258+
assert qt_canvas._dpi_ratio == 1.5
259+
260+
qt_canvas.draw()
261+
qApp.processEvents()
262+
# this second processEvents is required to fully run the draw.
263+
# On `update` we notice the DPI has changed and trigger a
264+
# resize event to refresh, the second processEvents is
265+
# required to process that and fully update the window sizes.
266+
qApp.processEvents()
267+
268+
# The DPI and the renderer width/height change
269+
assert fig.dpi == 180
270+
assert qt_canvas.renderer.width == 900
271+
assert qt_canvas.renderer.height == 360
272+
273+
# The actual widget size and figure physical size don't change
274+
assert size.width() == 600
275+
assert size.height() == 240
276+
assert qt_canvas.get_width_height() == (600, 240)
277+
assert (fig.get_size_inches() == (5, 2)).all()
278+
256279

257280
@pytest.mark.backend('Qt5Agg')
258281
def test_subplottool():

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