Added type hints

This commit is contained in:
Andrew Murray 2024-01-31 21:55:32 +11:00
parent 4a4b90c365
commit bb1fece57a
14 changed files with 327 additions and 101 deletions

View File

@ -10,7 +10,7 @@ from .helper import assert_image_similar
base = os.path.join("Tests", "images", "bmp") base = os.path.join("Tests", "images", "bmp")
def get_files(d, ext: str = ".bmp"): def get_files(d: str, ext: str = ".bmp") -> list[str]:
return [ return [
os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f
] ]
@ -29,7 +29,7 @@ def test_bad() -> None:
pass pass
def test_questionable(): def test_questionable() -> None:
"""These shouldn't crash/dos, but it's not well defined that these """These shouldn't crash/dos, but it's not well defined that these
are in spec""" are in spec"""
supported = [ supported = [
@ -80,7 +80,7 @@ def test_good() -> None:
"rgb32bf.bmp": "rgb24.png", "rgb32bf.bmp": "rgb24.png",
} }
def get_compare(f): def get_compare(f: str) -> str:
name = os.path.split(f)[1] name = os.path.split(f)[1]
if name in file_map: if name in file_map:
return os.path.join(base, "html", file_map[name]) return os.path.join(base, "html", file_map[name])

View File

@ -23,11 +23,11 @@ def test_imageops_box_blur() -> None:
assert isinstance(i, Image.Image) assert isinstance(i, Image.Image)
def box_blur(image, radius: int = 1, n: int = 1): def box_blur(image: Image.Image, radius: float = 1, n: int = 1) -> Image.Image:
return image._new(image.im.box_blur((radius, radius), n)) return image._new(image.im.box_blur((radius, radius), n))
def assert_image(im, data, delta: int = 0) -> None: def assert_image(im: Image.Image, data: list[list[int]], delta: int = 0) -> None:
it = iter(im.getdata()) it = iter(im.getdata())
for data_row in data: for data_row in data:
im_row = [next(it) for _ in range(im.size[0])] im_row = [next(it) for _ in range(im.size[0])]
@ -37,7 +37,13 @@ def assert_image(im, data, delta: int = 0) -> None:
next(it) next(it)
def assert_blur(im, radius, data, passes: int = 1, delta: int = 0) -> None: def assert_blur(
im: Image.Image,
radius: float,
data: list[list[int]],
passes: int = 1,
delta: int = 0,
) -> None:
# check grayscale image # check grayscale image
assert_image(box_blur(im, radius, passes), data, delta) assert_image(box_blur(im, radius, passes), data, delta)
rgba = Image.merge("RGBA", (im, im, im, im)) rgba = Image.merge("RGBA", (im, im, im, im))

View File

@ -47,7 +47,7 @@ def test_apng_basic() -> None:
"filename", "filename",
("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"), ("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"),
) )
def test_apng_fdat(filename) -> None: def test_apng_fdat(filename: str) -> None:
with Image.open(filename) as im: with Image.open(filename) as im:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
@ -338,7 +338,7 @@ def test_apng_syntax_errors() -> None:
"sequence_fdat_fctl.png", "sequence_fdat_fctl.png",
), ),
) )
def test_apng_sequence_errors(test_file) -> None: def test_apng_sequence_errors(test_file: str) -> None:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
with Image.open(f"Tests/images/apng/{test_file}") as im: with Image.open(f"Tests/images/apng/{test_file}") as im:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
@ -681,7 +681,7 @@ def test_seek_after_close() -> None:
@pytest.mark.parametrize("default_image", (True, False)) @pytest.mark.parametrize("default_image", (True, False))
@pytest.mark.parametrize("duplicate", (True, False)) @pytest.mark.parametrize("duplicate", (True, False))
def test_different_modes_in_later_frames( def test_different_modes_in_later_frames(
mode, default_image, duplicate, tmp_path: Path mode: str, default_image: bool, duplicate: bool, tmp_path: Path
) -> None: ) -> None:
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")

View File

