From 7e57660573abbe567a25434b7bdea07acbd9f365 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 21 May 2020 20:48:47 +0200 Subject: [PATCH] Add support for blitting in qt5cairo. Test with e.g. examples/event_handling/poly_editor.py. Doing the same for the other foocairo backends is left as an exercise to the reader. --- lib/matplotlib/backends/backend_cairo.py | 40 +++++++++++++++++++++++ lib/matplotlib/backends/backend_qt5.py | 9 +++++ lib/matplotlib/backends/backend_qt5agg.py | 12 ------- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index 240552423884..77f0b8abb6bc 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -7,6 +7,7 @@ """ import gzip +import math import numpy as np @@ -402,8 +403,47 @@ def set_linewidth(self, w): self.ctx.set_line_width(self.renderer.points_to_pixels(w)) +class _CairoRegion: + def __init__(self, slices, data): + self._slices = slices + self._data = data + + class FigureCanvasCairo(FigureCanvasBase): + def copy_from_bbox(self, bbox): + surface = self._renderer.gc.ctx.get_target() + if not isinstance(surface, cairo.ImageSurface): + raise RuntimeError( + "copy_from_bbox only works when rendering to an ImageSurface") + sw = surface.get_width() + sh = surface.get_height() + x0 = math.ceil(bbox.x0) + x1 = math.floor(bbox.x1) + y0 = math.ceil(sh - bbox.y1) + y1 = math.floor(sh - bbox.y0) + if not (0 <= x0 and x1 <= sw and bbox.x0 <= bbox.x1 + and 0 <= y0 and y1 <= sh and bbox.y0 <= bbox.y1): + raise ValueError("Invalid bbox") + sls = slice(y0, y0 + max(y1 - y0, 0)), slice(x0, x0 + max(x1 - x0, 0)) + data = (np.frombuffer(surface.get_data(), np.uint32) + .reshape((sh, sw))[sls].copy()) + return _CairoRegion(sls, data) + + def restore_region(self, region): + surface = self._renderer.gc.ctx.get_target() + if not isinstance(surface, cairo.ImageSurface): + raise RuntimeError( + "restore_region only works when rendering to an ImageSurface") + surface.flush() + sw = surface.get_width() + sh = surface.get_height() + sly, slx = region._slices + (np.frombuffer(surface.get_data(), np.uint32) + .reshape((sh, sw))[sly, slx]) = region._data + surface.mark_dirty_rectangle( + slx.start, sly.start, slx.stop - slx.start, sly.stop - sly.start) + def print_png(self, fobj, *args, **kwargs): self._get_printed_image_surface().write_to_png(fobj) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 104207d1f089..1ad56febd22a 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -460,6 +460,15 @@ def draw_idle(self): self._draw_pending = True QtCore.QTimer.singleShot(0, self._draw_idle) + def blit(self, bbox=None): + # docstring inherited + if bbox is None and self.figure: + bbox = self.figure.bbox # Blit the entire canvas if bbox is None. + # 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.rect().height() - t, w, h) + def _draw_idle(self): with self._idle_draw_cntx(): if not self._draw_pending: diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index 6e90dc8a88ff..2d4d636d4018 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -78,18 +78,6 @@ def paintEvent(self, event): 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()