Added type hints

This commit is contained in:
Andrew Murray 2024-01-25 22:18:46 +11:00
parent b3a7ae065c
commit ddb7df0ec6
20 changed files with 149 additions and 115 deletions

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image from PIL import Image
@ -7,8 +9,8 @@ from PIL import Image
from .helper import assert_image, assert_image_equal, assert_image_similar, hopper from .helper import assert_image, assert_image_equal, assert_image_similar, hopper
def test_sanity(): def test_sanity() -> None:
def convert(im, mode): def convert(im: Image.Image, mode: str) -> None:
out = im.convert(mode) out = im.convert(mode)
assert out.mode == mode assert out.mode == mode
assert out.size == im.size assert out.size == im.size
@ -40,13 +42,13 @@ def test_sanity():
convert(im, output_mode) convert(im, output_mode)
def test_unsupported_conversion(): def test_unsupported_conversion() -> None:
im = hopper() im = hopper()
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.convert("INVALID") im.convert("INVALID")
def test_default(): def test_default() -> None:
im = hopper("P") im = hopper("P")
assert im.mode == "P" assert im.mode == "P"
converted_im = im.convert() converted_im = im.convert()
@ -62,18 +64,18 @@ def test_default():
# ref https://github.com/python-pillow/Pillow/issues/274 # ref https://github.com/python-pillow/Pillow/issues/274
def _test_float_conversion(im): def _test_float_conversion(im: Image.Image) -> None:
orig = im.getpixel((5, 5)) orig = im.getpixel((5, 5))
converted = im.convert("F").getpixel((5, 5)) converted = im.convert("F").getpixel((5, 5))
assert orig == converted assert orig == converted
def test_8bit(): def test_8bit() -> None:
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
_test_float_conversion(im.convert("L")) _test_float_conversion(im.convert("L"))
def test_16bit(): def test_16bit() -> None:
with Image.open("Tests/images/16bit.cropped.tif") as im: with Image.open("Tests/images/16bit.cropped.tif") as im:
_test_float_conversion(im) _test_float_conversion(im)
@ -83,19 +85,19 @@ def test_16bit():
assert im_i16.getpixel((0, 0)) == 65535 assert im_i16.getpixel((0, 0)) == 65535
def test_16bit_workaround(): def test_16bit_workaround() -> None:
with Image.open("Tests/images/16bit.cropped.tif") as im: with Image.open("Tests/images/16bit.cropped.tif") as im:
_test_float_conversion(im.convert("I")) _test_float_conversion(im.convert("I"))
def test_opaque(): def test_opaque() -> None:
alpha = hopper("P").convert("PA").getchannel("A") alpha = hopper("P").convert("PA").getchannel("A")
solid = Image.new("L", (128, 128), 255) solid = Image.new("L", (128, 128), 255)
assert_image_equal(alpha, solid) assert_image_equal(alpha, solid)
def test_rgba_p(): def test_rgba_p() -> None:
im = hopper("RGBA") im = hopper("RGBA")
im.putalpha(hopper("L")) im.putalpha(hopper("L"))
@ -105,14 +107,14 @@ def test_rgba_p():
assert_image_similar(im, comparable, 20) assert_image_similar(im, comparable, 20)
def test_rgba(): def test_rgba() -> None:
with Image.open("Tests/images/transparent.png") as im: with Image.open("Tests/images/transparent.png") as im:
assert im.mode == "RGBA" assert im.mode == "RGBA"
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5) assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
def test_trns_p(tmp_path): def test_trns_p(tmp_path: Path) -> None:
im = hopper("P") im = hopper("P")
im.info["transparency"] = 0 im.info["transparency"] = 0
@ -131,7 +133,7 @@ def test_trns_p(tmp_path):
@pytest.mark.parametrize("mode", ("LA", "PA", "RGBA")) @pytest.mark.parametrize("mode", ("LA", "PA", "RGBA"))
def test_trns_p_transparency(mode): def test_trns_p_transparency(mode: str) -> None:
# Arrange # Arrange
im = hopper("P") im = hopper("P")
im.info["transparency"] = 128 im.info["transparency"] = 128
@ -148,7 +150,7 @@ def test_trns_p_transparency(mode):
assert converted_im.palette is None assert converted_im.palette is None
def test_trns_l(tmp_path): def test_trns_l(tmp_path: Path) -> None:
im = hopper("L") im = hopper("L")
im.info["transparency"] = 128 im.info["transparency"] = 128
@ -171,7 +173,7 @@ def test_trns_l(tmp_path):
im_p.save(f) im_p.save(f)
def test_trns_RGB(tmp_path): def test_trns_RGB(tmp_path: Path) -> None:
im = hopper("RGB") im = hopper("RGB")
im.info["transparency"] = im.getpixel((0, 0)) im.info["transparency"] = im.getpixel((0, 0))
@ -201,7 +203,7 @@ def test_trns_RGB(tmp_path):
@pytest.mark.parametrize("convert_mode", ("L", "LA", "I")) @pytest.mark.parametrize("convert_mode", ("L", "LA", "I"))
def test_l_macro_rounding(convert_mode): def test_l_macro_rounding(convert_mode: str) -> None:
for mode in ("P", "PA"): for mode in ("P", "PA"):
im = Image.new(mode, (1, 1)) im = Image.new(mode, (1, 1))
im.palette.getcolor((0, 1, 2)) im.palette.getcolor((0, 1, 2))
@ -214,7 +216,7 @@ def test_l_macro_rounding(convert_mode):
assert converted_color == 1 assert converted_color == 1
def test_gif_with_rgba_palette_to_p(): def test_gif_with_rgba_palette_to_p() -> None:
# See https://github.com/python-pillow/Pillow/issues/2433 # See https://github.com/python-pillow/Pillow/issues/2433
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
im.info["transparency"] = 255 im.info["transparency"] = 255
@ -226,7 +228,7 @@ def test_gif_with_rgba_palette_to_p():
im_p.load() im_p.load()
def test_p_la(): def test_p_la() -> None:
im = hopper("RGBA") im = hopper("RGBA")
alpha = hopper("L") alpha = hopper("L")
im.putalpha(alpha) im.putalpha(alpha)
@ -236,7 +238,7 @@ def test_p_la():
assert_image_similar(alpha, comparable, 5) assert_image_similar(alpha, comparable, 5)
def test_p2pa_alpha(): def test_p2pa_alpha() -> None:
with Image.open("Tests/images/tiny.png") as im: with Image.open("Tests/images/tiny.png") as im:
assert im.mode == "P" assert im.mode == "P"
@ -250,13 +252,13 @@ def test_p2pa_alpha():
assert im_a.getpixel((x, y)) == alpha assert im_a.getpixel((x, y)) == alpha
def test_p2pa_palette(): def test_p2pa_palette() -> None:
with Image.open("Tests/images/tiny.png") as im: with Image.open("Tests/images/tiny.png") as im:
im_pa = im.convert("PA") im_pa = im.convert("PA")
assert im_pa.getpalette() == im.getpalette() assert im_pa.getpalette() == im.getpalette()
def test_matrix_illegal_conversion(): def test_matrix_illegal_conversion() -> None:
# Arrange # Arrange
im = hopper("CMYK") im = hopper("CMYK")
# fmt: off # fmt: off
@ -272,7 +274,7 @@ def test_matrix_illegal_conversion():
im.convert(mode="CMYK", matrix=matrix) im.convert(mode="CMYK", matrix=matrix)
def test_matrix_wrong_mode(): def test_matrix_wrong_mode() -> None:
# Arrange # Arrange
im = hopper("L") im = hopper("L")
# fmt: off # fmt: off
@ -289,7 +291,7 @@ def test_matrix_wrong_mode():
@pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("mode", ("RGB", "L"))
def test_matrix_xyz(mode): def test_matrix_xyz(mode: str) -> None:
# Arrange # Arrange
im = hopper("RGB") im = hopper("RGB")
im.info["transparency"] = (255, 0, 0) im.info["transparency"] = (255, 0, 0)
@ -317,7 +319,7 @@ def test_matrix_xyz(mode):
assert converted_im.info["transparency"] == 105 assert converted_im.info["transparency"] == 105
def test_matrix_identity(): def test_matrix_identity() -> None:
# Arrange # Arrange
im = hopper("RGB") im = hopper("RGB")
# fmt: off # fmt: off

