Skip to content

Qt5Agg HiDPI support #7507

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions lib/matplotlib/backends/backend_qt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,17 +243,33 @@ 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)

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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand Down
33 changes: 27 additions & 6 deletions lib/matplotlib/backends/backend_qt5agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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)


Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down