@ -64,7 +64,7 @@ def test_seek_mode_2() -> None:
@pytest.mark.parametrize("bytesmode", (True, False)) @pytest.mark.parametrize("bytesmode", (True, False))
def test_read_n0(bytesmode) -> None: def test_read_n0(bytesmode: bool) -> None:
# Arrange # Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh: with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100) container = ContainerIO.ContainerIO(fh, 22, 100)
@ -80,7 +80,7 @@ def test_read_n0(bytesmode) -> None:
@pytest.mark.parametrize("bytesmode", (True, False)) @pytest.mark.parametrize("bytesmode", (True, False))
def test_read_n(bytesmode) -> None: def test_read_n(bytesmode: bool) -> None:
# Arrange # Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh: with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100) container = ContainerIO.ContainerIO(fh, 22, 100)
@ -96,7 +96,7 @@ def test_read_n(bytesmode) -> None:
@pytest.mark.parametrize("bytesmode", (True, False)) @pytest.mark.parametrize("bytesmode", (True, False))
def test_read_eof(bytesmode) -> None: def test_read_eof(bytesmode: bool) -> None:
# Arrange # Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh: with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100) container = ContainerIO.ContainerIO(fh, 22, 100)
@ -112,7 +112,7 @@ def test_read_eof(bytesmode) -> None:
@pytest.mark.parametrize("bytesmode", (True, False)) @pytest.mark.parametrize("bytesmode", (True, False))
def test_readline(bytesmode) -> None: def test_readline(bytesmode: bool) -> None:
# Arrange # Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh: with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120) container = ContainerIO.ContainerIO(fh, 0, 120)
@ -127,7 +127,7 @@ def test_readline(bytesmode) -> None:
@pytest.mark.parametrize("bytesmode", (True, False)) @pytest.mark.parametrize("bytesmode", (True, False))
def test_readlines(bytesmode) -> None: def test_readlines(bytesmode: bool) -> None:
# Arrange # Arrange
expected = [ expected = [
"This is line 1\n", "This is line 1\n",

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import warnings import warnings
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from typing import Generator
import pytest import pytest
@ -144,13 +145,13 @@ def test_strategy() -> None:
def test_optimize() -> None: def test_optimize() -> None:
def test_grayscale(optimize): def test_grayscale(optimize: int) -> int:
im = Image.new("L", (1, 1), 0) im = Image.new("L", (1, 1), 0)
filename = BytesIO() filename = BytesIO()
im.save(filename, "GIF", optimize=optimize) im.save(filename, "GIF", optimize=optimize)
return len(filename.getvalue()) return len(filename.getvalue())
def test_bilevel(optimize): def test_bilevel(optimize: int) -> int:
im = Image.new("1", (1, 1), 0) im = Image.new("1", (1, 1), 0)
test_file = BytesIO() test_file = BytesIO()
im.save(test_file, "GIF", optimize=optimize) im.save(test_file, "GIF", optimize=optimize)
@ -178,7 +179,9 @@ def test_optimize() -> None:
(4, 513, 256), (4, 513, 256),
), ),
) )
def test_optimize_correctness(colors, size, expected_palette_length) -> None: def test_optimize_correctness(
colors: int, size: int, expected_palette_length: int
) -> None:
# 256 color Palette image, posterize to > 128 and < 128 levels. # 256 color Palette image, posterize to > 128 and < 128 levels.
# Size bigger and smaller than 512x512. # Size bigger and smaller than 512x512.
# Check the palette for number of colors allocated. # Check the palette for number of colors allocated.
@ -297,7 +300,7 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None:
("Tests/images/dispose_bgnd_rgba.gif", "RGBA"), ("Tests/images/dispose_bgnd_rgba.gif", "RGBA"),
), ),
) )
def test_loading_multiple_palettes(path, mode) -> None: def test_loading_multiple_palettes(path: str, mode: str) -> None:
with Image.open(path) as im: with Image.open(path) as im:
assert im.mode == "P" assert im.mode == "P"
first_frame_colors = im.palette.colors.keys() first_frame_colors = im.palette.colors.keys()
@ -347,9 +350,9 @@ def test_palette_handling(tmp_path: Path) -> None:
def test_palette_434(tmp_path: Path) -> None: def test_palette_434(tmp_path: Path) -> None:
# see https://github.com/python-pillow/Pillow/issues/434 # see https://github.com/python-pillow/Pillow/issues/434
def roundtrip(im, *args, **kwargs): def roundtrip(im: Image.Image, **kwargs: bool) -> Image.Image:
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im.copy().save(out, *args, **kwargs) im.copy().save(out, **kwargs)
reloaded = Image.open(out) reloaded = Image.open(out)
return reloaded return reloaded
@ -429,7 +432,7 @@ def test_seek_rewind() -> None:
("Tests/images/iss634.gif", 42), ("Tests/images/iss634.gif", 42),
), ),
) )
def test_n_frames(path, n_frames) -> None: def test_n_frames(path: str, n_frames: int) -> None:
# Test is_animated before n_frames # Test is_animated before n_frames
with Image.open(path) as im: with Image.open(path) as im:
assert im.is_animated == (n_frames != 1) assert im.is_animated == (n_frames != 1)
@ -541,7 +544,10 @@ def test_dispose_background_transparency() -> None:
), ),
), ),
) )
def test_transparent_dispose(loading_strategy, expected_colors) -> None: def test_transparent_dispose(
loading_strategy: GifImagePlugin.LoadingStrategy,
expected_colors: tuple[tuple[int | tuple[int, int, int, int], ...]],
) -> None:
GifImagePlugin.LOADING_STRATEGY = loading_strategy GifImagePlugin.LOADING_STRATEGY = loading_strategy
try: try:
with Image.open("Tests/images/transparent_dispose.gif") as img: with Image.open("Tests/images/transparent_dispose.gif") as img:
@ -889,7 +895,9 @@ def test_identical_frames(tmp_path: Path) -> None:
1500, 1500,
), ),
) )
def test_identical_frames_to_single_frame(duration, tmp_path: Path) -> None: def test_identical_frames_to_single_frame(
duration: int | list[int], tmp_path: Path
) -> None:
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im_list = [ im_list = [
Image.new("L", (100, 100), "#000"), Image.new("L", (100, 100), "#000"),
@ -1049,7 +1057,7 @@ def test_retain_comment_in_subsequent_frames(tmp_path: Path) -> None:
def test_version(tmp_path: Path) -> None: def test_version(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
def assert_version_after_save(im, version) -> None: def assert_version_after_save(im: Image.Image, version: bytes) -> None:
im.save(out) im.save(out)
with Image.open(out) as reread: with Image.open(out) as reread:
assert reread.info["version"] == version assert reread.info["version"] == version
@ -1088,7 +1096,7 @@ def test_append_images(tmp_path: Path) -> None:
assert reread.n_frames == 3 assert reread.n_frames == 3
# Tests appending using a generator # Tests appending using a generator
def im_generator(ims): def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
yield from ims yield from ims
im.save(out, save_all=True, append_images=im_generator(ims)) im.save(out, save_all=True, append_images=im_generator(ims))

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import warnings import warnings
from io import BytesIO from io import BytesIO
from typing import Any
import pytest import pytest
@ -19,7 +20,7 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
pytestmark = skip_unless_feature("jpg") pytestmark = skip_unless_feature("jpg")
def roundtrip(im, **options): def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO() out = BytesIO()
im.save(out, "MPO", **options) im.save(out, "MPO", **options)
test_bytes = out.tell() test_bytes = out.tell()
@ -30,7 +31,7 @@ def roundtrip(im, **options):
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_sanity(test_file) -> None: def test_sanity(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
im.load() im.load()
assert im.mode == "RGB" assert im.mode == "RGB"
@ -70,7 +71,7 @@ def test_context_manager() -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_app(test_file) -> None: def test_app(test_file: str) -> None:
# Test APP/COM reader (@PIL135) # Test APP/COM reader (@PIL135)
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.applist[0][0] == "APP1" assert im.applist[0][0] == "APP1"
@ -82,7 +83,7 @@ def test_app(test_file) -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_exif(test_file) -> None: def test_exif(test_file: str) -> None:
with Image.open(test_file) as im_original: with Image.open(test_file) as im_original:
im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif()) im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif())
@ -143,7 +144,7 @@ def test_reload_exif_after_seek() -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_mp(test_file) -> None: def test_mp(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
@ -168,7 +169,7 @@ def test_mp_no_data() -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_mp_attribute(test_file) -> None: def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
mpinfo = im._getmp() mpinfo = im._getmp()
for frame_number, mpentry in enumerate(mpinfo[0xB002]): for frame_number, mpentry in enumerate(mpinfo[0xB002]):
@ -185,7 +186,7 @@ def test_mp_attribute(test_file) -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_seek(test_file) -> None: def test_seek(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.tell() == 0 assert im.tell() == 0
# prior to first image raises an error, both blatant and borderline # prior to first image raises an error, both blatant and borderline
@ -229,7 +230,7 @@ def test_eoferror() -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_image_grab(test_file) -> None: def test_image_grab(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.tell() == 0 assert im.tell() == 0
im0 = im.tobytes() im0 = im.tobytes()
@ -244,7 +245,7 @@ def test_image_grab(test_file) -> None:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_save(test_file) -> None: def test_save(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.tell() == 0 assert im.tell() == 0
jpg0 = roundtrip(im) jpg0 = roundtrip(im)

View File

@ -70,7 +70,9 @@ def test_sanity() -> None:
), ),
), ),
) )
def test_arbitrary_maxval(data, mode, pixels) -> None: def test_arbitrary_maxval(
data: bytes, mode: str, pixels: tuple[int | tuple[int, int, int], ...]
) -> None:
fp = BytesIO(data) fp = BytesIO(data)
with Image.open(fp) as im: with Image.open(fp) as im:
assert im.size == (3, 1) assert im.size == (3, 1)
@ -139,7 +141,7 @@ def test_pfm_big_endian(tmp_path: Path) -> None:
b"Pf 1 1 -0.0 \0\0\0\0", b"Pf 1 1 -0.0 \0\0\0\0",
], ],
) )
def test_pfm_invalid(data) -> None: def test_pfm_invalid(data: bytes) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
with Image.open(BytesIO(data)): with Image.open(BytesIO(data)):
pass pass
@ -162,7 +164,7 @@ def test_pfm_invalid(data) -> None:
), ),
), ),
) )
def test_plain(plain_path, raw_path) -> None: def test_plain(plain_path: str, raw_path: str) -> None:
with Image.open(plain_path) as im: with Image.open(plain_path) as im:
assert_image_equal_tofile(im, raw_path) assert_image_equal_tofile(im, raw_path)
@ -186,7 +188,9 @@ def test_16bit_plain_pgm() -> None:
(b"P3\n2 2\n255", b"0 0 0 001 1 1 2 2 2 255 255 255", 10**6), (b"P3\n2 2\n255", b"0 0 0 001 1 1 2 2 2 255 255 255", 10**6),
), ),
) )
def test_plain_data_with_comment(tmp_path: Path, header, data, comment_count) -> None: def test_plain_data_with_comment(
tmp_path: Path, header: bytes, data: bytes, comment_count: int
) -> None:
path1 = str(tmp_path / "temp1.ppm") path1 = str(tmp_path / "temp1.ppm")
path2 = str(tmp_path / "temp2.ppm") path2 = str(tmp_path / "temp2.ppm")
comment = b"# comment" * comment_count comment = b"# comment" * comment_count
@ -199,7 +203,7 @@ def test_plain_data_with_comment(tmp_path: Path, header, data, comment_count) ->
@pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n")) @pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n"))
def test_plain_truncated_data(tmp_path: Path, data) -> None: def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None:
path = str(tmp_path / "temp.ppm") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(data) f.write(data)
@ -210,7 +214,7 @@ def test_plain_truncated_data(tmp_path: Path, data) -> None:
@pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A")) @pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A"))
def test_plain_invalid_data(tmp_path: Path, data) -> None: def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None:
path = str(tmp_path / "temp.ppm") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(data) f.write(data)
@ -227,7 +231,7 @@ def test_plain_invalid_data(tmp_path: Path, data) -> None:
b"P3\n128 128\n255\n012345678910 0", # token too long b"P3\n128 128\n255\n012345678910 0", # token too long
), ),
) )
def test_plain_ppm_token_too_long(tmp_path: Path, data) -> None: def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None:
path = str(tmp_path / "temp.ppm") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(data) f.write(data)
@ -313,7 +317,7 @@ def test_not_enough_image_data(tmp_path: Path) -> None:
@pytest.mark.parametrize("maxval", (b"0", b"65536")) @pytest.mark.parametrize("maxval", (b"0", b"65536"))
def test_invalid_maxval(maxval, tmp_path: Path) -> None: def test_invalid_maxval(maxval: bytes, tmp_path: Path) -> None:
path = str(tmp_path / "temp.ppm") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P6\n3 1 " + maxval) f.write(b"P6\n3 1 " + maxval)
@ -351,7 +355,7 @@ def test_mimetypes(tmp_path: Path) -> None:
@pytest.mark.parametrize("buffer", (True, False)) @pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(buffer) -> None: def test_save_stdout(buffer: bool) -> None:
old_stdout = sys.stdout old_stdout = sys.stdout
if buffer: if buffer:

View File

@ -72,7 +72,7 @@ def test_invalid_file() -> None:
def test_write(tmp_path: Path) -> None: def test_write(tmp_path: Path) -> None:
def roundtrip(img) -> None: def roundtrip(img: Image.Image) -> None:
out = str(tmp_path / "temp.sgi") out = str(tmp_path / "temp.sgi")
img.save(out, format="sgi") img.save(out, format="sgi")
assert_image_equal_tofile(img, out) assert_image_equal_tofile(img, out)

View File

@ -8,7 +8,7 @@ from .helper import assert_image_equal, hopper
@pytest.mark.parametrize("data_type", ("bytes", "memoryview")) @pytest.mark.parametrize("data_type", ("bytes", "memoryview"))
def test_sanity(data_type) -> None: def test_sanity(data_type: str) -> None:
im1 = hopper() im1 = hopper()
data = im1.tobytes() data = im1.tobytes()

View File

@ -26,7 +26,7 @@ def test_close() -> None:
im.getpixel((0, 0)) im.getpixel((0, 0))
def test_close_after_load(caplog) -> None: def test_close_after_load(caplog: pytest.LogCaptureFixture) -> None:
im = Image.open("Tests/images/hopper.gif") im = Image.open("Tests/images/hopper.gif")
im.load() im.load()
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):