View File

@ -10,7 +10,7 @@ from .helper import hopper, skip_unless_feature
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
def test_copy(mode): def test_copy(mode: str) -> None:
cropped_coordinates = (10, 10, 20, 20) cropped_coordinates = (10, 10, 20, 20)
cropped_size = (10, 10) cropped_size = (10, 10)
@ -39,7 +39,7 @@ def test_copy(mode):
assert out.size == cropped_size assert out.size == cropped_size
def test_copy_zero(): def test_copy_zero() -> None:
im = Image.new("RGB", (0, 0)) im = Image.new("RGB", (0, 0))
out = im.copy() out = im.copy()
assert out.mode == im.mode assert out.mode == im.mode
@ -47,7 +47,7 @@ def test_copy_zero():
@skip_unless_feature("libtiff") @skip_unless_feature("libtiff")
def test_deepcopy(): def test_deepcopy() -> None:
with Image.open("Tests/images/g4_orientation_5.tif") as im: with Image.open("Tests/images/g4_orientation_5.tif") as im:
out = copy.deepcopy(im) out = copy.deepcopy(im)
assert out.size == (590, 88) assert out.size == (590, 88)

View File

@ -8,7 +8,7 @@ from .helper import assert_image_equal, hopper
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
def test_crop(mode): def test_crop(mode: str) -> None:
im = hopper(mode) im = hopper(mode)
assert_image_equal(im.crop(), im) assert_image_equal(im.crop(), im)
@ -17,8 +17,8 @@ def test_crop(mode):
assert cropped.size == (50, 50) assert cropped.size == (50, 50)
def test_wide_crop(): def test_wide_crop() -> None:
def crop(*bbox): def crop(*bbox: int) -> tuple[int, ...]:
i = im.crop(bbox) i = im.crop(bbox)
h = i.histogram() h = i.histogram()
while h and not h[-1]: while h and not h[-1]:
@ -47,14 +47,14 @@ def test_wide_crop():
@pytest.mark.parametrize("box", ((8, 2, 2, 8), (2, 8, 8, 2), (8, 8, 2, 2))) @pytest.mark.parametrize("box", ((8, 2, 2, 8), (2, 8, 8, 2), (8, 8, 2, 2)))
def test_negative_crop(box): def test_negative_crop(box: tuple[int, int, int, int]) -> None:
im = Image.new("RGB", (10, 10)) im = Image.new("RGB", (10, 10))
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.crop(box) im.crop(box)
def test_crop_float(): def test_crop_float() -> None:
# Check cropping floats are rounded to nearest integer # Check cropping floats are rounded to nearest integer
# https://github.com/python-pillow/Pillow/issues/1744 # https://github.com/python-pillow/Pillow/issues/1744
@ -69,7 +69,7 @@ def test_crop_float():
assert cropped.size == (3, 5) assert cropped.size == (3, 5)
def test_crop_crash(): def test_crop_crash() -> None:
# Image.crop crashes prepatch with an access violation # Image.crop crashes prepatch with an access violation
# apparently a use after free on Windows, see # apparently a use after free on Windows, see
# https://github.com/python-pillow/Pillow/issues/1077 # https://github.com/python-pillow/Pillow/issues/1077
@ -87,7 +87,7 @@ def test_crop_crash():
img.load() img.load()
def test_crop_zero(): def test_crop_zero() -> None:
im = Image.new("RGB", (0, 0), "white") im = Image.new("RGB", (0, 0), "white")
cropped = im.crop((0, 0, 0, 0)) cropped = im.crop((0, 0, 0, 0))

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): def test_sanity(data_type) -> None:
im1 = hopper() im1 = hopper()
data = im1.tobytes() data = im1.tobytes()

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from typing import Generator
import pytest import pytest
@ -18,7 +19,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture @pytest.fixture
def test_images(): def test_images() -> Generator[Image.Image, None, None]:
ims = [ ims = [
hopper(), hopper(),
Image.open("Tests/images/transparent.png"), Image.open("Tests/images/transparent.png"),
@ -31,7 +32,7 @@ def test_images():
im.close() im.close()
def roundtrip(expected): def roundtrip(expected: Image.Image) -> None:
# PIL -> Qt # PIL -> Qt
intermediate = expected.toqimage() intermediate = expected.toqimage()
# Qt -> PIL # Qt -> PIL
@ -43,26 +44,26 @@ def roundtrip(expected):
assert_image_equal(result, expected.convert("RGB")) assert_image_equal(result, expected.convert("RGB"))
def test_sanity_1(test_images): def test_sanity_1(test_images: Generator[Image.Image, None, None]) -> None:
for im in test_images: for im in test_images:
roundtrip(im.convert("1")) roundtrip(im.convert("1"))
def test_sanity_rgb(test_images): def test_sanity_rgb(test_images: Generator[Image.Image, None, None]) -> None:
for im in test_images: for im in test_images:
roundtrip(im.convert("RGB")) roundtrip(im.convert("RGB"))
def test_sanity_rgba(test_images): def test_sanity_rgba(test_images: Generator[Image.Image, None, None]) -> None:
for im in test_images: for im in test_images:
roundtrip(im.convert("RGBA")) roundtrip(im.convert("RGBA"))
def test_sanity_l(test_images): def test_sanity_l(test_images: Generator[Image.Image, None, None]) -> None:
for im in test_images: for im in test_images:
roundtrip(im.convert("L")) roundtrip(im.convert("L"))
def test_sanity_p(test_images): def test_sanity_p(test_images: Generator[Image.Image, None, None]) -> None:
for im in test_images: for im in test_images:
roundtrip(im.convert("P")) roundtrip(im.convert("P"))

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from PIL import Image from PIL import Image
def test_getbands(): def test_getbands() -> None:
assert Image.new("1", (1, 1)).getbands() == ("1",) assert Image.new("1", (1, 1)).getbands() == ("1",)
assert Image.new("L", (1, 1)).getbands() == ("L",) assert Image.new("L", (1, 1)).getbands() == ("L",)
assert Image.new("I", (1, 1)).getbands() == ("I",) assert Image.new("I", (1, 1)).getbands() == ("I",)

View File

@ -7,13 +7,13 @@ from PIL import Image
from .helper import hopper from .helper import hopper
def test_sanity(): def test_sanity() -> None:
bbox = hopper().getbbox() bbox = hopper().getbbox()
assert isinstance(bbox, tuple) assert isinstance(bbox, tuple)
def test_bbox(): def test_bbox() -> None:
def check(im, fill_color): def check(im: Image.Image, fill_color: int | tuple[int, ...]) -> None:
assert im.getbbox() is None assert im.getbbox() is None
im.paste(fill_color, (10, 25, 90, 75)) im.paste(fill_color, (10, 25, 90, 75))
@ -34,8 +34,8 @@ def test_bbox():
check(im, 255) check(im, 255)
for mode in ("RGBA", "RGBa"): for mode in ("RGBA", "RGBa"):
for color in ((0, 0, 0, 0), (127, 127, 127, 0), (255, 255, 255, 0)): for rgba_color in ((0, 0, 0, 0), (127, 127, 127, 0), (255, 255, 255, 0)):
im = Image.new(mode, (100, 100), color) im = Image.new(mode, (100, 100), rgba_color)
check(im, (255, 255, 255, 255)) check(im, (255, 255, 255, 255))
for mode in ("La", "LA", "PA"): for mode in ("La", "LA", "PA"):
@ -45,7 +45,7 @@ def test_bbox():
@pytest.mark.parametrize("mode", ("RGBA", "RGBa", "La", "LA", "PA")) @pytest.mark.parametrize("mode", ("RGBA", "RGBa", "La", "LA", "PA"))
def test_bbox_alpha_only_false(mode): def test_bbox_alpha_only_false(mode: str) -> None:
im = Image.new(mode, (100, 100)) im = Image.new(mode, (100, 100))
assert im.getbbox(alpha_only=False) is None assert im.getbbox(alpha_only=False) is None

View File

@ -3,8 +3,8 @@ from __future__ import annotations
from .helper import hopper from .helper import hopper
def test_getcolors(): def test_getcolors() -> None:
def getcolors(mode, limit=None): def getcolors(mode: str, limit: int | None = None) -> int | None:
im = hopper(mode) im = hopper(mode)
if limit: if limit:
colors = im.getcolors(limit) colors = im.getcolors(limit)
@ -39,7 +39,7 @@ def test_getcolors():
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def test_pack(): def test_pack() -> None:
# Pack problems for small tables (@PIL209) # Pack problems for small tables (@PIL209)
im = hopper().quantize(3).convert("RGB") im = hopper().quantize(3).convert("RGB")

View File

@ -5,7 +5,7 @@ from PIL import Image
from .helper import hopper from .helper import hopper
def test_sanity(): def test_sanity() -> None:
data = hopper().getdata() data = hopper().getdata()
len(data) len(data)
@ -14,8 +14,8 @@ def test_sanity():
assert data[0] == (20, 20, 70) assert data[0] == (20, 20, 70)
def test_roundtrip(): def test_roundtrip() -> None:
def getdata(mode): def getdata(mode: str) -> tuple[float | tuple[int, ...], int, int]:
im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST) im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST)
data = im.getdata() data = im.getdata()
return data[0], len(data), len(list(data)) return data[0], len(data), len(list(data))

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from .helper import hopper from .helper import hopper
def test_sanity(): def test_sanity() -> None:
im = hopper() im = hopper()
type_repr = repr(type(im.getim())) type_repr = repr(type(im.getim()))

