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

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Unify QtAgg nonblitting and blitting code paths.
Follow a similar architecture to the Gtk3Agg backend: a default draw can
be implemented as a blitting of the entire canvas.

Also unify the Qt4Agg and Qt5Agg canvas classes a bit more.
  • Loading branch information
anntzer committed Jul 9, 2017
commit 65a0ccc3294efd0bebd1485fb67d3c78920c0aaa
20 changes: 2 additions & 18 deletions lib/matplotlib/backends/backend_qt4agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from matplotlib.figure import Figure


from .backend_qt5agg import FigureCanvasQTAggBase as _FigureCanvasQTAggBase
from .backend_qt5agg import FigureCanvasQTAggBase

from .backend_agg import FigureCanvasAgg
from .backend_qt4 import QtCore
Expand Down Expand Up @@ -54,13 +54,7 @@ def new_figure_manager_given_figure(num, figure):
return FigureManagerQT(canvas, num)


class FigureCanvasQTAggBase(_FigureCanvasQTAggBase):
def __init__(self, figure):
self._agg_draw_pending = False


class FigureCanvasQTAgg(FigureCanvasQTAggBase,
FigureCanvasQT, FigureCanvasAgg):
class FigureCanvasQTAgg(FigureCanvasQTAggBase, FigureCanvasQT):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having the base class come in as the _ version of this is clearer (I had to think a bit for why this should work).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this MRO chain will get even further simplified in #8771 (to class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT), which seems as simple as one can do :-)), sorry for modifying the same thing twice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am very much 👍 on simplifying the MRO.

