Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add append_images support for ICO #4568

Merged
merged 9 commits into from Dec 24, 2020
18 changes: 18 additions & 0 deletions Tests/test_file_ico.py
Expand Up @@ -85,6 +85,24 @@ def test_only_save_relevant_sizes(tmp_path):
assert im_saved.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)}


def test_only_save_append_images(tmp_path):
"""append_images should work to provide alternative sizes"""
radarhere marked this conversation as resolved.
Show resolved Hide resolved
im = hopper()
provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 255))
outfile = str(tmp_path / "temp_saved_multi_icon.ico")
im.save(outfile, sizes=[(32, 32), (64, 64)], append_images=[provided_im])

with Image.open(outfile) as reread:
reread.size = (64, 64)
reread.load()
assert_image_equal(reread, hopper().resize((64, 64), Image.LANCZOS))

with Image.open(outfile) as reread:
reread.size = (32, 32)
reread.load()
assert_image_equal(reread, provided_im)


def test_unexpected_size():
# This image has been manually hexedited to state that it is 16x32
# while the image within is still 16x16
Expand Down
9 changes: 6 additions & 3 deletions src/PIL/IcoImagePlugin.py
Expand Up @@ -52,6 +52,7 @@ def _save(im, fp, filename):
sizes = list(sizes)
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
offset = fp.tell() + len(sizes) * 16
alt_images = {im.size: im for im in im.encoderinfo.get("append_images", [])}
for size in sizes:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like if someone were to add an unusual sized image to append_images (e.g. (20,20)), it would not be saved unless it is added to the sizes parameter as well. I feel like this could be confusing behaviour. Perhaps adding sizes.extend(alt_images.keys()) would be a good idea (sizes would have to be changed to a set to avoid duplicates).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct. I believe the ICNS saving currently suffers from the same issue. I don't know if it should be fixed for .ICOs; the documentation would need to mention the role of append_images either way, since if the behavior is changed as you suggest, it would then also save other sizes than the ones the user requested (via the sizes parameter).

width, height = size
# 0 means 256
Expand All @@ -63,9 +64,11 @@ def _save(im, fp, filename):
fp.write(struct.pack("<H", 32)) # wBitCount(2)

image_io = BytesIO()
# TODO: invent a more convenient method for proportional scalings
tmp = im.copy()
tmp.thumbnail(size, Image.LANCZOS, reducing_gap=None)
tmp = alt_images.get(size)
if not tmp:
# TODO: invent a more convenient method for proportional scalings
tmp = im.copy()
tmp.thumbnail(size, Image.LANCZOS, reducing_gap=None)
tmp.save(image_io, "png")
image_io.seek(0)
image_bytes = image_io.read()
Expand Down