Skip to content

Tighten a bit the RendererAgg API. #17788

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/api/next_api_changes/deprecations/17788-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
``RendererAgg.get_content_extents``, ``RendererAgg.tostring_rgba_minimized``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... are deprecated.
27 changes: 16 additions & 11 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,15 @@ def _update_methods(self):
# self.draw_path_collection = self._renderer.draw_path_collection
self.draw_quad_mesh = self._renderer.draw_quad_mesh
self.copy_from_bbox = self._renderer.copy_from_bbox
self.get_content_extents = self._renderer.get_content_extents

@cbook.deprecated("3.4")
def get_content_extents(self):
orig_img = np.asarray(self.buffer_rgba())
slice_y, slice_x = cbook._get_nonzero_slices(orig_img[..., 3])
return (slice_x.start, slice_y.start,
slice_x.stop - slice_x.start, slice_y.stop - slice_y.start)

@cbook.deprecated("3.4")
def tostring_rgba_minimized(self):
extents = self.get_content_extents()
bbox = [[extents[0], self.height - (extents[1] + extents[3])],
Expand Down Expand Up @@ -364,23 +371,21 @@ def post_processing(image, dpi):
The saved renderer is restored and the returned image from
post_processing is plotted (using draw_image) on it.
"""

width, height = int(self.width), int(self.height)

buffer, (l, b, w, h) = self.tostring_rgba_minimized()
orig_img = np.asarray(self.buffer_rgba())
slice_y, slice_x = cbook._get_nonzero_slices(orig_img[..., 3])
cropped_img = orig_img[slice_y, slice_x]

self._renderer = self._filter_renderers.pop()
self._update_methods()

if w > 0 and h > 0:
img = np.frombuffer(buffer, np.uint8)
img, ox, oy = post_processing(img.reshape((h, w, 4)) / 255.,
self.dpi)
if cropped_img.size:
img, ox, oy = post_processing(cropped_img / 255, self.dpi)
gc = self.new_gc()
if img.dtype.kind == 'f':
img = np.asarray(img * 255., np.uint8)
img = img[::-1]
self._renderer.draw_image(gc, l + ox, height - b - h + oy, img)
self._renderer.draw_image(
gc, slice_x.start + ox, int(self.height) - slice_y.stop + oy,
img[::-1])


class FigureCanvasAgg(FigureCanvasBase):
Expand Down
17 changes: 8 additions & 9 deletions lib/matplotlib/backends/backend_mixed.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numpy as np

from matplotlib import cbook
from matplotlib.backends.backend_agg import RendererAgg
from matplotlib.tight_bbox import process_figure_for_rasterizing

Expand Down Expand Up @@ -98,21 +99,19 @@ def stop_rasterizing(self):
self._renderer = self._vector_renderer

height = self._height * self.dpi
buffer, bounds = self._raster_renderer.tostring_rgba_minimized()
l, b, w, h = bounds
if w > 0 and h > 0:
image = np.frombuffer(buffer, dtype=np.uint8)
image = image.reshape((h, w, 4))
image = image[::-1]
img = np.asarray(self._raster_renderer.buffer_rgba())
slice_y, slice_x = cbook._get_nonzero_slices(img[..., 3])
cropped_img = img[slice_y, slice_x]
if cropped_img.size:
gc = self._renderer.new_gc()
# TODO: If the mixedmode resolution differs from the figure's
# dpi, the image must be scaled (dpi->_figdpi). Not all
# backends support this.
self._renderer.draw_image(
gc,
l * self._figdpi / self.dpi,
(height-b-h) * self._figdpi / self.dpi,
image)
slice_x.start * self._figdpi / self.dpi,
(height - slice_y.stop) * self._figdpi / self.dpi,
cropped_img[::-1])
self._raster_renderer = None

# restore the figure dpi.
Expand Down
18 changes: 18 additions & 0 deletions lib/matplotlib/cbook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2162,6 +2162,24 @@ def _unmultiplied_rgba8888_to_premultiplied_argb32(rgba8888):
return argb32


def _get_nonzero_slices(buf):
"""
Return the bounds of the nonzero region of a 2D array as a pair of slices.

``buf[_get_nonzero_slices(buf)]`` is the smallest sub-rectangle in *buf*
that encloses all non-zero entries in *buf*. If *buf* is fully zero, then
``(slice(0, 0), slice(0, 0))`` is returned.
"""
x_nz, = buf.any(axis=0).nonzero()
y_nz, = buf.any(axis=1).nonzero()
if len(x_nz) and len(y_nz):
l, r = x_nz[[0, -1]]
b, t = y_nz[[0, -1]]
return slice(b, t + 1), slice(l, r + 1)
else:
return slice(0, 0), slice(0, 0)


def _pformat_subprocess(command):
"""Pretty-format a subprocess command for printing/logging purposes."""
return (command if isinstance(command, str)
Expand Down
35 changes: 0 additions & 35 deletions src/_backend_agg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,41 +167,6 @@ bool RendererAgg::render_clippath(py::PathIterator &clippath,
return has_clippath;
}

agg::rect_i RendererAgg::get_content_extents()
{
agg::rect_i r(width, height, 0, 0);

// Looks at the alpha channel to find the minimum extents of the image
unsigned char *pixel = pixBuffer + 3;
for (int y = 0; y < (int)height; ++y) {
for (int x = 0; x < (int)width; ++x) {
if (*pixel) {
if (x < r.x1)
r.x1 = x;
if (y < r.y1)
r.y1 = y;
if (x > r.x2)
r.x2 = x;
if (y > r.y2)
r.y2 = y;
}
pixel += 4;
}
}

if (r.x1 == (int)width && r.x2 == 0) {
// The buffer is completely empty.
r.x1 = r.y1 = r.x2 = r.y2 = 0;
} else {
r.x1 = std::max(0, r.x1);
r.y1 = std::max(0, r.y1);
r.x2 = std::min(r.x2 + 1, (int)width);
r.y2 = std::min(r.y2 + 1, (int)height);
}

return r;
}

void RendererAgg::clear()
{
//"clear the rendered buffer";
Expand Down
12 changes: 0 additions & 12 deletions src/_backend_agg_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,17 +526,6 @@ PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args, PyObje
Py_RETURN_NONE;
}

static PyObject *
PyRendererAgg_get_content_extents(PyRendererAgg *self, PyObject *args, PyObject *kwds)
{
agg::rect_i extents;

CALL_CPP("get_content_extents", (extents = self->x->get_content_extents()));

return Py_BuildValue(
"iiii", extents.x1, extents.y1, extents.x2 - extents.x1, extents.y2 - extents.y1);
}

int PyRendererAgg_get_buffer(PyRendererAgg *self, Py_buffer *buf, int flags)
{
Py_INCREF(self);
Expand Down Expand Up @@ -627,7 +616,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},

{"get_content_extents", (PyCFunction)PyRendererAgg_get_content_extents, METH_NOARGS, NULL},
{"clear", (PyCFunction)PyRendererAgg_clear, METH_NOARGS, NULL},

{"copy_from_bbox", (PyCFunction)PyRendererAgg_copy_from_bbox, METH_VARARGS, NULL},
Expand Down