"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc...
Expand All @@ -72,16 +66,6 @@ class FigureCanvasQTAgg(FigureCanvasQTAggBase,

"""

def __init__(self, figure):
if DEBUG:
print('FigureCanvasQtAgg: ', figure)
FigureCanvasQT.__init__(self, figure)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be here or the MI does not work correctly (as pyqt5 made them cooperative, but they are not int pyqt4)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is further handled by the qtcairo commit, but in fact with the new MRO (specifically, due to FigureCanvasQT going after FigureCanvasAgg), this is not needed anymore, because the MRO FigureCanvasQT(4)Agg is

matplotlib.backends.backend_qt4agg.FigureCanvasQTAgg
matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase
matplotlib.backends.backend_agg.FigureCanvasAgg
matplotlib.backends.backend_qt4.FigureCanvasQT
matplotlib.backends.backend_qt5.FigureCanvasQT
PyQt4.QtGui.QWidget
PyQt4.QtCore.QObject
sip.wrapper
PyQt4.QtGui.QPaintDevice
sip.simplewrapper
matplotlib.backend_bases.FigureCanvasBase
builtins.object

and FigureCanvasAgg.__init__ is the same as FigureCanvasBase.__init__, which does not forward its arguments to the super class, so QWidget never sees the figure kwarg.

Or you can just test by yourself that it works :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I was lost in the classes here. It looks like the calls out to the PyQt base classes happen in the FigureCanvasQt.__init__ not here.

🐑

FigureCanvasQTAggBase.__init__(self, figure)
FigureCanvasAgg.__init__(self, figure)
self._drawRect = None
self.blitbox = []
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)


FigureCanvas = FigureCanvasQTAgg
FigureManager = FigureManagerQT
141 changes: 47 additions & 94 deletions lib/matplotlib/backends/backend_qt5agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
import six

import ctypes
import sys
import traceback

from matplotlib import cbook
from matplotlib.figure import Figure
from matplotlib.transforms import Bbox

from .backend_agg import FigureCanvasAgg
from .backend_qt5 import QtCore
Expand All @@ -28,11 +29,6 @@

DEBUG = False

_decref = ctypes.pythonapi.Py_DecRef
_decref.argtypes = [ctypes.py_object]
_decref.restype = None


def new_figure_manager(num, *args, **kwargs):
"""
Create a new figure manager instance
Expand All @@ -52,7 +48,7 @@ def new_figure_manager_given_figure(num, figure):
return FigureManagerQT(canvas, num)


class FigureCanvasQTAggBase(object):
class FigureCanvasQTAggBase(FigureCanvasAgg):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc...
Expand All @@ -66,7 +62,10 @@ class FigureCanvasQTAggBase(object):

def __init__(self, figure):
super(FigureCanvasQTAggBase, self).__init__(figure=figure)
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
self._agg_draw_pending = False
self._bbox_queue = []
self._drawRect = None

def drawRectangle(self, rect):
if rect is not None:
Expand All @@ -75,6 +74,11 @@ def drawRectangle(self, rect):
self._drawRect = None
self.update()

@property
@cbook.deprecated("2.1")
def blitbox(self):
return self._bbox_queue

def paintEvent(self, e):
"""
Copy the image from the Agg canvas to the qt.drawable.
Expand All @@ -86,88 +90,41 @@ def paintEvent(self, e):
if not hasattr(self, 'renderer'):
return

if DEBUG:
print('FigureCanvasQtAgg.paintEvent: ', self,
self.get_width_height())

if len(self.blitbox) == 0:
# matplotlib is in rgba byte order. QImage wants to put the bytes
# into argb format and is in a 4 byte unsigned int. Little endian
# system is LSB first and expects the bytes in reverse order
# (bgra).
if QtCore.QSysInfo.ByteOrder == QtCore.QSysInfo.LittleEndian:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why drop the little endian support?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just reusing the blitting codepath for both cases, so either region.to_string_argb (which is a different method from renderer.tostring_argb...) was already doing the right thing wrt. endianness, or there has been a bug for a while on one of the two endiannesses that no one ever noticed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair. There are not many little-endian systems left in practice (and they tend to be big main frame type systems these days).

stringBuffer = self.renderer._renderer.tostring_bgra()
else:
stringBuffer = self.renderer._renderer.tostring_argb()

refcnt = sys.getrefcount(stringBuffer)

# convert the Agg rendered image -> qImage
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)
# reset the image area of the canvas to be the back-ground color
p.eraseRect(rect)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is important if users have alpha in the final rgba. If this is not here text appears to 'get thicker' over time as the alpha stacks up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, restored the erase in the case where bbox_queue is empty.

# draw the rendered image on to the canvas
p.drawPixmap(QtCore.QPoint(0, 0), QtGui.QPixmap.fromImage(qImage))

# draw the zoom rectangle to the QPainter
if self._drawRect is not None:
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()

# This works around a bug in PySide 1.1.2 on Python 3.x,
# where the reference count of stringBuffer is incremented
# but never decremented by QImage.
# TODO: revert PR #1323 once the issue is fixed in PySide.
del qImage
if refcnt != sys.getrefcount(stringBuffer):
_decref(stringBuffer)
painter = QtGui.QPainter(self)

if self._bbox_queue:
bbox_queue = self._bbox_queue
else:
p = QtGui.QPainter(self)

while len(self.blitbox):
bbox = self.blitbox.pop()
l, b, r, t = bbox.extents
w = int(r) - int(l)
h = int(t) - int(b)
t = int(b) + h
reg = self.copy_from_bbox(bbox)
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(origin / self._dpi_ratio, pixmap)

# draw the zoom rectangle to the QPainter
if self._drawRect is not None:
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()
painter.eraseRect(self.rect())
bbox_queue = [
Bbox([[0, 0], [self.renderer.width, self.renderer.height]])]
self._bbox_queue = []
for bbox in bbox_queue:
l, b, r, t = map(int, bbox.extents)
w = r - l
h = t - b
reg = self.copy_from_bbox(bbox)
buf = reg.to_string_argb()
qimage = QtGui.QImage(buf, w, h, QtGui.QImage.Format_ARGB32)
if hasattr(qimage, 'setDevicePixelRatio'):
# Not available on Qt4 or some older Qt5.
qimage.setDevicePixelRatio(self._dpi_ratio)
origin = QtCore.QPoint(l, self.renderer.height - t)
painter.drawImage(origin / self._dpi_ratio, qimage)
# Adjust the buf reference count to work around a memory
# leak bug in QImage under PySide on Python 3.
if QT_API == 'PySide' and six.PY3:
ctypes.c_long.from_address(id(buf)).value = 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious what the history on why we had this done two different ways was.


# draw the zoom rectangle to the QPainter
if self._drawRect is not None:
pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio,
QtCore.Qt.DotLine)
painter.setPen(pen)
x, y, w, h = self._drawRect
painter.drawRect(x, y, w, h)

painter.end()

def draw(self):
"""
Expand Down Expand Up @@ -213,7 +170,7 @@ def blit(self, bbox=None):
if bbox is None and self.figure:
bbox = self.figure.bbox

self.blitbox.append(bbox)
self._bbox_queue.append(bbox)

# repaint uses logical pixels, not physical pixels like the renderer.
l, b, w, h = [pt / self._dpi_ratio for pt in bbox.bounds]
Expand All @@ -225,8 +182,7 @@ def print_figure(self, *args, **kwargs):
self.draw()


class FigureCanvasQTAgg(FigureCanvasQTAggBase,
FigureCanvasQT, FigureCanvasAgg):
class FigureCanvasQTAgg(FigureCanvasQTAggBase, FigureCanvasQT):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc.
Expand All @@ -244,14 +200,11 @@ def __init__(self, figure):
if DEBUG:
print('FigureCanvasQtAgg: ', figure)
super(FigureCanvasQTAgg, self).__init__(figure=figure)
self._drawRect = None
self.blitbox = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This going private needs to be documented and gracefully deprecated. Users who use blitting are extra clever and are likely abusing this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done (modulo comment below)

# 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)


FigureCanvas = FigureCanvasQTAgg
Expand Down