Skip to content

Commit 17ff6ba

Browse files
timhoffmtacaswell
authored andcommitted
Support fractional HiDpi scaling with Qt bakcends
Author: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
1 parent 21c3acb commit 17ff6ba

File tree

5 files changed

+68
-13
lines changed

5 files changed

+68
-13
lines changed

lib/matplotlib/backends/backend_qt5.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
from matplotlib.backends.qt_editor._formsubplottool import UiSubplotTool
1818
from . import qt_compat
1919
from .qt_compat import (
20-
QtCore, QtGui, QtWidgets, _isdeleted, is_pyqt5, __version__, QT_API)
20+
QtCore, QtGui, QtWidgets, __version__, QT_API,
21+
_devicePixelRatioF, _isdeleted,
22+
)
2123

2224
backend_version = __version__
2325

@@ -247,7 +249,7 @@ def _update_figure_dpi(self):
247249

248250
@property
249251
def _dpi_ratio(self):
250-
return qt_compat._devicePixelRatio(self)
252+
return _devicePixelRatioF(self)
251253

252254
def _update_dpi(self):
253255
# As described in __init__ above, we need to be careful in cases with
@@ -707,7 +709,7 @@ def _icon(self, name):
707709
if QtCore.qVersion() >= '5.':
708710
name = name.replace('.png', '_large.png')
709711
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))
710-
qt_compat._setDevicePixelRatio(pm, qt_compat._devicePixelRatio(self))
712+
qt_compat._setDevicePixelRatio(pm, _devicePixelRatioF(self))
711713
if self.palette().color(self.backgroundRole()).value() < 128:
712714
icon_color = self.palette().color(self.foregroundRole())
713715
mask = pm.createMaskFromColor(QtGui.QColor('black'),

lib/matplotlib/backends/backend_qt5agg.py

+2-4
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):
@@ -64,9 +64,7 @@ def paintEvent(self, event):
6464

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

lib/matplotlib/backends/backend_qt5cairo.py

+4-6
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

+34
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,41 @@ def _setDevicePixelRatio(obj, factor): pass
185185
# These globals are only defined for backcompatibility purposes.
186186
ETS = dict(pyqt=(QT_API_PYQTv2, 4), pyside=(QT_API_PYSIDE, 4),
187187
pyqt5=(QT_API_PYQT5, 5), pyside2=(QT_API_PYSIDE2, 5))
188+
188189
QT_RC_MAJOR_VERSION = int(QtCore.qVersion().split(".")[0])
189190

190191
if QT_RC_MAJOR_VERSION == 4:
191192
mpl.cbook.warn_deprecated("3.3", name="support for Qt4")
193+
194+
195+
def _devicePixelRatioF(obj):
196+
"""
197+
Return obj.devicePixelRatioF() with graceful fallback for older Qt.
198+
199+
This can be replaced by the direct call when we require Qt>=5.6.
200+
"""
201+
try:
202+
# Not available on Qt<5.6
203+
return obj.devicePixelRatioF() or 1
204+
except AttributeError:
205+
pass
206+
try:
207+
# Not available on Qt4 or some older Qt5.
208+
# self.devicePixelRatio() returns 0 in rare cases
209+
return obj.devicePixelRatio() or 1
210+
except AttributeError:
211+
return 1
212+
213+
214+
def _setDevicePixelRatioF(obj, val):
215+
"""
216+
Call obj.setDevicePixelRatioF(val) with graceful fallback for older Qt.
217+
218+
This can be replaced by the direct call when we require Qt>=5.6.
219+
"""
220+
if hasattr(obj, 'setDevicePixelRatioF'):
221+
# Not available on Qt<5.6
222+
obj.setDevicePixelRatioF(val)
223+
if hasattr(obj, 'setDevicePixelRatio'):
224+
# Not available on Qt4 or some older Qt5.
225+
obj.setDevicePixelRatio(val)

lib/matplotlib/tests/test_backend_qt.py

+23
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,29 @@ def test_dpi_ratio_change():
213213
assert qt_canvas.get_width_height() == (600, 240)
214214
assert (fig.get_size_inches() == (5, 2)).all()
215215

216+
p.return_value = 1.5
217+
218+
assert qt_canvas._dpi_ratio == 1.5
219+
220+
qt_canvas.draw()
221+
qApp.processEvents()
222+
# this second processEvents is required to fully run the draw.
223+
# On `update` we notice the DPI has changed and trigger a
224+
# resize event to refresh, the second processEvents is
225+
# required to process that and fully update the window sizes.
226+
qApp.processEvents()
227+
228+
# The DPI and the renderer width/height change
229+
assert fig.dpi == 180
230+
assert qt_canvas.renderer.width == 900
231+
assert qt_canvas.renderer.height == 360
232+
233+
# The actual widget size and figure physical size don't change
234+
assert size.width() == 600
235+
assert size.height() == 240
236+
assert qt_canvas.get_width_height() == (600, 240)
237+
assert (fig.get_size_inches() == (5, 2)).all()
238+
216239

217240
@pytest.mark.backend('Qt5Agg')
218241
def test_subplottool():

0 commit comments

Comments
 (0)