From b38c8e0df21a0c7c616e34f124a44b312e9db56a Mon Sep 17 00:00:00 2001 From: Herb Date: Sat, 8 Nov 2014 03:01:46 +0800 Subject: [PATCH 1/3] add ico save support --- PIL/IcoImagePlugin.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index 2dc46ea18..d18b37cfc 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -24,6 +24,12 @@ __version__ = "0.1" +import struct +try: + from io import BytesIO +except ImportError: + from cStringIO import StringIO as BytesIO + from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary from math import log, ceil @@ -37,6 +43,41 @@ i32 = _binary.i32le _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], reverse=True) + 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() + im.thumbnail(size, Image.ANTIALIAS) + im.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 @@ -241,4 +282,5 @@ class IcoImageFile(ImageFile.ImageFile): # -------------------------------------------------------------------- Image.register_open("ICO", IcoImageFile, _accept) +Image.register_save("ICO", _save) Image.register_extension("ICO", ".ico") From 79c7c7a01a0fc89e8b1b581bc9dbd6e326062881 Mon Sep 17 00:00:00 2001 From: Herb Date: Sat, 8 Nov 2014 14:49:50 +0800 Subject: [PATCH 2/3] add tests and docs --- PIL/IcoImagePlugin.py | 12 +++++------- Tests/test_file_ico.py | 15 ++++++++++++++- docs/handbook/image-file-formats.rst | 8 ++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index d18b37cfc..145816094 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -25,10 +25,7 @@ __version__ = "0.1" import struct -try: - from io import BytesIO -except ImportError: - from cStringIO import StringIO as BytesIO +from io import BytesIO from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary from math import log, ceil @@ -51,7 +48,7 @@ def _save(im, fp, filename): 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], reverse=True) + 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: @@ -64,8 +61,9 @@ def _save(im, fp, filename): fp.write(struct.pack("H", 32)) # wBitCount(2) image_io = BytesIO() - im.thumbnail(size, Image.ANTIALIAS) - im.save(image_io, "png") + 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) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 12f4ed3f3..200b6ba56 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,5 +1,6 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, hopper +import io from PIL import Image # sample ppm stream @@ -16,6 +17,18 @@ class TestFileIco(PillowTestCase): 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)]) + + output.seek(0) + reloaded = Image.open(output) + + self.assertEqual(im.mode, reloaded.mode) + self.assertEqual((64, 64), reloaded.size) + self.assertEqual(reloaded.format, "ICO") + if __name__ == '__main__': unittest.main() diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 50eecd9da..a1961fa7c 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -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 ^^^^ From 03d20d3b6a2894a9053512b7b2d2c382bffd9a59 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 12 Nov 2014 22:45:35 -0800 Subject: [PATCH 3/3] More tests for ico save --- Tests/test_file_ico.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 200b6ba56..01d3f5904 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -22,13 +22,27 @@ class TestFileIco(PillowTestCase): 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()