Skip to content

Commit b92ba6b

Browse files
committed
Added the rcParams 'pdf.combine_images', 'svg.combine_images', and
'ps.combine_images' to permit users to decide whether they want the vector graphics backend to combine all images within a set of axes into a single image. (If images do not get combined, users can open vector graphics files in Adobe Illustrator or Inkscape and edit each image individually.) Also changed 'renderer.option_nocomposite' to the clearer name 'renderer.option_combine_images'. Improved combine image tests to actually count the number of images in the vector graphics files. Also replaced the rcParams 'pdf.combine_images', 'svg.combine_images', and 'ps.combine_images' with 'vector_backends.combine_images' Corrected PEP8 coding standard violations Updated 'combine_images' tests for PS and SVG backends to hopefully make them Python 3 compatible. Fixed some PEP8 violations to leave the code cleaner than I found it. Fixed PEP8 violations introduced in my effort to fix PEP8 violations in my last commit. Changed the rcParam option name from 'vector_graphics.combine_images' to 'image.combine_images', and updated the matplotlibrc.template. Update the backend tests with the new rcParam name 'image.combine_images' Added the rcParams 'pdf.combine_images', 'svg.combine_images', and 'ps.combine_images' to permit users to decide whether they want the vector graphics backend to combine all images within a set of axes into a single image. (If images do not get combined, users can open vector graphics files in Adobe Illustrator or Inkscape and edit each image individually.) Also changed 'renderer.option_nocomposite' to the clearer name 'renderer.option_combine_images'. Improved combine image tests to actually count the number of images in the vector graphics files. Also replaced the rcParams 'pdf.combine_images', 'svg.combine_images', and 'ps.combine_images' with 'vector_backends.combine_images' Corrected PEP8 coding standard violations Updated 'combine_images' tests for PS and SVG backends to hopefully make them Python 3 compatible. Fixed some PEP8 violations to leave the code cleaner than I found it. Fixed PEP8 violations introduced in my effort to fix PEP8 violations in my last commit. Changed the rcParam option name from 'vector_graphics.combine_images' to 'image.combine_images', and updated the matplotlibrc.template. Update the backend tests with the new rcParam name 'image.combine_images'
1 parent ee086de commit b92ba6b

22 files changed

+146
-43
lines changed

CHANGELOG

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
corner masking. This is controlled by the 'corner_mask' keyword
33
in plotting commands 'contour' and 'contourf'. - IMT
44

5+
2015-01-31 Added the rcParam 'image.combine_images' to permit users
6+
to decide whether they want the vector graphics backends to combine
7+
all images within a set of axes into a single image. (If images do
8+
not get combined, users can open vector graphics files in Adobe
9+
Illustrator or Inkscape and edit each image individually.) Also
10+
changed 'renderer.option_nocomposite' to the clearer name
11+
'renderer.option_combine_images'.
12+
513
2015-01-23 Text bounding boxes are now computed with advance width rather than
614
ink area. This may result in slightly different placement of text.
715

lib/matplotlib/axes/_base.py

+10-10
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,13 +2028,13 @@ 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.
2033-
if len(self.images) <= 1 or renderer.option_image_nocomposite():
2031+
# add images to dsu if the backend supports combining images.
2032+
# otherwise, perform manual combining, without adding images to dsu.
2033+
if len(self.images) <= 1 or not renderer.option_combine_images():
20342034
dsu.extend([(im.zorder, im) for im in self.images])
2035-
_do_composite = False
2035+
_combine_images = False
20362036
else:
2037-
_do_composite = True
2037+
_combine_images = True
20382038

20392039
dsu.sort(key=itemgetter(0))
20402040

@@ -2054,8 +2054,8 @@ def draw(self, renderer=None, inframe=False):
20542054
if self.axison and self._frameon:
20552055
self.patch.draw(renderer)
20562056

2057-
if _do_composite:
2058-
# make a composite image blending alpha
2057+
if _combine_images:
2058+
# combine images, 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

