diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 4bde67f8eea8..8150e5a6262c 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -243,6 +243,18 @@ def __init__(self, figure): w, h = self.get_width_height() self.resize(w, h) + @property + def _dpi_ratio(self): + # Not available on Qt4 or some older Qt5. + try: + return self.devicePixelRatio() + except AttributeError: + return 1 + + def get_width_height(self): + w, h = FigureCanvasBase.get_width_height(self) + return int(w / self._dpi_ratio), int(h / self._dpi_ratio) + def enterEvent(self, event): FigureCanvasBase.enter_notify_event(self, guiEvent=event) @@ -250,10 +262,14 @@ def leaveEvent(self, event): QtWidgets.QApplication.restoreOverrideCursor() FigureCanvasBase.leave_notify_event(self, guiEvent=event) + def mouseEventCoords(self, pos): + x = pos.x() * self._dpi_ratio + # flip y so y=0 is bottom of canvas + y = self.figure.bbox.height - pos.y() * self._dpi_ratio + return x, y + def mousePressEvent(self, event): - x = event.pos().x() - # flipy so y=0 is bottom of canvas - y = self.figure.bbox.height - event.pos().y() + x, y = self.mouseEventCoords(event.pos()) button = self.buttond.get(event.button()) if button is not None: FigureCanvasBase.button_press_event(self, x, y, button, @@ -262,9 +278,7 @@ def mousePressEvent(self, event): print('button pressed:', event.button()) def mouseDoubleClickEvent(self, event): - x = event.pos().x() - # flipy so y=0 is bottom of canvas - y = self.figure.bbox.height - event.pos().y() + x, y = self.mouseEventCoords(event.pos()) button = self.buttond.get(event.button()) if button is not None: FigureCanvasBase.button_press_event(self, x, y, @@ -274,16 +288,12 @@ def mouseDoubleClickEvent(self, event): print('button doubleclicked:', event.button()) def mouseMoveEvent(self, event): - x = event.x() - # flipy so y=0 is bottom of canvas - y = self.figure.bbox.height - event.y() + x, y = self.mouseEventCoords(event) FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event) # if DEBUG: print('mouse move') def mouseReleaseEvent(self, event): - x = event.x() - # flipy so y=0 is bottom of canvas - y = self.figure.bbox.height - event.y() + x, y = self.mouseEventCoords(event) button = self.buttond.get(event.button()) if button is not None: FigureCanvasBase.button_release_event(self, x, y, button, @@ -292,9 +302,7 @@ def mouseReleaseEvent(self, event): print('button released') def wheelEvent(self, event): - x = event.x() - # flipy so y=0 is bottom of canvas - y = self.figure.bbox.height - event.y() + x, y = self.mouseEventCoords(event) # from QWheelEvent::delta doc if event.pixelDelta().x() == 0 and event.pixelDelta().y() == 0: steps = event.angleDelta().y() / 120 @@ -324,8 +332,8 @@ def keyReleaseEvent(self, event): print('key release', key) def resizeEvent(self, event): - w = event.size().width() - h = event.size().height() + w = event.size().width() * self._dpi_ratio + h = event.size().height() * self._dpi_ratio if DEBUG: print('resize (%d x %d)' % (w, h)) print("FigureCanvasQt.resizeEvent(%d, %d)" % (w, h)) diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index ecc49840b19a..55b09d78c4ef 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -67,7 +67,10 @@ def __init__(self, figure): self._agg_draw_pending = False def drawRectangle(self, rect): - self._drawRect = rect + if rect is not None: + self._drawRect = [pt / self._dpi_ratio for pt in rect] + else: + self._drawRect = None self.update() def paintEvent(self, e): @@ -101,6 +104,9 @@ def paintEvent(self, e): qImage = QtGui.QImage(stringBuffer, self.renderer.width, self.renderer.height, QtGui.QImage.Format_ARGB32) + if hasattr(qImage, 'setDevicePixelRatio'): + # Not available on Qt4 or some older Qt5. + qImage.setDevicePixelRatio(self._dpi_ratio) # get the rectangle for the image rect = qImage.rect() p = QtGui.QPainter(self) @@ -111,7 +117,9 @@ def paintEvent(self, e): # draw the zoom rectangle to the QPainter if self._drawRect is not None: - p.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.DotLine)) + pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio, + QtCore.Qt.DotLine) + p.setPen(pen) x, y, w, h = self._drawRect p.drawRect(x, y, w, h) p.end() @@ -136,18 +144,24 @@ def paintEvent(self, e): stringBuffer = reg.to_string_argb() qImage = QtGui.QImage(stringBuffer, w, h, QtGui.QImage.Format_ARGB32) + if hasattr(qImage, 'setDevicePixelRatio'): + # Not available on Qt4 or some older Qt5. + qImage.setDevicePixelRatio(self._dpi_ratio) # Adjust the stringBuffer reference count to work # around a memory leak bug in QImage() under PySide on # Python 3.x if QT_API == 'PySide' and six.PY3: ctypes.c_long.from_address(id(stringBuffer)).value = 1 + origin = QtCore.QPoint(l, self.renderer.height - t) pixmap = QtGui.QPixmap.fromImage(qImage) - p.drawPixmap(QtCore.QPoint(l, self.renderer.height-t), pixmap) + p.drawPixmap(origin / self._dpi_ratio, pixmap) # draw the zoom rectangle to the QPainter if self._drawRect is not None: - p.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.DotLine)) + pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio, + QtCore.Qt.DotLine) + p.setPen(pen) x, y, w, h = self._drawRect p.drawRect(x, y, w, h) @@ -198,9 +212,11 @@ def blit(self, bbox=None): bbox = self.figure.bbox self.blitbox.append(bbox) - l, b, w, h = bbox.bounds + + # repaint uses logical pixels, not physical pixels like the renderer. + l, b, w, h = [pt / self._dpi_ratio for pt in bbox.bounds] t = b + h - self.repaint(l, self.renderer.height-t, w, h) + self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h) def print_figure(self, *args, **kwargs): FigureCanvasAgg.print_figure(self, *args, **kwargs) @@ -226,6 +242,11 @@ def __init__(self, figure): super(FigureCanvasQTAgg, self).__init__(figure=figure) self._drawRect = None self.blitbox = [] + # We don't want to scale up the figure DPI more than once. + # Note, we don't handle a signal for changing DPI yet. + if not hasattr(self.figure, '_original_dpi'): + self.figure._original_dpi = self.figure.dpi + self.figure.dpi = self._dpi_ratio * self.figure._original_dpi self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 030d50263d87..f38fbcea58f6 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -713,7 +713,8 @@ def set_size_inches(self, w, h=None, forward=True): self.bbox_inches.p1 = w, h if forward: - dpival = self.dpi + ratio = getattr(self.canvas, '_dpi_ratio', 1) + dpival = self.dpi / ratio canvasw = w * dpival canvash = h * dpival manager = getattr(self.canvas, 'manager', None)