From 7aad47a379bf00da00a5e97df2156f609cd7ac3e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 17 Oct 2019 15:23:48 +0200 Subject: [PATCH 1/4] Reuse png metadata handling of imsave() in FigureCanvasAgg.print_png(). This avoids duplicating the conversion of metadata to PngInfo and revealed a bug in the priority between `metadata` and `pil_kwargs` in imsave(). Note that because `np.asarray(self.buffer_rgba())` is already a RGBA uint8 array, there is no colormapping step happening in imsave(). Ideally mplcairo should also be able to use imsave() for saving to png. --- lib/matplotlib/backends/backend_agg.py | 17 ++++------------- lib/matplotlib/image.py | 6 ++++-- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index bae911aa6cac..e45fcb104881 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -33,7 +33,6 @@ import numpy as np from PIL import Image -from PIL.PngImagePlugin import PngInfo import matplotlib as mpl from matplotlib import cbook @@ -502,24 +501,16 @@ def print_png(self, filename_or_obj, *args, if metadata is None: metadata = {} - if pil_kwargs is None: - pil_kwargs = {} metadata = { "Software": f"matplotlib version{mpl.__version__}, http://matplotlib.org/", **metadata, } FigureCanvasAgg.draw(self) - # Only use the metadata kwarg if pnginfo is not set, because the - # semantics of duplicate keys in pnginfo is unclear. - if "pnginfo" not in pil_kwargs: - pnginfo = PngInfo() - for k, v in metadata.items(): - pnginfo.add_text(k, v) - pil_kwargs["pnginfo"] = pnginfo - pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi)) - (Image.fromarray(np.asarray(self.buffer_rgba())) - .save(filename_or_obj, format="png", **pil_kwargs)) + mpl.image.imsave( + filename_or_obj, np.asarray(self.buffer_rgba()), format="png", + origin="upper", dpi=self.figure.dpi, + metadata=metadata, pil_kwargs=pil_kwargs) def print_to_buffer(self): FigureCanvasAgg.draw(self) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 1608db9f5090..3f558d1ab839 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1520,8 +1520,10 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, pil_shape = (rgba.shape[1], rgba.shape[0]) image = PIL.Image.frombuffer( "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) - if format == "png" and metadata is not None: - # cf. backend_agg's print_png. + if (format == "png" + and metadata is not None and "pnginfo" not in pil_kwargs): + # Only use the metadata kwarg if pnginfo is not set, because the + # semantics of duplicate keys in pnginfo is unclear. pnginfo = PIL.PngImagePlugin.PngInfo() for k, v in metadata.items(): pnginfo.add_text(k, v) From c212f2fe79c77424212e26e9f411db9fb7e20322 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 18 Oct 2019 02:23:52 +0200 Subject: [PATCH 2/4] Warn when pil_kwargs["pnginfo"] overrides metadata. --- lib/matplotlib/backends/backend_agg.py | 8 -------- lib/matplotlib/image.py | 20 ++++++++++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index e45fcb104881..c2331cb7ee8f 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -498,14 +498,6 @@ def print_png(self, filename_or_obj, *args, If the 'pnginfo' key is present, it completely overrides *metadata*, including the default 'Software' key. """ - - if metadata is None: - metadata = {} - metadata = { - "Software": - f"matplotlib version{mpl.__version__}, http://matplotlib.org/", - **metadata, - } FigureCanvasAgg.draw(self) mpl.image.imsave( filename_or_obj, np.asarray(self.buffer_rgba()), format="png", diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 3f558d1ab839..d2e3c263abac 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1520,14 +1520,22 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, pil_shape = (rgba.shape[1], rgba.shape[0]) image = PIL.Image.frombuffer( "RGBA", pil_shape, rgba, "raw", "RGBA", 0, 1) - if (format == "png" - and metadata is not None and "pnginfo" not in pil_kwargs): + if format == "png": # Only use the metadata kwarg if pnginfo is not set, because the # semantics of duplicate keys in pnginfo is unclear. - pnginfo = PIL.PngImagePlugin.PngInfo() - for k, v in metadata.items(): - pnginfo.add_text(k, v) - pil_kwargs["pnginfo"] = pnginfo + if "pnginfo" in pil_kwargs: + if metadata: + cbook._warn_external("'metadata' is overridden by the " + "'pnginfo' entry in 'pil_kwargs'.") + else: + metadata = { + "Software": (f"Matplotlib version{mpl.__version__}, " + f"https://matplotlib.org/"), + **(metadata if metadata is not None else {}), + } + pil_kwargs["pnginfo"] = pnginfo = PIL.PngImagePlugin.PngInfo() + for k, v in metadata.items(): + pnginfo.add_text(k, v) if format in ["jpg", "jpeg"]: format = "jpeg" # Pillow doesn't recognize "jpg". facecolor = mpl.rcParams["savefig.facecolor"] From 83f82f22552701871fa3aea4f1971b9dac6f54ed Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 24 May 2020 00:49:28 +0200 Subject: [PATCH 3/4] Save a couple of numpy conversions. --- lib/matplotlib/backends/backend_agg.py | 5 ++--- lib/matplotlib/image.py | 10 +++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index c2331cb7ee8f..2d1f0a0dfd39 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -500,9 +500,8 @@ def print_png(self, filename_or_obj, *args, """ FigureCanvasAgg.draw(self) mpl.image.imsave( - filename_or_obj, np.asarray(self.buffer_rgba()), format="png", - origin="upper", dpi=self.figure.dpi, - metadata=metadata, pil_kwargs=pil_kwargs) + filename_or_obj, self.buffer_rgba(), format="png", origin="upper", + dpi=self.figure.dpi, metadata=metadata, pil_kwargs=pil_kwargs) def print_to_buffer(self): FigureCanvasAgg.draw(self) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index d2e3c263abac..51f8a39825ff 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1514,7 +1514,15 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, origin = mpl.rcParams["image.origin"] if origin == "lower": arr = arr[::-1] - rgba = sm.to_rgba(arr, bytes=True) + if (isinstance(arr, memoryview) and arr.format == "B" + and arr.ndim == 3 and arr.shape[-1] == 4): + # Such an ``arr`` would also be handled fine by sm.to_rgba (after + # casting with asarray), but it is useful to special-case it + # because that's what backend_agg passes, and can be in fact used + # as is, saving a few operations. + rgba = arr + else: + rgba = sm.to_rgba(arr, bytes=True) if pil_kwargs is None: pil_kwargs = {} pil_shape = (rgba.shape[1], rgba.shape[0]) From 92a33aa7d85a8f59e600e579dfd44ee5a7214b8e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 29 May 2020 13:07:50 +0200 Subject: [PATCH 4/4] Skip None entries in png metadata. --- lib/matplotlib/backends/backend_agg.py | 4 ++-- lib/matplotlib/image.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 2d1f0a0dfd39..658503e3c37d 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -484,8 +484,8 @@ def print_png(self, filename_or_obj, *args, Other keywords may be invented for other purposes. - If 'Software' is not given, an autogenerated value for matplotlib - will be used. + If 'Software' is not given, an autogenerated value for Matplotlib + will be used. This can be removed by setting it to *None*. For more details see the `PNG specification`_. diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 51f8a39825ff..803baa673e2d 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1543,7 +1543,8 @@ def imsave(fname, arr, vmin=None, vmax=None, cmap=None, format=None, } pil_kwargs["pnginfo"] = pnginfo = PIL.PngImagePlugin.PngInfo() for k, v in metadata.items(): - pnginfo.add_text(k, v) + if v is not None: + pnginfo.add_text(k, v) if format in ["jpg", "jpeg"]: format = "jpeg" # Pillow doesn't recognize "jpg". facecolor = mpl.rcParams["savefig.facecolor"]