Skip to content

Commit 4f80cfd

Browse files
committed
Merge pull request matplotlib#4061 from Stretch97/master
Allow users to decide whether a vector graphics backend combines multiple images into a single image
2 parents be305a3 + ec76724 commit 4f80cfd

File tree

13 files changed

+126
-24
lines changed

13 files changed

+126
-24
lines changed

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
2015-02-27 Added the rcParam 'image.composite_image' to permit users
2+
to decide whether they want the vector graphics backends to combine
3+
all images within a set of axes into a single composite image.
4+
(If images do not get combined, users can open vector graphics files
5+
in Adobe Illustrator or Inkscape and edit each image individually.)
6+
17
2015-02-19 Rewrite of C++ code that calculates contours to add support for
28
corner masking. This is controlled by the 'corner_mask' keyword
39
in plotting commands 'contour' and 'contourf'. - IMT

doc/users/whats_new/rcparams.rst

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
Added "legend.framealpha" key to rcParams
22
`````````````````````````````````````````
3-
43
Added a key and the corresponding logic to control the default transparency of
54
legend frames. This feature was written into the docstring of axes.legend(),
65
but not yet implemented.
76

8-
97
Added "figure.titlesize" and "figure.titleweight" keys to rcParams
108
``````````````````````````````````````````````````````````````````
11-
129
Two new keys were added to rcParams to control the default font size and weight
1310
used by the figure title (as emitted by ``pyplot.suptitle()``).
1411

15-
16-
1712
Added "legend.facecolor" and "legend.edgecolor" keys to rcParams
1813
````````````````````````````````````````````````````````````````
19-
2014
The new keys control colors (background and edge) of legend patches.
15+
16+
``image.composite_image`` added to rcParams
17+
```````````````````````````````````````````
18+
Controls whether vector graphics backends (i.e. PDF, PS, and SVG) combine
19+
multiple images on a set of axes into a single composite image. Saving each
20+
image individually can be useful if you generate vector graphics files in
21+
matplotlib and then edit the files further in Inkscape or other programs.

lib/matplotlib/axes/_base.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
import matplotlib.text as mtext
3131
import matplotlib.image as mimage
3232
from matplotlib.artist import allow_rasterization
33-
34-
3533
from matplotlib.cbook import iterable
3634

3735
rcParams = matplotlib.rcParams
@@ -1241,7 +1239,9 @@ def apply_aspect(self, position=None):
12411239
Xsize = ysize / data_ratio
12421240
Xmarg = Xsize - xr
12431241
Ymarg = Ysize - yr
1244-
xm = 0 # Setting these targets to, e.g., 0.05*xr does not seem to help
1242+
# Setting these targets to, e.g., 0.05*xr does not seem to
1243+
# help.
1244+
xm = 0
12451245
ym = 0
12461246

