Repository: python-pillow/Pillow

Issue:
Image of size zero is not getting initialized
I want an image with zero size. I have a lot of use for it.

```py
>>> from PIL import Image as im
>>> import numpy as np
>>> im.fromarray(np.empty((0, 0), dtype=np.uint8))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.4/dist-packages/PIL/Image.py", line 2187, in fromarray
    return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
  File "/usr/local/lib/python3.4/dist-packages/PIL/Image.py", line 2114, in frombuffer
    _check_size(size)
  File "/usr/local/lib/python3.4/dist-packages/PIL/Image.py", line 2001, in _check_size
    raise ValueError("Width and Height must be > 0")
ValueError: Width and Height must be > 0
```

This was working previously. But after this [commit](https://github.com/python-pillow/Pillow/commit/445451c0b9347b50e0f603db33f196e207de470d), in the latest version (i.e. 3.4.2) it is throwing an error.

As @manthey points out in the commit, one can create a zero sized image by cropping an existing one. So this too should be allowed. 

Thanks.


Discussion:

Resolving patch:
diff --git a/PIL/Image.py b/PIL/Image.py
--- a/PIL/Image.py
+++ b/PIL/Image.py
@@ -1994,8 +1994,8 @@ def _check_size(size):
         raise ValueError("Size must be a tuple")
     if len(size) != 2:
         raise ValueError("Size must be a tuple of length 2")
-    if size[0] <= 0 or size[1] <= 0:
-        raise ValueError("Width and Height must be > 0")
+    if size[0] < 0 or size[1] < 0:
+        raise ValueError("Width and Height must be => 0")
 
     return True
 


Test patch:
diff --git a/Tests/test_image.py b/Tests/test_image.py
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -256,7 +256,11 @@ def test_check_size(self):
         with self.assertRaises(ValueError):
             Image.new('RGB', (0,))  # Tuple too short
         with self.assertRaises(ValueError):
-            Image.new('RGB', (0,0))  # w,h <= 0
+            Image.new('RGB', (-1,-1))  # w,h < 0
+
+        # this should pass with 0 sized images, #2259
+        im = Image.new('L', (0, 0))
+        self.assertEqual(im.size, (0, 0))
 
         self.assertTrue(Image.new('RGB', (1,1)))
         # Should pass lists too
diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py
--- a/Tests/test_image_access.py
+++ b/Tests/test_image_access.py
@@ -78,12 +78,25 @@ def check(self, mode, c=None):
             im.getpixel((0, 0)), c,
             "put/getpixel roundtrip failed for mode %s, color %s" % (mode, c))
 
+        # Check 0
+        im = Image.new(mode, (0, 0), None)
+        with self.assertRaises(IndexError):
+            im.putpixel((0, 0), c)
+        with self.assertRaises(IndexError):
+            im.getpixel((0, 0))
+            
         # check initial color
         im = Image.new(mode, (1, 1), c)
         self.assertEqual(
             im.getpixel((0, 0)), c,
             "initial color failed for mode %s, color %s " % (mode, c))
 
+        # Check 0
+        im = Image.new(mode, (0, 0), c)
+        with self.assertRaises(IndexError):
+            im.getpixel((0, 0))
+
+
     def test_basic(self):
         for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F",
                      "P", "PA", "RGB", "RGBA", "RGBX", "CMYK", "YCbCr"):
diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py
--- a/Tests/test_image_convert.py
+++ b/Tests/test_image_convert.py
@@ -19,6 +19,11 @@ def convert(im, mode):
             for mode in modes:
                 convert(im, mode)
 
+            # Check 0
+            im = Image.new(mode, (0,0))
+            for mode in modes:
+                convert(im, mode)
+                  
     def test_default(self):
 
         im = hopper("P")
diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py
--- a/Tests/test_image_copy.py
+++ b/Tests/test_image_copy.py
@@ -1,5 +1,7 @@
 from helper import unittest, PillowTestCase, hopper
 
+from PIL import Image
+
 import copy
 
 
@@ -33,5 +35,12 @@ def test_copy(self):
             self.assertEqual(out.mode, im.mode)
             self.assertEqual(out.size, croppedSize)
 
+    def test_copy_zero(self):
+        im = Image.new('RGB', (0,0))
+        out = im.copy()
+        self.assertEqual(out.mode, im.mode)
+        self.assertEqual(out.size, im.size)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py
--- a/Tests/test_image_crop.py
+++ b/Tests/test_image_crop.py
@@ -83,6 +83,23 @@ def test_crop_crash(self):
         img = img.crop(extents)
         img.load()
 
+    def test_crop_zero(self):
+        
+        im = Image.new('RGB', (0, 0), 'white')
+        
+        cropped = im.crop((0, 0, 0, 0))
+        self.assertEqual(cropped.size, (0, 0))
+
+        cropped = im.crop((10, 10, 20, 20))
+        self.assertEqual(cropped.size, (10, 10))
+        self.assertEqual(cropped.getdata()[0], (0, 0, 0))
+
+        im = Image.new('RGB', (0, 0))
+        
+        cropped = im.crop((10, 10, 20, 20))
+        self.assertEqual(cropped.size, (10, 10))
+        self.assertEqual(cropped.getdata()[2], (0, 0, 0))
+
 
 
 if __name__ == '__main__':
diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py
--- a/Tests/test_image_resize.py
+++ b/Tests/test_image_resize.py
@@ -89,6 +89,14 @@ def test_endianness(self):
                         # as separately resized channel
                         self.assert_image_equal(ch, references[channels[i]])
 
+    def test_enlarge_zero(self):
+        for f in [Image.NEAREST, Image.BOX, Image.BILINEAR, Image.HAMMING,
+                Image.BICUBIC, Image.LANCZOS]:
+            r = self.resize(Image.new('RGB', (0,0), "white"), (212, 195), f)
+            self.assertEqual(r.mode, "RGB")
+            self.assertEqual(r.size, (212, 195))
+            self.assertEqual(r.getdata()[0], (0,0,0))
+
 
 class TestImageResize(PillowTestCase):
 
diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py
--- a/Tests/test_image_rotate.py
+++ b/Tests/test_image_rotate.py
@@ -13,15 +13,24 @@ def rotate(im, mode, angle):
             self.assertEqual(out.mode, mode)
             if angle % 180 == 0:
                 self.assertEqual(out.size, im.size)
+            elif im.size == (0, 0):
+                self.assertEqual(out.size, im.size)
             else:
                 self.assertNotEqual(out.size, im.size)
-        for mode in "1", "P", "L", "RGB", "I", "F":
+
+                
+        for mode in ("1", "P", "L", "RGB", "I", "F"):
             im = hopper(mode)
             rotate(im, mode, 45)
-        for angle in 0, 90, 180, 270:
+
+        for angle in (0, 90, 180, 270):
             im = Image.open('Tests/images/test-card.png')
             rotate(im, im.mode, angle)
 
+        for angle in (0, 45, 90, 180, 270):
+            im = Image.new('RGB',(0,0))
+            rotate(im, im.mode, angle)
+
 
 if __name__ == '__main__':
     unittest.main()