+3-3
Original file line numberDiff line numberDiff line change
@@ -527,12 +527,12 @@ def draw_image(self, gc, x, y, im):
527527
"""
528528
raise NotImplementedError
529529

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

537537
def option_scale_image(self):
538538
"""

lib/matplotlib/backends/backend_agg.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -322,11 +322,11 @@ def buffer_rgba(self):
322322
def clear(self):
323323
self._renderer.clear()
324324

325-
def option_image_nocomposite(self):
326-
# It is generally faster to composite each image directly to
327-
# the Figure, and there's no file size benefit to compositing
325+
def option_combine_images(self):
326+
# It is generally faster to write each image directly to
327+
# the Figure, and there's no file size benefit to combining images
328328
# with the Agg backend
329-
return True
329+
return False
330330

331331
def option_scale_image(self):
332332
"""

lib/matplotlib/backends/backend_macosx.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ def flipy(self):
170170
def points_to_pixels(self, points):
171171
return points/72.0 * self.dpi
172172

173-
def option_image_nocomposite(self):
174-
return True
173+
def option_combine_images(self):
174+
return False
175175

176176

177177
class GraphicsContextMac(_macosx.GraphicsContext, GraphicsContextBase):

lib/matplotlib/backends/backend_mixed.py

+1-2
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
@@ -64,7 +63,7 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
6463
draw_path_collection draw_quad_mesh draw_tex draw_text
6564
finalize flipy get_canvas_width_height get_image_magnification
6665
get_texmanager get_text_width_height_descent new_gc open_group
67-
option_image_nocomposite points_to_pixels strip_math
66+
option_combine_images points_to_pixels strip_math
6867
start_filter stop_filter draw_gouraud_triangle
6968
draw_gouraud_triangles option_scale_image
7069
_text2path _get_text_path_transform height width

lib/matplotlib/backends/backend_pdf.py

+10-2
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_combine_images(self):
1581+
"""
1582+
return whether to combine multiple images on a set of axes into one
1583+
image
1584+
"""
1585+
return rcParams['image.combine_images']
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

+7
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_combine_images(self):
456+
"""
457+
return whether to combine multiple images on a set of axes into one
458+
image
459+
"""
460+
return rcParams['image.combine_images']
454461

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

lib/matplotlib/backends/backend_svg.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -527,11 +527,15 @@ def open_group(self, s, gid=None):
527527
def close_group(self, s):
528528
self.writer.end('g')
529529

530-
def option_image_nocomposite(self):
530+
def option_combine_images(self):
531531
"""
532-
if svg.image_noscale is True, compositing multiple images into one is prohibited
532+
if svg.image_noscale is True, combining multiple images into one is
533+
prohibited
533534
"""
534-
return rcParams['svg.image_noscale']
535+
if rcParams['svg.image_noscale']:
536+
return False
537+
else:
538+
return rcParams['image.combine_images']
535539

536540
def _convert_path(self, path, transform=None, clip=None, simplify=None):
537541
if clip:

lib/matplotlib/figure.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class Figure(Artist):
249249
250250
*suppressComposite*
251251
For multiple figure images, the figure will make composite
252-
images depending on the renderer option_image_nocomposite
252+
images depending on the renderer option_combine_images
253253
function. If suppressComposite is True|False, this will
254254
override the renderer.
255255
"""
@@ -1045,16 +1045,16 @@ def draw(self, renderer):
10451045

10461046
# override the renderer default if self.suppressComposite
10471047
# is not None
1048-
not_composite = renderer.option_image_nocomposite()
1048+
combine_images = renderer.option_combine_images()
10491049
if self.suppressComposite is not None:
1050-
not_composite = self.suppressComposite
1050+
combine_images = not self.suppressComposite
10511051

