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

Skip to content

Allow users to decide whether a vector graphics backend combines multiple images into a single image #4061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
2015-02-27 Added the rcParam 'image.composite_image' to permit users
to decide whether they want the vector graphics backends to combine
all images within a set of axes into a single composite image.
(If images do not get combined, users can open vector graphics files
in Adobe Illustrator or Inkscape and edit each image individually.)

2015-02-19 Rewrite of C++ code that calculates contours to add support for
corner masking. This is controlled by the 'corner_mask' keyword
in plotting commands 'contour' and 'contourf'. - IMT
Expand Down
13 changes: 7 additions & 6 deletions doc/users/whats_new/rcparams.rst
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
Added "legend.framealpha" key to rcParams
`````````````````````````````````````````

Added a key and the corresponding logic to control the default transparency of
legend frames. This feature was written into the docstring of axes.legend(),
but not yet implemented.


Added "figure.titlesize" and "figure.titleweight" keys to rcParams
``````````````````````````````````````````````````````````````````

Two new keys were added to rcParams to control the default font size and weight
used by the figure title (as emitted by ``pyplot.suptitle()``).



Added "legend.facecolor" and "legend.edgecolor" keys to rcParams
````````````````````````````````````````````````````````````````

The new keys control colors (background and edge) of legend patches.

``image.composite_image`` added to rcParams
```````````````````````````````````````````
Controls whether vector graphics backends (i.e. PDF, PS, and SVG) combine
multiple images on a set of axes into a single composite image. Saving each
image individually can be useful if you generate vector graphics files in
matplotlib and then edit the files further in Inkscape or other programs.
12 changes: 6 additions & 6 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
import matplotlib.text as mtext
import matplotlib.image as mimage
from matplotlib.artist import allow_rasterization


from matplotlib.cbook import iterable

rcParams = matplotlib.rcParams
Expand Down Expand Up @@ -1241,7 +1239,9 @@ def apply_aspect(self, position=None):
Xsize = ysize / data_ratio
Xmarg = Xsize - xr
Ymarg = Ysize - yr
xm = 0 # Setting these targets to, e.g., 0.05*xr does not seem to help
# Setting these targets to, e.g., 0.05*xr does not seem to
# help.
xm = 0
ym = 0

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

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

if _do_composite:
# make a composite image blending alpha
# make a composite image, blending alpha
# list of (mimage.Image, ox, oy)

