Skip to content

Commit c600130

Browse files
committed
Merge pull request matplotlib#3485 from jkseppan/reduce-xobjects
ENH : Reduce the use of XObjects in pdf backend
2 parents 917b9c1 + ef2c9fb commit c600130

File tree

8 files changed

+115
-5
lines changed

8 files changed

+115
-5
lines changed

lib/matplotlib/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,7 @@ def tk_window_focus():
14011401
'matplotlib.tests.test_artist',
14021402
'matplotlib.tests.test_axes',
14031403
'matplotlib.tests.test_axes_grid1',
1404+
'matplotlib.tests.test_backend_bases',
14041405
'matplotlib.tests.test_backend_pdf',
14051406
'matplotlib.tests.test_backend_pgf',
14061407
'matplotlib.tests.test_backend_ps',

lib/matplotlib/backend_bases.py

+17
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,23 @@ def _iter_collection_raw_paths(self, master_transform, paths,
384384
transform = Affine2D(all_transforms[i % Ntransforms])
385385
yield path, transform + master_transform
386386

387+
def _iter_collection_uses_per_path(self, paths, all_transforms,
388+
offsets, facecolors, edgecolors):
389+
"""
390+
Compute how many times each raw path object returned by
391+
_iter_collection_raw_paths would be used when calling
392+
_iter_collection. This is intended for the backend to decide
393+
on the tradeoff between using the paths in-line and storing
394+
them once and reusing. Rounds up in case the number of uses
395+
is not the same for every path.
396+
"""
397+
Npaths = len(paths)
398+
if Npaths == 0 or (len(facecolors) == 0 and len(edgecolors) == 0):
399+
return 0
400+
Npath_ids = max(Npaths, len(all_transforms))
401+
N = max(Npath_ids, len(offsets))
402+
return (N + Npath_ids - 1) // Npath_ids
403+
387404
def _iter_collection(self, gc, master_transform, all_transforms,
388405
path_ids, offsets, offsetTrans, facecolors,
389406
edgecolors, linewidths, linestyles,

lib/matplotlib/backends/backend_pdf.py

+18-5
Original file line numberDiff line numberDiff line change
@@ -1616,12 +1616,24 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
16161616
if not len(edgecolors):
16171617
stroked = False
16181618
else:
1619-
if np.all(edgecolors[:, 3] == edgecolors[0, 3]):
1619+
if np.all(np.asarray(linewidths) == 0.0):
1620+
stroked = False
1621+
elif np.all(edgecolors[:, 3] == edgecolors[0, 3]):
16201622
stroked = edgecolors[0, 3] != 0.0
16211623
else:
16221624
can_do_optimization = False
16231625

1624-
if not can_do_optimization:
1626+
# Is the optimization worth it? Rough calculation:
1627+
# cost of emitting a path in-line is len_path * uses_per_path
1628+
# cost of XObject is len_path + 5 for the definition,
1629+
# uses_per_path for the uses
1630+
len_path = len(paths[0].vertices) if len(paths) > 0 else 0
1631+
uses_per_path = self._iter_collection_uses_per_path(
1632+
paths, all_transforms, offsets, facecolors, edgecolors)
1633+
should_do_optimization = \
1634+
len_path + uses_per_path + 5 < len_path * uses_per_path
1635+
1636+
if (not can_do_optimization) or (not should_do_optimization):
16251637
return RendererBase.draw_path_collection(
16261638
self, gc, master_transform, paths, all_transforms,
16271639
offsets, offsetTrans, facecolors, edgecolors,
@@ -1653,9 +1665,10 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
16531665

16541666
def draw_markers(self, gc, marker_path, marker_trans, path, trans,
16551667
rgbFace=None):
1656-
# For simple paths or small numbers of markers, don't bother
1657-
# making an XObject
1658-
if len(path) * len(marker_path) <= 10:
1668+
# Same logic as in draw_path_collection
1669+
len_marker_path = len(marker_path)
1670+
uses = len(path)
1671+
if len_marker_path * uses < len_marker_path + uses + 5:
16591672
RendererBase.draw_markers(self, gc, marker_path, marker_trans,
16601673
path, trans, rgbFace)
16611674
return

lib/matplotlib/backends/backend_ps.py

+17
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,23 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
633633
offsets, offsetTrans, facecolors, edgecolors,
634634
linewidths, linestyles, antialiaseds, urls,
635635
offset_position):
636+
# Is the optimization worth it? Rough calculation:
637+
# cost of emitting a path in-line is
638+
# (len_path + 2) * uses_per_path
639+
# cost of definition+use is
640+
# (len_path + 3) + 3 * uses_per_path
641+
len_path = len(paths[0].vertices) if len(paths) > 0 else 0
642+
uses_per_path = self._iter_collection_uses_per_path(
643+
paths, all_transforms, offsets, facecolors, edgecolors)
644+
should_do_optimization = \
645+
len_path + 3 * uses_per_path + 3 < (len_path + 2) * uses_per_path
646+
if not should_do_optimization:
647+
return RendererBase.draw_path_collection(
648+
self, gc, master_transform, paths, all_transforms,
649+
offsets, offsetTrans, facecolors, edgecolors,
650+
linewidths, linestyles, antialiaseds, urls,
651+
offset_position)
652+
636653
write = self._pswriter.write
637654

638655
path_codes = []

lib/matplotlib/backends/backend_svg.py

+17
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,23 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
607607
offsets, offsetTrans, facecolors, edgecolors,
608608
linewidths, linestyles, antialiaseds, urls,
609609
offset_position):
610+
# Is the optimization worth it? Rough calculation:
611+
# cost of emitting a path in-line is
612+
# (len_path + 5) * uses_per_path
613+
# cost of definition+use is
614+
# (len_path + 3) + 9 * uses_per_path
615+
len_path = len(paths[0].vertices) if len(paths) > 0 else 0
616+
uses_per_path = self._iter_collection_uses_per_path(
617+
paths, all_transforms, offsets, facecolors, edgecolors)
618+
should_do_optimization = \
619+
len_path + 9 * uses_per_path + 3 < (len_path + 5) * uses_per_path
620+
if not should_do_optimization:
621+
return RendererBase.draw_path_collection(
622+
self, gc, master_transform, paths, all_transforms,
623+
offsets, offsetTrans, facecolors, edgecolors,
624+
linewidths, linestyles, antialiaseds, urls,
625+
offset_position)
626+
610627
writer = self.writer
611628
path_codes = []
612629
writer.start('defs')
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from matplotlib.backend_bases import RendererBase
2+
import matplotlib.transforms as transforms
3+
import matplotlib.path as path
4+
import numpy as np
5+
6+
7+
def test_uses_per_path():
8+
id = transforms.Affine2D()
9+
paths = [path.Path.unit_regular_polygon(i) for i in range(3, 7)]
10+
tforms = [id.rotate(i) for i in range(1, 5)]
11+
offsets = np.arange(20).reshape((10, 2))
12+
facecolors = ['red', 'green']
13+
edgecolors = ['red', 'green']
14+
15+
def check(master_transform, paths, all_transforms,
16+
offsets, facecolors, edgecolors):
17+
rb = RendererBase()
18+
raw_paths = list(rb._iter_collection_raw_paths(master_transform,
19+
paths, all_transforms))
20+
gc = rb.new_gc()
21+
ids = [path_id for xo, yo, path_id, gc0, rgbFace in
22+
rb._iter_collection(gc, master_transform, all_transforms,
23+
range(len(raw_paths)), offsets,
24+
transforms.IdentityTransform(),
25+
facecolors, edgecolors, [], [], [False],
26+
[], 'data')]
27+
uses = rb._iter_collection_uses_per_path(
28+
paths, all_transforms, offsets, facecolors, edgecolors)
29+
seen = [0] * len(raw_paths)
30+
for i in ids:
31+
seen[i] += 1
32+
for n in seen:
33+
assert n in (uses-1, uses)
34+
35+
check(id, paths, tforms, offsets, facecolors, edgecolors)
36+
check(id, paths[0:1], tforms, offsets, facecolors, edgecolors)
37+
check(id, [], tforms, offsets, facecolors, edgecolors)
38+
check(id, paths, tforms[0:1], offsets, facecolors, edgecolors)
39+
check(id, paths, [], offsets, facecolors, edgecolors)
40+
for n in range(0, offsets.shape[0]):
41+
check(id, paths, tforms, offsets[0:n, :], facecolors, edgecolors)
42+
check(id, paths, tforms, offsets, [], edgecolors)
43+
check(id, paths, tforms, offsets, facecolors, [])
44+
check(id, paths, tforms, offsets, [], [])
45+
check(id, paths, tforms, offsets, facecolors[0:1], edgecolors)
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)