1052-
if (len(self.images) <= 1 or not_composite or
1052+
if (len(self.images) <= 1 or not combine_images or
10531053
not cbook.allequal([im.origin for im in self.images])):
10541054
for a in self.images:
10551055
dsu.append((a.get_zorder(), a, a.draw, [renderer]))
10561056
else:
1057-
# make a composite image blending alpha
1057+
# make a combined image, blending alpha
10581058
# list of (_image.Image, ox, oy)
10591059
mag = renderer.get_image_magnification()
10601060
ims = [(im.make_image(mag), im.ox, im.oy, im.get_alpha())
@@ -1067,15 +1067,15 @@ def draw(self, renderer):
10671067
im.is_grayscale = False
10681068
l, b, w, h = self.bbox.bounds
10691069

1070-
def draw_composite():
1070+
def draw_combined_image():
10711071
gc = renderer.new_gc()
10721072
gc.set_clip_rectangle(self.bbox)
10731073
gc.set_clip_path(self.get_clip_path())
10741074
renderer.draw_image(gc, l, b, im)
10751075
gc.restore()
10761076

10771077
dsu.append((self.images[0].get_zorder(), self.images[0],
1078-
draw_composite, []))
1078+
draw_combined_image, []))
10791079

10801080
# render the axes
10811081
for a in self.axes:

lib/matplotlib/rcsetup.py

+4
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+
# Force vector graphics backends to combine all images on a set of axes
599+
# into a single image
600+
'image.combine_images': [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

+24-4
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_combine_images():
93+
#Test that figures can be saved with and without combining multiple images
94+
#(on a single set of axes) into a single 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.combine_images'] = 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.combine_images'] = False
107+
with PdfPages(io.BytesIO()) as pdf:
108+
fig.savefig(pdf, format="pdf")
109+
assert len(pdf._file.images.keys()) == 2

lib/matplotlib/tests/test_backend_ps.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import io
77
import re
8-
8+
import numpy as np
99
import six
1010

1111
import matplotlib
@@ -86,6 +86,30 @@ def test_savefig_to_stringio_with_usetex_eps():
8686
_test_savefig_to_stringio(format='eps')
8787

8888

89+
@cleanup
90+
def test_combine_images():
91+
#Test that figures can be saved with and without combining multiple images
92+
#(on a single set of axes) into a single image.
93+
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
94+
Z = np.sin(Y ** 2)
95+
fig = plt.figure()
96+
ax = fig.add_subplot(1, 1, 1)
97+
ax.set_xlim(0, 3)
98+
ax.imshow(Z, extent=[0, 1, 0, 1])
99+
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
100+
plt.rcParams['image.combine_images'] = True
101+
with io.BytesIO() as ps:
102+
fig.savefig(ps, format="ps")
103+
ps.seek(0)
104+
buff = ps.read()
105+
assert buff.count(six.b(' colorimage')) == 1
106+
plt.rcParams['image.combine_images'] = False
107+
with io.BytesIO() as ps:
108+
fig.savefig(ps, format="ps")
109+
ps.seek(0)
110+
buff = ps.read()
111+
assert buff.count(six.b(' colorimage')) == 2
112+
89113
if __name__ == '__main__':
90114
import nose
91115
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

lib/matplotlib/tests/test_backend_svg.py

+25
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ def test_noscale():
5555
plt.rcParams['svg.image_noscale'] = True
5656

5757

58+
@cleanup
59+
def test_combine_images():
60+
#Test that figures can be saved with and without combining multiple images
61+
#(on a single set of axes) into a single image.
62+
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
63+
Z = np.sin(Y ** 2)
64+
fig = plt.figure()
65+
ax = fig.add_subplot(1, 1, 1)
66+
ax.set_xlim(0, 3)
67+
ax.imshow(Z, extent=[0, 1, 0, 1])
68+
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
69+
plt.rcParams['image.combine_images'] = True
70+
with BytesIO() as svg:
71+
fig.savefig(svg, format="svg")
72+
svg.seek(0)
73+
buff = svg.read()
74+
assert buff.count(six.b('<image ')) == 1
75+
plt.rcParams['image.combine_images'] = False
76+
with BytesIO() as svg:
77+
fig.savefig(svg, format="svg")
78+
svg.seek(0)
79+
buff = svg.read()
80+
assert buff.count(six.b('<image ')) == 2
81+
82+
5883
@cleanup
5984
def test_text_urls():
6085
fig = plt.figure()

0 commit comments

Comments
 (0)