12471247
changex = (self in self._shared_y_axes and
@@ -2028,8 +2028,8 @@ def draw(self, renderer=None, inframe=False):
20282028
dsu = [(a.zorder, a) for a in artists
20292029
if not a.get_animated()]
20302030

2031-
# add images to dsu if the backend support compositing.
2032-
# otherwise, does the manaul compositing without adding images to dsu.
2031+
# add images to dsu if the backend supports compositing.
2032+
# otherwise, does the manual compositing without adding images to dsu.
20332033
if len(self.images) <= 1 or renderer.option_image_nocomposite():
20342034
dsu.extend([(im.zorder, im) for im in self.images])
20352035
_do_composite = False
@@ -2055,7 +2055,7 @@ def draw(self, renderer=None, inframe=False):
20552055
self.patch.draw(renderer)
20562056

20572057
if _do_composite:
2058-
# make a composite image blending alpha
2058+
# make a composite image, blending alpha
20592059
# list of (mimage.Image, ox, oy)
20602060

20612061
zorder_images = [(im.zorder, im) for im in self.images

lib/matplotlib/backend_bases.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,8 @@ def draw_image(self, gc, x, y, im):
529529

530530
def option_image_nocomposite(self):
531531
"""
532-
override this method for renderers that do not necessarily
533-
want to rescale and composite raster images. (like SVG)
532+
override this method for renderers that do not necessarily always
533+
want to rescale and composite raster images. (like SVG, PDF, or PS)
534534
"""
535535
return False
536536

lib/matplotlib/backends/backend_mixed.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
4343
self._height = height
4444
self.dpi = dpi
4545

46-
assert not vector_renderer.option_image_nocomposite()
4746
self._vector_renderer = vector_renderer
4847

4948
self._raster_renderer = None

lib/matplotlib/backends/backend_pdf.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,8 @@ def __init__(self, filename):
462462
self.fontNames = {} # maps filenames to internal font names
463463
self.nextFont = 1 # next free internal font name
464464
self.dviFontInfo = {} # information on dvi fonts
465-
self.type1Descriptors = {} # differently encoded Type-1 fonts may
466-
# share the same descriptor
465+
# differently encoded Type-1 fonts may share the same descriptor
466+
self.type1Descriptors = {}
467467
self.used_characters = {}
468468

469469
self.alphaStates = {} # maps alpha values to graphics state objects
@@ -1475,6 +1475,7 @@ def is_date(x):
14751475

14761476
check_trapped = (lambda x: isinstance(x, Name) and
14771477
x.name in ('True', 'False', 'Unknown'))
1478+
14781479
keywords = {'Title': is_string_like,
14791480
'Author': is_string_like,
14801481
'Subject': is_string_like,
@@ -1576,6 +1577,13 @@ def option_scale_image(self):
15761577
"""
15771578
return True
15781579

1580+
def option_image_nocomposite(self):
1581+
"""
1582+
return whether to generate a composite image from multiple images on
1583+
a set of axes
1584+
"""
1585+
return not rcParams['image.composite_image']
1586+
15791587
def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None):
15801588
self.check_gc(gc)
15811589

lib/matplotlib/backends/backend_ps.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,13 @@ def option_scale_image(self):
451451
ps backend support arbitrary scaling of image.
452452
"""
453453
return True
454+
455+
def option_image_nocomposite(self):
456+
"""
457+
return whether to generate a composite image from multiple images on
458+
a set of axes
459+
"""
460+
return not rcParams['image.composite_image']
454461

455462
def _get_image_h_w_bits_command(self, im):
456463
if im.is_grayscale:

lib/matplotlib/backends/backend_svg.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -532,9 +532,13 @@ def close_group(self, s):
532532

533533
def option_image_nocomposite(self):
534534
"""
535-
if svg.image_noscale is True, compositing multiple images into one is prohibited
535+
return whether to generate a composite image from multiple images on
536+
a set of axes
536537
"""
537-
return rcParams['svg.image_noscale']
538+
if rcParams['svg.image_noscale']:
539+
return True
540+
else:
541+
return not rcParams['image.composite_image']
538542

539543
def _convert_path(self, path, transform=None, clip=None, simplify=None):
540544
if clip:

lib/matplotlib/rcsetup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,9 @@ def __call__(self, s):
595595
'image.lut': [256, validate_int], # lookup table
596596
'image.origin': ['upper', six.text_type], # lookup table
597597
'image.resample': [False, validate_bool],
598+
# Specify whether vector graphics backends will combine all images on a
599+
# set of axes into a single composite image
600+
'image.composite_image': [True, validate_bool],
598601

599602
# contour props
600603
'contour.negative_linestyle': ['dashed',
@@ -764,6 +767,7 @@ def __call__(self, s):
764767
# Maintain shell focus for TkAgg
765768
'tk.window_focus': [False, validate_bool],
766769
'tk.pythoninspect': [False, validate_tkpythoninspect], # obsolete
770+
767771
# Set the papersize/type
768772
'ps.papersize': ['letter', validate_ps_papersize],
769773
'ps.useafm': [False, validate_bool], # Set PYTHONINSPECT

lib/matplotlib/tests/test_backend_pdf.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import os
1010

1111
import numpy as np
12-
1312
from matplotlib import cm, rcParams
13+
from matplotlib.backends.backend_pdf import PdfPages
1414
from matplotlib import pyplot as plt
1515
from matplotlib.testing.decorators import (image_comparison, knownfailureif,
1616
cleanup)
@@ -42,7 +42,6 @@ def test_type42():
4242

4343
@cleanup
4444
def test_multipage_pagecount():
45-
from matplotlib.backends.backend_pdf import PdfPages
4645
with PdfPages(io.BytesIO()) as pdf:
4746
assert pdf.get_pagecount() == 0
4847
fig = plt.figure()
@@ -58,7 +57,7 @@ def test_multipage_pagecount():
5857
def test_multipage_keep_empty():
5958
from matplotlib.backends.backend_pdf import PdfPages
6059
from tempfile import NamedTemporaryFile
61-
### test empty pdf files
60+
# test empty pdf files
6261
# test that an empty pdf is left behind with keep_empty=True (default)
6362
with NamedTemporaryFile(delete=False) as tmp:
6463
with PdfPages(tmp) as pdf:
@@ -69,7 +68,7 @@ def test_multipage_keep_empty():
6968
with PdfPages(filename, keep_empty=False) as pdf:
7069
pass
7170
assert not os.path.exists(filename)
72-
### test pdf files with content, they should never be deleted
71+
# test pdf files with content, they should never be deleted
7372
fig = plt.figure()
7473
ax = fig.add_subplot(111)
7574
ax.plot([1, 2, 3])
@@ -87,3 +86,24 @@ def test_multipage_keep_empty():
8786
pdf.savefig()
8887
assert os.path.exists(filename)
8988
os.remove(filename)
89+
90+
91+
@cleanup
92+
def test_composite_image():
93+
#Test that figures can be saved with and without combining multiple images
94+
#(on a single set of axes) into a single composite image.
95+
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
96+
Z = np.sin(Y ** 2)
97+
fig = plt.figure()
98+
ax = fig.add_subplot(1, 1, 1)
99+
ax.set_xlim(0, 3)
100+
ax.imshow(Z, extent=[0, 1, 0, 1])
101+
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
102+
plt.rcParams['image.composite_image'] = True
103+
with PdfPages(io.BytesIO()) as pdf:
104+
fig.savefig(pdf, format="pdf")
105+
assert len(pdf._file.images.keys()) == 1
106+
plt.rcParams['image.composite_image'] = False
107+
with PdfPages(io.BytesIO()) as pdf:
108+
fig.savefig(pdf, format="pdf")
109+
assert len(pdf._file.images.keys()) == 2

0 commit comments

Comments
 (0)