2023-12-21 14:13:31 +03:00
|
|
|
from __future__ import annotations
|
2024-01-20 14:23:03 +03:00
|
|
|
|
2014-11-08 09:49:50 +03:00
|
|
|
import io
|
2022-03-11 13:37:45 +03:00
|
|
|
import os
|
2024-01-31 12:12:58 +03:00
|
|
|
from pathlib import Path
|
2019-07-06 23:40:53 +03:00
|
|
|
|
2020-02-03 12:11:32 +03:00
|
|
|
import pytest
|
2020-08-07 13:28:33 +03:00
|
|
|
|
2024-07-01 12:36:15 +03:00
|
|
|
from PIL import IcoImagePlugin, Image, ImageDraw, ImageFile
|
2019-07-06 23:40:53 +03:00
|
|
|
|
2021-02-21 14:15:56 +03:00
|
|
|
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
2012-10-26 02:00:19 +04:00
|
|
|
|
2014-09-04 14:29:35 +04:00
|
|
|
TEST_ICO_FILE = "Tests/images/hopper.ico"
|
2012-10-26 02:00:19 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_sanity() -> None:
|
2020-02-23 00:03:01 +03:00
|
|
|
with Image.open(TEST_ICO_FILE) as im:
|
|
|
|
im.load()
|
|
|
|
assert im.mode == "RGBA"
|
|
|
|
assert im.size == (16, 16)
|
|
|
|
assert im.format == "ICO"
|
|
|
|
assert im.get_format_mimetype() == "image/x-icon"
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_load() -> None:
|
2022-02-02 03:49:31 +03:00
|
|
|
with Image.open(TEST_ICO_FILE) as im:
|
|
|
|
assert im.load()[0, 0] == (1, 1, 9, 255)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_mask() -> None:
|
2021-08-10 00:04:36 +03:00
|
|
|
with Image.open("Tests/images/hopper_mask.ico") as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/hopper_mask.png")
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_black_and_white() -> None:
|
2021-04-22 14:18:21 +03:00
|
|
|
with Image.open("Tests/images/black_and_white.ico") as im:
|
|
|
|
assert im.mode == "RGBA"
|
|
|
|
assert im.size == (16, 16)
|
|
|
|
|
|
|
|
|
2024-02-14 01:17:22 +03:00
|
|
|
def test_palette(tmp_path: Path) -> None:
|
|
|
|
temp_file = str(tmp_path / "temp.ico")
|
|
|
|
|
|
|
|
im = Image.new("P", (16, 16))
|
|
|
|
im.save(temp_file)
|
|
|
|
|
|
|
|
with Image.open(temp_file) as reloaded:
|
|
|
|
assert reloaded.mode == "P"
|
|
|
|
assert reloaded.palette is not None
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_invalid_file() -> None:
|
2020-02-23 00:03:01 +03:00
|
|
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
|
|
|
with pytest.raises(SyntaxError):
|
|
|
|
IcoImagePlugin.IcoImageFile(fp)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_to_bytes() -> None:
|
2020-02-23 00:03:01 +03:00
|
|
|
output = io.BytesIO()
|
|
|
|
im = hopper()
|
|
|
|
im.save(output, "ico", sizes=[(32, 32), (64, 64)])
|
|
|
|
|
|
|
|
# The default image
|
|
|
|
output.seek(0)
|
|
|
|
with Image.open(output) as reloaded:
|
|
|
|
assert reloaded.info["sizes"] == {(32, 32), (64, 64)}
|
|
|
|
|
|
|
|
assert im.mode == reloaded.mode
|
|
|
|
assert (64, 64) == reloaded.size
|
|
|
|
assert reloaded.format == "ICO"
|
2022-01-15 01:02:31 +03:00
|
|
|
assert_image_equal(
|
|
|
|
reloaded, hopper().resize((64, 64), Image.Resampling.LANCZOS)
|
|
|
|
)
|
2020-02-23 00:03:01 +03:00
|
|
|
|
|
|
|
# The other one
|
|
|
|
output.seek(0)
|
|
|
|
with Image.open(output) as reloaded:
|
|
|
|
reloaded.size = (32, 32)
|
|
|
|
|
|
|
|
assert im.mode == reloaded.mode
|
|
|
|
assert (32, 32) == reloaded.size
|
|
|
|
assert reloaded.format == "ICO"
|
2022-01-15 01:02:31 +03:00
|
|
|
assert_image_equal(
|
|
|
|
reloaded, hopper().resize((32, 32), Image.Resampling.LANCZOS)
|
|
|
|
)
|
2020-02-23 00:03:01 +03:00
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_getpixel(tmp_path: Path) -> None:
|
2022-12-23 15:20:06 +03:00
|
|
|
temp_file = str(tmp_path / "temp.ico")
|
|
|
|
|
|
|
|
im = hopper()
|
|
|
|
im.save(temp_file, "ico", sizes=[(32, 32), (64, 64)])
|
|
|
|
|
|
|
|
with Image.open(temp_file) as reloaded:
|
|
|
|
reloaded.load()
|
|
|
|
reloaded.size = (32, 32)
|
|
|
|
|
|
|
|
assert reloaded.getpixel((0, 0)) == (18, 20, 62)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_no_duplicates(tmp_path: Path) -> None:
|
2022-03-11 13:37:45 +03:00
|
|
|
temp_file = str(tmp_path / "temp.ico")
|
|
|
|
temp_file2 = str(tmp_path / "temp2.ico")
|
|
|
|
|
|
|
|
im = hopper()
|
|
|
|
sizes = [(32, 32), (64, 64)]
|
|
|
|
im.save(temp_file, "ico", sizes=sizes)
|
|
|
|
|
|
|
|
sizes.append(sizes[-1])
|
|
|
|
im.save(temp_file2, "ico", sizes=sizes)
|
|
|
|
|
|
|
|
assert os.path.getsize(temp_file) == os.path.getsize(temp_file2)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_different_bit_depths(tmp_path: Path) -> None:
|
2022-03-12 07:49:08 +03:00
|
|
|
temp_file = str(tmp_path / "temp.ico")
|
|
|
|
temp_file2 = str(tmp_path / "temp2.ico")
|
|
|
|
|
|
|
|
im = hopper()
|
|
|
|
im.save(temp_file, "ico", bitmap_format="bmp", sizes=[(128, 128)])
|
|
|
|
|
|
|
|
hopper("1").save(
|
|
|
|
temp_file2,
|
|
|
|
"ico",
|
|
|
|
bitmap_format="bmp",
|
|
|
|
sizes=[(128, 128)],
|
|
|
|
append_images=[im],
|
|
|
|
)
|
|
|
|
|
|
|
|
assert os.path.getsize(temp_file) != os.path.getsize(temp_file2)
|
|
|
|
|
|
|
|
# Test that only matching sizes of different bit depths are saved
|
|
|
|
temp_file3 = str(tmp_path / "temp3.ico")
|
|
|
|
temp_file4 = str(tmp_path / "temp4.ico")
|
|
|
|
|
|
|
|
im.save(temp_file3, "ico", bitmap_format="bmp", sizes=[(128, 128)])
|
|
|
|
im.save(
|
|
|
|
temp_file4,
|
|
|
|
"ico",
|
|
|
|
bitmap_format="bmp",
|
|
|
|
sizes=[(128, 128)],
|
|
|
|
append_images=[Image.new("P", (64, 64))],
|
|
|
|
)
|
|
|
|
|
|
|
|
assert os.path.getsize(temp_file3) == os.path.getsize(temp_file4)
|
|
|
|
|
|
|
|
|
2021-05-26 23:21:28 +03:00
|
|
|
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB", "RGBA"))
|
2024-02-20 07:41:20 +03:00
|
|
|
def test_save_to_bytes_bmp(mode: str) -> None:
|
2021-05-26 23:21:28 +03:00
|
|
|
output = io.BytesIO()
|
|
|
|
im = hopper(mode)
|
|
|
|
im.save(output, "ico", bitmap_format="bmp", sizes=[(32, 32), (64, 64)])
|
|
|
|
|
|
|
|
# The default image
|
|
|
|
output.seek(0)
|
|
|
|
with Image.open(output) as reloaded:
|
|
|
|
assert reloaded.info["sizes"] == {(32, 32), (64, 64)}
|
|
|
|
|
|
|
|
assert "RGBA" == reloaded.mode
|
|
|
|
assert (64, 64) == reloaded.size
|
|
|
|
assert reloaded.format == "ICO"
|
2022-01-15 01:02:31 +03:00
|
|
|
im = hopper(mode).resize((64, 64), Image.Resampling.LANCZOS).convert("RGBA")
|
2021-05-26 23:21:28 +03:00
|
|
|
assert_image_equal(reloaded, im)
|
|
|
|
|
|
|
|
# The other one
|
|
|
|
output.seek(0)
|
|
|
|
with Image.open(output) as reloaded:
|
|
|
|
reloaded.size = (32, 32)
|
|
|
|
|
|
|
|
assert "RGBA" == reloaded.mode
|
|
|
|
assert (32, 32) == reloaded.size
|
|
|
|
assert reloaded.format == "ICO"
|
2022-01-15 01:02:31 +03:00
|
|
|
im = hopper(mode).resize((32, 32), Image.Resampling.LANCZOS).convert("RGBA")
|
2021-05-26 23:21:28 +03:00
|
|
|
assert_image_equal(reloaded, im)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_incorrect_size() -> None:
|
2020-02-23 00:03:01 +03:00
|
|
|
with Image.open(TEST_ICO_FILE) as im:
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.size = (1, 1)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_256x256(tmp_path: Path) -> None:
|
2020-02-23 00:03:01 +03:00
|
|
|
"""Issue #2264 https://github.com/python-pillow/Pillow/issues/2264"""
|
|
|
|
# Arrange
|
|
|
|
with Image.open("Tests/images/hopper_256x256.ico") as im:
|
|
|
|
outfile = str(tmp_path / "temp_saved_hopper_256x256.ico")
|
|
|
|
|
|
|
|
# Act
|
|
|
|
im.save(outfile)
|
|
|
|
with Image.open(outfile) as im_saved:
|
|
|
|
# Assert
|
|
|
|
assert im_saved.size == (256, 256)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_only_save_relevant_sizes(tmp_path: Path) -> None:
|
2020-02-23 00:03:01 +03:00
|
|
|
"""Issue #2266 https://github.com/python-pillow/Pillow/issues/2266
|
|
|
|
Should save in 16x16, 24x24, 32x32, 48x48 sizes
|
|
|
|
and not in 16x16, 24x24, 32x32, 48x48, 48x48, 48x48, 48x48 sizes
|
|
|
|
"""
|
|
|
|
# Arrange
|
|
|
|
with Image.open("Tests/images/python.ico") as im: # 16x16, 32x32, 48x48
|
|
|
|
outfile = str(tmp_path / "temp_saved_python.ico")
|
|
|
|
# Act
|
|
|
|
im.save(outfile)
|
|
|
|
|
|
|
|
with Image.open(outfile) as im_saved:
|
|
|
|
# Assert
|
|
|
|
assert im_saved.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)}
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_append_images(tmp_path: Path) -> None:
|
2020-12-24 02:50:43 +03:00
|
|
|
# append_images should be used for scaled down versions of the image
|
2020-11-04 14:39:25 +03:00
|
|
|
im = hopper("RGBA")
|
|
|
|
provided_im = Image.new("RGBA", (32, 32), (255, 0, 0))
|
2020-04-19 16:45:12 +03:00
|
|
|
outfile = str(tmp_path / "temp_saved_multi_icon.ico")
|
2020-11-04 14:39:25 +03:00
|
|
|
im.save(outfile, sizes=[(32, 32), (128, 128)], append_images=[provided_im])
|
2020-04-19 16:45:12 +03:00
|
|
|
|
|
|
|
with Image.open(outfile) as reread:
|
2020-11-04 14:39:25 +03:00
|
|
|
assert_image_equal(reread, hopper("RGBA"))
|
2020-04-19 16:45:12 +03:00
|
|
|
|
|
|
|
reread.size = (32, 32)
|
|
|
|
assert_image_equal(reread, provided_im)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_unexpected_size() -> None:
|
2020-02-23 00:03:01 +03:00
|
|
|
# This image has been manually hexedited to state that it is 16x32
|
|
|
|
# while the image within is still 16x16
|
2023-02-23 16:30:38 +03:00
|
|
|
with pytest.warns(UserWarning):
|
2020-02-23 00:03:01 +03:00
|
|
|
with Image.open("Tests/images/hopper_unexpected.ico") as im:
|
|
|
|
assert im.size == (16, 16)
|
|
|
|
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_draw_reloaded(tmp_path: Path) -> None:
|
2020-02-23 00:03:01 +03:00
|
|
|
with Image.open(TEST_ICO_FILE) as im:
|
|
|
|
outfile = str(tmp_path / "temp_saved_hopper_draw.ico")
|
|
|
|
|
|
|
|
draw = ImageDraw.Draw(im)
|
|
|
|
draw.line((0, 0) + im.size, "#f00")
|
|
|
|
im.save(outfile)
|
|
|
|
|
|
|
|
with Image.open(outfile) as im:
|
2021-02-21 14:15:56 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico")
|
2024-07-01 12:36:15 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_truncated_mask() -> None:
|
|
|
|
# 1 bpp
|
|
|
|
with open("Tests/images/hopper_mask.ico", "rb") as fp:
|
|
|
|
data = fp.read()
|
|
|
|
|
|
|
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
|
|
|
data = data[:-3]
|
|
|
|
|
|
|
|
try:
|
|
|
|
with Image.open(io.BytesIO(data)) as im:
|
|
|
|
with Image.open("Tests/images/hopper_mask.png") as expected:
|
|
|
|
assert im.mode == "1"
|
|
|
|
|
|
|
|
# 32 bpp
|
|
|
|
output = io.BytesIO()
|
|
|
|
expected = hopper("RGBA")
|
|
|
|
expected.save(output, "ico", bitmap_format="bmp")
|
|
|
|
|
|
|
|
data = output.getvalue()[:-1]
|
|
|
|
|
|
|
|
with Image.open(io.BytesIO(data)) as im:
|
|
|
|
assert im.mode == "RGB"
|
|
|
|
finally:
|
|
|
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|