diff --git a/lib/matplotlib/backends/backend_qtagg.py b/lib/matplotlib/backends/backend_qtagg.py index 256e50a3d1c3..0fdea169a179 100644 --- a/lib/matplotlib/backends/backend_qtagg.py +++ b/lib/matplotlib/backends/backend_qtagg.py @@ -2,15 +2,14 @@ Render to qt from agg. """ -import ctypes +import numpy as np -from matplotlib.transforms import Bbox - -from .qt_compat import QT_API, QtCore, QtGui +from .qt_compat import QtGui from .backend_agg import FigureCanvasAgg from .backend_qt import _BackendQT, FigureCanvasQT from .backend_qt import ( # noqa: F401 # pylint: disable=W0611 FigureManagerQT, NavigationToolbar2QT) +from ..transforms import Bbox class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT): @@ -47,25 +46,19 @@ def paintEvent(self, event): right = left + width # create a buffer using the image bounding box bbox = Bbox([[left, bottom], [right, top]]) - buf = memoryview(self.copy_from_bbox(bbox)) + img = np.asarray(self.copy_from_bbox(bbox), dtype=np.uint8) - if QT_API == "PyQt6": - from PyQt6 import sip - ptr = int(sip.voidptr(buf)) - else: - ptr = buf + # Clear the widget canvas, to avoid issues as seen in + # https://github.com/matplotlib/matplotlib/issues/13012 + painter.eraseRect(rect) - painter.eraseRect(rect) # clear the widget canvas - qimage = QtGui.QImage(ptr, buf.shape[1], buf.shape[0], - QtGui.QImage.Format.Format_RGBA8888) + qimage = QtGui.QImage( + img, img.shape[1], img.shape[0], + QtGui.QImage.Format.Format_RGBA8888, + ) qimage.setDevicePixelRatio(self.device_pixel_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. - if QT_API == "PySide2" and QtCore.__version_info__ < (5, 12): - ctypes.c_long.from_address(id(buf)).value = 1 + painter.drawImage(rect.topLeft(), qimage) self._draw_rect_callback(painter) finally: diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index eaf4bf6f5f9d..1f271a1aeaeb 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -472,6 +472,12 @@ static PyObject *PyRendererAgg_clear(PyRendererAgg *self, PyObject *args) static PyObject *PyRendererAgg_copy_from_bbox(PyRendererAgg *self, PyObject *args) { + // Note that whilst the copy_from_bbox call can technically return an image that + // is of a different rect than was requested, this is not used in the underlying + // backend. In the future, this copy_from_bbox will not return a PyBufferRegion, + // and instead simply return an image (the renderer interface may still expose a + // bbox in the response for convenience, but this doesn't need to be a special + // type at the C++ level). agg::rect_d bbox; BufferRegion *reg; PyObject *regobj;