|
6 | 6 | #
|
7 | 7 | # history:
|
8 | 8 | # 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
|
| 9 | +# 2020-04-04 Allow saving on all operating systems. |
9 | 10 | #
|
10 | 11 | # Copyright (c) 2004 by Bob Ippolito.
|
11 | 12 | # Copyright (c) 2004 by Secret Labs.
|
12 | 13 | # Copyright (c) 2004 by Fredrik Lundh.
|
13 | 14 | # Copyright (c) 2014 by Alastair Houghton.
|
| 15 | +# Copyright (c) 2020 by Pan Jing. |
14 | 16 | #
|
15 | 17 | # See the README file for information on usage and redistribution.
|
16 | 18 | #
|
17 | 19 |
|
18 | 20 | import io
|
19 | 21 | import os
|
20 |
| -import shutil |
21 | 22 | import struct
|
22 |
| -import subprocess |
23 | 23 | import sys
|
24 |
| -import tempfile |
25 | 24 |
|
26 | 25 | from PIL import Image, ImageFile, PngImagePlugin, features
|
27 | 26 |
|
28 | 27 | enable_jpeg2k = features.check_codec("jpg_2000")
|
29 | 28 | if enable_jpeg2k:
|
30 | 29 | from PIL import Jpeg2KImagePlugin
|
31 | 30 |
|
| 31 | +MAGIC = b"icns" |
32 | 32 | HEADERSIZE = 8
|
33 | 33 |
|
34 | 34 |
|
@@ -167,7 +167,7 @@ def __init__(self, fobj):
|
167 | 167 | self.dct = dct = {}
|
168 | 168 | self.fobj = fobj
|
169 | 169 | sig, filesize = nextheader(fobj)
|
170 |
| - if sig != b"icns": |
| 170 | + if sig != MAGIC: |
171 | 171 | raise SyntaxError("not an icns file")
|
172 | 172 | i = HEADERSIZE
|
173 | 173 | while i < filesize:
|
@@ -306,74 +306,71 @@ def load(self):
|
306 | 306 | def _save(im, fp, filename):
|
307 | 307 | """
|
308 | 308 | Saves the image as a series of PNG files,
|
309 |
| - that are then converted to a .icns file |
310 |
| - using the macOS command line utility 'iconutil'. |
311 |
| -
|
312 |
| - macOS only. |
| 309 | + that are then combined into a .icns file. |
313 | 310 | """
|
314 | 311 | if hasattr(fp, "flush"):
|
315 | 312 | fp.flush()
|
316 | 313 |
|
317 |
| - # create the temporary set of pngs |
318 |
| - with tempfile.TemporaryDirectory(".iconset") as iconset: |
319 |
| - provided_images = { |
320 |
| - im.width: im for im in im.encoderinfo.get("append_images", []) |
321 |
| - } |
322 |
| - last_w = None |
323 |
| - second_path = None |
324 |
| - for w in [16, 32, 128, 256, 512]: |
325 |
| - prefix = f"icon_{w}x{w}" |
326 |
| - |
327 |
| - first_path = os.path.join(iconset, prefix + ".png") |
328 |
| - if last_w == w: |
329 |
| - shutil.copyfile(second_path, first_path) |
330 |
| - else: |
331 |
| - im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS)) |
332 |
| - im_w.save(first_path) |
333 |
| - |
334 |
| - second_path = os.path.join(iconset, prefix + "@2x.png") |
335 |
| - im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS)) |
336 |
| - im_w2.save(second_path) |
337 |
| - last_w = w * 2 |
338 |
| - |
339 |
| - # iconutil -c icns -o {} {} |
340 |
| - |
341 |
| - fp_only = not filename |
342 |
| - if fp_only: |
343 |
| - f, filename = tempfile.mkstemp(".icns") |
344 |
| - os.close(f) |
345 |
| - convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] |
346 |
| - convert_proc = subprocess.Popen( |
347 |
| - convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL |
| 314 | + sizes = { |
| 315 | + b"ic07": 128, |
| 316 | + b"ic08": 256, |
| 317 | + b"ic09": 512, |
| 318 | + b"ic10": 1024, |
| 319 | + b"ic11": 32, |
| 320 | + b"ic12": 64, |
| 321 | + b"ic13": 256, |
| 322 | + b"ic14": 512, |
| 323 | + } |
| 324 | + provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])} |
| 325 | + size_streams = {} |
| 326 | + for size in set(sizes.values()): |
| 327 | + image = ( |
| 328 | + provided_images[size] |
| 329 | + if size in provided_images |
| 330 | + else im.resize((size, size)) |
348 | 331 | )
|
349 | 332 |
|
350 |
| - convert_proc.stdout.close() |
| 333 | + temp = io.BytesIO() |
| 334 | + image.save(temp, "png") |
| 335 | + size_streams[size] = temp.getvalue() |
| 336 | + |
| 337 | + entries = [] |
| 338 | + for type, size in sizes.items(): |
| 339 | + stream = size_streams[size] |
| 340 | + entries.append({"type": type, "size": len(stream), "stream": stream}) |
351 | 341 |
|
352 |
| - retcode = convert_proc.wait() |
| 342 | + # Header |
| 343 | + fp.write(MAGIC) |
| 344 | + fp.write(struct.pack(">i", sum(entry["size"] for entry in entries))) |
353 | 345 |
|
354 |
| - if retcode: |
355 |
| - raise subprocess.CalledProcessError(retcode, convert_cmd) |
| 346 | + # TOC |
| 347 | + fp.write(b"TOC ") |
| 348 | + fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE)) |
| 349 | + for entry in entries: |
| 350 | + fp.write(entry["type"]) |
| 351 | + fp.write(struct.pack(">i", HEADERSIZE + entry["size"])) |
356 | 352 |
|
357 |
| - if fp_only: |
358 |
| - with open(filename, "rb") as f: |
359 |
| - fp.write(f.read()) |
| 353 | + # Data |
| 354 | + for entry in entries: |
| 355 | + fp.write(entry["type"]) |
| 356 | + fp.write(struct.pack(">i", HEADERSIZE + entry["size"])) |
| 357 | + fp.write(entry["stream"]) |
| 358 | + |
| 359 | + if hasattr(fp, "flush"): |
| 360 | + fp.flush() |
360 | 361 |
|
361 | 362 |
|
362 | 363 | def _accept(prefix):
|
363 |
| - return prefix[:4] == b"icns" |
| 364 | + return prefix[:4] == MAGIC |
364 | 365 |
|
365 | 366 |
|
366 | 367 | Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept)
|
367 | 368 | Image.register_extension(IcnsImageFile.format, ".icns")
|
368 | 369 |
|
369 |
| -if sys.platform == "darwin": |
370 |
| - Image.register_save(IcnsImageFile.format, _save) |
371 |
| - |
372 |
| - Image.register_mime(IcnsImageFile.format, "image/icns") |
373 |
| - |
| 370 | +Image.register_save(IcnsImageFile.format, _save) |
| 371 | +Image.register_mime(IcnsImageFile.format, "image/icns") |
374 | 372 |
|
375 | 373 | if __name__ == "__main__":
|
376 |
| - |
377 | 374 | if len(sys.argv) < 2:
|
378 | 375 | print("Syntax: python3 IcnsImagePlugin.py [file]")
|
379 | 376 | sys.exit()
|
|
0 commit comments