From b1fd9187499440d353e3143b8e1f52f88a88793d Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Sun, 22 Dec 2024 13:48:58 -0500 Subject: [PATCH 1/3] DOC / BUG: Fix savefig to GIF format with .gif suffix According to https://matplotlib.org/stable/users/explain/figure/figure_intro.html#saving-figures: > Many types of output are supported, including raster formats like > PNG, GIF, JPEG, TIFF and vector formats like PDF, EPS, and SVG. However, GIF support was broken by #6178. Restore GIF support. --- doc/users/next_whats_new/gif_savefig.rst | 6 ++++++ .../fill_between_alpha.py | 2 +- lib/matplotlib/backend_bases.py | 2 ++ lib/matplotlib/backends/backend_agg.py | 10 ++++++++-- lib/matplotlib/testing/compare.py | 16 ++++++++++++++++ lib/matplotlib/testing/decorators.py | 5 +++-- .../test_agg_filter/agg_filter_alpha.gif | Bin 0 -> 15787 bytes lib/matplotlib/tests/test_agg.py | 18 ++++++++++++++++++ lib/matplotlib/tests/test_agg_filter.py | 2 +- 9 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 doc/users/next_whats_new/gif_savefig.rst create mode 100644 lib/matplotlib/tests/baseline_images/test_agg_filter/agg_filter_alpha.gif diff --git a/doc/users/next_whats_new/gif_savefig.rst b/doc/users/next_whats_new/gif_savefig.rst new file mode 100644 index 000000000000..e6f4732e8b95 --- /dev/null +++ b/doc/users/next_whats_new/gif_savefig.rst @@ -0,0 +1,6 @@ +Saving figures as GIF works again +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +According to the figure documentation, the ``savefig`` method supports the +GIF format with the file extension ``.gif``. However, GIF support had been +broken since Matplotlib 2.0.0. It works again. diff --git a/galleries/examples/lines_bars_and_markers/fill_between_alpha.py b/galleries/examples/lines_bars_and_markers/fill_between_alpha.py index f462f6bf2428..487ae9aaf62d 100644 --- a/galleries/examples/lines_bars_and_markers/fill_between_alpha.py +++ b/galleries/examples/lines_bars_and_markers/fill_between_alpha.py @@ -44,7 +44,7 @@ # regions can overlap and alpha allows you to see both. Note that the # postscript format does not support alpha (this is a postscript # limitation, not a matplotlib limitation), so when using alpha save -# your figures in PNG, PDF or SVG. +# your figures in GIF, PNG, PDF or SVG. # # Our next example computes two populations of random walkers with a # different mean and standard deviation of the normal distributions from diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index cd39db6c3a51..4d087911a5b2 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -62,6 +62,7 @@ _log = logging.getLogger(__name__) _default_filetypes = { 'eps': 'Encapsulated Postscript', + 'gif': 'Graphics Interchange Format', 'jpg': 'Joint Photographic Experts Group', 'jpeg': 'Joint Photographic Experts Group', 'pdf': 'Portable Document Format', @@ -78,6 +79,7 @@ } _default_backends = { 'eps': 'matplotlib.backends.backend_ps', + 'gif': 'matplotlib.backends.backend_agg', 'jpg': 'matplotlib.backends.backend_agg', 'jpeg': 'matplotlib.backends.backend_agg', 'pdf': 'matplotlib.backends.backend_pdf', diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index c37427369267..901db85366ed 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -490,6 +490,12 @@ def print_to_buffer(self): # print_figure(), and the latter ensures that `self.figure.dpi` already # matches the dpi kwarg (if any). + def print_gif(self, filename_or_obj, *, metadata=None, pil_kwargs=None): + # savefig() has already applied savefig.facecolor; we now set it to + # white to make imsave() blend semi-transparent figures against an + # assumed white background. + self._print_pil(filename_or_obj, "gif", pil_kwargs, metadata) + def print_jpg(self, filename_or_obj, *, metadata=None, pil_kwargs=None): # savefig() has already applied savefig.facecolor; we now set it to # white to make imsave() blend semi-transparent figures against an @@ -507,7 +513,7 @@ def print_tif(self, filename_or_obj, *, metadata=None, pil_kwargs=None): def print_webp(self, filename_or_obj, *, metadata=None, pil_kwargs=None): self._print_pil(filename_or_obj, "webp", pil_kwargs, metadata) - print_jpg.__doc__, print_tif.__doc__, print_webp.__doc__ = map( + print_gif.__doc__, print_jpg.__doc__, print_tif.__doc__, print_webp.__doc__ = map( """ Write the figure to a {} file. @@ -518,7 +524,7 @@ def print_webp(self, filename_or_obj, *, metadata=None, pil_kwargs=None): pil_kwargs : dict, optional Additional keyword arguments that are passed to `PIL.Image.Image.save` when saving the figure. - """.format, ["JPEG", "TIFF", "WebP"]) + """.format, ["GIF", "JPEG", "TIFF", "WebP"]) @_Backend.export diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index 0f252bc1da8e..c598dfcddc66 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -99,6 +99,16 @@ def _read_until(self, terminator): return bytes(buf) +class _MagickConverter: + def __call__(self, orig, dest): + try: + subprocess.run( + [mpl._get_executable_info("magick").executable, orig, dest], + check=True) + except subprocess.CalledProcessError as e: + raise _ConverterError() from e + + class _GSConverter(_Converter): def __call__(self, orig, dest): if not self._proc: @@ -230,6 +240,12 @@ def __call__(self, orig, dest): def _update_converter(): + try: + mpl._get_executable_info("magick") + except mpl.ExecutableNotFoundError: + pass + else: + converter['gif'] = _MagickConverter() try: mpl._get_executable_info("gs") except mpl.ExecutableNotFoundError: diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 6f1af7debdb3..781feb90ddba 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -204,6 +204,7 @@ def wrapper(*args, extension, request, **kwargs): if extension not in comparable_formats(): reason = { + 'gif': 'because ImageMagick is not installed', 'pdf': 'because Ghostscript is not installed', 'eps': 'because Ghostscript is not installed', 'svg': 'because Inkscape is not installed', @@ -279,7 +280,7 @@ def image_comparison(baseline_images, extensions=None, tol=0, extensions : None or list of str The list of extensions to test, e.g. ``['png', 'pdf']``. - If *None*, defaults to all supported extensions: png, pdf, and svg. + If *None*, defaults to: png, pdf, and svg. When testing a single extension, it can be directly included in the names passed to *baseline_images*. In that case, *extensions* must not @@ -359,7 +360,7 @@ def check_figures_equal(*, extensions=("png", "pdf", "svg"), tol=0): Parameters ---------- - extensions : list, default: ["png", "pdf", "svg"] + extensions : list, default: ["gif", "png", "pdf", "svg"] The extensions to test. tol : float The RMS threshold above which the test is considered failed. diff --git a/lib/matplotlib/tests/baseline_images/test_agg_filter/agg_filter_alpha.gif b/lib/matplotlib/tests/baseline_images/test_agg_filter/agg_filter_alpha.gif new file mode 100644 index 0000000000000000000000000000000000000000..01a2d0bc288e1afcad3702514c9d8f52f6db46e9 GIT binary patch literal 15787 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 G-~R#5Q0~nD literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 59387793605a..56b26904d041 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -263,6 +263,24 @@ def test_pil_kwargs_webp(): assert buf_large.getbuffer().nbytes > buf_small.getbuffer().nbytes +def test_gif_no_alpha(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf = io.BytesIO() + plt.savefig(buf, format="gif", transparent=False) + im = Image.open(buf) + assert im.mode == "P" + assert im.info["transparency"] >= len(im.palette.colors) + + +def test_gif_alpha(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf = io.BytesIO() + plt.savefig(buf, format="gif", transparent=True) + im = Image.open(buf) + assert im.mode == "P" + assert im.info["transparency"] < len(im.palette.colors) + + @pytest.mark.skipif(not features.check("webp"), reason="WebP support not available") def test_webp_alpha(): plt.plot([0, 1, 2], [0, 1, 0]) diff --git a/lib/matplotlib/tests/test_agg_filter.py b/lib/matplotlib/tests/test_agg_filter.py index dc8cff6858ae..545e62d20d7c 100644 --- a/lib/matplotlib/tests/test_agg_filter.py +++ b/lib/matplotlib/tests/test_agg_filter.py @@ -5,7 +5,7 @@ @image_comparison(baseline_images=['agg_filter_alpha'], - extensions=['png', 'pdf']) + extensions=['gif', 'png', 'pdf']) def test_agg_filter_alpha(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False From 534f5a75275a71b6e478ad09f8bc7718bb379207 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Mon, 23 Dec 2024 11:46:00 -0500 Subject: [PATCH 2/3] Update lib/matplotlib/backends/backend_agg.py Co-authored-by: Thomas A Caswell --- lib/matplotlib/backends/backend_agg.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 901db85366ed..b435ae565ce4 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -491,9 +491,6 @@ def print_to_buffer(self): # matches the dpi kwarg (if any). def print_gif(self, filename_or_obj, *, metadata=None, pil_kwargs=None): - # savefig() has already applied savefig.facecolor; we now set it to - # white to make imsave() blend semi-transparent figures against an - # assumed white background. self._print_pil(filename_or_obj, "gif", pil_kwargs, metadata) def print_jpg(self, filename_or_obj, *, metadata=None, pil_kwargs=None): From b983a9739559abc7e5b3ce4227daed243c3fb5a3 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Wed, 15 Jan 2025 22:24:04 -0500 Subject: [PATCH 3/3] Update lib/matplotlib/testing/decorators.py --- lib/matplotlib/testing/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 781feb90ddba..af9ef48d66cc 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -360,7 +360,7 @@ def check_figures_equal(*, extensions=("png", "pdf", "svg"), tol=0): Parameters ---------- - extensions : list, default: ["gif", "png", "pdf", "svg"] + extensions : list, default: ["png", "pdf", "svg"] The extensions to test. tol : float The RMS threshold above which the test is considered failed.