View File

@ -74,7 +74,14 @@ def test_mode_mismatch() -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
@pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4))) @pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4)))
def test_arc(bbox, start, end) -> None: def test_arc(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int],
start: float,
end: float,
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -87,7 +94,12 @@ def test_arc(bbox, start, end) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_arc_end_le_start(bbox) -> None: def test_arc_end_le_start(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -102,7 +114,12 @@ def test_arc_end_le_start(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_arc_no_loops(bbox) -> None: def test_arc_no_loops(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# No need to go in loops # No need to go in loops
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
@ -118,7 +135,12 @@ def test_arc_no_loops(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_arc_width(bbox) -> None: def test_arc_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -131,7 +153,12 @@ def test_arc_width(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_arc_width_pieslice_large(bbox) -> None: def test_arc_width_pieslice_large(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Tests an arc with a large enough width that it is a pieslice # Tests an arc with a large enough width that it is a pieslice
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
@ -145,7 +172,12 @@ def test_arc_width_pieslice_large(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_arc_width_fill(bbox) -> None: def test_arc_width_fill(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -158,7 +190,12 @@ def test_arc_width_fill(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_arc_width_non_whole_angle(bbox) -> None: def test_arc_width_non_whole_angle(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -200,7 +237,13 @@ def test_bitmap() -> None:
@pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("mode", ("RGB", "L"))
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_chord(mode, bbox) -> None: def test_chord(
mode: str,
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int],
) -> None:
# Arrange # Arrange
im = Image.new(mode, (W, H)) im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -214,7 +257,12 @@ def test_chord(mode, bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_chord_width(bbox) -> None: def test_chord_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -227,7 +275,12 @@ def test_chord_width(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_chord_width_fill(bbox) -> None: def test_chord_width_fill(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -240,7 +293,12 @@ def test_chord_width_fill(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_chord_zero_width(bbox) -> None: def test_chord_zero_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -266,7 +324,13 @@ def test_chord_too_fat() -> None:
@pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("mode", ("RGB", "L"))
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_ellipse(mode, bbox) -> None: def test_ellipse(
mode: str,
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int],
) -> None:
# Arrange # Arrange
im = Image.new(mode, (W, H)) im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -280,7 +344,12 @@ def test_ellipse(mode, bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_ellipse_translucent(bbox) -> None: def test_ellipse_translucent(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im, "RGBA") draw = ImageDraw.Draw(im, "RGBA")
@ -317,7 +386,12 @@ def test_ellipse_symmetric() -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_ellipse_width(bbox) -> None: def test_ellipse_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -342,7 +416,12 @@ def test_ellipse_width_large() -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_ellipse_width_fill(bbox) -> None: def test_ellipse_width_fill(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -355,7 +434,12 @@ def test_ellipse_width_fill(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_ellipse_zero_width(bbox) -> None: def test_ellipse_zero_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -367,7 +451,7 @@ def test_ellipse_zero_width(bbox) -> None:
assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_zero_width.png") assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_zero_width.png")
def ellipse_various_sizes_helper(filled): def ellipse_various_sizes_helper(filled: bool) -> Image.Image:
ellipse_sizes = range(32) ellipse_sizes = range(32)
image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1 image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1
im = Image.new("RGB", (image_size, image_size)) im = Image.new("RGB", (image_size, image_size))
@ -409,7 +493,12 @@ def test_ellipse_various_sizes_filled() -> None:
@pytest.mark.parametrize("points", POINTS) @pytest.mark.parametrize("points", POINTS)
def test_line(points) -> None: def test_line(
points: tuple[tuple[int, int], ...]
| list[tuple[int, int]]
| tuple[int, ...]
| list[int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -482,7 +571,14 @@ def test_transform() -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
@pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2))) @pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2)))
def test_pieslice(bbox, start, end) -> None: def test_pieslice(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int],
start: float,
end: float,
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -495,7 +591,12 @@ def test_pieslice(bbox, start, end) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_pieslice_width(bbox) -> None: def test_pieslice_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -508,7 +609,12 @@ def test_pieslice_width(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_pieslice_width_fill(bbox) -> None: def test_pieslice_width_fill(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -522,7 +628,12 @@ def test_pieslice_width_fill(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_pieslice_zero_width(bbox) -> None: def test_pieslice_zero_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -577,7 +688,12 @@ def test_pieslice_no_spikes() -> None:
@pytest.mark.parametrize("points", POINTS) @pytest.mark.parametrize("points", POINTS)
def test_point(points) -> None: def test_point(
points: tuple[tuple[int, int], ...]
| list[tuple[int, int]]
| tuple[int, ...]
| list[int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -602,7 +718,12 @@ def test_point_I16() -> None:
@pytest.mark.parametrize("points", POINTS) @pytest.mark.parametrize("points", POINTS)
def test_polygon(points) -> None: def test_polygon(
points: tuple[tuple[int, int], ...]
| list[tuple[int, int]]
| tuple[int, ...]
| list[int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -616,7 +737,9 @@ def test_polygon(points) -> None:
@pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("mode", ("RGB", "L"))
@pytest.mark.parametrize("kite_points", KITE_POINTS) @pytest.mark.parametrize("kite_points", KITE_POINTS)
def test_polygon_kite(mode, kite_points) -> None: def test_polygon_kite(
mode: str, kite_points: tuple[tuple[int, int], ...] | list[tuple[int, int]]
) -> None:
# Test drawing lines of different gradients (dx>dy, dy>dx) and # Test drawing lines of different gradients (dx>dy, dy>dx) and
# vertical (dx==0) and horizontal (dy==0) lines # vertical (dx==0) and horizontal (dy==0) lines
# Arrange # Arrange
@ -673,7 +796,12 @@ def test_polygon_translucent() -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_rectangle(bbox) -> None: def test_rectangle(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -700,7 +828,12 @@ def test_big_rectangle() -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_rectangle_width(bbox) -> None: def test_rectangle_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -714,7 +847,12 @@ def test_rectangle_width(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_rectangle_width_fill(bbox) -> None: def test_rectangle_width_fill(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -728,7 +866,12 @@ def test_rectangle_width_fill(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_rectangle_zero_width(bbox) -> None: def test_rectangle_zero_width(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -741,7 +884,12 @@ def test_rectangle_zero_width(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_rectangle_I16(bbox) -> None: def test_rectangle_I16(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("I;16", (W, H)) im = Image.new("I;16", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -754,7 +902,12 @@ def test_rectangle_I16(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_rectangle_translucent_outline(bbox) -> None: def test_rectangle_translucent_outline(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im, "RGBA") draw = ImageDraw.Draw(im, "RGBA")
@ -772,7 +925,11 @@ def test_rectangle_translucent_outline(bbox) -> None:
"xy", "xy",
[(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))], [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))],
) )
def test_rounded_rectangle(xy) -> None: def test_rounded_rectangle(
xy: tuple[int, int, int, int]
| tuple[list[int]]
| tuple[tuple[int, int], tuple[int, int]]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (200, 200)) im = Image.new("RGB", (200, 200))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -789,7 +946,7 @@ def test_rounded_rectangle(xy) -> None:
@pytest.mark.parametrize("bottom_right", (True, False)) @pytest.mark.parametrize("bottom_right", (True, False))
@pytest.mark.parametrize("bottom_left", (True, False)) @pytest.mark.parametrize("bottom_left", (True, False))
def test_rounded_rectangle_corners( def test_rounded_rectangle_corners(
top_left, top_right, bottom_right, bottom_left top_left: bool, top_right: bool, bottom_right: bool, bottom_left: bool
) -> None: ) -> None:
corners = (top_left, top_right, bottom_right, bottom_left) corners = (top_left, top_right, bottom_right, bottom_left)
@ -824,7 +981,9 @@ def test_rounded_rectangle_corners(
((10, 20, 190, 181), 85, "height"), ((10, 20, 190, 181), 85, "height"),
], ],
) )
def test_rounded_rectangle_non_integer_radius(xy, radius, type) -> None: def test_rounded_rectangle_non_integer_radius(
xy: tuple[int, int, int, int], radius: float, type: str
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (200, 200)) im = Image.new("RGB", (200, 200))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -840,7 +999,12 @@ def test_rounded_rectangle_non_integer_radius(xy, radius, type) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_rounded_rectangle_zero_radius(bbox) -> None: def test_rounded_rectangle_zero_radius(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -862,7 +1026,9 @@ def test_rounded_rectangle_zero_radius(bbox) -> None:
((20, 20, 80, 80), "both"), ((20, 20, 80, 80), "both"),
], ],
) )
def test_rounded_rectangle_translucent(xy, suffix) -> None: def test_rounded_rectangle_translucent(
xy: tuple[int, int, int, int], suffix: str
) -> None:
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im, "RGBA") draw = ImageDraw.Draw(im, "RGBA")
@ -879,7 +1045,12 @@ def test_rounded_rectangle_translucent(xy, suffix) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_floodfill(bbox) -> None: def test_floodfill(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
red = ImageColor.getrgb("red") red = ImageColor.getrgb("red")
for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]:
@ -912,7 +1083,12 @@ def test_floodfill(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_floodfill_border(bbox) -> None: def test_floodfill_border(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# floodfill() is experimental # floodfill() is experimental
# Arrange # Arrange
@ -934,7 +1110,12 @@ def test_floodfill_border(bbox) -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_floodfill_thresh(bbox) -> None: def test_floodfill_thresh(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# floodfill() is experimental # floodfill() is experimental
# Arrange # Arrange
@ -968,8 +1149,11 @@ def test_floodfill_not_negative() -> None:
def create_base_image_draw( def create_base_image_draw(
size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY size: tuple[int, int],
): mode: str = DEFAULT_MODE,
background1: tuple[int, int, int] = WHITE,
background2: tuple[int, int, int] = GRAY,
) -> tuple[Image.Image, ImageDraw.ImageDraw]:
img = Image.new(mode, size, background1) img = Image.new(mode, size, background1)
for x in range(0, size[0]): for x in range(0, size[0]):
for y in range(0, size[1]): for y in range(0, size[1]):
@ -1003,7 +1187,7 @@ def test_triangle_right() -> None:
"fill, suffix", "fill, suffix",
((BLACK, "width"), (None, "width_no_fill")), ((BLACK, "width"), (None, "width_no_fill")),
) )
def test_triangle_right_width(fill, suffix) -> None: def test_triangle_right_width(fill: tuple[int, int, int] | None, suffix: str) -> None:
img, draw = create_base_image_draw((100, 100)) img, draw = create_base_image_draw((100, 100))
draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5) draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5)
assert_image_equal_tofile( assert_image_equal_tofile(
@ -1235,7 +1419,7 @@ def test_wide_line_larger_than_int() -> None:
], ],
], ],
) )
def test_line_joint(xy) -> None: def test_line_joint(xy: list[tuple[int, int]] | tuple[int, ...] | list[int]) -> None:
im = Image.new("RGB", (500, 325)) im = Image.new("RGB", (500, 325))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -1388,7 +1572,12 @@ def test_default_font_size() -> None:
@pytest.mark.parametrize("bbox", BBOX) @pytest.mark.parametrize("bbox", BBOX)
def test_same_color_outline(bbox) -> None: def test_same_color_outline(
bbox: tuple[tuple[int, int], tuple[int, int]]
| list[tuple[int, int]]
| list[int]
| tuple[int, int, int, int]
) -> None:
# Prepare shape # Prepare shape
x0, y0 = 5, 5 x0, y0 = 5, 5
x1, y1 = 5, 50 x1, y1 = 5, 50
@ -1402,7 +1591,8 @@ def test_same_color_outline(bbox) -> None:
# Begin # Begin
for mode in ["RGB", "L"]: for mode in ["RGB", "L"]:
for fill, outline in [["red", None], ["red", "red"], ["red", "#f00"]]: fill = "red"
for outline in [None, "red", "#f00"]:
for operation, args in { for operation, args in {
"chord": [bbox, 0, 180], "chord": [bbox, 0, 180],
"ellipse": [bbox], "ellipse": [bbox],
@ -1417,6 +1607,7 @@ def test_same_color_outline(bbox) -> None:
# Act # Act
draw_method = getattr(draw, operation) draw_method = getattr(draw, operation)
assert isinstance(args, list)
args += [fill, outline] args += [fill, outline]
draw_method(*args) draw_method(*args)
@ -1434,7 +1625,9 @@ def test_same_color_outline(bbox) -> None:
(3, "triangle_width", {"width": 5, "outline": "yellow"}), (3, "triangle_width", {"width": 5, "outline": "yellow"}),
], ],
) )
def test_draw_regular_polygon(n_sides, polygon_name, args) -> None: def test_draw_regular_polygon(
n_sides: int, polygon_name: str, args: dict[str, int | str]
) -> None:
im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0)) im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0))
filename = f"Tests/images/imagedraw_{polygon_name}.png" filename = f"Tests/images/imagedraw_{polygon_name}.png"
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -1471,7 +1664,9 @@ def test_draw_regular_polygon(n_sides, polygon_name, args) -> None:
), ),
], ],
) )
def test_compute_regular_polygon_vertices(n_sides, expected_vertices) -> None: def test_compute_regular_polygon_vertices(
n_sides: int, expected_vertices: list[tuple[float, float]]
) -> None:
bounding_circle = (W // 2, H // 2, 25) bounding_circle = (W // 2, H // 2, 25)
vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0) vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0)
assert vertices == expected_vertices assert vertices == expected_vertices
@ -1569,7 +1764,7 @@ def test_polygon2() -> None:
@pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0))) @pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0)))
def test_incorrectly_ordered_coordinates(xy) -> None: def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
with pytest.raises(ValueError): with pytest.raises(ValueError):

