Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b6a2739

Browse files
committed
Merge consecutive rasterizations
In vector output it is possible to flag artists to be rasterized. In many cases with multiple rasterized objects there can be significant file size savings by combining the rendered bitmaps into a single bitmap. This is achieved by moving the depth tracking logic from start_rasterizing() and stop_rasterizing() functions into the allow_rasterization() wrapper. This allows delaying the call to stop_rasterizing() until we are about to draw an non rasterized artist. The outer draw method, i.e. in Figure must be wraped with _finalize_rasterization() to ensure the that rasterization is completed. Figure.suppressComposite can be used to prevent merging. This fixes #17149
1 parent b94812c commit b6a2739

File tree

4 files changed

+63
-37
lines changed

4 files changed

+63
-37
lines changed

lib/matplotlib/artist.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,17 @@ def allow_rasterization(draw):
3434
def draw_wrapper(artist, renderer, *args, **kwargs):
3535
try:
3636
if artist.get_rasterized():
37-
renderer.start_rasterizing()
37+
if renderer._raster_depth == 0 and not renderer._rasterizing:
38+
renderer.start_rasterizing()
39+
renderer._rasterizing = True
40+
renderer._raster_depth += 1
41+
else:
42+
if renderer._raster_depth == 0 and renderer._rasterizing:
43+
# Only stop when we are not in a rasterized parent
44+
# and something has be rasterized since last stop
45+
renderer.stop_rasterizing()
46+
renderer._rasterizing = False
47+
3848
if artist.get_agg_filter() is not None:
3949
renderer.start_filter()
4050

@@ -43,12 +53,32 @@ def draw_wrapper(artist, renderer, *args, **kwargs):
4353
if artist.get_agg_filter() is not None:
4454
renderer.stop_filter(artist.get_agg_filter())
4555
if artist.get_rasterized():
56+
renderer._raster_depth -= 1
57+
if (renderer._rasterizing and artist.figure and
58+
artist.figure.suppressComposite):
59+
# restart rasterizing to prevent merging
4660
renderer.stop_rasterizing()
61+
renderer.start_rasterizing()
4762

4863
draw_wrapper._supports_rasterization = True
4964
return draw_wrapper
5065

5166

67+
def _finalize_rasterization(draw):
68+
"""
69+
Decorator for Artist.draw method. Needed on the outermost artist, i.e.
70+
Figure, to finish up if the render is still in rasterized mode.
71+
"""
72+
@wraps(draw)
73+
def draw_wrapper(artist, renderer, *args, **kwargs):
74+
result = draw(artist, renderer, *args, **kwargs)
75+
if renderer._rasterizing:
76+
renderer.stop_rasterizing()
77+
renderer._rasterizing = False
78+
return result
79+
return draw_wrapper
80+
81+
5282
def _stale_axes_callback(self, val):
5383
if self.axes:
5484
self.axes.stale = val

lib/matplotlib/backend_bases.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ def __init__(self):
142142
super().__init__()
143143
self._texmanager = None
144144
self._text2path = textpath.TextToPath()
145+
self._raster_depth = 0
146+
self._rasterizing = False
145147

146148
def open_group(self, s, gid=None):
147149
"""

lib/matplotlib/backends/backend_mixed.py

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
5252
self._vector_renderer = vector_renderer
5353

5454
self._raster_renderer = None
55-
self._rasterizing = 0
5655

5756
# A reference to the figure is needed as we need to change
5857
# the figure dpi before and after the rasterization. Although
@@ -84,47 +83,40 @@ def start_rasterizing(self):
8483
r = process_figure_for_rasterizing(self.figure,
8584
self._bbox_inches_restore)
8685
self._bbox_inches_restore = r
87-
if self._rasterizing == 0:
88-
self._raster_renderer = self._raster_renderer_class(
89-
self._width*self.dpi, self._height*self.dpi, self.dpi)
90-
self._renderer = self._raster_renderer
91-
self._rasterizing += 1
86+
87+
self._raster_renderer = self._raster_renderer_class(
88+
self._width*self.dpi, self._height*self.dpi, self.dpi)
89+
self._renderer = self._raster_renderer
9290

9391
def stop_rasterizing(self):
9492
"""
9593
Exit "raster" mode. All of the drawing that was done since
9694
the last `start_rasterizing` call will be copied to the
9795
vector backend by calling draw_image.
98-
99-
If `start_rasterizing` has been called multiple times,
100-
`stop_rasterizing` must be called the same number of times before
101-
"raster" mode is exited.
10296
"""
103-
self._rasterizing -= 1
104-
if self._rasterizing == 0:
105-
self._renderer = self._vector_renderer
106-
107-
height = self._height * self.dpi
108-
buffer, bounds = self._raster_renderer.tostring_rgba_minimized()
109-
l, b, w, h = bounds
110-
if w > 0 and h > 0:
111-
image = np.frombuffer(buffer, dtype=np.uint8)
112-
image = image.reshape((h, w, 4))
113-
image = image[::-1]
114-
gc = self._renderer.new_gc()
115-
# TODO: If the mixedmode resolution differs from the figure's
116-
# dpi, the image must be scaled (dpi->_figdpi). Not all
117-
# backends support this.
118-
self._renderer.draw_image(
119-
gc,
120-
l * self._figdpi / self.dpi,
121-
(height-b-h) * self._figdpi / self.dpi,
122-
image)
123-
self._raster_renderer = None
124-
self._rasterizing = False
125-
126-
# restore the figure dpi.
127-
self.figure.set_dpi(self._figdpi)
97+
98+
self._renderer = self._vector_renderer
99+
100+
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]
107+
gc = self._renderer.new_gc()
108+
# TODO: If the mixedmode resolution differs from the figure's
109+
# dpi, the image must be scaled (dpi->_figdpi). Not all
110+
# backends support this.
111+
self._renderer.draw_image(
112+
gc,
113+
l * self._figdpi / self.dpi,
114+
(height-b-h) * self._figdpi / self.dpi,
115+
image)
116+
self._raster_renderer = None
117+
118+
# restore the figure dpi.
119+
self.figure.set_dpi(self._figdpi)
128120

129121
if self._bbox_inches_restore: # when tight bbox is used
130122
r = process_figure_for_rasterizing(self.figure,

lib/matplotlib/figure.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from matplotlib import __version__ as _mpl_version
1919

2020
import matplotlib.artist as martist
21-
from matplotlib.artist import Artist, allow_rasterization
21+
from matplotlib.artist import (
22+
Artist, allow_rasterization, _finalize_rasterization)
2223
from matplotlib.backend_bases import (
2324
FigureCanvasBase, NonGuiException, MouseButton)
2425
import matplotlib.cbook as cbook
@@ -1689,6 +1690,7 @@ def clear(self, keep_observers=False):
16891690
"""Clear the figure -- synonym for `clf`."""
16901691
self.clf(keep_observers=keep_observers)
16911692

1693+
@_finalize_rasterization
16921694
@allow_rasterization
16931695
def draw(self, renderer):
16941696
# docstring inherited

0 commit comments

Comments
 (0)