From 54cd9ceeac50d2a986c86a1c98cc67113dc73381 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 22 Jul 2018 15:31:16 +0200 Subject: [PATCH 1/2] Change {FigureCanvasAgg,RendererAgg}.buffer_rgba to return a memoryview. The `buffer_rgba` method now allows direct access to the renderer's underlying buffer (as a `(m, n, 4)`-shape memoryview) rather than copying the data to a new bytestring. This is consistent with the behavior on Py2, where a buffer object was returned. While this is technically a backwards-incompatible change, memoryviews are in fact quite compatible with bytes for most uses, and I'd argue that the bigger compatibility break was the change from no-copy in Py2 to copy in Py3 (after all that's the main point of the method...). --- doc/api/next_api_changes/2018-07-22-AL.rst | 7 +++++++ examples/misc/agg_buffer_to_array.py | 2 +- lib/matplotlib/backends/backend_agg.py | 17 ++++++++--------- 3 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 doc/api/next_api_changes/2018-07-22-AL.rst diff --git a/doc/api/next_api_changes/2018-07-22-AL.rst b/doc/api/next_api_changes/2018-07-22-AL.rst new file mode 100644 index 000000000000..53a8e927e8d3 --- /dev/null +++ b/doc/api/next_api_changes/2018-07-22-AL.rst @@ -0,0 +1,7 @@ +`FigureCanvasAgg.buffer_rgba` and `RendererAgg.buffer_rgba` now return a memoryview +``````````````````````````````````````````````````````````````````````````````````` + +The ``buffer_rgba`` method now allows direct access to the renderer's +underlying buffer (as a ``(m, n, 4)``-shape memoryview) rather than copying the +data to a new bytestring. This is consistent with the behavior on Py2, where a +buffer object was returned. diff --git a/examples/misc/agg_buffer_to_array.py b/examples/misc/agg_buffer_to_array.py index e254a6855350..0085a75de0bf 100644 --- a/examples/misc/agg_buffer_to_array.py +++ b/examples/misc/agg_buffer_to_array.py @@ -15,7 +15,7 @@ fig.canvas.draw() # grab the pixel buffer and dump it into a numpy array -X = np.array(fig.canvas.renderer._renderer) +X = np.array(fig.canvas.renderer.buffer_rgba()) # now display the array X as an Axes in a new figure fig2 = plt.figure() diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index a5708a1746c5..3c349554ae54 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -272,7 +272,7 @@ def tostring_argb(self): return self._renderer.tostring_argb() def buffer_rgba(self): - return self._renderer.buffer_rgba() + return memoryview(self._renderer) def clear(self): self._renderer.clear() @@ -421,7 +421,7 @@ def get_renderer(self, cleared=False): return self.renderer def tostring_rgb(self): - '''Get the image as an RGB byte string. + """Get the image as an RGB byte string. `draw` must be called at least once before this function will work and to update the renderer for any subsequent changes to the Figure. @@ -429,11 +429,11 @@ def tostring_rgb(self): Returns ------- bytes - ''' + """ return self.renderer.tostring_rgb() def tostring_argb(self): - '''Get the image as an ARGB byte string + """Get the image as an ARGB byte string. `draw` must be called at least once before this function will work and to update the renderer for any subsequent changes to the Figure. @@ -441,20 +441,19 @@ def tostring_argb(self): Returns ------- bytes - - ''' + """ return self.renderer.tostring_argb() def buffer_rgba(self): - '''Get the image as an RGBA byte string. + """Get the image as a memoryview to the renderer's buffer. `draw` must be called at least once before this function will work and to update the renderer for any subsequent changes to the Figure. Returns ------- - bytes - ''' + memoryview + """ return self.renderer.buffer_rgba() def print_raw(self, filename_or_obj, *args, **kwargs): From 5c16eca829c3ec1dccdd9c31dfb98000fc0f8953 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 23 Jul 2018 21:30:52 +0200 Subject: [PATCH 2/2] Use numpy.take instead of C-level manipulations for tostring_argb etc. --- lib/matplotlib/backends/backend_agg.py | 10 ++++---- src/_backend_agg.cpp | 23 ----------------- src/_backend_agg.h | 2 -- src/_backend_agg_wrapper.cpp | 34 -------------------------- 4 files changed, 5 insertions(+), 64 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 3c349554ae54..0a11d31b0ada 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -265,14 +265,14 @@ def points_to_pixels(self, points): """ return points * self.dpi / 72 - def tostring_rgb(self): - return self._renderer.tostring_rgb() + def buffer_rgba(self): + return memoryview(self._renderer) def tostring_argb(self): - return self._renderer.tostring_argb() + return np.asarray(self._renderer).take([3, 0, 1, 2], axis=2).tobytes() - def buffer_rgba(self): - return memoryview(self._renderer) + def tostring_rgb(self): + return np.asarray(self._renderer).take([0, 1, 2], axis=2).tobytes() def clear(self): self._renderer.clear() diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index 6d0f90400913..eaad7a3f04bc 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -156,29 +156,6 @@ bool RendererAgg::render_clippath(py::PathIterator &clippath, return has_clippath; } -void RendererAgg::tostring_rgb(uint8_t *buf) -{ - // "Return the rendered buffer as an RGB string" - - int row_len = width * 3; - - agg::rendering_buffer renderingBufferTmp; - renderingBufferTmp.attach(buf, width, height, row_len); - - agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_rgb24()); -} - -void RendererAgg::tostring_argb(uint8_t *buf) -{ - //"Return the rendered buffer as an RGB string"; - - int row_len = width * 4; - - agg::rendering_buffer renderingBufferTmp; - renderingBufferTmp.attach(buf, width, height, row_len); - agg::color_conv(&renderingBufferTmp, &renderingBuffer, agg::color_conv_rgba32_to_argb32()); -} - agg::rect_i RendererAgg::get_content_extents() { agg::rect_i r(width, height, 0, 0); diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 835302ca5719..55af1950773f 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -203,8 +203,6 @@ class RendererAgg ColorArray &colors, agg::trans_affine &trans); - void tostring_rgb(uint8_t *buf); - void tostring_argb(uint8_t *buf); agg::rect_i get_content_extents(); void clear(); diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 3c26a8b67cde..9a657af1cda2 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -525,38 +525,6 @@ PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args, PyObje Py_RETURN_NONE; } -static PyObject *PyRendererAgg_tostring_rgb(PyRendererAgg *self, PyObject *args, PyObject *kwds) -{ - PyObject *buffobj = NULL; - - buffobj = PyBytes_FromStringAndSize(NULL, self->x->get_width() * self->x->get_height() * 3); - if (buffobj == NULL) { - return NULL; - } - - CALL_CPP_CLEANUP("tostring_rgb", - (self->x->tostring_rgb((uint8_t *)PyBytes_AS_STRING(buffobj))), - Py_DECREF(buffobj)); - - return buffobj; -} - -static PyObject *PyRendererAgg_tostring_argb(PyRendererAgg *self, PyObject *args, PyObject *kwds) -{ - PyObject *buffobj = NULL; - - buffobj = PyBytes_FromStringAndSize(NULL, self->x->get_width() * self->x->get_height() * 4); - if (buffobj == NULL) { - return NULL; - } - - CALL_CPP_CLEANUP("tostring_argb", - (self->x->tostring_argb((uint8_t *)PyBytes_AS_STRING(buffobj))), - Py_DECREF(buffobj)); - - return buffobj; -} - static PyObject * PyRendererAgg_get_content_extents(PyRendererAgg *self, PyObject *args, PyObject *kwds) { @@ -664,8 +632,6 @@ static PyTypeObject *PyRendererAgg_init_type(PyObject *m, PyTypeObject *type) {"draw_gouraud_triangle", (PyCFunction)PyRendererAgg_draw_gouraud_triangle, METH_VARARGS, NULL}, {"draw_gouraud_triangles", (PyCFunction)PyRendererAgg_draw_gouraud_triangles, METH_VARARGS, NULL}, - {"tostring_rgb", (PyCFunction)PyRendererAgg_tostring_rgb, METH_NOARGS, NULL}, - {"tostring_argb", (PyCFunction)PyRendererAgg_tostring_argb, METH_NOARGS, NULL}, {"get_content_extents", (PyCFunction)PyRendererAgg_get_content_extents, METH_NOARGS, NULL}, {"buffer_rgba", (PyCFunction)PyRendererAgg_buffer_rgba, METH_NOARGS, NULL}, {"clear", (PyCFunction)PyRendererAgg_clear, METH_NOARGS, NULL},