Repository: matplotlib/matplotlib

Issue:
[ENH]: Support avif as output format
### Problem

Since  v3.6.0, Matplotlib supports `webp` output (yay!), but I couldn't find a ticket tracking Avif support, so here's a feature request for `savefig` to support saving as `.avif`. 

*relevant links:*
- [CanIUse](https://caniuse.com/avif)
- [Pillow pull request](https://github.com/python-pillow/Pillow/pull/5201)


### Proposed solution

_No response_


Discussion:
I think this would need to wait for pillow to support? 
While Pillow support is a prerequisite (and probably sufficient) condition for `savefig` to support AVIF, animations actually may have some support already.



Newer versions of `ffmpeg` support AVIF (though I could not test easily because the version in my distro's repository is too old).


The `imagemagick` writer did save an AVIF file. However, it may have only saved a single frame... (Not 100% sure if that is a viewer program problem or file output problem)



```python
ani.save("test.avif", writer="imagemagick")
```

Gives: (zipped AVIF file because github doesn't allow AVIF directly)
[test.zip](https://github.com/user-attachments/files/16983429/test.zip)


### Good first issue - notes for new contributors

This issue is suited to new contributors because it does not require understanding of the
Matplotlib internals. To get started, please see our [contributing
guide](https://matplotlib.org/stable/devel/index).

**We do not assign issues**. Check the *Development* section in the sidebar for linked pull
requests (PRs). If there are none, feel free to start working on it. If there is an open PR, please
collaborate on the work by reviewing it rather than duplicating it in a competing PR.

If something is unclear, please reach out on any of our [communication
channels](https://matplotlib.org/stable/devel/contributing.html#get-connected).
Until that PR is merged you can use a separate package, [pillow-avif-plugin](https://github.com/fdintino/pillow-avif-plugin), to allow Pillow to save AVIF images.
hello, has this been worked on yet? Am I able to work on this as my first issue?
what would be the difficulty level of this issue?
I have successfully added this functionality by adding the [pillow-avif-plugin ](https://pypi.org/project/pillow-avif-plugin/) library to all relevant files. I am working on making a pull request with these changes. I added the pillow-avif-plugin library to all the requirements files where pillow is listed, so it is automatically installed.
We want to keep our dependencies small. Therefore, I don't think we should add the plugins to our requirements.
There seems to be progress on this upstream: https://github.com/python-pillow/Pillow/pull/5201
Pillow now supports avif (when built from source)
https://pillow.readthedocs.io/en/stable/releasenotes/11.2.1.html
So, would it make sense to try implementing support for AVIF now, or should we wait until it's officially included in the Pillow binaries (if that's supposed to happen)?
Hi! I’d like to work on this issue. Is anyone else already working on it or are there any specific guidelines I should follow?  (this is my first contribution)
With Pillow 1.3 avif support is now included in Pillow’s wheels (except some):

https://pillow.readthedocs.io/en/stable/releasenotes/11.3.0.html#avif-support-in-wheels
Is there anything to do on our side? I'd expect that saving will "just work" with 1.3?
> Is there anything to do on our side? I'd expect that saving will "just work" with 1.3?

With `matplotlib==3.10.3` and `pillow==11.3.0` I still get:

```
> import matplotlib.pyplot as plt
> fig = plt.figure()
> fig.savefig('test.avif')

/.../

`ValueError: Format 'avif' is not supported (supported formats: eps, jpeg, jpg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff, webp)
```


Ah, we need to internally register the format. Changes should be analogous to #21274.



Patch:
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


Test patch:
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)]))