|
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