8000 BUG: Fix savefig to GIF format with .gif suffix (#29372) · matplotlib/matplotlib@3b54c6a · GitHub
[go: up one dir, main page]

Skip to content

Commit 3b54c6a

Browse files
lpsingertacaswell
andauthored
BUG: Fix savefig to GIF format with .gif suffix (#29372)
* 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. * Update lib/matplotlib/backends/backend_agg.py Co-authored-by: Thomas A Caswell <tcaswell@gmail.com> * Update lib/matplotlib/testing/decorators.py --------- Co-authored-by: Thomas A Caswell <tcaswell@gmail.com>
1 parent 9cc62f1 commit 3b54c6a

File tree

9 files changed

+51
-5
lines changed

9 files changed

+51
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Saving figures as GIF works again
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
According to the figure documentation, the ``savefig`` method supports the
5+
GIF format with the file extension ``.gif``. However, GIF support had been
6+
broken since Matplotlib 2.0.0. It works again.

galleries/examples/lines_bars_and_markers/fill_between_alpha.py

Copy file name to clipboard
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
# regions can overlap and alpha allows you to see both. Note that the
4545
# postscript format does not support alpha (this is a postscript
4646
# limitation, not a matplotlib limitation), so when using alpha save
47-
# your figures in PNG, PDF or SVG.
47+
# your figures in GIF, PNG, PDF or SVG.
4848
#
4949
# Our next example computes two populations of random walkers with a
5050
# different mean and standard deviation of the normal distributions from

lib/matplotlib/backend_bases.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
_log = logging.getLogger(__name__)
6363
_default_filetypes = {
6464
'eps': 'Encapsulated Postscript',
65+
'gif': 'Graphics Interchange Format',
6566
'jpg': 'Joint Photographic Experts Group',
6667
'jpeg': 'Joint Photographic Experts Group',
6768
'pdf': 'Portable Document Format',
@@ -78,6 +79,7 @@
7879
}
7980
_default_backends = {
8081
'eps': 'matplotlib.backends.backend_ps',
82+
'gif': 'matplotlib.backends.backend_agg',
8183
'jpg': 'matplotlib.backends.backend_agg',
8284
'jpeg': 'matplotlib.backends.backend_agg',
8385
'pdf': 'matplotlib.backends.backend_pdf',

lib/matplotlib/backends/backend_agg.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ def print_to_buffer(self):
490490
# print_figure(), and the latter ensures that `self.figure.dpi` already
491491
# matches the dpi kwarg (if any).
492492

493+
def print_gif(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
494+
self._print_pil(filename_or_obj, "gif", pil_kwargs, metadata)
495+
493496
def print_jpg(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
494497
# savefig() has already applied savefig.facecolor; we now set it to
495498
# white to make imsave() blend semi-transparent figures against an
@@ -507,7 +510,7 @@ def print_tif(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
507510
def print_webp(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
508511
self._print_pil(filename_or_obj, "webp", pil_kwargs, metadata)
509512

510-
print_jpg.__doc__, print_tif.__doc__, print_webp.__doc__ = map(
513+
print_gif.__doc__, print_jpg.__doc__, print_tif.__doc__, print_webp.__doc__ = map(
511514
"""
512515
Write the figure to a {} file.
513516
@@ -518,7 +521,7 @@ def print_webp(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
518521
pil_kwargs : dict, optional
519522
Additional keyword arguments that are passed to
520523
`PIL.Image.Image.save` when saving the figure.
521-
""".format, ["JPEG", "TIFF", "WebP"])
524+
""".format, ["GIF", "JPEG", "TIFF", "WebP"])
522525

523526

524527
@_Backend.export

lib/matplotlib/testing/compare.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ def _read_until(self, terminator):
9999
return bytes(buf)
100100

101101

102+
class _MagickConverter:
103+
def __call__(self, orig, dest):
104+
try:
105+
subprocess.run(
106+
[mpl._get_executable_info("magick").executable, orig, dest],
107+
check=True)
108+
except subprocess.CalledProcessError as e:
109+
raise _ConverterError() from e
110+
111+
102112
class _GSConverter(_Converter):
103113
def __call__(self, orig, dest):
104114
if not self._proc:
@@ -230,6 +240,12 @@ def __call__(self, orig, dest):
230240

231241

232242
def _update_converter():
243+
try:
244+
mpl._get_executable_info("magick")
245+
except mpl.ExecutableNotFoundError:
246+
pass
247+
else:
248+
converter['gif'] = _MagickConverter()
233249
try:
234250
mpl._get_executable_info("gs")
235251
except mpl.ExecutableNotFoundError:

lib/matplotlib/testing/decorators.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ def wrapper(*args, extension, request, **kwargs):
204204

205205
if extension not in comparable_formats():
206206
reason = {
207+
'gif': 'because ImageMagick is not installed',
207208
'pdf': 'because Ghostscript is not installed',
208209
'eps': 'because Ghostscript is not installed',
209210
'svg': 'because Inkscape is not installed',
@@ -279,7 +280,7 @@ def image_comparison(baseline_images, extensions=None, tol=0,
279280
extensions : None or list of str
280281
The list of extensions to test, e.g. ``['png', 'pdf']``.
281282
282-
If *None*, defaults to all supported extensions: png, pdf, and svg.
283+
If *None*, defaults to: png, pdf, and svg.
283284
284285
When testing a single extension, it can be directly included in the
285286
names passed to *baseline_images*. In that case, *extensions* must not
Loading

lib/matplotlib/tests/test_agg.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,24 @@ def test_pil_kwargs_webp():
263263
assert buf_large.getbuffer().nbytes > buf_small.getbuffer().nbytes
264264

265265

266+
def test_gif_no_alpha():
267+
plt.plot([0, 1, 2], [0, 1, 0])
268+
buf = io.BytesIO()
269+
plt.savefig(buf, format="gif", transparent=False)
270+
im = Image.open(buf)
271+
assert im.mode == "P"
272+
assert im.info["transparency"] >= len(im.palette.colors)
273+
274+
275+
def test_gif_alpha():
276+
plt.plot([0, 1, 2], [0, 1, 0])
277+
buf = io.BytesIO()
278+
plt.savefig(buf, format="gif", transparent=True)
279+
im = Image.open(buf)
280+
assert im.mode == "P"
281+
assert im.info["transparency"] < len(im.palette.colors)
282+
283+
266284
@pytest.mark.skipif(not features.check("webp"), reason="WebP support not available")
267285
def test_webp_alpha():
268286
plt.plot([0, 1, 2], [0, 1, 0])

lib/matplotlib/tests/test_agg_filter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
@image_comparison(baseline_images=['agg_filter_alpha'],
8-
extensions=['png', 'pdf'])
8+
extensions=['gif', 'png', 'pdf'])
99
def test_agg_filter_alpha():
1010
# Remove this line when this test image is regenerated.
1111
plt.rcParams['pcolormesh.snap'] = False

0 commit comments

Comments
 (0)
0