backend_qt5agg.py 3.48 KB
Newer Older
Stelios Karozis's avatar
Stelios Karozis committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
"""
Render to qt from agg.
"""

import ctypes

from matplotlib.transforms import Bbox

from .. import cbook
from .backend_agg import FigureCanvasAgg
from .backend_qt5 import (
    QtCore, QtGui, QtWidgets, _BackendQT5, FigureCanvasQT, FigureManagerQT,
    NavigationToolbar2QT, backend_version)
from .qt_compat import QT_API


class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT):

    def __init__(self, figure):
        # Must pass 'figure' as kwarg to Qt base class.
        super().__init__(figure=figure)

    def paintEvent(self, event):
        """
        Copy the image from the Agg canvas to the qt.drawable.

        In Qt, all drawing should be done inside of here when a widget is
        shown onscreen.
        """
        if self._update_dpi():
            # The dpi update triggered its own paintEvent.
            return
        self._draw_idle()  # Only does something if a draw is pending.

        # If the canvas does not have a renderer, then give up and wait for
        # FigureCanvasAgg.draw(self) to be called.
        if not hasattr(self, 'renderer'):
            return

        painter = QtGui.QPainter(self)

        # See documentation of QRect: bottom() and right() are off by 1, so use
        # left() + width() and top() + height().
        rect = event.rect()
        # scale rect dimensions using the screen dpi ratio to get
        # correct values for the Figure coordinates (rather than QT5's coords)
        width = rect.width() * self._dpi_ratio
        height = rect.height() * self._dpi_ratio
        left, top = self.mouseEventCoords(rect.topLeft())
        # shift the "top" by the height of the image to get the
        # correct corner for our coordinate system
        bottom = top - height
        # same with the right side of the image
        right = left + width
        # create a buffer using the image bounding box
        bbox = Bbox([[left, bottom], [right, top]])
        reg = self.copy_from_bbox(bbox)
        buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(
            memoryview(reg))

        # clear the widget canvas
        painter.eraseRect(rect)

        qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0],
                              QtGui.QImage.Format_ARGB32_Premultiplied)
        if hasattr(qimage, 'setDevicePixelRatio'):
            # Not available on Qt4 or some older Qt5.
            qimage.setDevicePixelRatio(self._dpi_ratio)
        # set origin using original QT coordinates
        origin = QtCore.QPoint(rect.left(), rect.top())
        painter.drawImage(origin, qimage)
        # Adjust the buf reference count to work around a memory
        # leak bug in QImage under PySide on Python 3.
        if QT_API in ('PySide', 'PySide2'):
            ctypes.c_long.from_address(id(buf)).value = 1

        self._draw_rect_callback(painter)

        painter.end()

    def blit(self, bbox=None):
        # docstring inherited
        # If bbox is None, blit the entire canvas. Otherwise
        # blit only the area defined by the bbox.
        if bbox is None and self.figure:
            bbox = self.figure.bbox

        # 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 / self._dpi_ratio - t, w, h)

    def print_figure(self, *args, **kwargs):
        super().print_figure(*args, **kwargs)
        self.draw()


@_BackendQT5.export
class _BackendQT5Agg(_BackendQT5):
    FigureCanvas = FigureCanvasQTAgg