View File

@ -5,7 +5,7 @@ from PIL import Image
from .helper import hopper from .helper import hopper
def test_sanity(): def test_sanity() -> None:
im = hopper() im = hopper()
projection = im.getprojection() projection = im.getprojection()

View File

@ -3,8 +3,8 @@ from __future__ import annotations
from .helper import hopper from .helper import hopper
def test_histogram(): def test_histogram() -> None:
def histogram(mode): def histogram(mode: str) -> tuple[int, int, int]:
h = hopper(mode).histogram() h = hopper(mode).histogram()
return len(h), min(h), max(h) return len(h), min(h), max(h)

View File

@ -5,7 +5,7 @@ import pytest
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
def test_sanity(): def test_sanity() -> None:
im = hopper() im = hopper()
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -39,7 +39,7 @@ def test_sanity():
im.point(lambda x: x // 2) im.point(lambda x: x // 2)
def test_16bit_lut(): def test_16bit_lut() -> None:
"""Tests for 16 bit -> 8 bit lut for converting I->L images """Tests for 16 bit -> 8 bit lut for converting I->L images
see https://github.com/python-pillow/Pillow/issues/440 see https://github.com/python-pillow/Pillow/issues/440
""" """
@ -47,7 +47,7 @@ def test_16bit_lut():
im.point(list(range(256)) * 256, "L") im.point(list(range(256)) * 256, "L")
def test_f_lut(): def test_f_lut() -> None:
"""Tests for floating point lut of 8bit gray image""" """Tests for floating point lut of 8bit gray image"""
im = hopper("L") im = hopper("L")
lut = [0.5 * float(x) for x in range(256)] lut = [0.5 * float(x) for x in range(256)]
@ -58,7 +58,7 @@ def test_f_lut():
assert_image_equal(out.convert("L"), im.point(int_lut, "L")) assert_image_equal(out.convert("L"), im.point(int_lut, "L"))
def test_f_mode(): def test_f_mode() -> None:
im = hopper("F") im = hopper("F")
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.point(None) im.point(None)

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from PIL import Image from PIL import Image
def test_interface(): def test_interface() -> None:
im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) im = Image.new("RGBA", (1, 1), (1, 2, 3, 0))
assert im.getpixel((0, 0)) == (1, 2, 3, 0) assert im.getpixel((0, 0)) == (1, 2, 3, 0)
@ -17,7 +17,7 @@ def test_interface():
assert im.getpixel((0, 0)) == (1, 2, 3, 5) assert im.getpixel((0, 0)) == (1, 2, 3, 5)
def test_promote(): def test_promote() -> None:
im = Image.new("L", (1, 1), 1) im = Image.new("L", (1, 1), 1)
assert im.getpixel((0, 0)) == 1 assert im.getpixel((0, 0)) == 1
@ -40,7 +40,7 @@ def test_promote():
assert im.getpixel((0, 0)) == (1, 2, 3, 4) assert im.getpixel((0, 0)) == (1, 2, 3, 4)
def test_readonly(): def test_readonly() -> None:
im = Image.new("RGB", (1, 1), (1, 2, 3)) im = Image.new("RGB", (1, 1), (1, 2, 3))
im.readonly = 1 im.readonly = 1

