Skip to content

Commit

Permalink
Merge pull request #1007 from wiredfool/ico_save
Browse files Browse the repository at this point in the history
Ico save, additional tests
  • Loading branch information
hugovk committed Nov 13, 2014
2 parents 3065df6 + 03d20d3 commit 5ae3c1c
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 1 deletion.
40 changes: 40 additions & 0 deletions PIL/IcoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

__version__ = "0.1"

import struct
from io import BytesIO

from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
from math import log, ceil

Expand All @@ -37,6 +40,42 @@
_MAGIC = b"\0\0\1\0"


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)])
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 = sorted(sizes, key=lambda x: x[0])
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)
fp.write(b"\0") # bColorCount(1)
fp.write(b"\0") # bReserved(1)
fp.write(b"\0\0") # wPlanes(2)
fp.write(struct.pack("H", 32)) # wBitCount(2)

image_io = BytesIO()
tmp = im.copy()
tmp.thumbnail(size, Image.ANTIALIAS)
tmp.save(image_io, "png")
image_io.seek(0)
image_bytes = image_io.read()
bytes_len = len(image_bytes)
fp.write(struct.pack("I", bytes_len)) # dwBytesInRes(4)
fp.write(struct.pack("I", offset)) # dwImageOffset(4)
current = fp.tell()
fp.seek(offset)
fp.write(image_bytes)
offset = offset + bytes_len
fp.seek(current)


def _accept(prefix):
return prefix[:4] == _MAGIC

Expand Down Expand Up @@ -241,4 +280,5 @@ def load_seek(self):
# --------------------------------------------------------------------

Image.register_open("ICO", IcoImageFile, _accept)
Image.register_save("ICO", _save)
Image.register_extension("ICO", ".ico")
29 changes: 28 additions & 1 deletion Tests/test_file_ico.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from helper import unittest, PillowTestCase
from helper import unittest, PillowTestCase, hopper

import io
from PIL import Image

# sample ppm stream
Expand All @@ -16,6 +17,32 @@ def test_sanity(self):
self.assertEqual(im.size, (16, 16))
self.assertEqual(im.format, "ICO")

def test_save_to_bytes(self):
output = io.BytesIO()
im = hopper()
im.save(output, "ico", sizes=[(32, 32), (64, 64)])

# the default image
output.seek(0)
reloaded = Image.open(output)
self.assertEqual(reloaded.info['sizes'],set([(32, 32), (64, 64)]))

self.assertEqual(im.mode, reloaded.mode)
self.assertEqual((64, 64), reloaded.size)
self.assertEqual(reloaded.format, "ICO")
self.assert_image_equal(reloaded, hopper().resize((64,64), Image.ANTIALIAS))

# the other one
output.seek(0)
reloaded = Image.open(output)
reloaded.size = (32,32)

self.assertEqual(im.mode, reloaded.mode)
self.assertEqual((32, 32), reloaded.size)
self.assertEqual(reloaded.format, "ICO")
self.assert_image_equal(reloaded, hopper().resize((32,32), Image.ANTIALIAS))



if __name__ == '__main__':
unittest.main()
Expand Down
8 changes: 8 additions & 0 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,14 @@ ICO

ICO is used to store icons on Windows. The largest available icon is read.

The :py:meth:`~PIL.Image.Image.save` method supports the following options:

**sizes**
A list of sizes including in this ico file; these are a 2-tuple,
``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48),
(64, 64), (128, 128), (255, 255)]``. Any size is bigger then the original
size or 255 will be ignored.

ICNS
^^^^

Expand Down

0 comments on commit 5ae3c1c

Please sign in to comment.