diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 4adaecb7f8c0..4c8d8266c869 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -76,6 +76,7 @@ 'tif': 'Tagged Image File Format', 'tiff': 'Tagged Image File Format', 'webp': 'WebP Image Format', + 'avif': 'AV1 Image File Format', } _default_backends = { 'eps': 'matplotlib.backends.backend_ps', @@ -93,6 +94,7 @@ 'tif': 'matplotlib.backends.backend_agg', 'tiff': 'matplotlib.backends.backend_agg', 'webp': 'matplotlib.backends.backend_agg', + 'avif': 'matplotlib.backends.backend_agg', } diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index b435ae565ce4..33b0be18ca2d 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -25,6 +25,7 @@ from math import radians, cos, sin import numpy as np +from PIL import features import matplotlib as mpl from matplotlib import _api, cbook @@ -510,7 +511,19 @@ 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_gif.__doc__, print_jpg.__doc__, print_tif.__doc__, print_webp.__doc__ = map( + def print_avif(self, filename_or_obj, *, metadata=None, pil_kwargs=None): + if not features.check("avif"): + raise RuntimeError( + "The installed pillow version does not support avif. Full " + "avif support has been added in pillow 11.3." + ) + self._print_pil(filename_or_obj, "avif", pil_kwargs, metadata) + + (print_gif.__doc__, + print_jpg.__doc__, + print_tif.__doc__, + print_webp.__doc__, + print_avif.__doc__) = map( """ Write the figure to a {} file. @@ -518,10 +531,13 @@ def print_webp(self, filename_or_obj, *, metadata=None, pil_kwargs=None): ---------- filename_or_obj : str or path-like or file-like The file to write to. + metadata : None + Unused for pillow-based writers. All supported options + can be passed via *pil_kwargs*. pil_kwargs : dict, optional Additional keyword arguments that are passed to `PIL.Image.Image.save` when saving the figure. - """.format, ["GIF", "JPEG", "TIFF", "WebP"]) + """.format, ["GIF", "JPEG", "TIFF", "WebP", "AVIF"]) @_Backend.export diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 56b26904d041..100f632a9306 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -263,6 +263,20 @@ def test_pil_kwargs_webp(): assert buf_large.getbuffer().nbytes > buf_small.getbuffer().nbytes +@pytest.mark.skipif(not features.check("avif"), reason="AVIF support not available") +def test_pil_kwargs_avif(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf_small = io.BytesIO() + pil_kwargs_low = {"quality": 1} + plt.savefig(buf_small, format="avif", pil_kwargs=pil_kwargs_low) + assert len(pil_kwargs_low) == 1 + buf_large = io.BytesIO() + pil_kwargs_high = {"quality": 100} + plt.savefig(buf_large, format="avif", pil_kwargs=pil_kwargs_high) + assert len(pil_kwargs_high) == 1 + 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() @@ -290,6 +304,15 @@ def test_webp_alpha(): assert im.mode == "RGBA" +@pytest.mark.skipif(not features.check("avif"), reason="AVIF support not available") +def test_avif_alpha(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf = io.BytesIO() + plt.savefig(buf, format="avif", transparent=True) + im = Image.open(buf) + assert im.mode == "RGBA" + + def test_draw_path_collection_error_handling(): fig, ax = plt.subplots() ax.scatter([1], [1]).set_paths(Path([(0, 1), (2, 3)]))