mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 09:26:16 +03:00
Added type hints
This commit is contained in:
parent
912a33f5e9
commit
5ff7d926fd
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ def test_version() -> None:
|
||||||
# Check the correctness of the convenience function
|
# Check the correctness of the convenience function
|
||||||
# and the format of version numbers
|
# and the format of version numbers
|
||||||
|
|
||||||
def test(name, function) -> None:
|
def test(name: str, function: Callable[[str], bool]) -> None:
|
||||||
version = features.version(name)
|
version = features.version(name)
|
||||||
if not features.check(name):
|
if not features.check(name):
|
||||||
assert version is None
|
assert version is None
|
||||||
|
@ -73,12 +74,12 @@ def test_libimagequant_version() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("feature", features.modules)
|
@pytest.mark.parametrize("feature", features.modules)
|
||||||
def test_check_modules(feature) -> None:
|
def test_check_modules(feature: str) -> None:
|
||||||
assert features.check_module(feature) in [True, False]
|
assert features.check_module(feature) in [True, False]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("feature", features.codecs)
|
@pytest.mark.parametrize("feature", features.codecs)
|
||||||
def test_check_codecs(feature) -> None:
|
def test_check_codecs(feature: str) -> None:
|
||||||
assert features.check_codec(feature) in [True, False]
|
assert features.check_codec(feature) in [True, False]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ def test_save(tmp_path: Path) -> None:
|
||||||
"Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp",
|
"Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_crashes(test_file) -> None:
|
def test_crashes(test_file: str) -> None:
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
|
|
@ -16,7 +16,7 @@ from .helper import (
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(tmp_path: Path) -> None:
|
def test_sanity(tmp_path: Path) -> None:
|
||||||
def roundtrip(im) -> None:
|
def roundtrip(im: Image.Image) -> None:
|
||||||
outfile = str(tmp_path / "temp.bmp")
|
outfile = str(tmp_path / "temp.bmp")
|
||||||
|
|
||||||
im.save(outfile, "BMP")
|
im.save(outfile, "BMP")
|
||||||
|
@ -194,7 +194,7 @@ def test_rle4() -> None:
|
||||||
("Tests/images/bmp/g/pal8rle.bmp", 1064),
|
("Tests/images/bmp/g/pal8rle.bmp", 1064),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_rle8_eof(file_name, length) -> None:
|
def test_rle8_eof(file_name: str, length: int) -> None:
|
||||||
with open(file_name, "rb") as fp:
|
with open(file_name, "rb") as fp:
|
||||||
data = fp.read(length)
|
data = fp.read(length)
|
||||||
with Image.open(io.BytesIO(data)) as im:
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
|
|
@ -82,7 +82,7 @@ def test_eoferror() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
|
@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
|
||||||
def test_roundtrip(mode, tmp_path: Path) -> None:
|
def test_roundtrip(mode: str, tmp_path: Path) -> None:
|
||||||
out = str(tmp_path / "temp.im")
|
out = str(tmp_path / "temp.im")
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
im.save(out)
|
im.save(out)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from PIL import Image, ImageFile, PcxImagePlugin
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
|
||||||
def _roundtrip(tmp_path: Path, im) -> None:
|
def _roundtrip(tmp_path: Path, im: Image.Image) -> None:
|
||||||
f = str(tmp_path / "temp.pcx")
|
f = str(tmp_path / "temp.pcx")
|
||||||
im.save(f)
|
im.save(f)
|
||||||
with Image.open(f) as im2:
|
with Image.open(f) as im2:
|
||||||
|
@ -44,7 +44,7 @@ def test_invalid_file() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB"))
|
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB"))
|
||||||
def test_odd(tmp_path: Path, mode) -> None:
|
def test_odd(tmp_path: Path, mode: str) -> None:
|
||||||
# See issue #523, odd sized images should have a stride that's even.
|
# See issue #523, odd sized images should have a stride that's even.
|
||||||
# Not that ImageMagick or GIMP write PCX that way.
|
# Not that ImageMagick or GIMP write PCX that way.
|
||||||
# We were not handling properly.
|
# We were not handling properly.
|
||||||
|
@ -89,7 +89,7 @@ def test_large_count(tmp_path: Path) -> None:
|
||||||
_roundtrip(tmp_path, im)
|
_roundtrip(tmp_path, im)
|
||||||
|
|
||||||
|
|
||||||
def _test_buffer_overflow(tmp_path: Path, im, size: int = 1024) -> None:
|
def _test_buffer_overflow(tmp_path: Path, im: Image.Image, size: int = 1024) -> None:
|
||||||
_last = ImageFile.MAXBLOCK
|
_last = ImageFile.MAXBLOCK
|
||||||
ImageFile.MAXBLOCK = size
|
ImageFile.MAXBLOCK = size
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -6,6 +6,7 @@ import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ from PIL import Image, PdfParser, features
|
||||||
from .helper import hopper, mark_if_feature_version, skip_unless_feature
|
from .helper import hopper, mark_if_feature_version, skip_unless_feature
|
||||||
|
|
||||||
|
|
||||||
def helper_save_as_pdf(tmp_path: Path, mode, **kwargs):
|
def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
outfile = str(tmp_path / ("temp_" + mode + ".pdf"))
|
outfile = str(tmp_path / ("temp_" + mode + ".pdf"))
|
||||||
|
@ -41,13 +42,13 @@ def helper_save_as_pdf(tmp_path: Path, mode, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK"))
|
@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK"))
|
||||||
def test_save(tmp_path: Path, mode) -> None:
|
def test_save(tmp_path: Path, mode: str) -> None:
|
||||||
helper_save_as_pdf(tmp_path, mode)
|
helper_save_as_pdf(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("jpg_2000")
|
@skip_unless_feature("jpg_2000")
|
||||||
@pytest.mark.parametrize("mode", ("LA", "RGBA"))
|
@pytest.mark.parametrize("mode", ("LA", "RGBA"))
|
||||||
def test_save_alpha(tmp_path: Path, mode) -> None:
|
def test_save_alpha(tmp_path: Path, mode: str) -> None:
|
||||||
helper_save_as_pdf(tmp_path, mode)
|
helper_save_as_pdf(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,7 +113,7 @@ def test_resolution(tmp_path: Path) -> None:
|
||||||
{"dpi": (75, 150), "resolution": 200},
|
{"dpi": (75, 150), "resolution": 200},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_dpi(params, tmp_path: Path) -> None:
|
def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
||||||
outfile = str(tmp_path / "temp.pdf")
|
outfile = str(tmp_path / "temp.pdf")
|
||||||
|
@ -156,7 +157,7 @@ def test_save_all(tmp_path: Path) -> None:
|
||||||
assert os.path.getsize(outfile) > 0
|
assert os.path.getsize(outfile) > 0
|
||||||
|
|
||||||
# Test appending using a generator
|
# Test 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(outfile, save_all=True, append_images=im_generator(ims))
|
im.save(outfile, save_all=True, append_images=im_generator(ims))
|
||||||
|
@ -226,7 +227,7 @@ def test_pdf_append_fails_on_nonexistent_file() -> None:
|
||||||
im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True)
|
im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True)
|
||||||
|
|
||||||
|
|
||||||
def check_pdf_pages_consistency(pdf) -> None:
|
def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None:
|
||||||
pages_info = pdf.read_indirect(pdf.pages_ref)
|
pages_info = pdf.read_indirect(pdf.pages_ref)
|
||||||
assert b"Parent" not in pages_info
|
assert b"Parent" not in pages_info
|
||||||
assert b"Kids" in pages_info
|
assert b"Kids" in pages_info
|
||||||
|
@ -339,7 +340,7 @@ def test_pdf_append_to_bytesio() -> None:
|
||||||
@pytest.mark.timeout(1)
|
@pytest.mark.timeout(1)
|
||||||
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
|
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
|
||||||
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
|
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
|
||||||
def test_redos(newline) -> None:
|
def test_redos(newline: bytes) -> None:
|
||||||
malicious = b" trailer<<>>" + newline * 3456
|
malicious = b" trailer<<>>" + newline * 3456
|
||||||
|
|
||||||
# This particular exception isn't relevant here.
|
# This particular exception isn't relevant here.
|
||||||
|
|
|
@ -4,6 +4,8 @@ import os
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -20,6 +22,7 @@ from .helper import (
|
||||||
is_win32,
|
is_win32,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ElementTree: ModuleType | None
|
||||||
try:
|
try:
|
||||||
from defusedxml import ElementTree
|
from defusedxml import ElementTree
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -156,7 +159,7 @@ class TestFileTiff:
|
||||||
"resolution_unit, dpi",
|
"resolution_unit, dpi",
|
||||||
[(None, 72.8), (2, 72.8), (3, 184.912)],
|
[(None, 72.8), (2, 72.8), (3, 184.912)],
|
||||||
)
|
)
|
||||||
def test_load_float_dpi(self, resolution_unit, dpi) -> None:
|
def test_load_float_dpi(self, resolution_unit: int | None, dpi: float) -> None:
|
||||||
with Image.open(
|
with Image.open(
|
||||||
"Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
|
"Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
|
||||||
) as im:
|
) as im:
|
||||||
|
@ -284,7 +287,7 @@ class TestFileTiff:
|
||||||
("Tests/images/multipage.tiff", 3),
|
("Tests/images/multipage.tiff", 3),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_n_frames(self, path, n_frames) -> None:
|
def test_n_frames(self, path: str, n_frames: int) -> None:
|
||||||
with Image.open(path) as im:
|
with Image.open(path) as im:
|
||||||
assert im.n_frames == n_frames
|
assert im.n_frames == n_frames
|
||||||
assert im.is_animated == (n_frames != 1)
|
assert im.is_animated == (n_frames != 1)
|
||||||
|
@ -402,7 +405,7 @@ class TestFileTiff:
|
||||||
assert len_before == len_after + 1
|
assert len_before == len_after + 1
|
||||||
|
|
||||||
@pytest.mark.parametrize("legacy_api", (False, True))
|
@pytest.mark.parametrize("legacy_api", (False, True))
|
||||||
def test_load_byte(self, legacy_api) -> None:
|
def test_load_byte(self, legacy_api: bool) -> None:
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
data = b"abc"
|
data = b"abc"
|
||||||
ret = ifd.load_byte(data, legacy_api)
|
ret = ifd.load_byte(data, legacy_api)
|
||||||
|
@ -431,7 +434,7 @@ class TestFileTiff:
|
||||||
assert 0x8825 in im.tag_v2
|
assert 0x8825 in im.tag_v2
|
||||||
|
|
||||||
def test_exif(self, tmp_path: Path) -> None:
|
def test_exif(self, tmp_path: Path) -> None:
|
||||||
def check_exif(exif) -> None:
|
def check_exif(exif: Image.Exif) -> None:
|
||||||
assert sorted(exif.keys()) == [
|
assert sorted(exif.keys()) == [
|
||||||
256,
|
256,
|
||||||
257,
|
257,
|
||||||
|
@ -511,7 +514,7 @@ class TestFileTiff:
|
||||||
assert im.getexif()[273] == (1408, 1907)
|
assert im.getexif()[273] == (1408, 1907)
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("1", "L"))
|
@pytest.mark.parametrize("mode", ("1", "L"))
|
||||||
def test_photometric(self, mode, tmp_path: Path) -> None:
|
def test_photometric(self, mode: str, tmp_path: Path) -> None:
|
||||||
filename = str(tmp_path / "temp.tif")
|
filename = str(tmp_path / "temp.tif")
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
im.save(filename, tiffinfo={262: 0})
|
im.save(filename, tiffinfo={262: 0})
|
||||||
|
@ -660,7 +663,7 @@ class TestFileTiff:
|
||||||
assert_image_equal_tofile(reloaded, infile)
|
assert_image_equal_tofile(reloaded, infile)
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
def test_palette(self, mode, tmp_path: Path) -> None:
|
def test_palette(self, mode: str, tmp_path: Path) -> None:
|
||||||
outfile = str(tmp_path / "temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
|
@ -689,7 +692,7 @@ class TestFileTiff:
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
# Test appending using a generator
|
# Test 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
|
||||||
|
|
||||||
mp = BytesIO()
|
mp = BytesIO()
|
||||||
|
@ -860,7 +863,7 @@ class TestFileTiff:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.timeout(2)
|
@pytest.mark.timeout(2)
|
||||||
def test_oom(self, test_file) -> None:
|
def test_oom(self, test_file: str) -> None:
|
||||||
with pytest.raises(UnidentifiedImageError):
|
with pytest.raises(UnidentifiedImageError):
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(UserWarning):
|
||||||
with Image.open(test_file):
|
with Image.open(test_file):
|
||||||
|
|
|
@ -2,21 +2,22 @@ from __future__ import annotations
|
||||||
|
|
||||||
import colorsys
|
import colorsys
|
||||||
import itertools
|
import itertools
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import assert_image_similar, hopper
|
from .helper import assert_image_similar, hopper
|
||||||
|
|
||||||
|
|
||||||
def int_to_float(i):
|
def int_to_float(i: int) -> float:
|
||||||
return i / 255
|
return i / 255
|
||||||
|
|
||||||
|
|
||||||
def str_to_float(i):
|
def str_to_float(i: str) -> float:
|
||||||
return ord(i) / 255
|
return ord(i) / 255
|
||||||
|
|
||||||
|
|
||||||
def tuple_to_ints(tp):
|
def tuple_to_ints(tp: tuple[float, float, float]) -> tuple[int, int, int]:
|
||||||
x, y, z = tp
|
x, y, z = tp
|
||||||
return int(x * 255.0), int(y * 255.0), int(z * 255.0)
|
return int(x * 255.0), int(y * 255.0), int(z * 255.0)
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ def test_sanity() -> None:
|
||||||
Image.new("HSV", (100, 100))
|
Image.new("HSV", (100, 100))
|
||||||
|
|
||||||
|
|
||||||
def wedge():
|
def wedge() -> Image.Image:
|
||||||
w = Image._wedge()
|
w = Image._wedge()
|
||||||
w90 = w.rotate(90)
|
w90 = w.rotate(90)
|
||||||
|
|
||||||
|
@ -49,7 +50,11 @@ def wedge():
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
def to_xxx_colorsys(im, func, mode):
|
def to_xxx_colorsys(
|
||||||
|
im: Image.Image,
|
||||||
|
func: Callable[[float, float, float], tuple[float, float, float]],
|
||||||
|
mode: str,
|
||||||
|
) -> Image.Image:
|
||||||
# convert the hard way using the library colorsys routines.
|
# convert the hard way using the library colorsys routines.
|
||||||
|
|
||||||
(r, g, b) = im.split()
|
(r, g, b) = im.split()
|
||||||
|
@ -70,11 +75,11 @@ def to_xxx_colorsys(im, func, mode):
|
||||||
return hsv
|
return hsv
|
||||||
|
|
||||||
|
|
||||||
def to_hsv_colorsys(im):
|
def to_hsv_colorsys(im: Image.Image) -> Image.Image:
|
||||||
return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV")
|
return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV")
|
||||||
|
|
||||||
|
|
||||||
def to_rgb_colorsys(im):
|
def to_rgb_colorsys(im: Image.Image) -> Image.Image:
|
||||||
return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
|
return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
from PIL import Image, ImageChops
|
from PIL import Image, ImageChops
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
@ -387,7 +389,9 @@ def test_overlay() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_logical() -> None:
|
def test_logical() -> None:
|
||||||
def table(op, a, b):
|
def table(
|
||||||
|
op: Callable[[Image.Image, Image.Image], Image.Image], a: int, b: int
|
||||||
|
) -> tuple[int, int, int, int]:
|
||||||
out = []
|
out = []
|
||||||
for x in (a, b):
|
for x in (a, b):
|
||||||
imx = Image.new("1", (1, 1), x)
|
imx = Image.new("1", (1, 1), x)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import os.path
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageDraw2, features
|
from PIL import Image, ImageDraw, ImageDraw2, features
|
||||||
|
from PIL._typing import Coords
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -56,7 +57,7 @@ def test_sanity() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bbox", BBOX)
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
def test_ellipse(bbox) -> None:
|
def test_ellipse(bbox: Coords) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
@ -84,7 +85,7 @@ def test_ellipse_edge() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", POINTS)
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_line(points) -> None:
|
def test_line(points: Coords) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
@ -98,7 +99,7 @@ def test_line(points) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", POINTS)
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_line_pen_as_brush(points) -> None:
|
def test_line_pen_as_brush(points: Coords) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
@ -114,7 +115,7 @@ def test_line_pen_as_brush(points) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", POINTS)
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_polygon(points) -> None:
|
def test_polygon(points: Coords) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
@ -129,7 +130,7 @@ def test_polygon(points) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bbox", BBOX)
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
def test_rectangle(bbox) -> None:
|
def test_rectangle(bbox: Coords) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
|
|
@ -22,7 +22,7 @@ def test_crash() -> None:
|
||||||
ImageEnhance.Sharpness(im).enhance(0.5)
|
ImageEnhance.Sharpness(im).enhance(0.5)
|
||||||
|
|
||||||
|
|
||||||
def _half_transparent_image():
|
def _half_transparent_image() -> Image.Image:
|
||||||
# returns an image, half transparent, half solid
|
# returns an image, half transparent, half solid
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
|
|
||||||
|
@ -34,7 +34,9 @@ def _half_transparent_image():
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
def _check_alpha(im, original, op, amount) -> None:
|
def _check_alpha(
|
||||||
|
im: Image.Image, original: Image.Image, op: str, amount: float
|
||||||
|
) -> None:
|
||||||
assert im.getbands() == original.getbands()
|
assert im.getbands() == original.getbands()
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
im.getchannel("A"),
|
im.getchannel("A"),
|
||||||
|
@ -44,7 +46,7 @@ def _check_alpha(im, original, op, amount) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("op", ("Color", "Brightness", "Contrast", "Sharpness"))
|
@pytest.mark.parametrize("op", ("Color", "Brightness", "Contrast", "Sharpness"))
|
||||||
def test_alpha(op) -> None:
|
def test_alpha(op: str) -> None:
|
||||||
# Issue https://github.com/python-pillow/Pillow/issues/899
|
# Issue https://github.com/python-pillow/Pillow/issues/899
|
||||||
# Is alpha preserved through image enhancement?
|
# Is alpha preserved through image enhancement?
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ SAFEBLOCK = ImageFile.SAFEBLOCK
|
||||||
|
|
||||||
class TestImageFile:
|
class TestImageFile:
|
||||||
def test_parser(self) -> None:
|
def test_parser(self) -> None:
|
||||||
def roundtrip(format):
|
def roundtrip(format: str) -> tuple[Image.Image, Image.Image]:
|
||||||
im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST)
|
im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST)
|
||||||
if format in ("MSP", "XBM"):
|
if format in ("MSP", "XBM"):
|
||||||
im = im.convert("1")
|
im = im.convert("1")
|
||||||
|
|
|
@ -7,11 +7,13 @@ import shutil
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import BinaryIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, features
|
from PIL import Image, ImageDraw, ImageFont, features
|
||||||
|
from PIL._typing import StrOrBytesPath
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -47,11 +49,11 @@ def layout_engine(request):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def font(layout_engine):
|
def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont:
|
||||||
return ImageFont.truetype(FONT_PATH, FONT_SIZE, layout_engine=layout_engine)
|
return ImageFont.truetype(FONT_PATH, FONT_SIZE, layout_engine=layout_engine)
|
||||||
|
|
||||||
|
|
||||||
def test_font_properties(font) -> None:
|
def test_font_properties(font: ImageFont.FreeTypeFont) -> None:
|
||||||
assert font.path == FONT_PATH
|
assert font.path == FONT_PATH
|
||||||
assert font.size == FONT_SIZE
|
assert font.size == FONT_SIZE
|
||||||
|
|
||||||
|
@ -67,7 +69,9 @@ def test_font_properties(font) -> None:
|
||||||
assert font_copy.path == second_font_path
|
assert font_copy.path == second_font_path
|
||||||
|
|
||||||
|
|
||||||
def _render(font, layout_engine):
|
def _render(
|
||||||
|
font: StrOrBytesPath | BinaryIO, layout_engine: ImageFont.Layout
|
||||||
|
) -> Image.Image:
|
||||||
txt = "Hello World!"
|
txt = "Hello World!"
|
||||||
ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=layout_engine)
|
ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=layout_engine)
|
||||||
ttf.getbbox(txt)
|
ttf.getbbox(txt)
|
||||||
|
@ -80,12 +84,12 @@ def _render(font, layout_engine):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("font", (FONT_PATH, Path(FONT_PATH)))
|
@pytest.mark.parametrize("font", (FONT_PATH, Path(FONT_PATH)))
|
||||||
def test_font_with_name(layout_engine, font) -> None:
|
def test_font_with_name(layout_engine: ImageFont.Layout, font: str | Path) -> None:
|
||||||
_render(font, layout_engine)
|
_render(font, layout_engine)
|
||||||
|
|
||||||
|
|
||||||
def test_font_with_filelike(layout_engine) -> None:
|
def test_font_with_filelike(layout_engine: ImageFont.Layout) -> None:
|
||||||
def _font_as_bytes():
|
def _font_as_bytes() -> BytesIO:
|
||||||
with open(FONT_PATH, "rb") as f:
|
with open(FONT_PATH, "rb") as f:
|
||||||
font_bytes = BytesIO(f.read())
|
font_bytes = BytesIO(f.read())
|
||||||
return font_bytes
|
return font_bytes
|
||||||
|
@ -102,12 +106,12 @@ def test_font_with_filelike(layout_engine) -> None:
|
||||||
# _render(shared_bytes)
|
# _render(shared_bytes)
|
||||||
|
|
||||||
|
|
||||||
def test_font_with_open_file(layout_engine) -> None:
|
def test_font_with_open_file(layout_engine: ImageFont.Layout) -> None:
|
||||||
with open(FONT_PATH, "rb") as f:
|
with open(FONT_PATH, "rb") as f:
|
||||||
_render(f, layout_engine)
|
_render(f, layout_engine)
|
||||||
|
|
||||||
|
|
||||||
def test_render_equal(layout_engine) -> None:
|
def test_render_equal(layout_engine: ImageFont.Layout) -> None:
|
||||||
img_path = _render(FONT_PATH, layout_engine)
|
img_path = _render(FONT_PATH, layout_engine)
|
||||||
with open(FONT_PATH, "rb") as f:
|
with open(FONT_PATH, "rb") as f:
|
||||||
font_filelike = BytesIO(f.read())
|
font_filelike = BytesIO(f.read())
|
||||||
|
@ -116,7 +120,7 @@ def test_render_equal(layout_engine) -> None:
|
||||||
assert_image_equal(img_path, img_filelike)
|
assert_image_equal(img_path, img_filelike)
|
||||||
|
|
||||||
|
|
||||||
def test_non_ascii_path(tmp_path: Path, layout_engine) -> None:
|
def test_non_ascii_path(tmp_path: Path, layout_engine: ImageFont.Layout) -> None:
|
||||||
tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf"))
|
tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf"))
|
||||||
try:
|
try:
|
||||||
shutil.copy(FONT_PATH, tempfile)
|
shutil.copy(FONT_PATH, tempfile)
|
||||||
|
@ -126,7 +130,7 @@ def test_non_ascii_path(tmp_path: Path, layout_engine) -> None:
|
||||||
ImageFont.truetype(tempfile, FONT_SIZE, layout_engine=layout_engine)
|
ImageFont.truetype(tempfile, FONT_SIZE, layout_engine=layout_engine)
|
||||||
|
|
||||||
|
|
||||||
def test_transparent_background(font) -> None:
|
def test_transparent_background(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new(mode="RGBA", size=(300, 100))
|
im = Image.new(mode="RGBA", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
@ -140,7 +144,7 @@ def test_transparent_background(font) -> None:
|
||||||
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||||
|
|
||||||
|
|
||||||
def test_I16(font) -> None:
|
def test_I16(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new(mode="I;16", size=(300, 100))
|
im = Image.new(mode="I;16", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
@ -153,7 +157,7 @@ def test_I16(font) -> None:
|
||||||
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||||
|
|
||||||
|
|
||||||
def test_textbbox_equal(font) -> None:
|
def test_textbbox_equal(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
@ -181,7 +185,13 @@ def test_textbbox_equal(font) -> None:
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_getlength(
|
def test_getlength(
|
||||||
text, mode, fontname, size, layout_engine, length_basic, length_raqm
|
text: str,
|
||||||
|
mode: str,
|
||||||
|
fontname: str,
|
||||||
|
size: int,
|
||||||
|
layout_engine: ImageFont.Layout,
|
||||||
|
length_basic: int,
|
||||||
|
length_raqm: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
f = ImageFont.truetype("Tests/fonts/" + fontname, size, layout_engine=layout_engine)
|
f = ImageFont.truetype("Tests/fonts/" + fontname, size, layout_engine=layout_engine)
|
||||||
|
|
||||||
|
@ -207,7 +217,7 @@ def test_float_size() -> None:
|
||||||
assert lengths[0] != lengths[1] != lengths[2]
|
assert lengths[0] != lengths[1] != lengths[2]
|
||||||
|
|
||||||
|
|
||||||
def test_render_multiline(font) -> None:
|
def test_render_multiline(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
line_spacing = font.getbbox("A")[3] + 4
|
line_spacing = font.getbbox("A")[3] + 4
|
||||||
|
@ -223,7 +233,7 @@ def test_render_multiline(font) -> None:
|
||||||
assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2)
|
assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2)
|
||||||
|
|
||||||
|
|
||||||
def test_render_multiline_text(font) -> None:
|
def test_render_multiline_text(font: ImageFont.FreeTypeFont) -> None:
|
||||||
# Test that text() correctly connects to multiline_text()
|
# Test that text() correctly connects to multiline_text()
|
||||||
# and that align defaults to left
|
# and that align defaults to left
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
|
@ -243,7 +253,9 @@ def test_render_multiline_text(font) -> None:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"align, ext", (("left", ""), ("center", "_center"), ("right", "_right"))
|
"align, ext", (("left", ""), ("center", "_center"), ("right", "_right"))
|
||||||
)
|
)
|
||||||
def test_render_multiline_text_align(font, align, ext) -> None:
|
def test_render_multiline_text_align(
|
||||||
|
font: ImageFont.FreeTypeFont, align: str, ext: str
|
||||||
|
) -> None:
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.multiline_text((0, 0), TEST_TEXT, font=font, align=align)
|
draw.multiline_text((0, 0), TEST_TEXT, font=font, align=align)
|
||||||
|
@ -251,7 +263,7 @@ def test_render_multiline_text_align(font, align, ext) -> None:
|
||||||
assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01)
|
assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01)
|
||||||
|
|
||||||
|
|
||||||
def test_unknown_align(font) -> None:
|
def test_unknown_align(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
@ -260,14 +272,14 @@ def test_unknown_align(font) -> None:
|
||||||
draw.multiline_text((0, 0), TEST_TEXT, font=font, align="unknown")
|
draw.multiline_text((0, 0), TEST_TEXT, font=font, align="unknown")
|
||||||
|
|
||||||
|
|
||||||
def test_draw_align(font) -> None:
|
def test_draw_align(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new("RGB", (300, 100), "white")
|
im = Image.new("RGB", (300, 100), "white")
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
line = "some text"
|
line = "some text"
|
||||||
draw.text((100, 40), line, (0, 0, 0), font=font, align="left")
|
draw.text((100, 40), line, (0, 0, 0), font=font, align="left")
|
||||||
|
|
||||||
|
|
||||||
def test_multiline_bbox(font) -> None:
|
def test_multiline_bbox(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
@ -285,7 +297,7 @@ def test_multiline_bbox(font) -> None:
|
||||||
draw.textbbox((0, 0), TEST_TEXT, font=font, spacing=4)
|
draw.textbbox((0, 0), TEST_TEXT, font=font, spacing=4)
|
||||||
|
|
||||||
|
|
||||||
def test_multiline_width(font) -> None:
|
def test_multiline_width(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
@ -295,7 +307,7 @@ def test_multiline_width(font) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_multiline_spacing(font) -> None:
|
def test_multiline_spacing(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.multiline_text((0, 0), TEST_TEXT, font=font, spacing=10)
|
draw.multiline_text((0, 0), TEST_TEXT, font=font, spacing=10)
|
||||||
|
@ -306,7 +318,9 @@ def test_multiline_spacing(font) -> None:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270)
|
"orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270)
|
||||||
)
|
)
|
||||||
def test_rotated_transposed_font(font, orientation) -> None:
|
def test_rotated_transposed_font(
|
||||||
|
font: ImageFont.FreeTypeFont, orientation: Image.Transpose
|
||||||
|
) -> None:
|
||||||
img_gray = Image.new("L", (100, 100))
|
img_gray = Image.new("L", (100, 100))
|
||||||
draw = ImageDraw.Draw(img_gray)
|
draw = ImageDraw.Draw(img_gray)
|
||||||
word = "testing"
|
word = "testing"
|
||||||
|
@ -347,7 +361,9 @@ def test_rotated_transposed_font(font, orientation) -> None:
|
||||||
Image.Transpose.FLIP_TOP_BOTTOM,
|
Image.Transpose.FLIP_TOP_BOTTOM,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_unrotated_transposed_font(font, orientation) -> None:
|
def test_unrotated_transposed_font(
|
||||||
|
font: ImageFont.FreeTypeFont, orientation: Image.Transpose
|
||||||
|
) -> None:
|
||||||
img_gray = Image.new("L", (100, 100))
|
img_gray = Image.new("L", (100, 100))
|
||||||
draw = ImageDraw.Draw(img_gray)
|
draw = ImageDraw.Draw(img_gray)
|
||||||
word = "testing"
|
word = "testing"
|
||||||
|
@ -382,7 +398,9 @@ def test_unrotated_transposed_font(font, orientation) -> None:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270)
|
"orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270)
|
||||||
)
|
)
|
||||||
def test_rotated_transposed_font_get_mask(font, orientation) -> None:
|
def test_rotated_transposed_font_get_mask(
|
||||||
|
font: ImageFont.FreeTypeFont, orientation: Image.Transpose
|
||||||
|
) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
text = "mask this"
|
text = "mask this"
|
||||||
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
||||||
|
@ -403,7 +421,9 @@ def test_rotated_transposed_font_get_mask(font, orientation) -> None:
|
||||||
Image.Transpose.FLIP_TOP_BOTTOM,
|
Image.Transpose.FLIP_TOP_BOTTOM,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_unrotated_transposed_font_get_mask(font, orientation) -> None:
|
def test_unrotated_transposed_font_get_mask(
|
||||||
|
font: ImageFont.FreeTypeFont, orientation: Image.Transpose
|
||||||
|
) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
text = "mask this"
|
text = "mask this"
|
||||||
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
||||||
|
@ -415,11 +435,11 @@ def test_unrotated_transposed_font_get_mask(font, orientation) -> None:
|
||||||
assert mask.size == (108, 13)
|
assert mask.size == (108, 13)
|
||||||
|
|
||||||
|
|
||||||
def test_free_type_font_get_name(font) -> None:
|
def test_free_type_font_get_name(font: ImageFont.FreeTypeFont) -> None:
|
||||||
assert ("FreeMono", "Regular") == font.getname()
|
assert ("FreeMono", "Regular") == font.getname()
|
||||||
|
|
||||||
|
|
||||||
def test_free_type_font_get_metrics(font) -> None:
|
def test_free_type_font_get_metrics(font: ImageFont.FreeTypeFont) -> None:
|
||||||
ascent, descent = font.getmetrics()
|
ascent, descent = font.getmetrics()
|
||||||
|
|
||||||
assert isinstance(ascent, int)
|
assert isinstance(ascent, int)
|
||||||
|
@ -427,7 +447,7 @@ def test_free_type_font_get_metrics(font) -> None:
|
||||||
assert (ascent, descent) == (16, 4)
|
assert (ascent, descent) == (16, 4)
|
||||||
|
|
||||||
|
|
||||||
def test_free_type_font_get_mask(font) -> None:
|
def test_free_type_font_get_mask(font: ImageFont.FreeTypeFont) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
text = "mask this"
|
text = "mask this"
|
||||||
|
|
||||||
|
@ -473,16 +493,16 @@ def test_default_font() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
||||||
def test_getbbox(font, mode) -> None:
|
def test_getbbox(font: ImageFont.FreeTypeFont, mode: str | None) -> None:
|
||||||
assert (0, 4, 12, 16) == font.getbbox("A", mode)
|
assert (0, 4, 12, 16) == font.getbbox("A", mode)
|
||||||
|
|
||||||
|
|
||||||
def test_getbbox_empty(font) -> None:
|
def test_getbbox_empty(font: ImageFont.FreeTypeFont) -> None:
|
||||||
# issue #2614, should not crash.
|
# issue #2614, should not crash.
|
||||||
assert (0, 0, 0, 0) == font.getbbox("")
|
assert (0, 0, 0, 0) == font.getbbox("")
|
||||||
|
|
||||||
|
|
||||||
def test_render_empty(font) -> None:
|
def test_render_empty(font: ImageFont.FreeTypeFont) -> None:
|
||||||
# issue 2666
|
# issue 2666
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
target = im.copy()
|
target = im.copy()
|
||||||
|
@ -492,7 +512,7 @@ def test_render_empty(font) -> None:
|
||||||
assert_image_equal(im, target)
|
assert_image_equal(im, target)
|
||||||
|
|
||||||
|
|
||||||
def test_unicode_extended(layout_engine) -> None:
|
def test_unicode_extended(layout_engine: ImageFont.Layout) -> None:
|
||||||
# issue #3777
|
# issue #3777
|
||||||
text = "A\u278A\U0001F12B"
|
text = "A\u278A\U0001F12B"
|
||||||
target = "Tests/images/unicode_extended.png"
|
target = "Tests/images/unicode_extended.png"
|
||||||
|
@ -516,7 +536,7 @@ def test_unicode_extended(layout_engine) -> None:
|
||||||
)
|
)
|
||||||
@pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
@pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||||
def test_find_font(monkeypatch, platform, font_directory) -> None:
|
def test_find_font(monkeypatch, platform, font_directory) -> None:
|
||||||
def _test_fake_loading_font(path_to_fake, fontname) -> None:
|
def _test_fake_loading_font(path_to_fake: str, fontname: str) -> None:
|
||||||
# Make a copy of FreeTypeFont so we can patch the original
|
# Make a copy of FreeTypeFont so we can patch the original
|
||||||
free_type_font = copy.deepcopy(ImageFont.FreeTypeFont)
|
free_type_font = copy.deepcopy(ImageFont.FreeTypeFont)
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
|
@ -567,7 +587,7 @@ def test_find_font(monkeypatch, platform, font_directory) -> None:
|
||||||
_test_fake_loading_font(font_directory + "/Duplicate.ttf", "Duplicate")
|
_test_fake_loading_font(font_directory + "/Duplicate.ttf", "Duplicate")
|
||||||
|
|
||||||
|
|
||||||
def test_imagefont_getters(font) -> None:
|
def test_imagefont_getters(font: ImageFont.FreeTypeFont) -> None:
|
||||||
assert font.getmetrics() == (16, 4)
|
assert font.getmetrics() == (16, 4)
|
||||||
assert font.font.ascent == 16
|
assert font.font.ascent == 16
|
||||||
assert font.font.descent == 4
|
assert font.font.descent == 4
|
||||||
|
@ -588,7 +608,7 @@ def test_imagefont_getters(font) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("stroke_width", (0, 2))
|
@pytest.mark.parametrize("stroke_width", (0, 2))
|
||||||
def test_getsize_stroke(font, stroke_width) -> None:
|
def test_getsize_stroke(font: ImageFont.FreeTypeFont, stroke_width: int) -> None:
|
||||||
assert font.getbbox("A", stroke_width=stroke_width) == (
|
assert font.getbbox("A", stroke_width=stroke_width) == (
|
||||||
0 - stroke_width,
|
0 - stroke_width,
|
||||||
4 - stroke_width,
|
4 - stroke_width,
|
||||||
|
@ -607,7 +627,7 @@ def test_complex_font_settings() -> None:
|
||||||
t.getmask("абвг", language="sr")
|
t.getmask("абвг", language="sr")
|
||||||
|
|
||||||
|
|
||||||
def test_variation_get(font) -> None:
|
def test_variation_get(font: ImageFont.FreeTypeFont) -> None:
|
||||||
freetype = parse_version(features.version_module("freetype2"))
|
freetype = parse_version(features.version_module("freetype2"))
|
||||||
if freetype < parse_version("2.9.1"):
|
if freetype < parse_version("2.9.1"):
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
|
@ -662,7 +682,7 @@ def test_variation_get(font) -> None:
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _check_text(font, path, epsilon):
|
def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> None:
|
||||||
im = Image.new("RGB", (100, 75), "white")
|
im = Image.new("RGB", (100, 75), "white")
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
d.text((10, 10), "Text", font=font, fill="black")
|
d.text((10, 10), "Text", font=font, fill="black")
|
||||||
|
@ -677,7 +697,7 @@ def _check_text(font, path, epsilon):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def test_variation_set_by_name(font) -> None:
|
def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
|
||||||
freetype = parse_version(features.version_module("freetype2"))
|
freetype = parse_version(features.version_module("freetype2"))
|
||||||
if freetype < parse_version("2.9.1"):
|
if freetype < parse_version("2.9.1"):
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
|
@ -702,7 +722,7 @@ def test_variation_set_by_name(font) -> None:
|
||||||
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
|
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
|
||||||
|
|
||||||
|
|
||||||
def test_variation_set_by_axes(font) -> None:
|
def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None:
|
||||||
freetype = parse_version(features.version_module("freetype2"))
|
freetype = parse_version(features.version_module("freetype2"))
|
||||||
if freetype < parse_version("2.9.1"):
|
if freetype < parse_version("2.9.1"):
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
|
@ -737,7 +757,9 @@ def test_variation_set_by_axes(font) -> None:
|
||||||
),
|
),
|
||||||
ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"),
|
ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"),
|
||||||
)
|
)
|
||||||
def test_anchor(layout_engine, anchor, left, top) -> None:
|
def test_anchor(
|
||||||
|
layout_engine: ImageFont.Layout, anchor: str, left: int, top: int
|
||||||
|
) -> None:
|
||||||
name, text = "quick", "Quick"
|
name, text = "quick", "Quick"
|
||||||
path = f"Tests/images/test_anchor_{name}_{anchor}.png"
|
path = f"Tests/images/test_anchor_{name}_{anchor}.png"
|
||||||
|
|
||||||
|
@ -782,7 +804,9 @@ def test_anchor(layout_engine, anchor, left, top) -> None:
|
||||||
("md", "center"),
|
("md", "center"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_anchor_multiline(layout_engine, anchor, align) -> None:
|
def test_anchor_multiline(
|
||||||
|
layout_engine: ImageFont.Layout, anchor: str, align: str
|
||||||
|
) -> None:
|
||||||
target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png"
|
target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png"
|
||||||
text = "a\nlong\ntext sample"
|
text = "a\nlong\ntext sample"
|
||||||
|
|
||||||
|
@ -800,7 +824,7 @@ def test_anchor_multiline(layout_engine, anchor, align) -> None:
|
||||||
assert_image_similar_tofile(im, target, 4)
|
assert_image_similar_tofile(im, target, 4)
|
||||||
|
|
||||||
|
|
||||||
def test_anchor_invalid(font) -> None:
|
def test_anchor_invalid(font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new("RGB", (100, 100), "white")
|
im = Image.new("RGB", (100, 100), "white")
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
d.font = font
|
d.font = font
|
||||||
|
@ -826,7 +850,7 @@ def test_anchor_invalid(font) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
|
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
|
||||||
def test_bitmap_font(layout_engine, bpp) -> None:
|
def test_bitmap_font(layout_engine: ImageFont.Layout, bpp: int) -> None:
|
||||||
text = "Bitmap Font"
|
text = "Bitmap Font"
|
||||||
layout_name = ["basic", "raqm"][layout_engine]
|
layout_name = ["basic", "raqm"][layout_engine]
|
||||||
target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png"
|
target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png"
|
||||||
|
@ -843,7 +867,7 @@ def test_bitmap_font(layout_engine, bpp) -> None:
|
||||||
assert_image_equal_tofile(im, target)
|
assert_image_equal_tofile(im, target)
|
||||||
|
|
||||||
|
|
||||||
def test_bitmap_font_stroke(layout_engine) -> None:
|
def test_bitmap_font_stroke(layout_engine: ImageFont.Layout) -> None:
|
||||||
text = "Bitmap Font"
|
text = "Bitmap Font"
|
||||||
layout_name = ["basic", "raqm"][layout_engine]
|
layout_name = ["basic", "raqm"][layout_engine]
|
||||||
target = f"Tests/images/bitmap_font_stroke_{layout_name}.png"
|
target = f"Tests/images/bitmap_font_stroke_{layout_name}.png"
|
||||||
|
@ -861,7 +885,7 @@ def test_bitmap_font_stroke(layout_engine) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("embedded_color", (False, True))
|
@pytest.mark.parametrize("embedded_color", (False, True))
|
||||||
def test_bitmap_blend(layout_engine, embedded_color) -> None:
|
def test_bitmap_blend(layout_engine: ImageFont.Layout, embedded_color: bool) -> None:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
"Tests/fonts/EBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
"Tests/fonts/EBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
||||||
)
|
)
|
||||||
|
@ -873,7 +897,7 @@ def test_bitmap_blend(layout_engine, embedded_color) -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/bitmap_font_blend.png")
|
assert_image_equal_tofile(im, "Tests/images/bitmap_font_blend.png")
|
||||||
|
|
||||||
|
|
||||||
def test_standard_embedded_color(layout_engine) -> None:
|
def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None:
|
||||||
txt = "Hello World!"
|
txt = "Hello World!"
|
||||||
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
||||||
ttf.getbbox(txt)
|
ttf.getbbox(txt)
|
||||||
|
@ -886,7 +910,7 @@ def test_standard_embedded_color(layout_engine) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("fontmode", ("1", "L", "RGBA"))
|
@pytest.mark.parametrize("fontmode", ("1", "L", "RGBA"))
|
||||||
def test_float_coord(layout_engine, fontmode):
|
def test_float_coord(layout_engine: ImageFont.Layout, fontmode: str) -> None:
|
||||||
txt = "Hello World!"
|
txt = "Hello World!"
|
||||||
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
||||||
|
|
||||||
|
@ -908,7 +932,7 @@ def test_float_coord(layout_engine, fontmode):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def test_cbdt(layout_engine) -> None:
|
def test_cbdt(layout_engine: ImageFont.Layout) -> None:
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
"Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
"Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
||||||
|
@ -925,7 +949,7 @@ def test_cbdt(layout_engine) -> None:
|
||||||
pytest.skip("freetype compiled without libpng or CBDT support")
|
pytest.skip("freetype compiled without libpng or CBDT support")
|
||||||
|
|
||||||
|
|
||||||
def test_cbdt_mask(layout_engine) -> None:
|
def test_cbdt_mask(layout_engine: ImageFont.Layout) -> None:
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
"Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
"Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
||||||
|
@ -942,7 +966,7 @@ def test_cbdt_mask(layout_engine) -> None:
|
||||||
pytest.skip("freetype compiled without libpng or CBDT support")
|
pytest.skip("freetype compiled without libpng or CBDT support")
|
||||||
|
|
||||||
|
|
||||||
def test_sbix(layout_engine) -> None:
|
def test_sbix(layout_engine: ImageFont.Layout) -> None:
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
"Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine
|
"Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine
|
||||||
|
@ -959,7 +983,7 @@ def test_sbix(layout_engine) -> None:
|
||||||
pytest.skip("freetype compiled without libpng or SBIX support")
|
pytest.skip("freetype compiled without libpng or SBIX support")
|
||||||
|
|
||||||
|
|
||||||
def test_sbix_mask(layout_engine) -> None:
|
def test_sbix_mask(layout_engine: ImageFont.Layout) -> None:
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
"Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine
|
"Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine
|
||||||
|
@ -977,7 +1001,7 @@ def test_sbix_mask(layout_engine) -> None:
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.10.0")
|
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||||
def test_colr(layout_engine) -> None:
|
def test_colr(layout_engine: ImageFont.Layout) -> None:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
"Tests/fonts/BungeeColor-Regular_colr_Windows.ttf",
|
"Tests/fonts/BungeeColor-Regular_colr_Windows.ttf",
|
||||||
size=64,
|
size=64,
|
||||||
|
@ -993,7 +1017,7 @@ def test_colr(layout_engine) -> None:
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.10.0")
|
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||||
def test_colr_mask(layout_engine) -> None:
|
def test_colr_mask(layout_engine: ImageFont.Layout) -> None:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
"Tests/fonts/BungeeColor-Regular_colr_Windows.ttf",
|
"Tests/fonts/BungeeColor-Regular_colr_Windows.ttf",
|
||||||
size=64,
|
size=64,
|
||||||
|
@ -1008,7 +1032,7 @@ def test_colr_mask(layout_engine) -> None:
|
||||||
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
|
||||||
|
|
||||||
|
|
||||||
def test_woff2(layout_engine) -> None:
|
def test_woff2(layout_engine: ImageFont.Layout) -> None:
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
"Tests/fonts/OpenSans.woff2",
|
"Tests/fonts/OpenSans.woff2",
|
||||||
|
@ -1042,7 +1066,7 @@ def test_render_mono_size() -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/text_mono.gif")
|
assert_image_equal_tofile(im, "Tests/images/text_mono.gif")
|
||||||
|
|
||||||
|
|
||||||
def test_too_many_characters(font) -> None:
|
def test_too_many_characters(font: ImageFont.FreeTypeFont) -> None:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
font.getlength("A" * 1_000_001)
|
font.getlength("A" * 1_000_001)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
@ -1070,7 +1094,7 @@ def test_too_many_characters(font) -> None:
|
||||||
"Tests/fonts/oom-4da0210eb7081b0bf15bf16cc4c52ce02c1e1bbc.ttf",
|
"Tests/fonts/oom-4da0210eb7081b0bf15bf16cc4c52ce02c1e1bbc.ttf",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_oom(test_file) -> None:
|
def test_oom(test_file: str) -> None:
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
font = ImageFont.truetype(BytesIO(f.read()))
|
font = ImageFont.truetype(BytesIO(f.read()))
|
||||||
with pytest.raises(Image.DecompressionBombError):
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
|
@ -1091,6 +1115,8 @@ def test_raqm_missing_warning(monkeypatch) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("size", [-1, 0])
|
@pytest.mark.parametrize("size", [-1, 0])
|
||||||
def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size) -> None:
|
def test_invalid_truetype_sizes_raise_valueerror(
|
||||||
|
layout_engine: ImageFont.Layout, size: int
|
||||||
|
) -> None:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)
|
ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)
|
||||||
|
|
|
@ -14,7 +14,7 @@ from .helper import (
|
||||||
|
|
||||||
|
|
||||||
class Deformer:
|
class Deformer:
|
||||||
def getmesh(self, im):
|
def getmesh(self, im: Image.Image) -> list[tuple[tuple[int, ...], tuple[int, ...]]]:
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))]
|
return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))]
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ def test_fit_same_ratio() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("new_size", ((256, 256), (512, 256), (256, 512)))
|
@pytest.mark.parametrize("new_size", ((256, 256), (512, 256), (256, 512)))
|
||||||
def test_contain(new_size) -> None:
|
def test_contain(new_size: tuple[int, int]) -> None:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
new_im = ImageOps.contain(im, new_size)
|
new_im = ImageOps.contain(im, new_size)
|
||||||
assert new_im.size == (256, 256)
|
assert new_im.size == (256, 256)
|
||||||
|
@ -132,7 +132,7 @@ def test_contain_round() -> None:
|
||||||
("hopper.png", (256, 256)), # square
|
("hopper.png", (256, 256)), # square
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_cover(image_name, expected_size) -> None:
|
def test_cover(image_name: str, expected_size: tuple[int, int]) -> None:
|
||||||
with Image.open("Tests/images/" + image_name) as im:
|
with Image.open("Tests/images/" + image_name) as im:
|
||||||
new_im = ImageOps.cover(im, (256, 256))
|
new_im = ImageOps.cover(im, (256, 256))
|
||||||
assert new_im.size == expected_size
|
assert new_im.size == expected_size
|
||||||
|
@ -168,7 +168,7 @@ def test_pad_round() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
def test_palette(mode) -> None:
|
def test_palette(mode: str) -> None:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
|
|
||||||
# Expand
|
# Expand
|
||||||
|
@ -210,7 +210,7 @@ def test_scale() -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("border", (10, (1, 2, 3, 4)))
|
@pytest.mark.parametrize("border", (10, (1, 2, 3, 4)))
|
||||||
def test_expand_palette(border) -> None:
|
def test_expand_palette(border: int | tuple[int, int, int, int]) -> None:
|
||||||
with Image.open("Tests/images/p_16.tga") as im:
|
with Image.open("Tests/images/p_16.tga") as im:
|
||||||
im_expanded = ImageOps.expand(im, border, (255, 0, 0))
|
im_expanded = ImageOps.expand(im, border, (255, 0, 0))
|
||||||
|
|
||||||
|
@ -366,7 +366,7 @@ def test_exif_transpose() -> None:
|
||||||
for ext in exts:
|
for ext in exts:
|
||||||
with Image.open("Tests/images/hopper" + ext) as base_im:
|
with Image.open("Tests/images/hopper" + ext) as base_im:
|
||||||
|
|
||||||
def check(orientation_im) -> None:
|
def check(orientation_im: Image.Image) -> None:
|
||||||
for im in [
|
for im in [
|
||||||
orientation_im,
|
orientation_im,
|
||||||
orientation_im.copy(),
|
orientation_im.copy(),
|
||||||
|
@ -445,7 +445,7 @@ def test_autocontrast_cutoff() -> None:
|
||||||
# Test the cutoff argument of autocontrast
|
# Test the cutoff argument of autocontrast
|
||||||
with Image.open("Tests/images/bw_gradient.png") as img:
|
with Image.open("Tests/images/bw_gradient.png") as img:
|
||||||
|
|
||||||
def autocontrast(cutoff):
|
def autocontrast(cutoff: int | tuple[int, int]):
|
||||||
return ImageOps.autocontrast(img, cutoff).histogram()
|
return ImageOps.autocontrast(img, cutoff).histogram()
|
||||||
|
|
||||||
assert autocontrast(10) == autocontrast((10, 10))
|
assert autocontrast(10) == autocontrast((10, 10))
|
||||||
|
@ -486,20 +486,20 @@ def test_autocontrast_mask_real_input() -> None:
|
||||||
assert result_nomask != result
|
assert result_nomask != result
|
||||||
assert_tuple_approx_equal(
|
assert_tuple_approx_equal(
|
||||||
ImageStat.Stat(result, mask=rect_mask).median,
|
ImageStat.Stat(result, mask=rect_mask).median,
|
||||||
[195, 202, 184],
|
(195, 202, 184),
|
||||||
threshold=2,
|
threshold=2,
|
||||||
msg="autocontrast with mask pixel incorrect",
|
msg="autocontrast with mask pixel incorrect",
|
||||||
)
|
)
|
||||||
assert_tuple_approx_equal(
|
assert_tuple_approx_equal(
|
||||||
ImageStat.Stat(result_nomask).median,
|
ImageStat.Stat(result_nomask).median,
|
||||||
[119, 106, 79],
|
(119, 106, 79),
|
||||||
threshold=2,
|
threshold=2,
|
||||||
msg="autocontrast without mask pixel incorrect",
|
msg="autocontrast without mask pixel incorrect",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_autocontrast_preserve_tone() -> None:
|
def test_autocontrast_preserve_tone() -> None:
|
||||||
def autocontrast(mode, preserve_tone):
|
def autocontrast(mode: str, preserve_tone: bool) -> Image.Image:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram()
|
return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram()
|
||||||
|
|
||||||
|
@ -533,7 +533,7 @@ def test_autocontrast_preserve_gradient() -> None:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"color", ((255, 255, 255), (127, 255, 0), (127, 127, 127), (0, 0, 0))
|
"color", ((255, 255, 255), (127, 255, 0), (127, 127, 127), (0, 0, 0))
|
||||||
)
|
)
|
||||||
def test_autocontrast_preserve_one_color(color) -> None:
|
def test_autocontrast_preserve_one_color(color: tuple[int, int, int]) -> None:
|
||||||
img = Image.new("RGB", (10, 10), color)
|
img = Image.new("RGB", (10, 10), color)
|
||||||
|
|
||||||
# single color images shouldn't change
|
# single color images shouldn't change
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFilter
|
from PIL import Image, ImageFilter
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_images():
|
def test_images() -> Generator[dict[str, Image.Image], None, None]:
|
||||||
ims = {
|
ims = {
|
||||||
"im": Image.open("Tests/images/hopper.ppm"),
|
"im": Image.open("Tests/images/hopper.ppm"),
|
||||||
"snakes": Image.open("Tests/images/color_snakes.png"),
|
"snakes": Image.open("Tests/images/color_snakes.png"),
|
||||||
|
@ -18,7 +20,7 @@ def test_images():
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
|
|
||||||
def test_filter_api(test_images) -> None:
|
def test_filter_api(test_images: dict[str, Image.Image]) -> None:
|
||||||
im = test_images["im"]
|
im = test_images["im"]
|
||||||
|
|
||||||
test_filter = ImageFilter.GaussianBlur(2.0)
|
test_filter = ImageFilter.GaussianBlur(2.0)
|
||||||
|
@ -32,7 +34,7 @@ def test_filter_api(test_images) -> None:
|
||||||
assert i.size == (128, 128)
|
assert i.size == (128, 128)
|
||||||
|
|
||||||
|
|
||||||
def test_usm_formats(test_images) -> None:
|
def test_usm_formats(test_images: dict[str, Image.Image]) -> None:
|
||||||
im = test_images["im"]
|
im = test_images["im"]
|
||||||
|
|
||||||
usm = ImageFilter.UnsharpMask
|
usm = ImageFilter.UnsharpMask
|
||||||
|
@ -50,7 +52,7 @@ def test_usm_formats(test_images) -> None:
|
||||||
im.convert("YCbCr").filter(usm)
|
im.convert("YCbCr").filter(usm)
|
||||||
|
|
||||||
|
|
||||||
def test_blur_formats(test_images) -> None:
|
def test_blur_formats(test_images: dict[str, Image.Image]) -> None:
|
||||||
im = test_images["im"]
|
im = test_images["im"]
|
||||||
|
|
||||||
blur = ImageFilter.GaussianBlur
|
blur = ImageFilter.GaussianBlur
|
||||||
|
@ -68,7 +70,7 @@ def test_blur_formats(test_images) -> None:
|
||||||
im.convert("YCbCr").filter(blur)
|
im.convert("YCbCr").filter(blur)
|
||||||
|
|
||||||
|
|
||||||
def test_usm_accuracy(test_images) -> None:
|
def test_usm_accuracy(test_images: dict[str, Image.Image]) -> None:
|
||||||
snakes = test_images["snakes"]
|
snakes = test_images["snakes"]
|
||||||
|
|
||||||
src = snakes.convert("RGB")
|
src = snakes.convert("RGB")
|
||||||
|
@ -77,7 +79,7 @@ def test_usm_accuracy(test_images) -> None:
|
||||||
assert i.tobytes() == src.tobytes()
|
assert i.tobytes() == src.tobytes()
|
||||||
|
|
||||||
|
|
||||||
def test_blur_accuracy(test_images) -> None:
|
def test_blur_accuracy(test_images: dict[str, Image.Image]) -> None:
|
||||||
snakes = test_images["snakes"]
|
snakes = test_images["snakes"]
|
||||||
|
|
||||||
i = snakes.filter(ImageFilter.GaussianBlur(0.4))
|
i = snakes.filter(ImageFilter.GaussianBlur(0.4))
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
import array
|
import array
|
||||||
import math
|
import math
|
||||||
import struct
|
import struct
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -75,7 +76,9 @@ def test_path_constructors(coords) -> None:
|
||||||
[[0.0, 1.0]],
|
[[0.0, 1.0]],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_invalid_path_constructors(coords) -> None:
|
def test_invalid_path_constructors(
|
||||||
|
coords: tuple[str, str] | Sequence[Sequence[int]]
|
||||||
|
) -> None:
|
||||||
# Act
|
# Act
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
ImagePath.Path(coords)
|
ImagePath.Path(coords)
|
||||||
|
@ -93,7 +96,7 @@ def test_invalid_path_constructors(coords) -> None:
|
||||||
[0, 1, 2],
|
[0, 1, 2],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_path_odd_number_of_coordinates(coords) -> None:
|
def test_path_odd_number_of_coordinates(coords: Sequence[int]) -> None:
|
||||||
# Act
|
# Act
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
ImagePath.Path(coords)
|
ImagePath.Path(coords)
|
||||||
|
@ -111,7 +114,9 @@ def test_path_odd_number_of_coordinates(coords) -> None:
|
||||||
(1, (0.0, 0.0, 0.0, 0.0)),
|
(1, (0.0, 0.0, 0.0, 0.0)),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_getbbox(coords, expected) -> None:
|
def test_getbbox(
|
||||||
|
coords: int | list[int], expected: tuple[float, float, float, float]
|
||||||
|
) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
p = ImagePath.Path(coords)
|
p = ImagePath.Path(coords)
|
||||||
|
|
||||||
|
@ -135,7 +140,7 @@ def test_getbbox_no_args() -> None:
|
||||||
(list(range(6)), [(0.0, 3.0), (4.0, 9.0), (8.0, 15.0)]),
|
(list(range(6)), [(0.0, 3.0), (4.0, 9.0), (8.0, 15.0)]),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_map(coords, expected) -> None:
|
def test_map(coords: int | list[int], expected: list[tuple[float, float]]) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
p = ImagePath.Path(coords)
|
p = ImagePath.Path(coords)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user