Repository: python-pillow/Pillow

Issue:
Error when save a particular image
Hi all,
I try to simply resize one image with Pillow (3.1.1, and i try on last 3.4.2 with same result) and the save function return an error.

The image is in this link 
https://drive.google.com/file/d/0BxZoHLj1LrNeNU5vSHpnSjFNNFE/view?usp=sharing

This is the code i use
```python
from PIL import Image

img = Image.open('path/to/image')
img.thumbnail((500, 500))
img.save("path/to/save", format="JPEG", quality=90, progressive=True)
```

And this is the error returned
```
Traceback (most recent call last):
  File "path/my/script.py", line 6, in <module>
    img.save("path/to/save", format="JPEG", quality=90, progressive=True)
  File "C:\Python27\lib\site-packages\PIL\Image.py", line 1675, in save
    save_handler(self, fp, filename)
  File "C:\Python27\lib\site-packages\PIL\JpegImagePlugin.py", line 708, in _save
    ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
  File "C:\Python27\lib\site-packages\PIL\ImageFile.py", line 486, in _save
    raise IOError("encoder error %d when writing image file" % s)
IOError: encoder error -2 when writing image file
```

This error don't appear with resize (900, 900) or quality 80.
The colorspace of image is CMYK, and if convert image in RGB before i save image all is right.
But why this error appear?



Discussion:
It appears from quality=90 to quality=94. 

You're hitting this region of the code: https://github.com/python-pillow/Pillow/blob/master/PIL/JpegImagePlugin.py#L720

There's a heuristic there to make the buffer large enough to get the entire image at one shot, and from quality=90 to quality=94, the compressed image size is too large for the buffer.  In this case, the buffer size is 133000 bytes when quality < 95. The size of the image at quality=89 is 126029, at 95 is 185252. 

It's probably larger by 4/3 in CMYK vs RGB.  (actually, testing it I'm seeing 44k in RGB vs 126k in CMYK.) So that's why it fails in CMYK and not RGB.  

Resolving patch:
diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py
--- a/PIL/JpegImagePlugin.py
+++ b/PIL/JpegImagePlugin.py
@@ -716,8 +716,11 @@ def validate_qtables(qtables):
     # https://github.com/matthewwithanm/django-imagekit/issues/50
     bufsize = 0
     if optimize or progressive:
+        # CMYK can be bigger
+        if im.mode == 'CMYK':
+            bufsize = 4 * im.size[0] * im.size[1]
         # keep sets quality to 0, but the actual value may be high.
-        if quality >= 95 or quality == 0:
+        elif quality >= 95 or quality == 0:
             bufsize = 2 * im.size[0] * im.size[1]
         else:
             bufsize = im.size[0] * im.size[1]


Test patch:
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -1,7 +1,6 @@
 from helper import unittest, PillowTestCase, hopper, py3
 from helper import djpeg_available, cjpeg_available
 
-import random
 from io import BytesIO
 import os
 
@@ -29,6 +28,15 @@ def roundtrip(self, im, **options):
         im.bytes = test_bytes  # for testing only
         return im
 
+    def gen_random_image(self, size, mode='RGB'):
+        """ Generates a very hard to compress file
+        :param size: tuple
+        :param mode: optional image mode
+        
+        """
+        return Image.frombytes(mode, size,
+                               os.urandom(size[0]*size[1]*len(mode)))
+    
     def test_sanity(self):
 
         # internal version number
@@ -159,15 +167,16 @@ def test_progressive_large_buffer(self):
 
     def test_progressive_large_buffer_highest_quality(self):
         f = self.tempfile('temp.jpg')
-        if py3:
-            a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3))
-        else:
-            a = b''.join(chr(random.randint(0, 255)) for _ in range(
-                256 * 256 * 3))
-        im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1)
+        im = self.gen_random_image((255,255))
         # this requires more bytes than pixels in the image
         im.save(f, format="JPEG", progressive=True, quality=100)
 
+    def test_progressive_cmyk_buffer(self):
+        # Issue 2272, quality 90 cmyk image is tripping the large buffer bug.
+        f = BytesIO()
+        im = self.gen_random_image((256,256), 'CMYK')
+        im.save(f, format='JPEG', progressive=True, quality=94)
+        
     def test_large_exif(self):
         # https://github.com/python-pillow/Pillow/issues/148
         f = self.tempfile('temp.jpg')
@@ -436,14 +445,7 @@ def test_no_duplicate_0x1001_tag(self):
         self.assertEqual(tag_ids['RelatedImageLength'], 0x1002)
 
     def test_MAXBLOCK_scaling(self):
-        def gen_random_image(size):
-            """ Generates a very hard to compress file
-            :param size: tuple
-            """
-            return Image.frombytes('RGB',
-                                   size, os.urandom(size[0]*size[1] * 3))
-
-        im = gen_random_image((512, 512))
+        im = self.gen_random_image((512, 512))
         f = self.tempfile("temp.jpeg")
         im.save(f, quality=100, optimize=True)