From 641f097c2edd8f46712c1bd8d137eaadd75d9c28 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 9 Jan 2018 22:01:08 -0800 Subject: [PATCH] WxCairo backend. --- doc/users/next_whats_new/more-cairo.rst | 8 +- lib/matplotlib/backends/backend_wx.py | 202 +++++++++++---------- lib/matplotlib/backends/backend_wxagg.py | 26 +-- lib/matplotlib/backends/backend_wxcairo.py | 53 ++++++ lib/matplotlib/rcsetup.py | 2 +- 5 files changed, 164 insertions(+), 127 deletions(-) create mode 100644 lib/matplotlib/backends/backend_wxcairo.py diff --git a/doc/users/next_whats_new/more-cairo.rst b/doc/users/next_whats_new/more-cairo.rst index 8f6f62f630a3..b81c6a981b01 100644 --- a/doc/users/next_whats_new/more-cairo.rst +++ b/doc/users/next_whats_new/more-cairo.rst @@ -1,5 +1,5 @@ -Cairo rendering for Qt canvases -------------------------------- +Cairo rendering for Qt and WX canvases +-------------------------------------- -The new ``Qt4Cairo`` and ``Qt5Cairo`` backends allow Qt canvases to use Cairo -rendering instead of Agg. +The new ``Qt4Cairo``, ``Qt5Cairo``, and ``WXCairo`` backends allow Qt and Wx +canvases to use Cairo rendering instead of Agg. diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index d2fd3c5218ab..424fc4973f1a 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -547,7 +547,7 @@ def get_wxcolour(self, color): alpha=int(a)) -class FigureCanvasWx(FigureCanvasBase, wx.Panel): +class _FigureCanvasWxBase(FigureCanvasBase, wx.Panel): """ The FigureCanvas contains the figure and does event handling. @@ -708,23 +708,11 @@ def draw_idle(self): """ DEBUG_MSG("draw_idle()", 1, self) self._isDrawn = False # Force redraw - # Triggering a paint event is all that is needed to defer drawing # until later. The platform will send the event when it thinks it is # a good time (usually as soon as there are no other events pending). self.Refresh(eraseBackground=False) - def draw(self, drawDC=None): - """ - Render the figure using RendererWx instance renderer, or using a - previously defined renderer if none is specified. - """ - DEBUG_MSG("draw()", 1, self) - self.renderer = RendererWx(self.bitmap, self.figure.dpi) - self.figure.draw(self.renderer) - self._isDrawn = True - self.gui_repaint(drawDC=drawDC) - def new_timer(self, *args, **kwargs): """ Creates a new backend-specific subclass of @@ -837,95 +825,14 @@ def gui_repaint(self, drawDC=None, origin='WX'): filetypes['xpm'] = 'X pixmap' def print_figure(self, filename, *args, **kwargs): - # Use pure Agg renderer to draw - FigureCanvasBase.print_figure(self, filename, *args, **kwargs) - # Restore the current view; this is needed because the - # artist contains methods rely on particular attributes - # of the rendered figure for determining things like - # bounding boxes. + super(_FigureCanvasWxBase, self).print_figure( + filename, *args, **kwargs) + # Restore the current view; this is needed because the artist contains + # methods rely on particular attributes of the rendered figure for + # determining things like bounding boxes. if self._isDrawn: self.draw() - def print_bmp(self, filename, *args, **kwargs): - return self._print_image(filename, wx.BITMAP_TYPE_BMP, *args, **kwargs) - - if not _has_pil: - def print_jpeg(self, filename, *args, **kwargs): - return self._print_image(filename, wx.BITMAP_TYPE_JPEG, - *args, **kwargs) - print_jpg = print_jpeg - - def print_pcx(self, filename, *args, **kwargs): - return self._print_image(filename, wx.BITMAP_TYPE_PCX, *args, **kwargs) - - def print_png(self, filename, *args, **kwargs): - return self._print_image(filename, wx.BITMAP_TYPE_PNG, *args, **kwargs) - - if not _has_pil: - def print_tiff(self, filename, *args, **kwargs): - return self._print_image(filename, wx.BITMAP_TYPE_TIF, - *args, **kwargs) - print_tif = print_tiff - - def print_xpm(self, filename, *args, **kwargs): - return self._print_image(filename, wx.BITMAP_TYPE_XPM, *args, **kwargs) - - def _print_image(self, filename, filetype, *args, **kwargs): - origBitmap = self.bitmap - - l, b, width, height = self.figure.bbox.bounds - width = int(math.ceil(width)) - height = int(math.ceil(height)) - - self.bitmap = wxc.EmptyBitmap(width, height) - - renderer = RendererWx(self.bitmap, self.figure.dpi) - - gc = renderer.new_gc() - - self.figure.draw(renderer) - - # image is the object that we call SaveFile on. - image = self.bitmap - # set the JPEG quality appropriately. Unfortunately, it is only - # possible to set the quality on a wx.Image object. So if we - # are saving a JPEG, convert the wx.Bitmap to a wx.Image, - # and set the quality. - if filetype == wx.BITMAP_TYPE_JPEG: - jpeg_quality = kwargs.get('quality', - rcParams['savefig.jpeg_quality']) - image = self.bitmap.ConvertToImage() - image.SetOption(wx.IMAGE_OPTION_QUALITY, str(jpeg_quality)) - - # Now that we have rendered into the bitmap, save it - # to the appropriate file type and clean up - if isinstance(filename, six.string_types): - if not image.SaveFile(filename, filetype): - DEBUG_MSG('print_figure() file save error', 4, self) - raise RuntimeError( - 'Could not save figure to %s\n' % - (filename)) - elif is_writable_file_like(filename): - if not isinstance(image, wx.Image): - image = image.ConvertToImage() - if not image.SaveStream(filename, filetype): - DEBUG_MSG('print_figure() file save error', 4, self) - raise RuntimeError( - 'Could not save figure to %s\n' % - (filename)) - - # Restore everything to normal - self.bitmap = origBitmap - - # Note: draw is required here since bits of state about the - # last renderer are strewn about the artist draw methods. Do - # not remove the draw without first verifying that these have - # been cleaned up. The artist contains() methods will fail - # otherwise. - if self._isDrawn: - self.draw() - self.Refresh() - def _onPaint(self, evt): """ Called when wxPaintEvt is generated @@ -1137,6 +1044,101 @@ def _onEnter(self, evt): FigureCanvasBase.enter_notify_event(self, guiEvent=evt) +class FigureCanvasWx(_FigureCanvasWxBase): + # Rendering to a Wx canvas using the deprecated Wx renderer. + + def draw(self, drawDC=None): + """ + Render the figure using RendererWx instance renderer, or using a + previously defined renderer if none is specified. + """ + DEBUG_MSG("draw()", 1, self) + self.renderer = RendererWx(self.bitmap, self.figure.dpi) + self.figure.draw(self.renderer) + self._isDrawn = True + self.gui_repaint(drawDC=drawDC) + + def print_bmp(self, filename, *args, **kwargs): + return self._print_image(filename, wx.BITMAP_TYPE_BMP, *args, **kwargs) + + if not _has_pil: + def print_jpeg(self, filename, *args, **kwargs): + return self._print_image(filename, wx.BITMAP_TYPE_JPEG, + *args, **kwargs) + print_jpg = print_jpeg + + def print_pcx(self, filename, *args, **kwargs): + return self._print_image(filename, wx.BITMAP_TYPE_PCX, *args, **kwargs) + + def print_png(self, filename, *args, **kwargs): + return self._print_image(filename, wx.BITMAP_TYPE_PNG, *args, **kwargs) + + if not _has_pil: + def print_tiff(self, filename, *args, **kwargs): + return self._print_image(filename, wx.BITMAP_TYPE_TIF, + *args, **kwargs) + print_tif = print_tiff + + def print_xpm(self, filename, *args, **kwargs): + return self._print_image(filename, wx.BITMAP_TYPE_XPM, *args, **kwargs) + + def _print_image(self, filename, filetype, *args, **kwargs): + origBitmap = self.bitmap + + l, b, width, height = self.figure.bbox.bounds + width = int(math.ceil(width)) + height = int(math.ceil(height)) + + self.bitmap = wxc.EmptyBitmap(width, height) + + renderer = RendererWx(self.bitmap, self.figure.dpi) + + gc = renderer.new_gc() + + self.figure.draw(renderer) + + # image is the object that we call SaveFile on. + image = self.bitmap + # set the JPEG quality appropriately. Unfortunately, it is only + # possible to set the quality on a wx.Image object. So if we + # are saving a JPEG, convert the wx.Bitmap to a wx.Image, + # and set the quality. + if filetype == wx.BITMAP_TYPE_JPEG: + jpeg_quality = kwargs.get('quality', + rcParams['savefig.jpeg_quality']) + image = self.bitmap.ConvertToImage() + image.SetOption(wx.IMAGE_OPTION_QUALITY, str(jpeg_quality)) + + # Now that we have rendered into the bitmap, save it + # to the appropriate file type and clean up + if isinstance(filename, six.string_types): + if not image.SaveFile(filename, filetype): + DEBUG_MSG('print_figure() file save error', 4, self) + raise RuntimeError( + 'Could not save figure to %s\n' % + (filename)) + elif is_writable_file_like(filename): + if not isinstance(image, wx.Image): + image = image.ConvertToImage() + if not image.SaveStream(filename, filetype): + DEBUG_MSG('print_figure() file save error', 4, self) + raise RuntimeError( + 'Could not save figure to %s\n' % + (filename)) + + # Restore everything to normal + self.bitmap = origBitmap + + # Note: draw is required here since bits of state about the + # last renderer are strewn about the artist draw methods. Do + # not remove the draw without first verifying that these have + # been cleaned up. The artist contains() methods will fail + # otherwise. + if self._isDrawn: + self.draw() + self.Refresh() + + ######################################################################## # # The following functions and classes are for pylab compatibility @@ -1213,7 +1215,7 @@ def _get_toolbar(self, statbar): return toolbar def get_canvas(self, fig): - return FigureCanvasWx(self, -1, fig) + return type(self.canvas)(self, -1, fig) def get_figure_manager(self): DEBUG_MSG("get_figure_manager()", 1, self) diff --git a/lib/matplotlib/backends/backend_wxagg.py b/lib/matplotlib/backends/backend_wxagg.py index 8383f50cde89..14864b1e47c1 100644 --- a/lib/matplotlib/backends/backend_wxagg.py +++ b/lib/matplotlib/backends/backend_wxagg.py @@ -6,27 +6,19 @@ import wx import matplotlib +from .. import cbook from . import wx_compat as wxc -from . import backend_wx from .backend_agg import FigureCanvasAgg from .backend_wx import ( - _BackendWx, FigureCanvasWx, FigureFrameWx, NavigationToolbar2Wx, DEBUG_MSG) + _BackendWx, _FigureCanvasWxBase, FigureFrameWx, NavigationToolbar2Wx) class FigureFrameWxAgg(FigureFrameWx): def get_canvas(self, fig): return FigureCanvasWxAgg(self, -1, fig) - def _get_toolbar(self, statbar): - if matplotlib.rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2WxAgg(self.canvas) - toolbar.set_status_bar(statbar) - else: - toolbar = None - return toolbar - -class FigureCanvasWxAgg(FigureCanvasAgg, FigureCanvasWx): +class FigureCanvasWxAgg(FigureCanvasAgg, _FigureCanvasWxBase): """ The FigureCanvas contains the figure and does event handling. @@ -41,7 +33,6 @@ def draw(self, drawDC=None): """ Render the figure using agg. """ - DEBUG_MSG("draw()", 1, self) FigureCanvasAgg.draw(self) self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) @@ -79,17 +70,8 @@ def blit(self, bbox=None): filetypes = FigureCanvasAgg.filetypes - def print_figure(self, filename, *args, **kwargs): - # Use pure Agg renderer to draw - FigureCanvasAgg.print_figure(self, filename, *args, **kwargs) - # Restore the current view; this is needed because the - # artist contains methods rely on particular attributes - # of the rendered figure for determining things like - # bounding boxes. - if self._isDrawn: - self.draw() - +@cbook.deprecated("2.2") class NavigationToolbar2WxAgg(NavigationToolbar2Wx): def get_canvas(self, frame, fig): return FigureCanvasWxAgg(frame, -1, fig) diff --git a/lib/matplotlib/backends/backend_wxcairo.py b/lib/matplotlib/backends/backend_wxcairo.py new file mode 100644 index 000000000000..bd61fa03780a --- /dev/null +++ b/lib/matplotlib/backends/backend_wxcairo.py @@ -0,0 +1,53 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +import wx + +from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo +from .backend_wx import ( + _BackendWx, _FigureCanvasWxBase, FigureFrameWx, NavigationToolbar2Wx) +from . import wx_compat as wxc + + +class FigureFrameWxCairo(FigureFrameWx): + def get_canvas(self, fig): + return FigureCanvasWxCairo(self, -1, fig) + + +class FigureCanvasWxCairo(_FigureCanvasWxBase, FigureCanvasCairo): + """ + The FigureCanvas contains the figure and does event handling. + + In the wxPython backend, it is derived from wxPanel, and (usually) lives + inside a frame instantiated by a FigureManagerWx. The parent window + probably implements a wxSizer to control the displayed control size - but + we give a hint as to our preferred minimum size. + """ + + def __init__(self, parent, id, figure): + # _FigureCanvasWxBase should be fixed to have the same signature as + # every other FigureCanvas and use cooperative inheritance, but in the + # meantime the following will make do. + _FigureCanvasWxBase.__init__(self, parent, id, figure) + FigureCanvasCairo.__init__(self, figure) + self._renderer = RendererCairo(self.figure.dpi) + + def draw(self, drawDC=None): + width = int(self.figure.bbox.width) + height = int(self.figure.bbox.height) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + self._renderer.set_ctx_from_surface(surface) + self._renderer.set_width_height(width, height) + self.figure.draw(self._renderer) + buf = surface.get_data() + self.bitmap = wxc.BitmapFromBuffer(width, height, buf) + self._isDrawn = True + self.gui_repaint(drawDC=drawDC, origin='WXCairo') + + +@_BackendWx.export +class _BackendWxCairo(_BackendWx): + FigureCanvas = FigureCanvasWxCairo + _frame_class = FigureFrameWxCairo diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 4890a0b1e9fd..6b1e453ba192 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -44,7 +44,7 @@ 'Qt4Agg', 'Qt4Cairo', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'WebAgg', - 'WX', 'WXAgg'] + 'WX', 'WXAgg', 'WXCairo'] non_interactive_bk = ['agg', 'cairo', 'gdk', 'pdf', 'pgf', 'ps', 'svg', 'template'] all_backends = interactive_bk + non_interactive_bk