View File

@ -8,7 +8,7 @@ from PIL import Image, features
from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature
def test_sanity(): def test_sanity() -> None:
image = hopper() image = hopper()
converted = image.quantize() converted = image.quantize()
assert converted.mode == "P" assert converted.mode == "P"
@ -21,7 +21,7 @@ def test_sanity():
@skip_unless_feature("libimagequant") @skip_unless_feature("libimagequant")
def test_libimagequant_quantize(): def test_libimagequant_quantize() -> None:
image = hopper() image = hopper()
if is_ppc64le(): if is_ppc64le():
libimagequant = parse_version(features.version_feature("libimagequant")) libimagequant = parse_version(features.version_feature("libimagequant"))
@ -33,7 +33,7 @@ def test_libimagequant_quantize():
assert len(converted.getcolors()) == 100 assert len(converted.getcolors()) == 100
def test_octree_quantize(): def test_octree_quantize() -> None:
image = hopper() image = hopper()
converted = image.quantize(100, Image.Quantize.FASTOCTREE) converted = image.quantize(100, Image.Quantize.FASTOCTREE)
assert converted.mode == "P" assert converted.mode == "P"
@ -41,7 +41,7 @@ def test_octree_quantize():
assert len(converted.getcolors()) == 100 assert len(converted.getcolors()) == 100
def test_rgba_quantize(): def test_rgba_quantize() -> None:
image = hopper("RGBA") image = hopper("RGBA")
with pytest.raises(ValueError): with pytest.raises(ValueError):
image.quantize(method=0) image.quantize(method=0)
@ -49,7 +49,7 @@ def test_rgba_quantize():
assert image.quantize().convert().mode == "RGBA" assert image.quantize().convert().mode == "RGBA"
def test_quantize(): def test_quantize() -> None:
with Image.open("Tests/images/caption_6_33_22.png") as image: with Image.open("Tests/images/caption_6_33_22.png") as image:
image = image.convert("RGB") image = image.convert("RGB")
converted = image.quantize() converted = image.quantize()
@ -57,7 +57,7 @@ def test_quantize():
assert_image_similar(converted.convert("RGB"), image, 1) assert_image_similar(converted.convert("RGB"), image, 1)
def test_quantize_no_dither(): def test_quantize_no_dither() -> None:
image = hopper() image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette: with Image.open("Tests/images/caption_6_33_22.png") as palette:
palette = palette.convert("P") palette = palette.convert("P")
@ -67,7 +67,7 @@ def test_quantize_no_dither():
assert converted.palette.palette == palette.palette.palette assert converted.palette.palette == palette.palette.palette
def test_quantize_no_dither2(): def test_quantize_no_dither2() -> None:
im = Image.new("RGB", (9, 1)) im = Image.new("RGB", (9, 1))
im.putdata([(p,) * 3 for p in range(0, 36, 4)]) im.putdata([(p,) * 3 for p in range(0, 36, 4)])
@ -83,7 +83,7 @@ def test_quantize_no_dither2():
assert px[x, 0] == (0 if x < 5 else 1) assert px[x, 0] == (0 if x < 5 else 1)
def test_quantize_dither_diff(): def test_quantize_dither_diff() -> None:
image = hopper() image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette: with Image.open("Tests/images/caption_6_33_22.png") as palette:
palette = palette.convert("P") palette = palette.convert("P")
@ -94,14 +94,14 @@ def test_quantize_dither_diff():
assert dither.tobytes() != nodither.tobytes() assert dither.tobytes() != nodither.tobytes()
def test_colors(): def test_colors() -> None:
im = hopper() im = hopper()
colors = 2 colors = 2
converted = im.quantize(colors) converted = im.quantize(colors)
assert len(converted.palette.palette) == colors * len("RGB") assert len(converted.palette.palette) == colors * len("RGB")
def test_transparent_colors_equal(): def test_transparent_colors_equal() -> None:
im = Image.new("RGBA", (1, 2), (0, 0, 0, 0)) im = Image.new("RGBA", (1, 2), (0, 0, 0, 0))
px = im.load() px = im.load()
px[0, 1] = (255, 255, 255, 0) px[0, 1] = (255, 255, 255, 0)
@ -120,7 +120,7 @@ def test_transparent_colors_equal():
(Image.Quantize.FASTOCTREE, (0, 0, 0, 0)), (Image.Quantize.FASTOCTREE, (0, 0, 0, 0)),
), ),
) )
def test_palette(method, color): def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None:
im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color) im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color)
converted = im.quantize(method=method) converted = im.quantize(method=method)
@ -128,7 +128,7 @@ def test_palette(method, color):
assert converted_px[0, 0] == converted.palette.colors[color] assert converted_px[0, 0] == converted.palette.colors[color]
def test_small_palette(): def test_small_palette() -> None:
# Arrange # Arrange
im = hopper() im = hopper()

