Repository: python-pillow/Pillow

Issue:
Unclear error when displaying images from arrays of float dtype in notebook
Floating point images from numpy arrays via the `PIL.Image.fromarray()` raise an unclear error when accidentally returned in jupyter notebook as the final line in a cell. This triggers `IPython.display.display()` on the image object, which in turn calls the image object `_repr_png_()` method, which raises the error below.

### What did you do?
Called
```python
import numpy as np
from PIL.Image import fromarray
fromarray(np.random.random((30, 30)))
```

### What did you expect to happen?
Either:
A) Attempt to show the image given some defaults (like showing the image on a 8bit depth (resulting in a black image or very dark image)
B) More likely: Raise a clear error saying that you can't create images out of floating point values, and should instead (probably rescale to an appropriate bitdepth range and) change the dtype to be `uint8` or some other accepted dtype.

### What actually happened?
I get an error about how one cannot write mode F as png:
```python
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~\Miniconda3\envs\py\lib\site-packages\PIL\PngImagePlugin.py in _save(im, fp, filename, chunk, save_all)
   1219     try:
-> 1220         rawmode, mode = _OUTMODES[mode]
   1221     except KeyError as e:

KeyError: 'F'

The above exception was the direct cause of the following exception:

OSError                                   Traceback (most recent call last)
~\Miniconda3\envs\py\lib\site-packages\IPython\core\formatters.py in __call__(self, obj)
    343             method = get_real_method(obj, self.print_method)
    344             if method is not None:
--> 345                 return method()
    346             return None
    347         else:

~\Miniconda3\envs\py\lib\site-packages\PIL\Image.py in _repr_png_(self)
    671         """
    672         b = io.BytesIO()
--> 673         self.save(b, "PNG")
    674         return b.getvalue()
    675 

~\Miniconda3\envs\py\lib\site-packages\PIL\Image.py in save(self, fp, format, **params)
   2149 
   2150         try:
-> 2151             save_handler(self, fp, filename)
   2152         finally:
   2153             # do what we can to clean up

~\Miniconda3\envs\py\lib\site-packages\PIL\PngImagePlugin.py in _save(im, fp, filename, chunk, save_all)
   1220         rawmode, mode = _OUTMODES[mode]
   1221     except KeyError as e:
-> 1222         raise OSError(f"cannot write mode {mode} as PNG") from e
   1223 
   1224     #

OSError: cannot write mode F as PNG
```
### What are your OS, Python and Pillow versions?

* OS:  Win 10
* Python: 3.9
* Pillow: 8.0.1

<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.

The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->


Discussion:
For A, you may be interested in #2663 and the suggested PR #3172. The idea is that you could append `convert_mode=True` and it would then convert automatically if saving wasn't possible.

Resolving patch:
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -670,7 +670,10 @@ def _repr_png_(self):
         :returns: png version of the image as bytes
         """
         b = io.BytesIO()
-        self.save(b, "PNG")
+        try:
+            self.save(b, "PNG")
+        except Exception as e:
+            raise ValueError("Could not save to PNG for display") from e
         return b.getvalue()
 
     @property


Test patch:
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -537,6 +537,12 @@ def test_repr_png(self):
             assert repr_png.format == "PNG"
             assert_image_equal(im, repr_png)
 
+    def test_repr_png_error(self):
+        im = hopper("F")
+
+        with pytest.raises(ValueError):
+            im._repr_png_()
+
     def test_chunk_order(self, tmp_path):
         with Image.open("Tests/images/icc_profile.png") as im:
             test_file = str(tmp_path / "temp.png")