Skip to content

Commit 56e87ef

Browse files
committed
Tighten a bit the RendererAgg API.
The get_content_extents and tostring_rgba_minimized methods of RendererAgg are used to implement 1) agg_filter and 2) rasterization in MixedModeRenderer (i.e. for vector backends). They can easily instead be implemented at the Python level (i.e., via _get_nonzero_slices), which makes it easier to plug in an alternative renderer (e.g., mplcairo) for rasterization in MixedModeRenderer. I'm not convinced _get_nonzero_slices needs to be exposed as public API (it's quite simple anyways), but it could be if we absolutely want to present an alternative for the deprecated methods.
1 parent 98961f6 commit 56e87ef

File tree

4 files changed

+42
-20
lines changed

4 files changed

+42
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``RendererAgg.get_content_extents``, ``RendererAgg.tostring_rgba_minimized``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
... are deprecated.

lib/matplotlib/backends/backend_agg.py

+13-11
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,12 @@ def _update_methods(self):
116116
# self.draw_path_collection = self._renderer.draw_path_collection
117117
self.draw_quad_mesh = self._renderer.draw_quad_mesh
118118
self.copy_from_bbox = self._renderer.copy_from_bbox
119-
self.get_content_extents = self._renderer.get_content_extents
120119

120+
@cbook.deprecated("3.4") # Also needs to be removed at C-level.
121+
def get_content_extents(self):
122+
return self._renderer.get_content_extents()
123+
124+
@cbook.deprecated("3.4")
121125
def tostring_rgba_minimized(self):
122126
extents = self.get_content_extents()
123127
bbox = [[extents[0], self.height - (extents[1] + extents[3])],
@@ -364,23 +368,21 @@ def post_processing(image, dpi):
364368
The saved renderer is restored and the returned image from
365369
post_processing is plotted (using draw_image) on it.
366370
"""
367-
368-
width, height = int(self.width), int(self.height)
369-
370-
buffer, (l, b, w, h) = self.tostring_rgba_minimized()
371+
orig_img = np.asarray(self.buffer_rgba())
372+
slice_y, slice_x = cbook._get_nonzero_slices(orig_img[..., 3])
373+
cropped_img = orig_img[slice_y, slice_x]
371374

372375
self._renderer = self._filter_renderers.pop()
373376
self._update_methods()
374377

375-
if w > 0 and h > 0:
376-
img = np.frombuffer(buffer, np.uint8)
377-
img, ox, oy = post_processing(img.reshape((h, w, 4)) / 255.,
378-
self.dpi)
378+
if cropped_img.size:
379+
img, ox, oy = post_processing(cropped_img / 255, self.dpi)
379380
gc = self.new_gc()
380381
if img.dtype.kind == 'f':
381382
img = np.asarray(img * 255., np.uint8)
382-
img = img[::-1]
383-
self._renderer.draw_image(gc, l + ox, height - b - h + oy, img)
383+
self._renderer.draw_image(
384+
gc, slice_x.start + ox, int(self.height) - slice_y.stop + oy,
385+
img[::-1])
384386

385387

386388
class FigureCanvasAgg(FigureCanvasBase):

lib/matplotlib/backends/backend_mixed.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import numpy as np
22

3+
from matplotlib import cbook
34
from matplotlib.backends.backend_agg import RendererAgg
45
from matplotlib.tight_bbox import process_figure_for_rasterizing
56

@@ -98,21 +99,19 @@ def stop_rasterizing(self):
9899
self._renderer = self._vector_renderer
99100

100101
height = self._height * self.dpi
101-
buffer, bounds = self._raster_renderer.tostring_rgba_minimized()
102-
l, b, w, h = bounds
103-
if w > 0 and h > 0:
104-
image = np.frombuffer(buffer, dtype=np.uint8)
105-
image = image.reshape((h, w, 4))
106-
image = image[::-1]
102+
img = np.asarray(self._raster_renderer.buffer_rgba())
103+
slice_y, slice_x = cbook._get_nonzero_slices(img[..., 3])
104+
cropped_img = img[slice_y, slice_x]
105+
if cropped_img.size:
107106
gc = self._renderer.new_gc()
108107
# TODO: If the mixedmode resolution differs from the figure's
109108
# dpi, the image must be scaled (dpi->_figdpi). Not all
110109
# backends support this.
111110
self._renderer.draw_image(
112111
gc,
113-
l * self._figdpi / self.dpi,
114-
(height-b-h) * self._figdpi / self.dpi,
115-
image)
112+
slice_x.start * self._figdpi / self.dpi,
113+
(height - slice_y.stop) * self._figdpi / self.dpi,
114+
cropped_img[::-1])
116115
self._raster_renderer = None
117116

118117
# restore the figure dpi.

lib/matplotlib/cbook/__init__.py

+18
Original file line numberDiff line numberDiff line change
@@ -2162,6 +2162,24 @@ def _unmultiplied_rgba8888_to_premultiplied_argb32(rgba8888):
21622162
return argb32
21632163

21642164

2165+
def _get_nonzero_slices(buf):
2166+
"""
2167+
Return the bounds of the nonzero region of a 2D array as a pair of slices.
2168+
2169+
``buf[_get_nonzero_slices(buf)]`` is the smallest sub-rectangle in *buf*
2170+
that encloses all non-zero entries in *buf*. If *buf* is fully zero, then
2171+
``(slice(0, 0), slice(0, 0))`` is returned.
2172+
"""
2173+
x_nz, = buf.any(axis=0).nonzero()
2174+
y_nz, = buf.any(axis=1).nonzero()
2175+
if len(x_nz) and len(y_nz):
2176+
l, r = x_nz[[0, -1]]
2177+
b, t = y_nz[[0, -1]]
2178+
return slice(b, t + 1), slice(l, r + 1)
2179+
else:
2180+
return slice(0, 0), slice(0, 0)
2181+
2182+
21652183
def _pformat_subprocess(command):
21662184
"""Pretty-format a subprocess command for printing/logging purposes."""
21672185
return (command if isinstance(command, str)

0 commit comments

Comments
 (0)