Save in icns format to support all operating systems.

This commit is contained in:
panjing 2020-04-04 11:22:11 +08:00
parent 8c9100e267
commit b685c18e95

View File

@ -6,11 +6,13 @@
# #
# history: # history:
# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies. # 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
# 2020-04-04 Save in icns format to support all operating systems.
# #
# Copyright (c) 2004 by Bob Ippolito. # Copyright (c) 2004 by Bob Ippolito.
# Copyright (c) 2004 by Secret Labs. # Copyright (c) 2004 by Secret Labs.
# Copyright (c) 2004 by Fredrik Lundh. # Copyright (c) 2004 by Fredrik Lundh.
# Copyright (c) 2014 by Alastair Houghton. # Copyright (c) 2014 by Alastair Houghton.
# Copyright (c) 2020 by Pan Jing.
# #
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
@ -130,7 +132,6 @@ def read_png_or_jpeg2000(fobj, start_length, size):
class IcnsFile: class IcnsFile:
SIZES = { SIZES = {
(512, 512, 2): [(b"ic10", read_png_or_jpeg2000)], (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)],
(512, 512, 1): [(b"ic09", read_png_or_jpeg2000)], (512, 512, 1): [(b"ic09", read_png_or_jpeg2000)],
@ -302,65 +303,71 @@ class IcnsImageFile(ImageFile.ImageFile):
self.load_end() self.load_end()
def to_int(s):
b = s.encode('ascii')
return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]
MAGIC = to_int("icns")
HEADER_SIZE = 8
TOC = 'TOC '
def _save(im, fp, filename): def _save(im, fp, filename):
""" """
Saves the image as a series of PNG files, Saves the image as a series of PNG files,
that are then converted to a .icns file that are then converted to a .icns file
using the macOS command line utility 'iconutil'.
macOS only. Support for arbitrary systems
""" """
if hasattr(fp, "flush"): if hasattr(fp, "flush"):
fp.flush() fp.flush()
# create the temporary set of pngs # size
with tempfile.TemporaryDirectory(".iconset") as iconset: sizes = [128, 256, 512, 32, 64, 256, 512, 1024]
provided_images = { size_str = ['ic07', 'ic08', 'ic09', 'ic11', 'ic12', 'ic13', 'ic14', 'ic10']
im.width: im for im in im.encoderinfo.get("append_images", []) file_size = 0
} entries = []
last_w = None for index, s in enumerate(sizes):
second_path = None temp = io.BytesIO()
for w in [16, 32, 128, 256, 512]: nb = im.resize((s, s))
prefix = "icon_{}x{}".format(w, w) nb.save(temp, 'png')
file_size += len(temp.getvalue())
entries.append({
'type': size_str[index],
'size': len(temp.getvalue()),
'stream': temp
})
first_path = os.path.join(iconset, prefix + ".png") # Header
if last_w == w: fp.write(struct.pack('i', MAGIC)[::-1])
shutil.copyfile(second_path, first_path) fp.write(struct.pack('i', file_size)[::-1])
else:
im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS))
im_w.save(first_path)
second_path = os.path.join(iconset, prefix + "@2x.png") # TOC
im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS)) toc_size = HEADER_SIZE + (len(entries) * HEADER_SIZE)
im_w2.save(second_path) fp.write(struct.pack('i', to_int(TOC))[::-1])
last_w = w * 2 fp.write(struct.pack('i', toc_size)[::-1])
for e in entries:
fp.write(struct.pack('i', to_int(e.get('type')))[::-1])
fp.write(struct.pack('i', HEADER_SIZE + e.get('size'))[::-1])
# iconutil -c icns -o {} {} # Data
for index, e in enumerate(entries):
fp.write(struct.pack('i', to_int(e.get('type')))[::-1])
fp.write(struct.pack('i', HEADER_SIZE + e.get('size'))[::-1])
fp.write(e.get('stream').getvalue())
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] fp.flush()
convert_proc = subprocess.Popen(
convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
)
convert_proc.stdout.close()
retcode = convert_proc.wait()
if retcode:
raise subprocess.CalledProcessError(retcode, convert_cmd)
Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns") Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns")
Image.register_extension(IcnsImageFile.format, ".icns") Image.register_extension(IcnsImageFile.format, ".icns")
if sys.platform == "darwin": # if sys.platform == "darwin":
Image.register_save(IcnsImageFile.format, _save) Image.register_save(IcnsImageFile.format, _save)
Image.register_mime(IcnsImageFile.format, "image/icns")
Image.register_mime(IcnsImageFile.format, "image/icns")
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("Syntax: python IcnsImagePlugin.py [file]") print("Syntax: python IcnsImagePlugin.py [file]")
sys.exit() sys.exit()