mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 18:06:18 +03:00
Merge pull request #4526 from newpanjing/master
This commit is contained in:
commit
06f88ddff4
|
@ -1,5 +1,4 @@
|
||||||
import io
|
import io
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -28,7 +27,6 @@ def test_sanity():
|
||||||
assert im.format == "ICNS"
|
assert im.format == "ICNS"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS")
|
|
||||||
def test_save(tmp_path):
|
def test_save(tmp_path):
|
||||||
temp_file = str(tmp_path / "temp.icns")
|
temp_file = str(tmp_path / "temp.icns")
|
||||||
|
|
||||||
|
@ -41,7 +39,6 @@ def test_save(tmp_path):
|
||||||
assert reread.format == "ICNS"
|
assert reread.format == "ICNS"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS")
|
|
||||||
def test_save_append_images(tmp_path):
|
def test_save_append_images(tmp_path):
|
||||||
temp_file = str(tmp_path / "temp.icns")
|
temp_file = str(tmp_path / "temp.icns")
|
||||||
provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128))
|
provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128))
|
||||||
|
@ -57,7 +54,6 @@ def test_save_append_images(tmp_path):
|
||||||
assert_image_equal(reread, provided_im)
|
assert_image_equal(reread, provided_im)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS")
|
|
||||||
def test_save_fp():
|
def test_save_fp():
|
||||||
fp = io.BytesIO()
|
fp = io.BytesIO()
|
||||||
|
|
||||||
|
|
|
@ -215,12 +215,16 @@ attributes before loading the file::
|
||||||
ICNS
|
ICNS
|
||||||
^^^^
|
^^^^
|
||||||
|
|
||||||
Pillow reads and (macOS only) writes macOS ``.icns`` files. By default, the
|
Pillow reads and writes macOS ``.icns`` files. By default, the
|
||||||
largest available icon is read, though you can override this by setting the
|
largest available icon is read, though you can override this by setting the
|
||||||
:py:attr:`~PIL.Image.Image.size` property before calling
|
:py:attr:`~PIL.Image.Image.size` property before calling
|
||||||
:py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.open` method
|
:py:meth:`~PIL.Image.Image.load`. The :py:meth:`~PIL.Image.open` method
|
||||||
sets the following :py:attr:`~PIL.Image.Image.info` property:
|
sets the following :py:attr:`~PIL.Image.Image.info` property:
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Prior to version 8.3.0, Pillow could only write ICNS files on macOS.
|
||||||
|
|
||||||
**sizes**
|
**sizes**
|
||||||
A list of supported sizes found in this icon file; these are a
|
A list of supported sizes found in this icon file; these are a
|
||||||
3-tuple, ``(width, height, scale)``, where ``scale`` is 2 for a retina
|
3-tuple, ``(width, height, scale)``, where ``scale`` is 2 for a retina
|
||||||
|
|
|
@ -6,22 +6,21 @@
|
||||||
#
|
#
|
||||||
# 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 Allow saving on 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.
|
||||||
#
|
#
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from PIL import Image, ImageFile, PngImagePlugin, features
|
from PIL import Image, ImageFile, PngImagePlugin, features
|
||||||
|
|
||||||
|
@ -29,6 +28,7 @@ enable_jpeg2k = features.check_codec("jpg_2000")
|
||||||
if enable_jpeg2k:
|
if enable_jpeg2k:
|
||||||
from PIL import Jpeg2KImagePlugin
|
from PIL import Jpeg2KImagePlugin
|
||||||
|
|
||||||
|
MAGIC = b"icns"
|
||||||
HEADERSIZE = 8
|
HEADERSIZE = 8
|
||||||
|
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ class IcnsFile:
|
||||||
self.dct = dct = {}
|
self.dct = dct = {}
|
||||||
self.fobj = fobj
|
self.fobj = fobj
|
||||||
sig, filesize = nextheader(fobj)
|
sig, filesize = nextheader(fobj)
|
||||||
if sig != b"icns":
|
if sig != MAGIC:
|
||||||
raise SyntaxError("not an icns file")
|
raise SyntaxError("not an icns file")
|
||||||
i = HEADERSIZE
|
i = HEADERSIZE
|
||||||
while i < filesize:
|
while i < filesize:
|
||||||
|
@ -306,74 +306,71 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
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 combined into a .icns file.
|
||||||
using the macOS command line utility 'iconutil'.
|
|
||||||
|
|
||||||
macOS only.
|
|
||||||
"""
|
"""
|
||||||
if hasattr(fp, "flush"):
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
# create the temporary set of pngs
|
sizes = {
|
||||||
with tempfile.TemporaryDirectory(".iconset") as iconset:
|
b"ic07": 128,
|
||||||
provided_images = {
|
b"ic08": 256,
|
||||||
im.width: im for im in im.encoderinfo.get("append_images", [])
|
b"ic09": 512,
|
||||||
}
|
b"ic10": 1024,
|
||||||
last_w = None
|
b"ic11": 32,
|
||||||
second_path = None
|
b"ic12": 64,
|
||||||
for w in [16, 32, 128, 256, 512]:
|
b"ic13": 256,
|
||||||
prefix = f"icon_{w}x{w}"
|
b"ic14": 512,
|
||||||
|
}
|
||||||
first_path = os.path.join(iconset, prefix + ".png")
|
provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])}
|
||||||
if last_w == w:
|
size_streams = {}
|
||||||
shutil.copyfile(second_path, first_path)
|
for size in set(sizes.values()):
|
||||||
else:
|
image = (
|
||||||
im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS))
|
provided_images[size]
|
||||||
im_w.save(first_path)
|
if size in provided_images
|
||||||
|
else im.resize((size, size))
|
||||||
second_path = os.path.join(iconset, prefix + "@2x.png")
|
|
||||||
im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS))
|
|
||||||
im_w2.save(second_path)
|
|
||||||
last_w = w * 2
|
|
||||||
|
|
||||||
# iconutil -c icns -o {} {}
|
|
||||||
|
|
||||||
fp_only = not filename
|
|
||||||
if fp_only:
|
|
||||||
f, filename = tempfile.mkstemp(".icns")
|
|
||||||
os.close(f)
|
|
||||||
convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
|
|
||||||
convert_proc = subprocess.Popen(
|
|
||||||
convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
|
|
||||||
)
|
)
|
||||||
|
|
||||||
convert_proc.stdout.close()
|
temp = io.BytesIO()
|
||||||
|
image.save(temp, "png")
|
||||||
|
size_streams[size] = temp.getvalue()
|
||||||
|
|
||||||
retcode = convert_proc.wait()
|
entries = []
|
||||||
|
for type, size in sizes.items():
|
||||||
|
stream = size_streams[size]
|
||||||
|
entries.append({"type": type, "size": len(stream), "stream": stream})
|
||||||
|
|
||||||
if retcode:
|
# Header
|
||||||
raise subprocess.CalledProcessError(retcode, convert_cmd)
|
fp.write(MAGIC)
|
||||||
|
fp.write(struct.pack(">i", sum(entry["size"] for entry in entries)))
|
||||||
|
|
||||||
if fp_only:
|
# TOC
|
||||||
with open(filename, "rb") as f:
|
fp.write(b"TOC ")
|
||||||
fp.write(f.read())
|
fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE))
|
||||||
|
for entry in entries:
|
||||||
|
fp.write(entry["type"])
|
||||||
|
fp.write(struct.pack(">i", HEADERSIZE + entry["size"]))
|
||||||
|
|
||||||
|
# Data
|
||||||
|
for entry in entries:
|
||||||
|
fp.write(entry["type"])
|
||||||
|
fp.write(struct.pack(">i", HEADERSIZE + entry["size"]))
|
||||||
|
fp.write(entry["stream"])
|
||||||
|
|
||||||
|
if hasattr(fp, "flush"):
|
||||||
|
fp.flush()
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[:4] == b"icns"
|
return prefix[:4] == MAGIC
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept)
|
Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept)
|
||||||
Image.register_extension(IcnsImageFile.format, ".icns")
|
Image.register_extension(IcnsImageFile.format, ".icns")
|
||||||
|
|
||||||
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: python3 IcnsImagePlugin.py [file]")
|
print("Syntax: python3 IcnsImagePlugin.py [file]")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user