Repository: python-pillow/Pillow

Issue:
`Image.frombytes` throws on empty image
<!--
Thank you for reporting an issue.

Follow these guidelines to ensure your issue is handled properly.

If you have a ...

1.  General question: consider asking the question on Stack Overflow
    with the python-imaging-library tag:

    * https://stackoverflow.com/questions/tagged/python-imaging-library

    Do not ask a question in both places.

    If you think you have found a bug or have an unexplained exception
    then file a bug report here.

2.  Bug report: include a self-contained, copy-pastable example that
    generates the issue if possible. Be concise with code posted.
    Guidelines on how to provide a good bug report:

    * https://stackoverflow.com/help/mcve

    Bug reports which follow these guidelines are easier to diagnose,
    and are often handled much more quickly.

3.  Feature request: do a quick search of existing issues
    to make sure this has not been asked before.

We know asking good questions takes effort, and we appreciate your time.
Thank you.
-->

### What did you do?

I have some code I use to render text as images. When trying to render an empty string, it fails on `Image.frombytes`

### What did you expect to happen?

I expected `Image.frombytes` with size `(0, 0)` and bytes `[]` (as a numpy array of uint8) to successfully yield an empty image.

### What actually happened?

An exception is thrown:

```pytb
Traceback (most recent call last):
  File "/home/devin/Desktop/pyblox/netsblox/turtle.py", line 89, in wrapped
    fn(*args, **kwargs)
  File "/home/devin/Desktop/pyblox/netsblox/turtle.py", line 119, in wrapped
    return f(*args, **kwargs)
  File "<stdin>", line 34, in my_onstart
  File "/home/devin/Desktop/pyblox/netsblox/turtle.py", line 1250, in say
    imgs = [_render_text(x, size = 8, color = (0, 0, 0)) for x in lines]
  File "/home/devin/Desktop/pyblox/netsblox/turtle.py", line 1250, in <listcomp>
    imgs = [_render_text(x, size = 8, color = (0, 0, 0)) for x in lines]
  File "/home/devin/Desktop/pyblox/netsblox/turtle.py", line 80, in _render_text
    text_mask = Image.frombytes('L', text_mask.size, _np.array(text_mask).astype(_np.uint8)) # convert ImagingCore to Image
  File "/home/devin/.local/lib/python3.8/site-packages/PIL/Image.py", line 2976, in frombytes
    im.frombytes(data, decoder_name, args)
  File "/home/devin/.local/lib/python3.8/site-packages/PIL/Image.py", line 804, in frombytes
    d.setimage(self.im)
ValueError: tile cannot extend outside image
```

### What are your OS, Python and Pillow versions?

* OS: Linux Mint 20
* Python: 3.8.10
* Pillow: 10.1.0

<!--
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.
-->

```python
font = ImageFont.truetype('example-font.otf')
text = ''
color = (0, 0, 0)

text_mask = font.getmask(text, mode = 'L')
text_mask = Image.frombytes('L', text_mask.size, _np.array(text_mask).astype(_np.uint8)) # fails here with exception
```



Discussion:
Looks like I constructed a minimal example incorrectly. The issue was actually from trying to render empty string, due to a pagination step in between getting the text and rendering it. In that case, the issue would actually be that `Image.frombytes` throws an error when initializing it with size `(0, 0)` and bytes `[]`. It seems like this should just successfully return an empty image.

**updated the issue report**

Resolving patch:
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -791,6 +791,9 @@ def frombytes(self, data, decoder_name="raw", *args):
         but loads data into this image instead of creating a new image object.
         """
 
+        if self.width == 0 or self.height == 0:
+            return
+
         # may pass tuple instead of argument list
         if len(args) == 1 and isinstance(args[0], tuple):
             args = args[0]
@@ -2967,15 +2970,16 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
 
     _check_size(size)
 
-    # may pass tuple instead of argument list
-    if len(args) == 1 and isinstance(args[0], tuple):
-        args = args[0]
+    im = new(mode, size)
+    if im.width != 0 and im.height != 0:
+        # may pass tuple instead of argument list
+        if len(args) == 1 and isinstance(args[0], tuple):
+            args = args[0]
 
-    if decoder_name == "raw" and args == ():
-        args = mode
+        if decoder_name == "raw" and args == ():
+            args = mode
 
-    im = new(mode, size)
-    im.frombytes(data, decoder_name, args)
+        im.frombytes(data, decoder_name, args)
     return im
 
 


Test patch:
diff --git a/Tests/test_image.py b/Tests/test_image.py
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -906,6 +906,13 @@ def test_zero_tobytes(self, size):
         im = Image.new("RGB", size)
         assert im.tobytes() == b""
 
+    @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
+    def test_zero_frombytes(self, size):
+        Image.frombytes("RGB", size, b"")
+
+        im = Image.new("RGB", size)
+        im.frombytes(b"")
+
     def test_has_transparency_data(self):
         for mode in ("1", "L", "P", "RGB"):
             im = Image.new(mode, (1, 1))