View File

@ -57,7 +57,7 @@ def test_kw() -> None:
@pytest.mark.parametrize("mode", TK_MODES) @pytest.mark.parametrize("mode", TK_MODES)
def test_photoimage(mode) -> None: def test_photoimage(mode: str) -> None:
# test as image: # test as image:
im = hopper(mode) im = hopper(mode)
@ -79,7 +79,7 @@ def test_photoimage_apply_transparency() -> None:
@pytest.mark.parametrize("mode", TK_MODES) @pytest.mark.parametrize("mode", TK_MODES)
def test_photoimage_blank(mode) -> None: def test_photoimage_blank(mode: str) -> None:
# test a image using mode/size: # test a image using mode/size:
im_tk = ImageTk.PhotoImage(mode, (100, 100)) im_tk = ImageTk.PhotoImage(mode, (100, 100))

View File

@ -10,7 +10,13 @@ X = 255
class TestLibPack: class TestLibPack:
def assert_pack(self, mode, rawmode, data, *pixels) -> None: def assert_pack(
self,
mode: str,
rawmode: str,
data: int | bytes,
*pixels: int | float | tuple[int, ...],
) -> None:
""" """
data - either raw bytes with data or just number of bytes in rawmode. data - either raw bytes with data or just number of bytes in rawmode.
""" """
@ -228,7 +234,13 @@ class TestLibPack:
class TestLibUnpack: class TestLibUnpack:
def assert_unpack(self, mode, rawmode, data, *pixels) -> None: def assert_unpack(
self,
mode: str,
rawmode: str,
data: int | bytes,
*pixels: int | float | tuple[int, ...],
) -> None:
""" """
data - either raw bytes with data or just number of bytes in rawmode. data - either raw bytes with data or just number of bytes in rawmode.
""" """