View File

@ -4,6 +4,8 @@ Tests for resize functionality.
from __future__ import annotations from __future__ import annotations
from itertools import permutations from itertools import permutations
from pathlib import Path
from typing import Generator
import pytest import pytest
@ -19,7 +21,9 @@ from .helper import (
class TestImagingCoreResize: class TestImagingCoreResize:
def resize(self, im, size, f): def resize(
self, im: Image.Image, size: tuple[int, int], f: Image.Resampling
) -> Image.Image:
# Image class independent version of resize. # Image class independent version of resize.
im.load() im.load()
return im._new(im.im.resize(size, f)) return im._new(im.im.resize(size, f))
@ -27,14 +31,14 @@ class TestImagingCoreResize:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"mode", ("1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", "I;16") "mode", ("1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", "I;16")
) )
def test_nearest_mode(self, mode): def test_nearest_mode(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
r = self.resize(im, (15, 12), Image.Resampling.NEAREST) r = self.resize(im, (15, 12), Image.Resampling.NEAREST)
assert r.mode == mode assert r.mode == mode
assert r.size == (15, 12) assert r.size == (15, 12)
assert r.im.bands == im.im.bands assert r.im.bands == im.im.bands
def test_convolution_modes(self): def test_convolution_modes(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.resize(hopper("1"), (15, 12), Image.Resampling.BILINEAR) self.resize(hopper("1"), (15, 12), Image.Resampling.BILINEAR)
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -59,7 +63,7 @@ class TestImagingCoreResize:
Image.Resampling.LANCZOS, Image.Resampling.LANCZOS,
), ),
) )
def test_reduce_filters(self, resample): def test_reduce_filters(self, resample: Image.Resampling) -> None:
r = self.resize(hopper("RGB"), (15, 12), resample) r = self.resize(hopper("RGB"), (15, 12), resample)
assert r.mode == "RGB" assert r.mode == "RGB"
assert r.size == (15, 12) assert r.size == (15, 12)
@ -75,7 +79,7 @@ class TestImagingCoreResize:
Image.Resampling.LANCZOS, Image.Resampling.LANCZOS,
), ),
) )
def test_enlarge_filters(self, resample): def test_enlarge_filters(self, resample: Image.Resampling) -> None:
r = self.resize(hopper("RGB"), (212, 195), resample) r = self.resize(hopper("RGB"), (212, 195), resample)
assert r.mode == "RGB" assert r.mode == "RGB"
assert r.size == (212, 195) assert r.size == (212, 195)
@ -99,7 +103,9 @@ class TestImagingCoreResize:
("LA", ("filled", "dirty")), ("LA", ("filled", "dirty")),
), ),
) )
def test_endianness(self, resample, mode, channels_set): def test_endianness(
self, resample: Image.Resampling, mode: str, channels_set: tuple[str, ...]
) -> None:
# Make an image with one colored pixel, in one channel. # Make an image with one colored pixel, in one channel.
# When resized, that channel should be the same as a GS image. # When resized, that channel should be the same as a GS image.
# Other channels should be unaffected. # Other channels should be unaffected.
@ -139,17 +145,17 @@ class TestImagingCoreResize:
Image.Resampling.LANCZOS, Image.Resampling.LANCZOS,
), ),
) )
def test_enlarge_zero(self, resample): def test_enlarge_zero(self, resample: Image.Resampling) -> None:
r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample) r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample)
assert r.mode == "RGB" assert r.mode == "RGB"
assert r.size == (212, 195) assert r.size == (212, 195)
assert r.getdata()[0] == (0, 0, 0) assert r.getdata()[0] == (0, 0, 0)
def test_unknown_filter(self): def test_unknown_filter(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.resize(hopper(), (10, 10), 9) self.resize(hopper(), (10, 10), 9)
def test_cross_platform(self, tmp_path): def test_cross_platform(self, tmp_path: Path) -> None:
# This test is intended for only check for consistent behaviour across # This test is intended for only check for consistent behaviour across
# platforms. So if a future Pillow change requires that the test file # platforms. So if a future Pillow change requires that the test file
# be updated, that is okay. # be updated, that is okay.
@ -162,7 +168,7 @@ class TestImagingCoreResize:
@pytest.fixture @pytest.fixture
def gradients_image(): def gradients_image() -> Generator[Image.Image, None, None]:
with Image.open("Tests/images/radial_gradients.png") as im: with Image.open("Tests/images/radial_gradients.png") as im:
im.load() im.load()
try: try:
@ -172,7 +178,7 @@ def gradients_image():
class TestReducingGapResize: class TestReducingGapResize:
def test_reducing_gap_values(self, gradients_image): def test_reducing_gap_values(self, gradients_image: Image.Image) -> None:
ref = gradients_image.resize( ref = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, reducing_gap=None (52, 34), Image.Resampling.BICUBIC, reducing_gap=None
) )
@ -191,7 +197,12 @@ class TestReducingGapResize:
"box, epsilon", "box, epsilon",
((None, 4), ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10)), ((None, 4), ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10)),
) )
def test_reducing_gap_1(self, gradients_image, box, epsilon): def test_reducing_gap_1(
self,
gradients_image: Image.Image,
box: tuple[float, float, float, float],
epsilon: float,
) -> None:
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
im = gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0 (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
@ -206,7 +217,12 @@ class TestReducingGapResize:
"box, epsilon", "box, epsilon",
((None, 1.5), ((1.1, 2.2, 510.8, 510.9), 1.5), ((3, 10, 410, 256), 1)), ((None, 1.5), ((1.1, 2.2, 510.8, 510.9), 1.5), ((3, 10, 410, 256), 1)),
) )
def test_reducing_gap_2(self, gradients_image, box, epsilon): def test_reducing_gap_2(
self,
gradients_image: Image.Image,
box: tuple[float, float, float, float],
epsilon: float,
) -> None:
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
im = gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0 (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
@ -221,7 +237,12 @@ class TestReducingGapResize:
"box, epsilon", "box, epsilon",
((None, 1), ((1.1, 2.2, 510.8, 510.9), 1), ((3, 10, 410, 256), 0.5)), ((None, 1), ((1.1, 2.2, 510.8, 510.9), 1), ((3, 10, 410, 256), 0.5)),
) )
def test_reducing_gap_3(self, gradients_image, box, epsilon): def test_reducing_gap_3(
self,
gradients_image: Image.Image,
box: tuple[float, float, float, float],
epsilon: float,
) -> None:
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
im = gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0 (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
@ -233,7 +254,9 @@ class TestReducingGapResize:
assert_image_similar(ref, im, epsilon) assert_image_similar(ref, im, epsilon)
@pytest.mark.parametrize("box", (None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256))) @pytest.mark.parametrize("box", (None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)))
def test_reducing_gap_8(self, gradients_image, box): def test_reducing_gap_8(
self, gradients_image: Image.Image, box: tuple[float, float, float, float]
) -> None:
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
im = gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0 (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0
@ -245,7 +268,12 @@ class TestReducingGapResize:
"box, epsilon", "box, epsilon",
(((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5)), (((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5)),
) )
def test_box_filter(self, gradients_image, box, epsilon): def test_box_filter(
self,
gradients_image: Image.Image,
box: tuple[float, float, float, float],
epsilon: float,
) -> None:
ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box) ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box)
im = gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0 (52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0
@ -255,8 +283,8 @@ class TestReducingGapResize:
class TestImageResize: class TestImageResize:
def test_resize(self): def test_resize(self) -> None:
def resize(mode, size): def resize(mode: str, size: tuple[int, int]) -> None:
out = hopper(mode).resize(size) out = hopper(mode).resize(size)
assert out.mode == mode assert out.mode == mode
assert out.size == size assert out.size == size
@ -271,7 +299,7 @@ class TestImageResize:
im.resize((10, 10), "unknown") im.resize((10, 10), "unknown")
@skip_unless_feature("libtiff") @skip_unless_feature("libtiff")
def test_load_first(self): def test_load_first(self) -> None:
# load() may change the size of the image # load() may change the size of the image
# Test that resize() is calling it before getting the size # Test that resize() is calling it before getting the size
with Image.open("Tests/images/g4_orientation_5.tif") as im: with Image.open("Tests/images/g4_orientation_5.tif") as im:
@ -279,13 +307,13 @@ class TestImageResize:
assert im.size == (64, 64) assert im.size == (64, 64)
@pytest.mark.parametrize("mode", ("L", "RGB", "I", "F")) @pytest.mark.parametrize("mode", ("L", "RGB", "I", "F"))
def test_default_filter_bicubic(self, mode): def test_default_filter_bicubic(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20)) assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
@pytest.mark.parametrize( @pytest.mark.parametrize(
"mode", ("1", "P", "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16") "mode", ("1", "P", "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16")
) )
def test_default_filter_nearest(self, mode): def test_default_filter_nearest(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20)) assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image, features from PIL import Image, features
@ -7,8 +9,8 @@ from PIL import Image, features
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
def test_split(): def test_split() -> None:
def split(mode): def split(mode: str) -> list[tuple[str, int, int]]:
layers = hopper(mode).split() layers = hopper(mode).split()
return [(i.mode, i.size[0], i.size[1]) for i in layers] return [(i.mode, i.size[0], i.size[1]) for i in layers]
@ -36,18 +38,18 @@ def test_split():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"mode", ("1", "L", "I", "F", "P", "RGB", "RGBA", "CMYK", "YCbCr") "mode", ("1", "L", "I", "F", "P", "RGB", "RGBA", "CMYK", "YCbCr")
) )
def test_split_merge(mode): def test_split_merge(mode: str) -> None:
expected = Image.merge(mode, hopper(mode).split()) expected = Image.merge(mode, hopper(mode).split())
assert_image_equal(hopper(mode), expected) assert_image_equal(hopper(mode), expected)
def test_split_open(tmp_path): def test_split_open(tmp_path: Path) -> None:
if features.check("zlib"): if features.check("zlib"):
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
else: else:
test_file = str(tmp_path / "temp.pcx") test_file = str(tmp_path / "temp.pcx")
def split_open(mode): def split_open(mode: str) -> int:
hopper(mode).save(test_file) hopper(mode).save(test_file)
with Image.open(test_file) as im: with Image.open(test_file) as im:
return len(im.split()) return len(im.split())

