Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 8159c81

Browse filesBrowse files
committed
Refactor mechanism for saving files.
Move all print_ methods to their backends. Manage default format-to-backend mappings in backend_bases. print_figure() can now determine the backend for saving figures. Remove print_ code duplication, add canonical FigureCanvas names.
1 parent 0b1820d commit 8159c81
Copy full SHA for 8159c81
Expand file treeCollapse file tree

23 files changed

+201
-156
lines changed

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+93-136Lines changed: 93 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from six.moves import xrange
3535

3636
import os
37+
import sys
3738
import warnings
3839
import time
3940
import io
@@ -56,17 +57,76 @@
5657
from matplotlib.path import Path
5758
from matplotlib.cbook import mplDeprecation
5859

60+
try:
61+
from importlib import import_module
62+
except:
63+
# simple python 2.6 implementation (no relative imports)
64+
def import_module(name):
65+
__import__(name)
66+
return sys.modules[name]
67+
5968
try:
6069
from PIL import Image
6170
_has_pil = True
6271
except ImportError:
6372
_has_pil = False
6473

65-
_backend_d = {}
74+
75+
_default_filetypes = {
76+
'ps': 'Postscript',
77+
'eps': 'Encapsulated Postscript',
78+
'pdf': 'Portable Document Format',
79+
'pgf': 'PGF code for LaTeX',
80+
'png': 'Portable Network Graphics',
81+
'raw': 'Raw RGBA bitmap',
82+
'rgba': 'Raw RGBA bitmap',
83+
'svg': 'Scalable Vector Graphics',
84+
'svgz': 'Scalable Vector Graphics'
85+
}
86+
87+
88+
_default_backends = {
89+
'ps': 'matplotlib.backends.backend_ps',
90+
'eps': 'matplotlib.backends.backend_ps',
91+
'pdf': 'matplotlib.backends.backend_pdf',
92+
'pgf': 'matplotlib.backends.backend_pgf',
93+
'png': 'matplotlib.backends.backend_agg',
94+
'raw': 'matplotlib.backends.backend_agg',
95+
'rgba': 'matplotlib.backends.backend_agg',
96+
'svg': 'matplotlib.backends.backend_svg',
97+
'svgz': 'matplotlib.backends.backend_svg',
98+
}
99+
100+
101+
def register_backend(format, backend, description):
102+
"""
103+
Register a backend for saving to a given file format.
104+
105+
*format*
106+
File extention
107+
108+
*backend*
109+
Backend for handling file output (module string or canvas class)
110+
111+
*description*
112+
Description of the file type
113+
"""
114+
_default_backends[format] = backend
115+
_default_filetypes[format] = description
66116

67117

68-
def register_backend(format, backend_class):
69-
_backend_d[format] = backend_class
118+
def get_registered_canvas_class(format):
119+
"""
120+
Return the registered default canvas for given file format.
121+
Handles deferred import of required backend.
122+
"""
123+
if format not in _default_backends:
124+
return None
125+
backend_class = _default_backends[format]
126+
if cbook.is_string_like(backend_class):
127+
backend_class = import_module(backend_class).FigureCanvas
128+
_default_backends[format] = backend_class
129+
return backend_class
70130

71131

72132
class ShowBase(object):
@@ -1566,6 +1626,20 @@ class FigureCanvasBase(object):
15661626

15671627
supports_blit = True
15681628

