From 005f29480c820a1355a2e06973e363197722ddf4 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 3 Jan 2019 22:25:13 +0100 Subject: [PATCH] Allow passing arguments to PIL.Image.save(). --- doc/conf.py | 3 +- doc/users/next_whats_new/2018-01-03-AL.rst | 6 +++ lib/matplotlib/backends/backend_agg.py | 47 ++++++++++++++-------- lib/matplotlib/figure.py | 5 +++ lib/matplotlib/tests/test_agg.py | 11 +++++ 5 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 doc/users/next_whats_new/2018-01-03-AL.rst diff --git a/doc/conf.py b/doc/conf.py index cc8a1754d9c0..c07735e2bec7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -95,7 +95,8 @@ def _check_deps(): 'python': ('https://docs.python.org/3', None), 'numpy': ('https://docs.scipy.org/doc/numpy/', None), 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), - 'pandas': ('https://pandas.pydata.org/pandas-docs/stable', None), + 'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None), + 'Pillow': ('https://pillow.readthedocs.io/en/stable/', None), 'cycler': ('https://matplotlib.org/cycler', None), } diff --git a/doc/users/next_whats_new/2018-01-03-AL.rst b/doc/users/next_whats_new/2018-01-03-AL.rst new file mode 100644 index 000000000000..7f3da98c8a7b --- /dev/null +++ b/doc/users/next_whats_new/2018-01-03-AL.rst @@ -0,0 +1,6 @@ +*pil_kwargs* argument to savefig +```````````````````````````````` + +Matplotlib uses Pillow to handle saving to the JPEG and TIFF formats. The +`~Figure.savefig()` function gained a *pil_kwargs* keyword argument, which can +be used to forward arguments to Pillow's `PIL.Image.save()`. diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 2d1682608a89..5fce6803c50a 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -450,6 +450,7 @@ def print_raw(self, filename_or_obj, *args, **kwargs): with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \ cbook.open_file_cm(filename_or_obj, "wb") as fh: fh.write(renderer._renderer.buffer_rgba()) + print_rgba = print_raw def print_png(self, filename_or_obj, *args, **kwargs): @@ -507,7 +508,7 @@ def print_png(self, filename_or_obj, *args, **kwargs): with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \ cbook.open_file_cm(filename_or_obj, "wb") as fh: _png.write_png(renderer._renderer, fh, - self.figure.dpi, metadata=metadata) + self.figure.dpi, metadata=metadata) def print_to_buffer(self): FigureCanvasAgg.draw(self) @@ -517,8 +518,13 @@ def print_to_buffer(self): (int(renderer.width), int(renderer.height))) if _has_pil: - # add JPEG support - def print_jpg(self, filename_or_obj, *args, dryrun=False, **kwargs): + + # Note that these methods should typically be called via savefig() and + # print_figure(), and the latter ensures that `self.figure.dpi` already + # matches the dpi kwarg (if any). + + def print_jpg(self, filename_or_obj, *args, dryrun=False, + pil_kwargs=None, **kwargs): """ Write the figure to a JPEG file. @@ -543,6 +549,11 @@ def print_jpg(self, filename_or_obj, *args, dryrun=False, **kwargs): progressive : bool If present, indicates that this image should be stored as a progressive JPEG file. + + pil_kwargs : dict, optional + Additional keyword arguments that are passed to + `PIL.Image.save` when saving the figure. These take precedence + over *quality*, *optimize* and *progressive*. """ buf, size = self.print_to_buffer() if dryrun: @@ -554,25 +565,29 @@ def print_jpg(self, filename_or_obj, *args, dryrun=False, **kwargs): color = tuple([int(x * 255) for x in rgba[:3]]) background = Image.new('RGB', size, color) background.paste(image, image) - options = {k: kwargs[k] - for k in ['quality', 'optimize', 'progressive', 'dpi'] - if k in kwargs} - options.setdefault('quality', rcParams['savefig.jpeg_quality']) - if 'dpi' in options: - # Set the same dpi in both x and y directions - options['dpi'] = (options['dpi'], options['dpi']) - - return background.save(filename_or_obj, format='jpeg', **options) + if pil_kwargs is None: + pil_kwargs = {} + for k in ["quality", "optimize", "progressive"]: + if k in kwargs: + pil_kwargs.setdefault(k, kwargs[k]) + pil_kwargs.setdefault("quality", rcParams["savefig.jpeg_quality"]) + pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) + return background.save( + filename_or_obj, format='jpeg', **pil_kwargs) + print_jpeg = print_jpg - # add TIFF support - def print_tif(self, filename_or_obj, *args, dryrun=False, **kwargs): + def print_tif(self, filename_or_obj, *args, dryrun=False, + pil_kwargs=None, **kwargs): buf, size = self.print_to_buffer() if dryrun: return image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) - dpi = (self.figure.dpi, self.figure.dpi) - return image.save(filename_or_obj, format='tiff', dpi=dpi) + if pil_kwargs is None: + pil_kwargs = {} + pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) + return image.save(filename_or_obj, format='tiff', **pil_kwargs) + print_tiff = print_tif diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 551129f82fad..ae0c9a35a00f 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2103,7 +2103,12 @@ def savefig(self, fname, *, frameon=None, transparent=None, **kwargs): `~.backend_pdf.PdfPages`. - 'eps' and 'ps' with PS backend: Only 'Creator' is supported. + pil_kwargs : dict, optional + Additional keyword arguments that are passed to `PIL.Image.save` + when saving the figure. Only applicable for formats that are saved + using Pillow, i.e. JPEG and TIFF. """ + kwargs.setdefault('dpi', rcParams['savefig.dpi']) if frameon is None: frameon = rcParams['savefig.frameon'] diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 028291d18f86..13e46aeeccb5 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -239,3 +239,14 @@ def test_jpeg_dpi(): plt.savefig(buf, format="jpg", dpi=200) im = Image.open(buf) assert im.info['dpi'] == (200, 200) + + +def test_pil_kwargs(): + Image = pytest.importorskip("PIL.Image") + from PIL.TiffTags import TAGS_V2 as TAGS + buf = io.BytesIO() + pil_kwargs = {"description": "test image"} + plt.figure().savefig(buf, format="tiff", pil_kwargs=pil_kwargs) + im = Image.open(buf) + tags = {TAGS[k].name: v for k, v in im.tag_v2.items()} + assert tags["ImageDescription"] == "test image"