View File

@ -5,7 +5,7 @@ import pytest
from .helper import assert_image_equal, fromstring, hopper from .helper import assert_image_equal, fromstring, hopper
def test_sanity(): def test_sanity() -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
hopper().tobitmap() hopper().tobitmap()

View File

@ -3,6 +3,6 @@ from __future__ import annotations
from .helper import hopper from .helper import hopper
def test_sanity(): def test_sanity() -> None:
data = hopper().tobytes() data = hopper().tobytes()
assert isinstance(data, bytes) assert isinstance(data, bytes)

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import pytest import pytest
from PIL import Image
from PIL.Image import Transpose from PIL.Image import Transpose
from . import helper from . import helper
@ -14,7 +15,7 @@ HOPPER = {
@pytest.mark.parametrize("mode", HOPPER) @pytest.mark.parametrize("mode", HOPPER)
def test_flip_left_right(mode): def test_flip_left_right(mode: str) -> None:
im = HOPPER[mode] im = HOPPER[mode]
out = im.transpose(Transpose.FLIP_LEFT_RIGHT) out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
assert out.mode == mode assert out.mode == mode
@ -28,7 +29,7 @@ def test_flip_left_right(mode):
@pytest.mark.parametrize("mode", HOPPER) @pytest.mark.parametrize("mode", HOPPER)
def test_flip_top_bottom(mode): def test_flip_top_bottom(mode: str) -> None:
im = HOPPER[mode] im = HOPPER[mode]
out = im.transpose(Transpose.FLIP_TOP_BOTTOM) out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
assert out.mode == mode assert out.mode == mode
@ -42,7 +43,7 @@ def test_flip_top_bottom(mode):
@pytest.mark.parametrize("mode", HOPPER) @pytest.mark.parametrize("mode", HOPPER)
def test_rotate_90(mode): def test_rotate_90(mode: str) -> None:
im = HOPPER[mode] im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_90) out = im.transpose(Transpose.ROTATE_90)
assert out.mode == mode assert out.mode == mode
@ -56,7 +57,7 @@ def test_rotate_90(mode):
@pytest.mark.parametrize("mode", HOPPER) @pytest.mark.parametrize("mode", HOPPER)
def test_rotate_180(mode): def test_rotate_180(mode: str) -> None:
im = HOPPER[mode] im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_180) out = im.transpose(Transpose.ROTATE_180)
assert out.mode == mode assert out.mode == mode
@ -70,7 +71,7 @@ def test_rotate_180(mode):
@pytest.mark.parametrize("mode", HOPPER) @pytest.mark.parametrize("mode", HOPPER)
def test_rotate_270(mode): def test_rotate_270(mode: str) -> None:
im = HOPPER[mode] im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_270) out = im.transpose(Transpose.ROTATE_270)
assert out.mode == mode assert out.mode == mode
@ -84,7 +85,7 @@ def test_rotate_270(mode):
@pytest.mark.parametrize("mode", HOPPER) @pytest.mark.parametrize("mode", HOPPER)
def test_transpose(mode): def test_transpose(mode: str) -> None:
im = HOPPER[mode] im = HOPPER[mode]
out = im.transpose(Transpose.TRANSPOSE) out = im.transpose(Transpose.TRANSPOSE)
assert out.mode == mode assert out.mode == mode
@ -98,7 +99,7 @@ def test_transpose(mode):
@pytest.mark.parametrize("mode", HOPPER) @pytest.mark.parametrize("mode", HOPPER)
def test_tranverse(mode): def test_tranverse(mode: str) -> None:
im = HOPPER[mode] im = HOPPER[mode]
out = im.transpose(Transpose.TRANSVERSE) out = im.transpose(Transpose.TRANSVERSE)
assert out.mode == mode assert out.mode == mode
@ -112,10 +113,10 @@ def test_tranverse(mode):
@pytest.mark.parametrize("mode", HOPPER) @pytest.mark.parametrize("mode", HOPPER)
def test_roundtrip(mode): def test_roundtrip(mode: str) -> None:
im = HOPPER[mode] im = HOPPER[mode]
def transpose(first, second): def transpose(first: Transpose, second: Transpose) -> Image.Image:
return im.transpose(first).transpose(second) return im.transpose(first).transpose(second)
assert_image_equal( assert_image_equal(