Repository: yt-project/yt

Issue:
Matplotlib 3.3.0 Breaks _png
<!--To help us understand and resolve your issue, please fill out the form to
the best of your ability.-->
<!--You can feel free to delete the sections that do not apply.-->

### Bug report

**Bug summary**

Matplotlib 3.3.0 removed the internal `_png` module, which breaks
https://github.com/yt-project/yt/blob/yt-3.6.0/yt/utilities/png_writer.py#L13

See the last mention in https://matplotlib.org/3.3.0/api/api_changes.html#matplotlib-now-uses-pillow-to-save-and-read-pngs

**Code for reproduction**

Just saw this on our CI:
https://travis-ci.com/github/ECP-WarpX/WarpX/jobs/361956903

MPL 3.3.0 was release about 5hrs ago.
https://github.com/matplotlib/matplotlib/releases/tag/v3.3.0

**Actual outcome**

```
File "/home/travis/.local/lib/python3.6/site-packages/yt/utilities/png_writer.py", line 13, in <module>
    import matplotlib._png as _png
ModuleNotFoundError: No module named 'matplotlib._png'
```

**Expected outcome**

:-)

**Version Information**
<!--Please specify your platform and versions of the relevant libraries you are
using:-->
  * Operating System: Ubuntu 18.04
  * Python Version: 3.6
  * yt version: 3.6.0

Installed via `python -m pip install --upgrade cmake matplotlib mpi4py numpy scipy yt`.

**Work-Around**

Downgrade matplotlib via `python -m pip install --upgrade matplotlib==3.2.2`.

Exact details:
- https://travis-ci.com/github/ECP-WarpX/WarpX/jobs/361956903
- https://github.com/ECP-WarpX/WarpX/blob/384c6ab9a864d430868a39a065f4a1d4426231af/.travis.yml#L30-L31



Discussion:
Hi, and welcome to yt!  Thanks for opening your first issue.  We have an issue template that helps us to gather relevant information to help diagnosing and fixing the issue.

Thank you for reporting this !
If I'm not mistaken, the only function that needs to be updated is `call_png_write_png` since it calls `matplotlib._png.write_png`.
breadcrumbs: here are some of the iterations matplotlib did itself during their transition to pillow
- https://github.com/matplotlib/matplotlib/pull/13207
- https://github.com/matplotlib/matplotlib/pull/13094

Furthermore it looks like they started using pillow as an optional dep since their 3.1.0 release (see [release notes](https://github.com/matplotlib/matplotlib/blob/d4bd4e70161ea438ba39080fd9168b34c0276250/doc/users/prev_whats_new/github_stats_3.1.0.rst)).
and this is the crucial commit of this transition: https://github.com/matplotlib/matplotlib/commit/370e9a2d5d9e637abc90b3270d368642c69f66c6

Resolving patch:
diff --git a/yt/utilities/png_writer.py b/yt/utilities/png_writer.py
--- a/yt/utilities/png_writer.py
+++ b/yt/utilities/png_writer.py
@@ -1,24 +1,29 @@
 from io import BytesIO
 
-import matplotlib._png as _png
+try:
+    # matplotlib switched from an internal submodule _png to using pillow (PIL)
+    # between v3.1.0 and v3.3.0
+    # So PIL should be available on any system where matplotlib._png doesn't exist
+    import matplotlib._png as _png
+except ImportError:
+    from PIL import Image
 
 
-def call_png_write_png(buffer, width, height, fileobj, dpi):
-    _png.write_png(buffer, fileobj, dpi)
+def call_png_write_png(buffer, fileobj, dpi):
+    try:
+        _png.write_png(buffer, fileobj, dpi)
+    except NameError:
+        Image.fromarray(buffer).save(fileobj, dpi=(dpi, dpi))
 
 
 def write_png(buffer, filename, dpi=100):
-    width = buffer.shape[1]
-    height = buffer.shape[0]
     with open(filename, "wb") as fileobj:
-        call_png_write_png(buffer, width, height, fileobj, dpi)
+        call_png_write_png(buffer, fileobj, dpi)
 
 
-def write_png_to_string(buffer, dpi=100, gray=0):
-    width = buffer.shape[1]
-    height = buffer.shape[0]
+def write_png_to_string(buffer, dpi=100):
     fileobj = BytesIO()
-    call_png_write_png(buffer, width, height, fileobj, dpi)
+    call_png_write_png(buffer, fileobj, dpi)
     png_str = fileobj.getvalue()
     fileobj.close()
     return png_str


Test patch: