From 65a0ccc3294efd0bebd1485fb67d3c78920c0aaa Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 17 Jun 2017 21:33:52 -0700 Subject: [PATCH 1/4] 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. --- lib/matplotlib/backends/backend_qt4agg.py | 20 +-- lib/matplotlib/backends/backend_qt5agg.py | 141 ++++++++-------------- 2 files changed, 49 insertions(+), 112 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4agg.py b/lib/matplotlib/backends/backend_qt4agg.py index 21997a06e9d7..e3668331960b 100644 --- a/lib/matplotlib/backends/backend_qt4agg.py +++ b/lib/matplotlib/backends/backend_qt4agg.py @@ -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 @@ -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): """ The canvas the figure renders into. Calls the draw and print fig methods, creates the renderers, etc... @@ -72,16 +66,6 @@ class FigureCanvasQTAgg(FigureCanvasQTAggBase, """ - def __init__(self, figure): - if DEBUG: - print('FigureCanvasQtAgg: ', figure) - FigureCanvasQT.__init__(self, figure) - FigureCanvasQTAggBase.__init__(self, figure) - FigureCanvasAgg.__init__(self, figure) - self._drawRect = None - self.blitbox = [] - self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) - FigureCanvas = FigureCanvasQTAgg FigureManager = FigureManagerQT diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index 9f7d20c2bca0..f49cc31337ee 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -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 @@ -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 @@ -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... @@ -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: @@ -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. @@ -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: - 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) - # 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 + + # 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): """ @@ -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] @@ -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. @@ -244,14 +200,11 @@ def __init__(self, figure): if DEBUG: print('FigureCanvasQtAgg: ', figure) super(FigureCanvasQTAgg, self).__init__(figure=figure) - self._drawRect = None - self.blitbox = [] # 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 From 05e5ae1074927aa8c4fae60289468de52c69a52e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 14 Jun 2017 21:39:44 -0700 Subject: [PATCH 2/4] Remove unused _needs_redraw from gtk3 backends. --- lib/matplotlib/backends/backend_gtk3.py | 5 ----- lib/matplotlib/backends/backend_gtk3agg.py | 7 ++----- lib/matplotlib/backends/backend_gtk3cairo.py | 5 ----- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index c084f6d6dccc..ace65526be21 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -186,7 +186,6 @@ def __init__(self, figure): GObject.GObject.__init__(self) self._idle_draw_id = 0 - self._need_redraw = True self._lastCursor = None self.connect('scroll_event', self.scroll_event) @@ -299,12 +298,9 @@ def configure_event(self, widget, event): w, h = event.width, event.height if w < 3 or h < 3: return # empty fig - # resize the figure (in inches) dpi = self.figure.dpi self.figure.set_size_inches(w/dpi, h/dpi, forward=False) - self._need_redraw = True - return False # finish event propagation? def on_draw_event(self, widget, ctx): @@ -312,7 +308,6 @@ def on_draw_event(self, widget, ctx): pass def draw(self): - self._need_redraw = True if self.get_visible() and self.get_mapped(): self.queue_draw() # do a synchronous draw (its less efficient than an async draw, diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index c3eb1da68be3..9cb02971ea21 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -38,11 +38,8 @@ def on_draw_event(self, widget, ctx): w, h = allocation.width, allocation.height if not len(self._bbox_queue): - if self._need_redraw: - self._render_figure(w, h) - bbox_queue = [transforms.Bbox([[0, 0], [w, h]])] - else: - return + self._render_figure(w, h) + bbox_queue = [transforms.Bbox([[0, 0], [w, h]])] else: bbox_queue = self._bbox_queue diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index da8f099be7f6..b01f51b638ad 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -36,15 +36,10 @@ def _render_figure(self, width, height): def on_draw_event(self, widget, ctx): """ GtkDrawable draw event, like expose_event in GTK 2.X """ - # the _need_redraw flag doesnt work. it sometimes prevents - # the rendering and leaving the canvas blank - #if self._need_redraw: self._renderer.set_context(ctx) allocation = self.get_allocation() x, y, w, h = allocation.x, allocation.y, allocation.width, allocation.height self._render_figure(w, h) - #self._need_redraw = False - return False # finish event propagation? From 6d2380ef034beab47426b81f8275a0d04e65fd32 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 13 Jun 2017 23:51:45 -0700 Subject: [PATCH 3/4] Minor cleanups. --- lib/matplotlib/backends/backend_cairo.py | 215 ++++++++++----------- lib/matplotlib/backends/backend_gtk3.py | 1 + lib/matplotlib/backends/backend_gtk3agg.py | 1 - lib/matplotlib/backends/backend_qt4agg.py | 29 +-- lib/matplotlib/backends/backend_qt5agg.py | 39 ++-- lib/matplotlib/backends/backend_tkagg.py | 1 + 6 files changed, 117 insertions(+), 169 deletions(-) diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index c41c74711214..be59deae2aee 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -23,7 +23,10 @@ import six -import os, sys, warnings, gzip +import gzip +import os +import sys +import warnings import numpy as np @@ -40,11 +43,11 @@ else: HAS_CAIRO_CFFI = True -_version_required = (1,2,0) +_version_required = (1, 2, 0) if cairo.version_info < _version_required: - raise ImportError ("Pycairo %d.%d.%d is installed\n" - "Pycairo %d.%d.%d or later is required" - % (cairo.version_info + _version_required)) + raise ImportError("Pycairo %d.%d.%d is installed\n" + "Pycairo %d.%d.%d or later is required" + % (cairo.version_info + _version_required)) backend_version = cairo.version del _version_required @@ -56,12 +59,6 @@ from matplotlib.transforms import Bbox, Affine2D from matplotlib.font_manager import ttfFontProperty -# Image::color_conv(format) for draw_image() -if sys.byteorder == 'little': - BYTE_FORMAT = 0 # BGRA -else: - BYTE_FORMAT = 1 # ARGB - class ArrayWrapper: """Thin wrapper around numpy ndarray to expose the interface @@ -107,36 +104,31 @@ class RendererCairo(RendererBase): def __init__(self, dpi): - """ - """ self.dpi = dpi - self.gc = GraphicsContextCairo (renderer=self) - self.text_ctx = cairo.Context ( - cairo.ImageSurface (cairo.FORMAT_ARGB32,1,1)) + self.gc = GraphicsContextCairo(renderer=self) + self.text_ctx = cairo.Context( + cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)) self.mathtext_parser = MathTextParser('Cairo') - RendererBase.__init__(self) - def set_ctx_from_surface (self, surface): - self.gc.ctx = cairo.Context (surface) - + def set_ctx_from_surface(self, surface): + self.gc.ctx = cairo.Context(surface) def set_width_height(self, width, height): self.width = width self.height = height - self.matrix_flipy = cairo.Matrix (yy=-1, y0=self.height) + self.matrix_flipy = cairo.Matrix(yy=-1, y0=self.height) # use matrix_flipy for ALL rendering? # - problem with text? - will need to switch matrix_flipy off, or do a # font transform? - - def _fill_and_stroke (self, ctx, fill_c, alpha, alpha_overrides): + def _fill_and_stroke(self, ctx, fill_c, alpha, alpha_overrides): if fill_c is not None: ctx.save() if len(fill_c) == 3 or alpha_overrides: - ctx.set_source_rgba (fill_c[0], fill_c[1], fill_c[2], alpha) + ctx.set_source_rgba(fill_c[0], fill_c[1], fill_c[2], alpha) else: - ctx.set_source_rgba (fill_c[0], fill_c[1], fill_c[2], fill_c[3]) + ctx.set_source_rgba(fill_c[0], fill_c[1], fill_c[2], fill_c[3]) ctx.fill_preserve() ctx.restore() ctx.stroke() @@ -157,7 +149,6 @@ def convert_path(ctx, path, transform, clip=None): elif code == Path.CURVE4: ctx.curve_to(*points) - def draw_path(self, gc, path, transform, rgbFace=None): ctx = gc.ctx @@ -168,20 +159,23 @@ def draw_path(self, gc, path, transform, rgbFace=None): else: clip = None - transform = transform + \ - Affine2D().scale(1.0, -1.0).translate(0, self.height) + transform = (transform + + Affine2D().scale(1.0, -1.0).translate(0, self.height)) ctx.new_path() self.convert_path(ctx, path, transform, clip) - self._fill_and_stroke(ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) + self._fill_and_stroke( + ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) - def draw_markers(self, gc, marker_path, marker_trans, path, transform, rgbFace=None): + def draw_markers( + self, gc, marker_path, marker_trans, path, transform, rgbFace=None): ctx = gc.ctx ctx.new_path() # Create the path for the marker; it needs to be flipped here already! - self.convert_path(ctx, marker_path, marker_trans + Affine2D().scale(1.0, -1.0)) + self.convert_path( + ctx, marker_path, marker_trans + Affine2D().scale(1.0, -1.0)) marker_path = ctx.copy_path_flat() # Figure out whether the path has a fill @@ -193,11 +187,12 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, rgbFace=N else: filled = True - transform = transform + \ - Affine2D().scale(1.0, -1.0).translate(0, self.height) + transform = (transform + + Affine2D().scale(1.0, -1.0).translate(0, self.height)) ctx.new_path() - for i, (vertices, codes) in enumerate(path.iter_segments(transform, simplify=False)): + for i, (vertices, codes) in enumerate( + path.iter_segments(transform, simplify=False)): if len(vertices): x, y = vertices[-2:] ctx.save() @@ -213,11 +208,13 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, rgbFace=N # Also flush out the drawing every once in a while to # prevent the paths from getting way too long. if filled or i % 1000 == 0: - self._fill_and_stroke(ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) + self._fill_and_stroke( + ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) # Fast path, if there is no fill, draw everything in one step if not filled: - self._fill_and_stroke(ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) + self._fill_and_stroke( + ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) def draw_image(self, gc, x, y, im): # bbox - not currently used @@ -239,11 +236,9 @@ def draw_image(self, gc, x, y, im): # At the time of writing the latest release version of # py3cairo still does not support create_for_data imbuffer = im.flatten() - surface = cairo.ImageSurface.create_for_data(imbuffer, - cairo.FORMAT_ARGB32, - im.shape[1], - im.shape[0], - im.shape[1]*4) + surface = cairo.ImageSurface.create_for_data( + imbuffer, cairo.FORMAT_ARGB32, + im.shape[1], im.shape[0], im.shape[1]*4) ctx = gc.ctx y = self.height - y - im.shape[0] @@ -264,17 +259,17 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): else: ctx = gc.ctx ctx.new_path() - ctx.move_to (x, y) - ctx.select_font_face (prop.get_name(), - self.fontangles [prop.get_style()], - self.fontweights[prop.get_weight()]) + ctx.move_to(x, y) + ctx.select_font_face(prop.get_name(), + self.fontangles[prop.get_style()], + self.fontweights[prop.get_weight()]) size = prop.get_size_in_points() * self.dpi / 72.0 ctx.save() if angle: ctx.rotate(np.deg2rad(-angle)) - ctx.set_font_size (size) + ctx.set_font_size(size) if HAS_CAIRO_CFFI: if not isinstance(s, six.text_type): @@ -302,9 +297,9 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle): fontProp = ttfFontProperty(font) ctx.save() - ctx.select_font_face (fontProp.name, - self.fontangles [fontProp.style], - self.fontweights[fontProp.weight]) + ctx.select_font_face(fontProp.name, + self.fontangles[fontProp.style], + self.fontweights[fontProp.weight]) size = fontsize * self.dpi / 72.0 ctx.set_font_size(size) @@ -315,23 +310,20 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle): for ox, oy, w, h in rects: ctx.new_path() - ctx.rectangle (ox, oy, w, h) - ctx.set_source_rgb (0, 0, 0) + ctx.rectangle(ox, oy, w, h) + ctx.set_source_rgb(0, 0, 0) ctx.fill_preserve() ctx.restore() - def flipy(self): return True #return False # tried - all draw objects ok except text (and images?) # which comes out mirrored! - def get_canvas_width_height(self): return self.width, self.height - def get_text_width_height_descent(self, s, prop, ismath): if ismath: width, height, descent, fonts, used_characters = self.mathtext_parser.parse( @@ -340,35 +332,32 @@ def get_text_width_height_descent(self, s, prop, ismath): ctx = self.text_ctx ctx.save() - ctx.select_font_face (prop.get_name(), - self.fontangles [prop.get_style()], - self.fontweights[prop.get_weight()]) + ctx.select_font_face(prop.get_name(), + self.fontangles[prop.get_style()], + self.fontweights[prop.get_weight()]) # Cairo (says it) uses 1/96 inch user space units, ref: cairo_gstate.c # but if /96.0 is used the font is too small - - size = prop.get_size_in_points() * self.dpi / 72.0 + size = prop.get_size_in_points() * self.dpi / 72 # problem - scale remembers last setting and font can become # enormous causing program to crash # save/restore prevents the problem - ctx.set_font_size (size) + ctx.set_font_size(size) - y_bearing, w, h = ctx.text_extents (s)[1:4] + y_bearing, w, h = ctx.text_extents(s)[1:4] ctx.restore() return w, h, h + y_bearing - def new_gc(self): self.gc.ctx.save() self.gc._alpha = 1.0 self.gc._forced_alpha = False # if True, _alpha overrides A from RGBA return self.gc - def points_to_pixels(self, points): - return points/72.0 * self.dpi + return points / 72 * self.dpi class GraphicsContextCairo(GraphicsContextBase): @@ -384,54 +373,51 @@ class GraphicsContextCairo(GraphicsContextBase): 'round' : cairo.LINE_CAP_ROUND, } - def __init__(self, renderer): GraphicsContextBase.__init__(self) self.renderer = renderer - def restore(self): self.ctx.restore() - def set_alpha(self, alpha): GraphicsContextBase.set_alpha(self, alpha) _alpha = self.get_alpha() rgb = self._rgb if self.get_forced_alpha(): - self.ctx.set_source_rgba (rgb[0], rgb[1], rgb[2], _alpha) + self.ctx.set_source_rgba(rgb[0], rgb[1], rgb[2], _alpha) else: - self.ctx.set_source_rgba (rgb[0], rgb[1], rgb[2], rgb[3]) - + self.ctx.set_source_rgba(rgb[0], rgb[1], rgb[2], rgb[3]) #def set_antialiased(self, b): # enable/disable anti-aliasing is not (yet) supported by Cairo - def set_capstyle(self, cs): if cs in ('butt', 'round', 'projecting'): self._capstyle = cs - self.ctx.set_line_cap (self._capd[cs]) + self.ctx.set_line_cap(self._capd[cs]) else: raise ValueError('Unrecognized cap style. Found %s' % cs) - def set_clip_rectangle(self, rectangle): - if not rectangle: return - x,y,w,h = rectangle.bounds + if not rectangle: + return + x, y, w, h = rectangle.bounds # pixel-aligned clip-regions are faster x,y,w,h = np.round(x), np.round(y), np.round(w), np.round(h) ctx = self.ctx ctx.new_path() - ctx.rectangle (x, self.renderer.height - h - y, w, h) - ctx.clip () + ctx.rectangle(x, self.renderer.height - h - y, w, h) + ctx.clip() def set_clip_path(self, path): - if not path: return + if not path: + return tpath, affine = path.get_transformed_path_and_affine() ctx = self.ctx ctx.new_path() - affine = affine + Affine2D().scale(1.0, -1.0).translate(0.0, self.renderer.height) + affine = (affine + + Affine2D().scale(1, -1).translate(0, self.renderer.height)) RendererCairo.convert_path(ctx, tpath, affine) ctx.clip() @@ -441,8 +427,8 @@ def set_dashes(self, offset, dashes): self.ctx.set_dash([], 0) # switch dashes off else: self.ctx.set_dash( - list(self.renderer.points_to_pixels(np.asarray(dashes))), offset) - + list(self.renderer.points_to_pixels(np.asarray(dashes))), + offset) def set_foreground(self, fg, isRGBA=None): GraphicsContextBase.set_foreground(self, fg, isRGBA) @@ -461,10 +447,9 @@ def set_joinstyle(self, js): else: raise ValueError('Unrecognized join style. Found %s' % js) - def set_linewidth(self, w): self._linewidth = float(w) - self.ctx.set_line_width (self.renderer.points_to_pixels(w)) + self.ctx.set_line_width(self.renderer.points_to_pixels(w)) def new_figure_manager(num, *args, **kwargs): # called by backends/__init__.py @@ -485,17 +470,17 @@ def new_figure_manager_given_figure(num, figure): return manager -class FigureCanvasCairo (FigureCanvasBase): +class FigureCanvasCairo(FigureCanvasBase): def print_png(self, fobj, *args, **kwargs): width, height = self.get_width_height() - renderer = RendererCairo (self.figure.dpi) - renderer.set_width_height (width, height) - surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, width, height) - renderer.set_ctx_from_surface (surface) + renderer = RendererCairo(self.figure.dpi) + renderer.set_width_height(width, height) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + renderer.set_ctx_from_surface(surface) - self.figure.draw (renderer) - surface.write_to_png (fobj) + self.figure.draw(renderer) + surface.write_to_png(fobj) def print_pdf(self, fobj, *args, **kwargs): return self._save(fobj, 'pdf', *args, **kwargs) @@ -509,7 +494,7 @@ def print_svg(self, fobj, *args, **kwargs): def print_svgz(self, fobj, *args, **kwargs): return self._save(fobj, 'svgz', *args, **kwargs) - def _save (self, fo, format, **kwargs): + def _save(self, fo, fmt, **kwargs): # save PDF/PS/SVG orientation = kwargs.get('orientation', 'portrait') @@ -519,42 +504,42 @@ def _save (self, fo, format, **kwargs): width_in_points, height_in_points = w_in * dpi, h_in * dpi if orientation == 'landscape': - width_in_points, height_in_points = (height_in_points, - width_in_points) + width_in_points, height_in_points = ( + height_in_points, width_in_points) - if format == 'ps': + if fmt == 'ps': if not hasattr(cairo, 'PSSurface'): - raise RuntimeError ('cairo has not been compiled with PS ' - 'support enabled') - surface = cairo.PSSurface (fo, width_in_points, height_in_points) - elif format == 'pdf': + raise RuntimeError('cairo has not been compiled with PS ' + 'support enabled') + surface = cairo.PSSurface(fo, width_in_points, height_in_points) + elif fmt == 'pdf': if not hasattr(cairo, 'PDFSurface'): - raise RuntimeError ('cairo has not been compiled with PDF ' - 'support enabled') - surface = cairo.PDFSurface (fo, width_in_points, height_in_points) - elif format in ('svg', 'svgz'): + raise RuntimeError('cairo has not been compiled with PDF ' + 'support enabled') + surface = cairo.PDFSurface(fo, width_in_points, height_in_points) + elif fmt in ('svg', 'svgz'): if not hasattr(cairo, 'SVGSurface'): - raise RuntimeError ('cairo has not been compiled with SVG ' - 'support enabled') - if format == 'svgz': + raise RuntimeError('cairo has not been compiled with SVG ' + 'support enabled') + if fmt == 'svgz': if isinstance(fo, six.string_types): fo = gzip.GzipFile(fo, 'wb') else: fo = gzip.GzipFile(None, 'wb', fileobj=fo) - surface = cairo.SVGSurface (fo, width_in_points, height_in_points) + surface = cairo.SVGSurface(fo, width_in_points, height_in_points) else: - warnings.warn ("unknown format: %s" % format) + warnings.warn("unknown format: %s" % fmt) return # surface.set_dpi() can be used - renderer = RendererCairo (self.figure.dpi) - renderer.set_width_height (width_in_points, height_in_points) - renderer.set_ctx_from_surface (surface) + renderer = RendererCairo(self.figure.dpi) + renderer.set_width_height(width_in_points, height_in_points) + renderer.set_ctx_from_surface(surface) ctx = renderer.gc.ctx if orientation == 'landscape': - ctx.rotate (np.pi/2) - ctx.translate (0, -height_in_points) + ctx.rotate(np.pi/2) + ctx.translate(0, -height_in_points) # cairo/src/cairo_ps_surface.c # '%%Orientation: Portrait' is always written to the file header # '%%Orientation: Landscape' would possibly cause problems @@ -562,7 +547,7 @@ def _save (self, fo, format, **kwargs): # TODO: # add portrait/landscape checkbox to FileChooser - self.figure.draw (renderer) + self.figure.draw(renderer) show_fig_border = False # for testing figure orientation and scaling if show_fig_border: @@ -572,13 +557,13 @@ def _save (self, fo, format, **kwargs): ctx.set_source_rgb(1,0,0) ctx.stroke() ctx.move_to(30,30) - ctx.select_font_face ('sans-serif') + ctx.select_font_face('sans-serif') ctx.set_font_size(20) ctx.show_text('Origin corner') ctx.show_page() surface.finish() - if format == 'svgz': + if fmt == 'svgz': fo.close() diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index ace65526be21..4e53c1a4ee2e 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -116,6 +116,7 @@ def _on_timer(self): self._timer = None return False + class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase): keyvald = {65507 : 'control', 65505 : 'shift', diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index 9cb02971ea21..f60ac9b8154d 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -4,7 +4,6 @@ import six import numpy as np -import sys import warnings from . import backend_agg diff --git a/lib/matplotlib/backends/backend_qt4agg.py b/lib/matplotlib/backends/backend_qt4agg.py index e3668331960b..2ca9c3a0c02b 100644 --- a/lib/matplotlib/backends/backend_qt4agg.py +++ b/lib/matplotlib/backends/backend_qt4agg.py @@ -6,41 +6,20 @@ import six -import os # not used -import sys -import ctypes -import warnings - import matplotlib from matplotlib.figure import Figure - -from .backend_qt5agg import FigureCanvasQTAggBase - from .backend_agg import FigureCanvasAgg -from .backend_qt4 import QtCore -from .backend_qt4 import FigureManagerQT -from .backend_qt4 import FigureCanvasQT -from .backend_qt4 import NavigationToolbar2QT -##### not used -from .backend_qt4 import show -from .backend_qt4 import draw_if_interactive -from .backend_qt4 import backend_version -###### - -DEBUG = False - -_decref = ctypes.pythonapi.Py_DecRef -_decref.argtypes = [ctypes.py_object] -_decref.restype = None +from .backend_qt4 import ( + QtCore, FigureCanvasQT, FigureManagerQT, NavigationToolbar2QT, + backend_version, draw_if_interactive, show) +from .backend_qt5agg import FigureCanvasQTAggBase def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance """ - if DEBUG: - print('backend_qt4agg.new_figure_manager') FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) return new_figure_manager_given_figure(num, thisFig) diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index f49cc31337ee..50d05a4f5c24 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -14,27 +14,16 @@ from matplotlib.transforms import Bbox from .backend_agg import FigureCanvasAgg -from .backend_qt5 import QtCore -from .backend_qt5 import QtGui -from .backend_qt5 import FigureManagerQT -from .backend_qt5 import NavigationToolbar2QT -##### Modified Qt5 backend import -from .backend_qt5 import FigureCanvasQT -##### not used -from .backend_qt5 import show -from .backend_qt5 import draw_if_interactive -from .backend_qt5 import backend_version -###### +from .backend_qt5 import ( + QtCore, QtGui, FigureCanvasQT, FigureManagerQT, NavigationToolbar2QT, + backend_version, draw_if_interactive, show) from .qt_compat import QT_API -DEBUG = False def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance """ - if DEBUG: - print('backend_qt5agg.new_figure_manager') FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) return new_figure_manager_given_figure(num, thisFig) @@ -80,8 +69,8 @@ def blitbox(self): return self._bbox_queue def paintEvent(self, e): - """ - Copy the image from the Agg canvas to the qt.drawable. + """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. """ @@ -127,17 +116,15 @@ def paintEvent(self, e): painter.end() def draw(self): - """ - Draw the figure with Agg, and queue a request for a Qt draw. + """Draw the figure with Agg, and queue a request for a Qt draw. """ # The Agg draw is done here; delaying causes problems with code that # uses the result of the draw() to update plot elements. - FigureCanvasAgg.draw(self) + super(FigureCanvasQTAggBase, self).draw() self.update() def draw_idle(self): - """ - Queue redraw of the Agg buffer and request Qt paintEvent. + """Queue redraw of the Agg buffer and request Qt paintEvent. """ # The Agg draw needs to be handled by the same thread matplotlib # modifies the scene graph from. Post Agg draw request to the @@ -153,8 +140,7 @@ def __draw_idle_agg(self, *args): self._agg_draw_pending = False return try: - FigureCanvasAgg.draw(self) - self.update() + self.draw() except Exception: # Uncaught exceptions are fatal for PyQt5, so catch them instead. traceback.print_exc() @@ -162,8 +148,7 @@ def __draw_idle_agg(self, *args): self._agg_draw_pending = False def blit(self, bbox=None): - """ - Blit the region in bbox + """Blit the region in bbox. """ # If bbox is None, blit the entire canvas. Otherwise # blit only the area defined by the bbox. @@ -178,7 +163,7 @@ def blit(self, bbox=None): self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h) def print_figure(self, *args, **kwargs): - FigureCanvasAgg.print_figure(self, *args, **kwargs) + super(FigureCanvasQTAggBase, self).print_figure(*args, **kwargs) self.draw() @@ -197,8 +182,6 @@ class FigureCanvasQTAgg(FigureCanvasQTAggBase, FigureCanvasQT): """ def __init__(self, figure): - if DEBUG: - print('FigureCanvasQtAgg: ', figure) super(FigureCanvasQTAgg, self).__init__(figure=figure) # We don't want to scale up the figure DPI more than once. # Note, we don't handle a signal for changing DPI yet. diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index cf0ef737c893..a3d8a3d9bc7f 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -522,6 +522,7 @@ def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ + class FigureManagerTkAgg(FigureManagerBase): """ Attributes From 79cfe14466900dea1ad10176e4c644a09da5c6f7 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 9 Jul 2017 00:05:24 -0700 Subject: [PATCH 4/4] And some more cleanups. The removed comment in backend_agg regarding rgb came in in 281b7b7, but is now irrelevant. --- lib/matplotlib/backend_bases.py | 12 +++++------- lib/matplotlib/backends/backend_agg.py | 13 +++++-------- lib/matplotlib/backends/backend_cairo.py | 12 ------------ 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 0cecc46a1a24..0d381c5e11d7 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -694,17 +694,17 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath): def get_text_width_height_descent(self, s, prop, ismath): """ - get the width and height, and the offset from the bottom to the - baseline (descent), in display coords of the string s with - :class:`~matplotlib.font_manager.FontProperties` prop + Get the width, height, and descent (offset from the bottom + to the baseline), in display coords, of the string *s* with + :class:`~matplotlib.font_manager.FontProperties` *prop* """ if ismath == 'TeX': # todo: handle props size = prop.get_size_in_points() texmanager = self._text2path.get_texmanager() fontsize = prop.get_size_in_points() - w, h, d = texmanager.get_text_width_height_descent(s, fontsize, - renderer=self) + w, h, d = texmanager.get_text_width_height_descent( + s, fontsize, renderer=self) return w, h, d dpi = self.points_to_pixels(72) @@ -2151,7 +2151,6 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, # the backend to support file-like object, i'm going # to leave it as it is. However, a better solution # than stringIO seems to be needed. -JJL - #result = getattr(self, method_name) result = print_method( io.BytesIO(), dpi=dpi, @@ -2203,7 +2202,6 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, _bbox_inches_restore = None try: - #result = getattr(self, method_name)( result = print_method( filename, dpi=dpi, diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 3ff8338e8a1b..b0c876418344 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -212,20 +212,17 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): def get_text_width_height_descent(self, s, prop, ismath): """ - get the width and height in display coords of the string s - with FontPropertry prop - - # passing rgb is a little hack to make caching in the - # texmanager more efficient. It is not meant to be used - # outside the backend + Get the width, height, and descent (offset from the bottom + to the baseline), in display coords, of the string *s* with + :class:`~matplotlib.font_manager.FontProperties` *prop* """ if rcParams['text.usetex']: # todo: handle props size = prop.get_size_in_points() texmanager = self.get_texmanager() fontsize = prop.get_size_in_points() - w, h, d = texmanager.get_text_width_height_descent(s, fontsize, - renderer=self) + w, h, d = texmanager.get_text_width_height_descent( + s, fontsize, renderer=self) return w, h, d if ismath: diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index be59deae2aee..9318cca4f885 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -549,18 +549,6 @@ def _save(self, fo, fmt, **kwargs): self.figure.draw(renderer) - show_fig_border = False # for testing figure orientation and scaling - if show_fig_border: - ctx.new_path() - ctx.rectangle(0, 0, width_in_points, height_in_points) - ctx.set_line_width(4.0) - ctx.set_source_rgb(1,0,0) - ctx.stroke() - ctx.move_to(30,30) - ctx.select_font_face('sans-serif') - ctx.set_font_size(20) - ctx.show_text('Origin corner') - ctx.show_page() surface.finish() if fmt == 'svgz':