mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 17:24:31 +03:00
Merge branch 'main' into type_hints
This commit is contained in:
commit
c1f10c107a
|
@ -1252,10 +1252,11 @@ def test_palette_save_L(tmp_path: Path) -> None:
|
|||
|
||||
im = hopper("P")
|
||||
im_l = Image.frombytes("L", im.size, im.tobytes())
|
||||
palette = bytes(im.getpalette())
|
||||
palette = im.getpalette()
|
||||
assert palette is not None
|
||||
|
||||
out = str(tmp_path / "temp.gif")
|
||||
im_l.save(out, palette=palette)
|
||||
im_l.save(out, palette=bytes(palette))
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert_image_equal(reloaded.convert("RGB"), im.convert("RGB"))
|
||||
|
|
|
@ -154,7 +154,7 @@ class TestFileJpeg:
|
|||
assert k > 0.9
|
||||
|
||||
def test_rgb(self) -> None:
|
||||
def getchannels(im: Image.Image) -> tuple[int, int, int]:
|
||||
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, int, int]:
|
||||
return tuple(v[0] for v in im.layer)
|
||||
|
||||
im = hopper()
|
||||
|
@ -443,7 +443,7 @@ class TestFileJpeg:
|
|||
assert_image(im1, im2.mode, im2.size)
|
||||
|
||||
def test_subsampling(self) -> None:
|
||||
def getsampling(im: Image.Image):
|
||||
def getsampling(im: JpegImagePlugin.JpegImageFile):
|
||||
layer = im.layer
|
||||
return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]
|
||||
|
||||
|
|
|
@ -668,7 +668,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
pilim.save(buffer_io, format="tiff", compression=compression)
|
||||
buffer_io.seek(0)
|
||||
|
||||
assert_image_similar_tofile(pilim, buffer_io, 0)
|
||||
with Image.open(buffer_io) as saved_im:
|
||||
assert_image_similar(pilim, saved_im, 0)
|
||||
|
||||
save_bytesio()
|
||||
save_bytesio("raw")
|
||||
|
|
|
@ -12,7 +12,7 @@ class TestTTypeFontLeak(PillowLeakTestCase):
|
|||
iterations = 10
|
||||
mem_limit = 4096 # k
|
||||
|
||||
def _test_font(self, font: ImageFont.FreeTypeFont) -> None:
|
||||
def _test_font(self, font: ImageFont.FreeTypeFont | ImageFont.ImageFont) -> None:
|
||||
im = Image.new("RGB", (255, 255), "white")
|
||||
draw = ImageDraw.ImageDraw(im)
|
||||
self._test_leak(
|
||||
|
|
|
@ -25,6 +25,7 @@ from PIL import (
|
|||
from .helper import (
|
||||
assert_image_equal,
|
||||
assert_image_equal_tofile,
|
||||
assert_image_similar,
|
||||
assert_image_similar_tofile,
|
||||
assert_not_all_same,
|
||||
hopper,
|
||||
|
@ -99,10 +100,18 @@ class TestImage:
|
|||
JPGFILE = "Tests/images/hopper.jpg"
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
with Image.open(PNGFILE, formats=123):
|
||||
with Image.open(PNGFILE, formats=123): # type: ignore[arg-type]
|
||||
pass
|
||||
|
||||
for formats in [["JPEG"], ("JPEG",), ["jpeg"], ["Jpeg"], ["jPeG"], ["JpEg"]]:
|
||||
format_list: list[list[str] | tuple[str, ...]] = [
|
||||
["JPEG"],
|
||||
("JPEG",),
|
||||
["jpeg"],
|
||||
["Jpeg"],
|
||||
["jPeG"],
|
||||
["JpEg"],
|
||||
]
|
||||
for formats in format_list:
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
with Image.open(PNGFILE, formats=formats):
|
||||
pass
|
||||
|
@ -138,7 +147,7 @@ class TestImage:
|
|||
|
||||
def test_bad_mode(self) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
with Image.open("filename", "bad mode"):
|
||||
with Image.open("filename", "bad mode"): # type: ignore[arg-type]
|
||||
pass
|
||||
|
||||
def test_stringio(self) -> None:
|
||||
|
@ -185,7 +194,8 @@ class TestImage:
|
|||
with tempfile.TemporaryFile() as fp:
|
||||
im.save(fp, "JPEG")
|
||||
fp.seek(0)
|
||||
assert_image_similar_tofile(im, fp, 20)
|
||||
with Image.open(fp) as reloaded:
|
||||
assert_image_similar(im, reloaded, 20)
|
||||
|
||||
def test_unknown_extension(self, tmp_path: Path) -> None:
|
||||
im = hopper()
|
||||
|
@ -497,9 +507,11 @@ class TestImage:
|
|||
def test_check_size(self) -> None:
|
||||
# Checking that the _check_size function throws value errors when we want it to
|
||||
with pytest.raises(ValueError):
|
||||
Image.new("RGB", 0) # not a tuple
|
||||
# not a tuple
|
||||
Image.new("RGB", 0) # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
Image.new("RGB", (0,)) # Tuple too short
|
||||
# tuple too short
|
||||
Image.new("RGB", (0,)) # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
Image.new("RGB", (-1, -1)) # w,h < 0
|
||||
|
||||
|
|
|
@ -86,8 +86,8 @@ def test_fromarray() -> None:
|
|||
assert test("RGBX") == ("RGBA", (128, 100), True)
|
||||
|
||||
# Test mode is None with no "typestr" in the array interface
|
||||
wrapped = Wrapper(hopper("L"), {"shape": (100, 128)})
|
||||
with pytest.raises(TypeError):
|
||||
wrapped = Wrapper(test("L"), {"shape": (100, 128)})
|
||||
Image.fromarray(wrapped)
|
||||
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ def test_crop(mode: str) -> None:
|
|||
|
||||
|
||||
def test_wide_crop() -> None:
|
||||
def crop(*bbox: int) -> tuple[int, ...]:
|
||||
def crop(bbox: tuple[int, int, int, int]) -> tuple[int, ...]:
|
||||
i = im.crop(bbox)
|
||||
h = i.histogram()
|
||||
while h and not h[-1]:
|
||||
|
@ -27,23 +27,23 @@ def test_wide_crop() -> None:
|
|||
|
||||
im = Image.new("L", (100, 100), 1)
|
||||
|
||||
assert crop(0, 0, 100, 100) == (0, 10000)
|
||||
assert crop(25, 25, 75, 75) == (0, 2500)
|
||||
assert crop((0, 0, 100, 100)) == (0, 10000)
|
||||
assert crop((25, 25, 75, 75)) == (0, 2500)
|
||||
|
||||
# sides
|
||||
assert crop(-25, 0, 25, 50) == (1250, 1250)
|
||||
assert crop(0, -25, 50, 25) == (1250, 1250)
|
||||
assert crop(75, 0, 125, 50) == (1250, 1250)
|
||||
assert crop(0, 75, 50, 125) == (1250, 1250)
|
||||
assert crop((-25, 0, 25, 50)) == (1250, 1250)
|
||||
assert crop((0, -25, 50, 25)) == (1250, 1250)
|
||||
assert crop((75, 0, 125, 50)) == (1250, 1250)
|
||||
assert crop((0, 75, 50, 125)) == (1250, 1250)
|
||||
|
||||
assert crop(-25, 25, 125, 75) == (2500, 5000)
|
||||
assert crop(25, -25, 75, 125) == (2500, 5000)
|
||||
assert crop((-25, 25, 125, 75)) == (2500, 5000)
|
||||
assert crop((25, -25, 75, 125)) == (2500, 5000)
|
||||
|
||||
# corners
|
||||
assert crop(-25, -25, 25, 25) == (1875, 625)
|
||||
assert crop(75, -25, 125, 25) == (1875, 625)
|
||||
assert crop(75, 75, 125, 125) == (1875, 625)
|
||||
assert crop(-25, 75, 25, 125) == (1875, 625)
|
||||
assert crop((-25, -25, 25, 25)) == (1875, 625)
|
||||
assert crop((75, -25, 125, 25)) == (1875, 625)
|
||||
assert crop((75, 75, 125, 125)) == (1875, 625)
|
||||
assert crop((-25, 75, 25, 125)) == (1875, 625)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("box", ((8, 2, 2, 8), (2, 8, 8, 2), (8, 8, 2, 2)))
|
||||
|
|
|
@ -46,9 +46,9 @@ def test_sanity(filter_to_apply: ImageFilter.Filter, mode: str) -> None:
|
|||
|
||||
@pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
|
||||
def test_sanity_error(mode: str) -> None:
|
||||
im = hopper(mode)
|
||||
with pytest.raises(TypeError):
|
||||
im = hopper(mode)
|
||||
im.filter("hello")
|
||||
im.filter("hello") # type: ignore[arg-type]
|
||||
|
||||
|
||||
# crashes on small images
|
||||
|
|
|
@ -6,7 +6,7 @@ from .helper import hopper
|
|||
|
||||
|
||||
def test_extrema() -> None:
|
||||
def extrema(mode: str) -> tuple[int, int] | tuple[tuple[int, int], ...]:
|
||||
def extrema(mode: str) -> tuple[float, float] | tuple[tuple[int, int], ...]:
|
||||
return hopper(mode).getextrema()
|
||||
|
||||
assert extrema("1") == (0, 255)
|
||||
|
|
|
@ -7,7 +7,7 @@ import shutil
|
|||
import sys
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -209,13 +209,13 @@ def test_exceptions() -> None:
|
|||
ImageCms.buildTransform("foo", "bar", "RGB", "RGB")
|
||||
|
||||
with pytest.raises(ImageCms.PyCMSError, match="Invalid type for Profile"):
|
||||
ImageCms.getProfileName(None)
|
||||
ImageCms.getProfileName(None) # type: ignore[arg-type]
|
||||
skip_missing()
|
||||
|
||||
# Python <= 3.9: "an integer is required (got type NoneType)"
|
||||
# Python > 3.9: "'NoneType' object cannot be interpreted as an integer"
|
||||
with pytest.raises(ImageCms.PyCMSError, match="integer"):
|
||||
ImageCms.isIntentSupported(SRGB, None, None)
|
||||
ImageCms.isIntentSupported(SRGB, None, None) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_display_profile() -> None:
|
||||
|
@ -239,7 +239,7 @@ def test_unsupported_color_space() -> None:
|
|||
"Color space not supported for on-the-fly profile creation (unsupported)"
|
||||
),
|
||||
):
|
||||
ImageCms.createProfile("unsupported")
|
||||
ImageCms.createProfile("unsupported") # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_invalid_color_temperature() -> None:
|
||||
|
@ -247,7 +247,7 @@ def test_invalid_color_temperature() -> None:
|
|||
ImageCms.PyCMSError,
|
||||
match='Color temperature must be numeric, "invalid" not valid',
|
||||
):
|
||||
ImageCms.createProfile("LAB", "invalid")
|
||||
ImageCms.createProfile("LAB", "invalid") # type: ignore[arg-type]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("flag", ("my string", -1))
|
||||
|
@ -256,7 +256,7 @@ def test_invalid_flag(flag: str | int) -> None:
|
|||
with pytest.raises(
|
||||
ImageCms.PyCMSError, match="flags must be an integer between 0 and "
|
||||
):
|
||||
ImageCms.profileToProfile(im, "foo", "bar", flags=flag)
|
||||
ImageCms.profileToProfile(im, "foo", "bar", flags=flag) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_simple_lab() -> None:
|
||||
|
@ -352,7 +352,7 @@ def test_extended_information() -> None:
|
|||
p = o.profile
|
||||
|
||||
def assert_truncated_tuple_equal(
|
||||
tup1: tuple[Any, ...], tup2: tuple[Any, ...], digits: int = 10
|
||||
tup1: tuple[Any, ...] | None, tup2: tuple[Any, ...], digits: int = 10
|
||||
) -> None:
|
||||
# Helper function to reduce precision of tuples of floats
|
||||
# recursively and then check equality.
|
||||
|
@ -368,6 +368,7 @@ def test_extended_information() -> None:
|
|||
for val in tuple_value
|
||||
)
|
||||
|
||||
assert tup1 is not None
|
||||
assert truncate_tuple(tup1) == truncate_tuple(tup2)
|
||||
|
||||
assert p.attributes == 4294967296
|
||||
|
@ -513,22 +514,22 @@ def test_non_ascii_path(tmp_path: Path) -> None:
|
|||
def test_profile_typesafety() -> None:
|
||||
# does not segfault
|
||||
with pytest.raises(TypeError, match="Invalid type for Profile"):
|
||||
ImageCms.ImageCmsProfile(0).tobytes()
|
||||
ImageCms.ImageCmsProfile(0) # type: ignore[arg-type]
|
||||
with pytest.raises(TypeError, match="Invalid type for Profile"):
|
||||
ImageCms.ImageCmsProfile(1).tobytes()
|
||||
ImageCms.ImageCmsProfile(1) # type: ignore[arg-type]
|
||||
|
||||
# also check core function
|
||||
with pytest.raises(TypeError):
|
||||
ImageCms.core.profile_tobytes(0)
|
||||
ImageCms.core.profile_tobytes(0) # type: ignore[arg-type]
|
||||
with pytest.raises(TypeError):
|
||||
ImageCms.core.profile_tobytes(1)
|
||||
ImageCms.core.profile_tobytes(1) # type: ignore[arg-type]
|
||||
|
||||
if not is_pypy():
|
||||
# core profile should not be directly instantiable
|
||||
with pytest.raises(TypeError):
|
||||
ImageCms.core.CmsProfile()
|
||||
with pytest.raises(TypeError):
|
||||
ImageCms.core.CmsProfile(0)
|
||||
ImageCms.core.CmsProfile(0) # type: ignore[call-arg]
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_pypy(), reason="fails on PyPy")
|
||||
|
@ -537,7 +538,7 @@ def test_transform_typesafety() -> None:
|
|||
with pytest.raises(TypeError):
|
||||
ImageCms.core.CmsTransform()
|
||||
with pytest.raises(TypeError):
|
||||
ImageCms.core.CmsTransform(0)
|
||||
ImageCms.core.CmsTransform(0) # type: ignore[call-arg]
|
||||
|
||||
|
||||
def assert_aux_channel_preserved(
|
||||
|
@ -587,11 +588,13 @@ def assert_aux_channel_preserved(
|
|||
)
|
||||
|
||||
# apply transform
|
||||
result_image: Image.Image | None
|
||||
if transform_in_place:
|
||||
ImageCms.applyTransform(source_image, t, inPlace=True)
|
||||
result_image = source_image
|
||||
else:
|
||||
result_image = ImageCms.applyTransform(source_image, t, inPlace=False)
|
||||
assert result_image is not None
|
||||
result_image_aux = result_image.getchannel(preserved_channel)
|
||||
|
||||
assert_image_equal(source_image_aux, result_image_aux)
|
||||
|
@ -637,7 +640,8 @@ def test_auxiliary_channels_isolated() -> None:
|
|||
continue
|
||||
|
||||
# convert with and without AUX data, test colors are equal
|
||||
source_profile = ImageCms.createProfile(src_format[1])
|
||||
src_colorSpace = cast(Literal["LAB", "XYZ", "sRGB"], src_format[1])
|
||||
source_profile = ImageCms.createProfile(src_colorSpace)
|
||||
destination_profile = ImageCms.createProfile(dst_format[1])
|
||||
source_image = src_format[3]
|
||||
test_transform = ImageCms.buildTransform(
|
||||
|
@ -648,6 +652,7 @@ def test_auxiliary_channels_isolated() -> None:
|
|||
)
|
||||
|
||||
# test conversion from aux-ful source
|
||||
test_image: Image.Image | None
|
||||
if transform_in_place:
|
||||
test_image = source_image.copy()
|
||||
ImageCms.applyTransform(test_image, test_transform, inPlace=True)
|
||||
|
@ -655,6 +660,7 @@ def test_auxiliary_channels_isolated() -> None:
|
|||
test_image = ImageCms.applyTransform(
|
||||
source_image, test_transform, inPlace=False
|
||||
)
|
||||
assert test_image is not None
|
||||
|
||||
# reference conversion from aux-less source
|
||||
reference_transform = ImageCms.buildTransform(
|
||||
|
|
|
@ -1083,8 +1083,8 @@ def test_line_horizontal() -> None:
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="failing test")
|
||||
def test_line_h_s1_w2() -> None:
|
||||
pytest.skip("failing")
|
||||
img, draw = create_base_image_draw((20, 20))
|
||||
draw.line((5, 5, 14, 6), BLACK, 2)
|
||||
assert_image_equal_tofile(
|
||||
|
|
|
@ -25,10 +25,10 @@ def test_sanity() -> None:
|
|||
st.stddev
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
st.spam()
|
||||
st.spam() # type: ignore[attr-defined]
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
ImageStat.Stat(1)
|
||||
ImageStat.Stat(1) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_hopper() -> None:
|
||||
|
|
|
@ -754,7 +754,7 @@ def applyTransform(
|
|||
|
||||
|
||||
def createProfile(
|
||||
colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = -1
|
||||
colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0
|
||||
) -> core.CmsProfile:
|
||||
"""
|
||||
(pyCMS) Creates a profile.
|
||||
|
@ -777,7 +777,7 @@ def createProfile(
|
|||
:param colorSpace: String, the color space of the profile you wish to
|
||||
create.
|
||||
Currently only "LAB", "XYZ", and "sRGB" are supported.
|
||||
:param colorTemp: Positive integer for the white point for the profile, in
|
||||
:param colorTemp: Positive number for the white point for the profile, in
|
||||
degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
|
||||
illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
|
||||
profiles, and is ignored for XYZ and sRGB.
|
||||
|
@ -1089,7 +1089,7 @@ def isIntentSupported(
|
|||
raise PyCMSError(v) from v
|
||||
|
||||
|
||||
def versions() -> tuple[str, str, str, str]:
|
||||
def versions() -> tuple[str, str | None, str, str]:
|
||||
"""
|
||||
(pyCMS) Fetches versions.
|
||||
"""
|
||||
|
|
|
@ -2,7 +2,7 @@ import datetime
|
|||
import sys
|
||||
from typing import Literal, SupportsFloat, TypedDict
|
||||
|
||||
littlecms_version: str
|
||||
littlecms_version: str | None
|
||||
|
||||
_Tuple3f = tuple[float, float, float]
|
||||
_Tuple2x3f = tuple[_Tuple3f, _Tuple3f]
|
||||
|
|
Loading…
Reference in New Issue
Block a user