zorder_images = [(im.zorder, im) for im in self.images
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,8 @@ def draw_image(self, gc, x, y, im):

def option_image_nocomposite(self):
"""
override this method for renderers that do not necessarily
want to rescale and composite raster images. (like SVG)
override this method for renderers that do not necessarily always
want to rescale and composite raster images. (like SVG, PDF, or PS)
"""
return False

Expand Down
1 change: 0 additions & 1 deletion lib/matplotlib/backends/backend_mixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
self._height = height
self.dpi = dpi

assert not vector_renderer.option_image_nocomposite()
self._vector_renderer = vector_renderer

self._raster_renderer = None
Expand Down
12 changes: 10 additions & 2 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,8 @@ def __init__(self, filename):
self.fontNames = {} # maps filenames to internal font names
self.nextFont = 1 # next free internal font name
self.dviFontInfo = {} # information on dvi fonts
self.type1Descriptors = {} # differently encoded Type-1 fonts may
# share the same descriptor
# differently encoded Type-1 fonts may share the same descriptor
self.type1Descriptors = {}
self.used_characters = {}

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

check_trapped = (lambda x: isinstance(x, Name) and
x.name in ('True', 'False', 'Unknown'))

keywords = {'Title': is_string_like,
'Author': is_string_like,
'Subject': is_string_like,
Expand Down Expand Up @@ -1576,6 +1577,13 @@ def option_scale_image(self):
"""
return True

def option_image_nocomposite(self):
"""
return whether to generate a composite image from multiple images on
a set of axes
"""
return not rcParams['image.composite_image']

def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None):
self.check_gc(gc)

Expand Down
7 changes: 7 additions & 0 deletions lib/matplotlib/backends/backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,13 @@ def option_scale_image(self):
ps backend support arbitrary scaling of image.
"""
return True

def option_image_nocomposite(self):
"""
return whether to generate a composite image from multiple images on
a set of axes
"""
return not rcParams['image.composite_image']

def _get_image_h_w_bits_command(self, im):
if im.is_grayscale:
Expand Down
8 changes: 6 additions & 2 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,9 +529,13 @@ def close_group(self, s):

def option_image_nocomposite(self):
"""
if svg.image_noscale is True, compositing multiple images into one is prohibited
return whether to generate a composite image from multiple images on
a set of axes
"""
return rcParams['svg.image_noscale']
if rcParams['svg.image_noscale']:
return True
else:
return not rcParams['image.composite_image']

def _convert_path(self, path, transform=None, clip=None, simplify=None):
if clip:
Expand Down
4 changes: 4 additions & 0 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,9 @@ def __call__(self, s):
'image.lut': [256, validate_int], # lookup table
'image.origin': ['upper', six.text_type], # lookup table
'image.resample': [False, validate_bool],
# Specify whether vector graphics backends will combine all images on a
# set of axes into a single composite image
'image.composite_image': [True, validate_bool],

# contour props
'contour.negative_linestyle': ['dashed',
Expand Down Expand Up @@ -764,6 +767,7 @@ def __call__(self, s):
# Maintain shell focus for TkAgg
'tk.window_focus': [False, validate_bool],
'tk.pythoninspect': [False, validate_tkpythoninspect], # obsolete

# Set the papersize/type
'ps.papersize': ['letter', validate_ps_papersize],
'ps.useafm': [False, validate_bool], # Set PYTHONINSPECT
Expand Down
28 changes: 24 additions & 4 deletions lib/matplotlib/tests/test_backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import os

import numpy as np

from matplotlib import cm, rcParams
from matplotlib.backends.backend_pdf import PdfPages
from matplotlib import pyplot as plt
from matplotlib.testing.decorators import (image_comparison, knownfailureif,
cleanup)
Expand Down Expand Up @@ -42,7 +42,6 @@ def test_type42():

@cleanup
def test_multipage_pagecount():
from matplotlib.backends.backend_pdf import PdfPages
with PdfPages(io.BytesIO()) as pdf:
assert pdf.get_pagecount() == 0
fig = plt.figure()
Expand All @@ -58,7 +57,7 @@ def test_multipage_pagecount():
def test_multipage_keep_empty():
from matplotlib.backends.backend_pdf import PdfPages
from tempfile import NamedTemporaryFile
### test empty pdf files
# test empty pdf files
# test that an empty pdf is left behind with keep_empty=True (default)
with NamedTemporaryFile(delete=False) as tmp:
with PdfPages(tmp) as pdf:
Expand All @@ -69,7 +68,7 @@ def test_multipage_keep_empty():
with PdfPages(filename, keep_empty=False) as pdf:
pass
assert not os.path.exists(filename)
### test pdf files with content, they should never be deleted
# test pdf files with content, they should never be deleted
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1, 2, 3])
Expand All @@ -87,3 +86,24 @@ def test_multipage_keep_empty():
pdf.savefig()
assert os.path.exists(filename)
os.remove(filename)


@cleanup
def test_composite_image():
#Test that figures can be saved with and without combining multiple images
#(on a single set of axes) into a single composite image.
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
Z = np.sin(Y ** 2)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlim(0, 3)
ax.imshow(Z, extent=[0, 1, 0, 1])
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
plt.rcParams['image.composite_image'] = True
with PdfPages(io.BytesIO()) as pdf:
fig.savefig(pdf, format="pdf")
assert len(pdf._file.images.keys()) == 1
plt.rcParams['image.composite_image'] = False
with PdfPages(io.BytesIO()) as pdf:
fig.savefig(pdf, format="pdf")
assert len(pdf._file.images.keys()) == 2
26 changes: 25 additions & 1 deletion lib/matplotlib/tests/test_backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import io
import re

import numpy as np
import six

import matplotlib
Expand Down Expand Up @@ -86,6 +86,30 @@ def test_savefig_to_stringio_with_usetex_eps():
_test_savefig_to_stringio(format='eps')


@cleanup
def test_composite_image():
#Test that figures can be saved with and without combining multiple images
#(on a single set of axes) into a single composite image.
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
Z = np.sin(Y ** 2)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlim(0, 3)
ax.imshow(Z, extent=[0, 1, 0, 1])
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
plt.rcParams['image.composite_image'] = True
with io.BytesIO() as ps:
fig.savefig(ps, format="ps")
ps.seek(0)
buff = ps.read()
assert buff.count(six.b(' colorimage')) == 1
plt.rcParams['image.composite_image'] = False
with io.BytesIO() as ps:
fig.savefig(ps, format="ps")
ps.seek(0)
buff = ps.read()
assert buff.count(six.b(' colorimage')) == 2

if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
25 changes: 25 additions & 0 deletions lib/matplotlib/tests/test_backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ def test_noscale():
plt.rcParams['svg.image_noscale'] = True


@cleanup
def test_composite_images():
#Test that figures can be saved with and without combining multiple images
#(on a single set of axes) into a single composite image.
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
Z = np.sin(Y ** 2)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlim(0, 3)
ax.imshow(Z, extent=[0, 1, 0, 1])
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
plt.rcParams['image.composite_image'] = True
with BytesIO() as svg:
fig.savefig(svg, format="svg")
svg.seek(0)
buff = svg.read()
assert buff.count(six.b('<image ')) == 1
plt.rcParams['image.composite_image'] = False
with BytesIO() as svg:
fig.savefig(svg, format="svg")
svg.seek(0)
buff = svg.read()
assert buff.count(six.b('<image ')) == 2


@cleanup
def test_text_urls():
fig = plt.figure()
Expand Down
4 changes: 4 additions & 0 deletions matplotlibrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ backend : %(backend)s
#image.lut : 256 # the size of the colormap lookup table
#image.origin : upper # lower | upper
#image.resample : False
#image.composite_image : True # When True, all the images on a set of axes are
# combined into a single composite image before
# saving a figure as a vector graphics file,
# such as a PDF.

### CONTOUR PLOTS
#contour.negative_linestyle : dashed # dashed | solid
Expand Down