From 71212343ed7ef6e468063ecb82fb58b4ba2d67f3 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 13 Mar 2024 04:35:46 +0100 Subject: [PATCH 1/4] Simplify the qt backend by using buffers to construct the image to be restored --- lib/matplotlib/backends/backend_qtagg.py | 28 ++++++++++-------------- src/_backend_agg_wrapper.cpp | 9 ++++++++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/backends/backend_qtagg.py b/lib/matplotlib/backends/backend_qtagg.py index 256e50a3d1c3..830f0b29329e 100644 --- a/lib/matplotlib/backends/backend_qtagg.py +++ b/lib/matplotlib/backends/backend_qtagg.py @@ -4,13 +4,14 @@ import ctypes -from matplotlib.transforms import Bbox +import numpy as np from .qt_compat import QT_API, QtCore, 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 +48,20 @@ 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)) + region = self.copy_from_bbox(bbox) - if QT_API == "PyQt6": - from PyQt6 import sip - ptr = int(sip.voidptr(buf)) - else: - ptr = buf + # Now draw the region we copied with the painter. + r_width, r_height = region.get_bounds()[2:] - painter.eraseRect(rect) # clear the widget canvas - qimage = QtGui.QImage(ptr, buf.shape[1], buf.shape[0], - QtGui.QImage.Format.Format_RGBA8888) + # Draw the region copied with the QPainter + qimage = QtGui.QImage( + np.frombuffer(region, dtype=np.uint8), + r_width, r_height, + 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..77e68785476e 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -75,6 +75,14 @@ static PyObject *PyBufferRegion_get_extents(PyBufferRegion *self, PyObject *args return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2, rect.y2); } +static PyObject *PyBufferRegion_get_bounds(PyBufferRegion *self, PyObject *args) +{ + agg::rect_i rect = self->x->get_rect(); + + return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); +} + + int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags) { Py_INCREF(self); @@ -105,6 +113,7 @@ static PyTypeObject *PyBufferRegion_init_type() { "set_x", (PyCFunction)PyBufferRegion_set_x, METH_VARARGS, NULL }, { "set_y", (PyCFunction)PyBufferRegion_set_y, METH_VARARGS, NULL }, { "get_extents", (PyCFunction)PyBufferRegion_get_extents, METH_NOARGS, NULL }, + { "get_bounds", (PyCFunction)PyBufferRegion_get_bounds, METH_NOARGS, NULL }, { NULL } }; From 3d1ce960afa030c264381f5694f5673550c6f6c4 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 13 Mar 2024 05:02:48 +0100 Subject: [PATCH 2/4] Fix flake --- lib/matplotlib/backends/backend_qtagg.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_qtagg.py b/lib/matplotlib/backends/backend_qtagg.py index 830f0b29329e..70a99c20ce21 100644 --- a/lib/matplotlib/backends/backend_qtagg.py +++ b/lib/matplotlib/backends/backend_qtagg.py @@ -2,11 +2,9 @@ Render to qt from agg. """ -import ctypes - import numpy as np -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 From 391ac40ae085d94ef2dacd2b9a5cdc4d46327c81 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 13 Mar 2024 09:02:55 +0100 Subject: [PATCH 3/4] Ensure that we maintain the eraseRect behaviour to avoid reverting the fix from https://github.com/matplotlib/matplotlib/pull/13050 --- lib/matplotlib/backends/backend_qtagg.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/matplotlib/backends/backend_qtagg.py b/lib/matplotlib/backends/backend_qtagg.py index 70a99c20ce21..624bee2d0b66 100644 --- a/lib/matplotlib/backends/backend_qtagg.py +++ b/lib/matplotlib/backends/backend_qtagg.py @@ -33,6 +33,7 @@ def paintEvent(self, event): # 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) @@ -58,6 +59,12 @@ def paintEvent(self, event): QtGui.QImage.Format.Format_RGBA8888, ) qimage.setDevicePixelRatio(self.device_pixel_ratio) + + # Clear the rect, ensuring that issues encountered with images + # with transparency compose correctly, as seen in + # https://github.com/matplotlib/matplotlib/pull/13050. + painter.eraseRect(rect) + # set origin using original QT coordinates painter.drawImage(rect.topLeft(), qimage) From 0258fb73bb043c09f49b9da91bb82a25d3693a55 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 14 Mar 2024 10:14:36 +0100 Subject: [PATCH 4/4] Apply the proposed improvement to backend_qtagg to avoid needing to extend the API of the inaccessible PyBufferRegion object (Agg only) --- lib/matplotlib/backends/backend_qtagg.py | 18 +++++------------- src/_backend_agg_wrapper.cpp | 15 ++++++--------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/lib/matplotlib/backends/backend_qtagg.py b/lib/matplotlib/backends/backend_qtagg.py index 624bee2d0b66..0fdea169a179 100644 --- a/lib/matplotlib/backends/backend_qtagg.py +++ b/lib/matplotlib/backends/backend_qtagg.py @@ -33,7 +33,6 @@ def paintEvent(self, event): # 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) @@ -47,24 +46,17 @@ def paintEvent(self, event): right = left + width # create a buffer using the image bounding box bbox = Bbox([[left, bottom], [right, top]]) - region = self.copy_from_bbox(bbox) + img = np.asarray(self.copy_from_bbox(bbox), dtype=np.uint8) - # Now draw the region we copied with the painter. - r_width, r_height = region.get_bounds()[2:] + # Clear the widget canvas, to avoid issues as seen in + # https://github.com/matplotlib/matplotlib/issues/13012 + painter.eraseRect(rect) - # Draw the region copied with the QPainter qimage = QtGui.QImage( - np.frombuffer(region, dtype=np.uint8), - r_width, r_height, + img, img.shape[1], img.shape[0], QtGui.QImage.Format.Format_RGBA8888, ) qimage.setDevicePixelRatio(self.device_pixel_ratio) - - # Clear the rect, ensuring that issues encountered with images - # with transparency compose correctly, as seen in - # https://github.com/matplotlib/matplotlib/pull/13050. - painter.eraseRect(rect) - # set origin using original QT coordinates painter.drawImage(rect.topLeft(), qimage) diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 77e68785476e..1f271a1aeaeb 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -75,14 +75,6 @@ static PyObject *PyBufferRegion_get_extents(PyBufferRegion *self, PyObject *args return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2, rect.y2); } -static PyObject *PyBufferRegion_get_bounds(PyBufferRegion *self, PyObject *args) -{ - agg::rect_i rect = self->x->get_rect(); - - return Py_BuildValue("IIII", rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); -} - - int PyBufferRegion_get_buffer(PyBufferRegion *self, Py_buffer *buf, int flags) { Py_INCREF(self); @@ -113,7 +105,6 @@ static PyTypeObject *PyBufferRegion_init_type() { "set_x", (PyCFunction)PyBufferRegion_set_x, METH_VARARGS, NULL }, { "set_y", (PyCFunction)PyBufferRegion_set_y, METH_VARARGS, NULL }, { "get_extents", (PyCFunction)PyBufferRegion_get_extents, METH_NOARGS, NULL }, - { "get_bounds", (PyCFunction)PyBufferRegion_get_bounds, METH_NOARGS, NULL }, { NULL } }; @@ -481,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; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy