Repository: python-pillow/Pillow

Issue:
Saving .ico saves more sizes than necessary
### What did you do?
```python
from PIL import Image
im = Image.open('python.ico')
im.save('saved.ico')
im_saved = Image.open('saved.ico')

print im.info['sizes']
print im_saved.info['sizes']

assert im_saved.info['sizes'] == set([(16, 16), (24, 24), (32, 32), (48, 48)])
```

### What did you expect to happen?

No assertion, and only relevant sizes saved:

```
set([(16, 16), (48, 48), (32, 32)])
set([(16, 16), (24, 24), (48, 48), (32, 32)])
```

```bash
$ identify python.ico
python.ico[0] ICO 16x16 16x16+0+0 32-bit sRGB 15.1KB 0.000u 0:00.002
python.ico[1] ICO 32x32 32x32+0+0 32-bit sRGB 15.1KB 0.000u 0:00.008
python.ico[2] ICO 48x48 48x48+0+0 32-bit sRGB 15.1KB 0.000u 0:00.013
$ identify saved.ico
saved.ico[0] PNG 16x16 16x16+0+0 8-bit sRGB 12.7KB 0.000u 0:00.003
saved.ico[1] PNG 24x24 24x24+0+0 8-bit sRGB 12.7KB 0.000u 0:00.006
saved.ico[2] PNG 32x32 32x32+0+0 8-bit sRGB 12.7KB 0.000u 0:00.007
saved.ico[3] PNG 48x48 48x48+0+0 8-bit sRGB 12.7KB 0.000u 0:00.010
```


### What actually happened?

```
set([(16, 16), (48, 48), (32, 32)])
set([(64, 64), (16, 16), (256, 256), (24, 24), (32, 32), (128, 128), (48, 48)])
Traceback (most recent call last):
  File "test.py", line 9, in <module>
    assert im_saved.info['sizes'] == set([(16, 16), (24, 24), (32, 32), (48, 48)])
AssertionError
```
```bash
$ identify python.ico
python.ico[0] ICO 16x16 16x16+0+0 32-bit sRGB 15.1KB 0.000u 0:00.002
python.ico[1] ICO 32x32 32x32+0+0 32-bit sRGB 15.1KB 0.000u 0:00.008
python.ico[2] ICO 48x48 48x48+0+0 32-bit sRGB 15.1KB 0.000u 0:00.013
$ identify saved.ico
saved.ico[0] PNG 16x16 16x16+0+0 8-bit sRGB 12.7KB 0.000u 0:00.003
saved.ico[1] PNG 24x24 24x24+0+0 8-bit sRGB 12.7KB 0.000u 0:00.006
saved.ico[2] PNG 32x32 32x32+0+0 8-bit sRGB 12.7KB 0.000u 0:00.007
saved.ico[3] PNG 48x48 48x48+0+0 8-bit sRGB 12.7KB 0.000u 0:00.010
saved.ico[4] PNG 48x48 48x48+0+0 8-bit sRGB 12.7KB 0.000u 0:00.011
saved.ico[5] PNG 48x48 48x48+0+0 8-bit sRGB 12.7KB 0.000u 0:00.013
saved.ico[6] PNG 48x48 48x48+0+0 8-bit sRGB 12.7KB 0.000u 0:00.016
```

### What versions of Pillow and Python are you using?

Python 2.7.11
Pillow 3.4.2

.ico files are saved as 255x255
### What did you do?

Loading an .ico of the size 256x256 and then saving it results in an .ico file of the size 255x255. I tested it on an example .ico file: http://www.axialis.com/tutorials/iw/down.ico

### What did you expect to happen?

The icon file should stay the same after saving. No other adjustments have been made. See the code below.

### What actually happened?

The saved .ico file ended up as 255x255 file. Because Windows doesn't support 255x255 size for .ico files, it is downscaled to 128x128 when used as desktop icons. [Example](http://i.imgur.com/fm7FTfw.png). The Chromium and Chrome icons are default ones, the Chrome Canary one was saved with Pillow.

### What versions of Pillow and Python are you using?
Pillow 3.4.2, Python 3.5.2

```python
from PIL import Image
im = Image.open('down.ico')
im.save('saved.ico')
im_saved = Image.open('saved.ico')

In [2]: im.size
Out[2]: (256, 256)

In [3]: im_saved.size
Out[3]: (255, 255)
```

`(255, 255)` appears on the list of sizes at https://github.com/python-pillow/Pillow/blob/master/PIL/IcoImagePlugin.py#L45 .


Discussion:

Resolving patch:
diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py
--- a/PIL/IcoImagePlugin.py
+++ b/PIL/IcoImagePlugin.py
@@ -44,16 +44,19 @@ def _save(im, fp, filename):
     fp.write(_MAGIC)  # (2+2)
     sizes = im.encoderinfo.get("sizes",
                                [(16, 16), (24, 24), (32, 32), (48, 48),
-                                (64, 64), (128, 128), (255, 255)])
+                                (64, 64), (128, 128), (256, 256)])
     width, height = im.size
-    filter(lambda x: False if (x[0] > width or x[1] > height or
-                               x[0] > 255 or x[1] > 255) else True, sizes)
+    sizes = filter(lambda x: False if (x[0] > width or x[1] > height or
+                                       x[0] > 256 or x[1] > 256) else True,
+                   sizes)
+    sizes = list(sizes)
     fp.write(struct.pack("<H", len(sizes)))  # idCount(2)
     offset = fp.tell() + len(sizes)*16
     for size in sizes:
         width, height = size
-        fp.write(struct.pack("B", width))  # bWidth(1)
-        fp.write(struct.pack("B", height))  # bHeight(1)
+        # 0 means 256
+        fp.write(struct.pack("B", width if width < 256 else 0))  # bWidth(1)
+        fp.write(struct.pack("B", height if height < 256 else 0))  # bHeight(1)
         fp.write(b"\0")  # bColorCount(1)
         fp.write(b"\0")  # bReserved(1)
         fp.write(b"\0\0")  # wPlanes(2)
@@ -215,13 +218,13 @@ def frame(self, idx):
                 total_bytes = int((w * im.size[1]) / 8)
 
                 self.buf.seek(and_mask_offset)
-                maskData = self.buf.read(total_bytes)
+                mask_data = self.buf.read(total_bytes)
 
                 # convert raw data to image
                 mask = Image.frombuffer(
                     '1',            # 1 bpp
                     im.size,        # (w, h)
-                    maskData,       # source chars
+                    mask_data,      # source chars
                     'raw',          # raw decoder
                     ('1;I', int(w/8), -1)  # 1bpp inverted, padded, reversed
                 )
@@ -278,6 +281,7 @@ def load_seek(self):
 #
 # --------------------------------------------------------------------
 
+
 Image.register_open(IcoImageFile.format, IcoImageFile, _accept)
 Image.register_save(IcoImageFile.format, _save)
 Image.register_extension(IcoImageFile.format, ".ico")


Test patch:
diff --git a/Tests/images/hopper_256x256.ico b/Tests/images/hopper_256x256.ico
new file mode 100644
Binary files /dev/null and b/Tests/images/hopper_256x256.ico differ
diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py
--- a/Tests/test_file_ico.py
+++ b/Tests/test_file_ico.py
@@ -48,6 +48,37 @@ def test_save_to_bytes(self):
         self.assert_image_equal(reloaded,
                                 hopper().resize((32, 32), Image.LANCZOS))
 
+    def test_save_256x256(self):
+        """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264"""
+        # Arrange
+        im = Image.open("Tests/images/hopper_256x256.ico")
+        outfile = self.tempfile("temp_saved_hopper_256x256.ico")
+
+        # Act
+        im.save(outfile)
+        im_saved = Image.open(outfile)
+
+        # Assert
+        self.assertEqual(im_saved.size, (256, 256))
+
+    def test_only_save_relevant_sizes(self):
+        """Issue #2266 https://github.com/python-pillow/Pillow/issues/2266
+        Should save in 16x16, 24x24, 32x32, 48x48 sizes
+        and not in 16x16, 24x24, 32x32, 48x48, 48x48, 48x48, 48x48 sizes
+        """
+        # Arrange
+        im = Image.open("Tests/images/python.ico")  # 16x16, 32x32, 48x48
+        outfile = self.tempfile("temp_saved_python.ico")
+
+        # Act
+        im.save(outfile)
+        im_saved = Image.open(outfile)
+
+        # Assert
+        self.assertEqual(
+            im_saved.info['sizes'],
+            set([(16, 16), (24, 24), (32, 32), (48, 48)]))
+
 
 if __name__ == '__main__':
     unittest.main()