View File

@ -11,7 +11,7 @@ from .helper import hopper
original = hopper().resize((32, 32)).convert("I") original = hopper().resize((32, 32)).convert("I")
def verify(im1) -> None: def verify(im1: Image.Image) -> None:
im2 = original.copy() im2 = original.copy()
assert im1.size == im2.size assert im1.size == im2.size
pix1 = im1.load() pix1 = im1.load()
@ -27,7 +27,7 @@ def verify(im1) -> None:
@pytest.mark.parametrize("mode", ("L", "I;16", "I;16B", "I;16L", "I")) @pytest.mark.parametrize("mode", ("L", "I;16", "I;16B", "I;16L", "I"))
def test_basic(tmp_path: Path, mode) -> None: def test_basic(tmp_path: Path, mode: str) -> None:
# PIL 1.1 has limited support for 16-bit image data. Check that # PIL 1.1 has limited support for 16-bit image data. Check that
# create/copy/transform and save works as expected. # create/copy/transform and save works as expected.
@ -78,7 +78,7 @@ def test_basic(tmp_path: Path, mode) -> None:
def test_tobytes() -> None: def test_tobytes() -> None:
def tobytes(mode): def tobytes(mode: str) -> Image.Image:
return Image.new(mode, (1, 1), 1).tobytes() return Image.new(mode, (1, 1), 1).tobytes()
order = 1 if Image._ENDIAN == "<" else -1 order = 1 if Image._ENDIAN == "<" else -1