Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions Tests/test_file_ico.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,15 @@ def test_incorrect_size() -> None:
im.size = (1, 1)


def test_save_1x2(tmp_path: Path) -> None:
im = Image.new("1", (1, 2))
outfile = tmp_path / "temp.ico"
im.save(outfile)

with Image.open(outfile) as reloaded:
assert_image_equal(im, reloaded)


def test_save_256x256(tmp_path: Path) -> None:
"""Issue #2264 https://github.com/python-pillow/Pillow/issues/2264"""
# Arrange
Expand All @@ -195,9 +204,9 @@ def test_save_256x256(tmp_path: Path) -> None:

# Act
im.save(outfile)
with Image.open(outfile) as im_saved:
with Image.open(outfile) as reloaded:
# Assert
assert im_saved.size == (256, 256)
assert reloaded.size == (256, 256)


def test_only_save_relevant_sizes(tmp_path: Path) -> None:
Expand All @@ -211,9 +220,14 @@ def test_only_save_relevant_sizes(tmp_path: Path) -> None:
# Act
im.save(outfile)

with Image.open(outfile) as im_saved:
with Image.open(outfile) as reloaded:
# Assert
assert im_saved.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)}
assert reloaded.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)}

im2 = Image.new("1", (1, 1))
outfile = tmp_path / "temp.ico"
with pytest.raises(ValueError, match="All sizes too large for image"):
im2.save(outfile, sizes=[(2, 2)])


def test_save_append_images(tmp_path: Path) -> None:
Expand Down
4 changes: 2 additions & 2 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
**sizes**
A list of sizes included in this ico file; these are a 2-tuple,
``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48),
(64, 64), (128, 128), (256, 256)]``. Any sizes bigger than the original
size or 256 will be ignored.
(64, 64), (128, 128), (256, 256)]``, or if it is smaller, only the image size.
Any sizes bigger than the original size or 256 will be ignored.

The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:

Expand Down
20 changes: 13 additions & 7 deletions src/PIL/IcoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,18 @@
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(_MAGIC) # (2+2)
bmp = im.encoderinfo.get("bitmap_format") == "bmp"
sizes = im.encoderinfo.get(
"sizes",
[(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)],
)
if "sizes" in im.encoderinfo:
sizes = sorted(set(im.encoderinfo["sizes"]))
else:
sizes = (
[im.size]
if min(im.size) < 16
else [(d, d) for d in (16, 24, 32, 48, 64, 128, 256)]
)
frames = []
provided_ims = [im] + im.encoderinfo.get("append_images", [])
width, height = im.size
for size in sorted(set(sizes)):
if size[0] > width or size[1] > height or size[0] > 256 or size[1] > 256:
for size in sizes:
if size[0] > min(256, im.width) or size[1] > min(256, im.height):
continue

for provided_im in provided_ims:
Expand All @@ -90,6 +93,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
frame = provided_im.copy()
frame.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None)
frames.append(frame)
if not frames:
msg = "All sizes too large for image"
raise ValueError(msg)
fp.write(o16(len(frames))) # idCount(2)
offset = fp.tell() + len(frames) * 16
for frame in frames:
Expand Down
Loading