1629+
filetypes = _default_filetypes
1630+
if _has_pil:
1631+
# JPEG support
1632+
register_backend('jpg', 'matplotlib.backends.backend_agg',
1633+
'Joint Photographic Experts Group')
1634+
register_backend('jpeg', 'matplotlib.backends.backend_agg',
1635+
'Joint Photographic Experts Group')
1636+
1637+
# TIFF support
1638+
register_backend('tif', 'matplotlib.backends.backend_agg',
1639+
'Tagged Image File Format')
1640+
register_backend('tiff', 'matplotlib.backends.backend_agg',
1641+
'Tagged Image File Format')
1642+
15691643
def __init__(self, figure):
15701644
figure.set_canvas(self)
15711645
self.figure = figure
@@ -1927,119 +2001,6 @@ def get_width_height(self):
19272001
"""
19282002
return int(self.figure.bbox.width), int(self.figure.bbox.height)
19292003

1930-
filetypes = {
1931-
'eps': 'Encapsulated Postscript',
1932-
'pdf': 'Portable Document Format',
1933-
'pgf': 'LaTeX PGF Figure',
1934-
'png': 'Portable Network Graphics',
1935-
'ps': 'Postscript',
1936-
'raw': 'Raw RGBA bitmap',
1937-
'rgba': 'Raw RGBA bitmap',
1938-
'svg': 'Scalable Vector Graphics',
1939-
'svgz': 'Scalable Vector Graphics'}
1940-
1941-
# All of these print_* functions do a lazy import because
1942-
# a) otherwise we'd have cyclical imports, since all of these
1943-
# classes inherit from FigureCanvasBase
1944-
# b) so we don't import a bunch of stuff the user may never use
1945-
1946-
# TODO: these print_* throw ImportErrror when called from
1947-
# compare_images_decorator (decorators.py line 112)
1948-
# if the backend has not already been loaded earlier on. Simple trigger:
1949-
# >>> import matplotlib.tests.test_spines
1950-
# >>> list(matplotlib.tests.test_spines.test_spines_axes_positions())[0][0]()
1951-
1952-
def print_eps(self, *args, **kwargs):
1953-
from .backends.backend_ps import FigureCanvasPS # lazy import
1954-
ps = self.switch_backends(FigureCanvasPS)
1955-
return ps.print_eps(*args, **kwargs)
1956-
1957-
def print_pdf(self, *args, **kwargs):
1958-
from .backends.backend_pdf import FigureCanvasPdf # lazy import
1959-
pdf = self.switch_backends(FigureCanvasPdf)
1960-
return pdf.print_pdf(*args, **kwargs)
1961-
1962-
def print_pgf(self, *args, **kwargs):
1963-
from .backends.backend_pgf import FigureCanvasPgf # lazy import
1964-
pgf = self.switch_backends(FigureCanvasPgf)
1965-
return pgf.print_pgf(*args, **kwargs)
1966-
1967-
def print_png(self, *args, **kwargs):
1968-
from .backends.backend_agg import FigureCanvasAgg # lazy import
1969-
agg = self.switch_backends(FigureCanvasAgg)
1970-
return agg.print_png(*args, **kwargs)
1971-
1972-
def print_ps(self, *args, **kwargs):
1973-
from .backends.backend_ps import FigureCanvasPS # lazy import
1974-
ps = self.switch_backends(FigureCanvasPS)
1975-
return ps.print_ps(*args, **kwargs)
1976-
1977-
def print_raw(self, *args, **kwargs):
1978-
from .backends.backend_agg import FigureCanvasAgg # lazy import
1979-
agg = self.switch_backends(FigureCanvasAgg)
1980-
return agg.print_raw(*args, **kwargs)
1981-
print_bmp = print_rgba = print_raw
1982-
1983-
def print_svg(self, *args, **kwargs):
1984-
from .backends.backend_svg import FigureCanvasSVG # lazy import
1985-
svg = self.switch_backends(FigureCanvasSVG)
1986-
return svg.print_svg(*args, **kwargs)
1987-
1988-
def print_svgz(self, *args, **kwargs):
1989-
from .backends.backend_svg import FigureCanvasSVG # lazy import
1990-
svg = self.switch_backends(FigureCanvasSVG)
1991-
return svg.print_svgz(*args, **kwargs)
1992-
1993-
if _has_pil:
1994-
filetypes['jpg'] = 'Joint Photographic Experts Group'
1995-
filetypes['jpeg'] = filetypes['jpg']
1996-
1997-
def print_jpg(self, filename_or_obj, *args, **kwargs):
1998-
"""
1999-
Supported kwargs:
2000-
2001-
*quality*: The image quality, on a scale from 1 (worst) to
2002-
95 (best). The default is 95, if not given in the
2003-
matplotlibrc file in the savefig.jpeg_quality parameter.
2004-
Values above 95 should be avoided; 100 completely
2005-
disables the JPEG quantization stage.
2006-
2007-
*optimize*: If present, indicates that the encoder should
2008-
make an extra pass over the image in order to select
2009-
optimal encoder settings.
2010-
2011-
*progressive*: If present, indicates that this image
2012-
should be stored as a progressive JPEG file.
2013-
"""
2014-
from .backends.backend_agg import FigureCanvasAgg # lazy import
2015-
agg = self.switch_backends(FigureCanvasAgg)
2016-
buf, size = agg.print_to_buffer()
2017-
if kwargs.pop("dryrun", False):
2018-
return
2019-
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
2020-
options = cbook.restrict_dict(kwargs, ['quality', 'optimize',
2021-
'progressive'])
2022-
2023-
if 'quality' not in options:
2024-
options['quality'] = rcParams['savefig.jpeg_quality']
2025-
2026-
return image.save(filename_or_obj, format='jpeg', **options)
2027-
print_jpeg = print_jpg
2028-
2029-
filetypes['tif'] = filetypes['tiff'] = 'Tagged Image File Format'
2030-
2031-
def print_tif(self, filename_or_obj, *args, **kwargs):
2032-
from .backends.backend_agg import FigureCanvasAgg # lazy import
2033-
agg = self.switch_backends(FigureCanvasAgg)
2034-
buf, size = agg.print_to_buffer()
2035-
if kwargs.pop("dryrun", False):
2036-
return
2037-
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
2038-
dpi = (self.figure.dpi, self.figure.dpi)
2039-
return image.save(filename_or_obj, format='tiff',
2040-
dpi=dpi)
2041-
print_tiff = print_tif
2042-
20432004
@classmethod
20442005
def get_supported_filetypes(cls):
20452006
"""Return dict of savefig file formats supported by this backend"""
@@ -2057,29 +2018,23 @@ def get_supported_filetypes_grouped(cls):
20572018
groupings[name].sort()
20582019
return groupings
20592020

2060-
def _get_print_method(self, format):
2021+
def _get_print_canvas(self, format):
20612022
method_name = 'print_%s' % format
20622023

2063-
# check for registered backends
2064-
if format in _backend_d:
2065-
backend_class = _backend_d[format]
2066-
2067-
def _print_method(*args, **kwargs):
2068-
backend = self.switch_backends(backend_class)
2069-
print_method = getattr(backend, method_name)
2070-
return print_method(*args, **kwargs)
2071-
2072-
return _print_method
2024+
# check if this canvas supports the requested format
2025+
if hasattr(self, method_name):
2026+
return self
20732027

2074-
formats = self.get_supported_filetypes()
2075-
if (format not in formats or not hasattr(self, method_name)):
2076-
formats = sorted(formats)
2077-
raise ValueError(
2078-
'Format "%s" is not supported.\n'
2079-
'Supported formats: '
2080-
'%s.' % (format, ', '.join(formats)))
2028+
# check if there is a default canvas for the requested format
2029+
canvas_class = get_registered_canvas_class(format)
2030+
if canvas_class:
2031+
return self.switch_backends(canvas_class)
20812032

2082-
return getattr(self, method_name)
2033+
# else report error for unsupported format
2034+
formats = sorted(self.get_supported_filetypes())
2035+
raise ValueError('Format "%s" is not supported.\n'
2036+
'Supported formats: '
2037+
'%s.' % (format, ', '.join(formats)))
20832038

20842039
def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w',
20852040
orientation='portrait', format=None, **kwargs):
@@ -2136,7 +2091,9 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w',
21362091
filename = filename.rstrip('.') + '.' + format
21372092
format = format.lower()
21382093

2139-
print_method = self._get_print_method(format)
2094+
# get canvas object and print method for format
2095+
canvas = self._get_print_canvas(format)
2096+
print_method = getattr(canvas, 'print_%s' % format)
21402097

21412098
if dpi is None:
21422099
dpi = rcParams['savefig.dpi']

‎lib/matplotlib/backends/backend_agg.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_agg.py
+55-4Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* linewidth
99
* lines, rectangles, ellipses
1010
* clipping to a rectangle
11-
* output to RGBA and PNG
11+
* output to RGBA and PNG, optionally JPEG and TIFF
1212
* alpha blending
1313
* DPI scaling properly - everything scales properly (dashes, linewidths, etc)
1414
* draw polygon
@@ -30,7 +30,7 @@
3030
from matplotlib import verbose, rcParams
3131
from matplotlib.backend_bases import RendererBase,\
3232
FigureManagerBase, FigureCanvasBase
33-
from matplotlib.cbook import is_string_like, maxdict
33+
from matplotlib.cbook import is_string_like, maxdict, restrict_dict
3434
from matplotlib.figure import Figure
3535
from matplotlib.font_manager import findfont
3636
from matplotlib.ft2font import FT2Font, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, \
@@ -42,6 +42,12 @@
4242
from matplotlib.backends._backend_agg import RendererAgg as _RendererAgg
4343
from matplotlib import _png
4444

45+
try:
46+
from PIL import Image
47+
_has_pil = True
48+
except ImportError:
49+
_has_pil = False
50+
4551
backend_version = 'v2.2'
4652

4753
def get_hinting_flag():
@@ -456,8 +462,6 @@ def draw(self):
456462
finally:
457463
RendererAgg.lock.release()
458464

459-
460-
461465
def get_renderer(self, cleared=False):
462466
l, b, w, h = self.figure.bbox.bounds
463467
key = w, h, self.figure.dpi
@@ -533,3 +537,50 @@ def print_to_buffer(self):
533537
(int(renderer.width), int(renderer.height)))
534538
renderer.dpi = original_dpi
535539
return result
540+
541+
if _has_pil:
542+
543+
# add JPEG support
544+
def print_jpg(self, filename_or_obj, *args, **kwargs):
545+
"""
546+
Supported kwargs:
547+
548+
*quality*: The image quality, on a scale from 1 (worst) to
549+
95 (best). The default is 95, if not given in the
550+
matplotlibrc file in the savefig.jpeg_quality parameter.
551+
Values above 95 should be avoided; 100 completely
552+
disables the JPEG quantization stage.
553+
554+
*optimize*: If present, indicates that the encoder should
555+
make an extra pass over the image in order to select
556+
optimal encoder settings.
557+
558+
*progressive*: If present, indicates that this image
559+
should be stored as a progressive JPEG file.
560+
"""
561+
buf, size = self.print_to_buffer()
562+
if kwargs.pop("dryrun", False):
563+
return
564+
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
565+
options = restrict_dict(kwargs, ['quality', 'optimize',
566+
'progressive'])
567+
568+
if 'quality' not in options:
569+
options['quality'] = rcParams['savefig.jpeg_quality']
570+
571+
return image.save(filename_or_obj, format='jpeg', **options)
572+
print_jpeg = print_jpg
573+
574+
# add TIFF support
575+
def print_tif(self, filename_or_obj, *args, **kwargs):
576+
buf, size = self.print_to_buffer()
577+
if kwargs.pop("dryrun", False):
578+
return
579+
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
580+
dpi = (self.figure.dpi, self.figure.dpi)
581+
return image.save(filename_or_obj, format='tiff',
582+
dpi=dpi)
583+
print_tiff = print_tif
584+
585+
586+
FigureCanvas = FigureCanvasAgg

‎lib/matplotlib/backends/backend_cairo.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_cairo.py
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,6 @@ def _save (self, fo, format, **kwargs):
514514

515515
ctx.show_page()
516516
surface.finish()
517+
518+
519+
FigureCanvas = FigureCanvasCairo

‎lib/matplotlib/backends/backend_cocoaagg.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_cocoaagg.py
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,6 @@ def show(self):
249249
NSApplication.sharedApplication().run()
250250

251251

252-
FigureManager = FigureManagerCocoaAgg
253-
254252
#### Everything below taken from PyObjC examples
255253
#### This is a hack to allow python scripts to access
256254
#### the window manager without running pythonw.
@@ -302,3 +300,7 @@ def WMEnable(name='Python'):
302300
print('SetFrontProcess', (err, psn), file=sys.stderr)
303301
return False
304302
return True
303+
304+
305+
FigureCanvas = FigureCanvasCocoaAgg
306+
FigureManager = FigureManagerCocoaAgg

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.