Merge pull request #1185 from radarhere/icns

Added icns save for OS X
This commit is contained in:
Hugo 2015-04-23 10:47:58 +03:00
commit 5302acae37
2 changed files with 73 additions and 5 deletions

View File

@ -18,6 +18,7 @@
from PIL import Image, ImageFile, PngImagePlugin, _binary from PIL import Image, ImageFile, PngImagePlugin, _binary
import io import io
import struct import struct
import tempfile, shutil, os, sys
enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
if enable_jpeg2k: if enable_jpeg2k:
@ -247,7 +248,7 @@ class IcnsFile:
class IcnsImageFile(ImageFile.ImageFile): class IcnsImageFile(ImageFile.ImageFile):
""" """
PIL read-only image support for Mac OS .icns files. PIL image support for Mac OS .icns files.
Chooses the best resolution, but will possibly load Chooses the best resolution, but will possibly load
a different size image if you mutate the size attribute a different size image if you mutate the size attribute
before calling 'load'. before calling 'load'.
@ -293,12 +294,63 @@ class IcnsImageFile(ImageFile.ImageFile):
self.tile = () self.tile = ()
self.load_end() self.load_end()
def _save(im, fp, filename):
"""
Saves the image as a series of PNG files,
that are then converted to a .icns file
using the OS X command line utility 'iconutil'.
OS X only.
"""
try:
fp.flush()
except:
pass
# create the temporary set of pngs
iconset = tempfile.mkdtemp('.iconset')
last_w = None
last_im = None
for w in [16,32,128,256,512]:
prefix = 'icon_{}x{}'.format(w,w)
if last_w == w:
im_scaled = last_im
else:
im_scaled = im.resize((w,w), Image.LANCZOS)
im_scaled.save(os.path.join(iconset, prefix+'.png'))
im_scaled = im.resize((w*2,w*2), Image.LANCZOS)
im_scaled.save(os.path.join(iconset, prefix+'@2x.png'))
last_im = im_scaled
# iconutil -c icns -o {} {}
from subprocess import Popen, PIPE, CalledProcessError
convert_cmd = ["iconutil","-c","icns","-o",filename,iconset]
stderr = tempfile.TemporaryFile()
convert_proc = Popen(convert_cmd, stdout=PIPE, stderr=stderr)
convert_proc.stdout.close()
retcode = convert_proc.wait()
# remove the temporary files
shutil.rmtree(iconset)
if retcode:
raise CalledProcessError(retcode, convert_cmd)
Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns') Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
Image.register_extension("ICNS", '.icns') Image.register_extension("ICNS", '.icns')
if sys.platform == 'darwin':
Image.register_save("ICNS", _save)
Image.register_mime("ICNS", "image/icns")
if __name__ == '__main__': if __name__ == '__main__':
import os
import sys
imf = IcnsImageFile(open(sys.argv[1], 'rb')) imf = IcnsImageFile(open(sys.argv[1], 'rb'))
for size in imf.info['sizes']: for size in imf.info['sizes']:
imf.size = size imf.size = size

View File

@ -2,6 +2,8 @@ from helper import unittest, PillowTestCase
from PIL import Image from PIL import Image
import sys
# sample icon file # sample icon file
file = "Tests/images/pillow.icns" file = "Tests/images/pillow.icns"
data = open(file, "rb").read() data = open(file, "rb").read()
@ -20,6 +22,20 @@ class TestFileIcns(PillowTestCase):
self.assertEqual(im.size, (1024, 1024)) self.assertEqual(im.size, (1024, 1024))
self.assertEqual(im.format, "ICNS") self.assertEqual(im.format, "ICNS")
@unittest.skipIf(sys.platform != 'darwin',
"requires MacOS")
def test_save(self):
im = Image.open(file)
test_file = self.tempfile("temp.icns")
im.save(test_file)
reread = Image.open(test_file)
self.assertEqual(reread.mode, "RGBA")
self.assertEqual(reread.size, (1024, 1024))
self.assertEqual(reread.format, "ICNS")
def test_sizes(self): def test_sizes(self):
# Check that we can load all of the sizes, and that the final pixel # Check that we can load all of the sizes, and that the final pixel
# dimensions are as expected # dimensions are as expected