Merge pull request #8191 from radarhere/type_hint

This commit is contained in:
Hugo van Kemenade 2024-07-02 14:31:04 -06:00 committed by GitHub
commit c8df36f650
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 107 additions and 31 deletions

View File

@ -321,6 +321,7 @@ class TestColorLut3DCoreAPI:
-1, 2, 2, 2, 2, 2, -1, 2, 2, 2, 2, 2,
])).load() ])).load()
# fmt: on # fmt: on
assert transformed is not None
assert transformed[0, 0] == (0, 0, 255) assert transformed[0, 0] == (0, 0, 255)
assert transformed[50, 50] == (0, 0, 255) assert transformed[50, 50] == (0, 0, 255)
assert transformed[255, 0] == (0, 255, 255) assert transformed[255, 0] == (0, 255, 255)
@ -341,6 +342,7 @@ class TestColorLut3DCoreAPI:
-3, 5, 5, 5, 5, 5, -3, 5, 5, 5, 5, 5,
])).load() ])).load()
# fmt: on # fmt: on
assert transformed is not None
assert transformed[0, 0] == (0, 0, 255) assert transformed[0, 0] == (0, 0, 255)
assert transformed[50, 50] == (0, 0, 255) assert transformed[50, 50] == (0, 0, 255)
assert transformed[255, 0] == (0, 255, 255) assert transformed[255, 0] == (0, 255, 255)

View File

@ -76,6 +76,7 @@ def test_pil184() -> None:
def test_1px_width(tmp_path: Path) -> None: def test_1px_width(tmp_path: Path) -> None:
im = Image.new("L", (1, 256)) im = Image.new("L", (1, 256))
px = im.load() px = im.load()
assert px is not None
for y in range(256): for y in range(256):
px[0, y] = y px[0, y] = y
_roundtrip(tmp_path, im) _roundtrip(tmp_path, im)
@ -84,6 +85,7 @@ def test_1px_width(tmp_path: Path) -> None:
def test_large_count(tmp_path: Path) -> None: def test_large_count(tmp_path: Path) -> None:
im = Image.new("L", (256, 1)) im = Image.new("L", (256, 1))
px = im.load() px = im.load()
assert px is not None
for x in range(256): for x in range(256):
px[x, 0] = x // 67 * 67 px[x, 0] = x // 67 * 67
_roundtrip(tmp_path, im) _roundtrip(tmp_path, im)
@ -101,6 +103,7 @@ def _test_buffer_overflow(tmp_path: Path, im: Image.Image, size: int = 1024) ->
def test_break_in_count_overflow(tmp_path: Path) -> None: def test_break_in_count_overflow(tmp_path: Path) -> None:
im = Image.new("L", (256, 5)) im = Image.new("L", (256, 5))
px = im.load() px = im.load()
assert px is not None
for y in range(4): for y in range(4):
for x in range(256): for x in range(256):
px[x, y] = x % 128 px[x, y] = x % 128
@ -110,6 +113,7 @@ def test_break_in_count_overflow(tmp_path: Path) -> None:
def test_break_one_in_loop(tmp_path: Path) -> None: def test_break_one_in_loop(tmp_path: Path) -> None:
im = Image.new("L", (256, 5)) im = Image.new("L", (256, 5))
px = im.load() px = im.load()
assert px is not None
for y in range(5): for y in range(5):
for x in range(256): for x in range(256):
px[x, y] = x % 128 px[x, y] = x % 128
@ -119,6 +123,7 @@ def test_break_one_in_loop(tmp_path: Path) -> None:
def test_break_many_in_loop(tmp_path: Path) -> None: def test_break_many_in_loop(tmp_path: Path) -> None:
im = Image.new("L", (256, 5)) im = Image.new("L", (256, 5))
px = im.load() px = im.load()
assert px is not None
for y in range(4): for y in range(4):
for x in range(256): for x in range(256):
px[x, y] = x % 128 px[x, y] = x % 128
@ -130,6 +135,7 @@ def test_break_many_in_loop(tmp_path: Path) -> None:
def test_break_one_at_end(tmp_path: Path) -> None: def test_break_one_at_end(tmp_path: Path) -> None:
im = Image.new("L", (256, 5)) im = Image.new("L", (256, 5))
px = im.load() px = im.load()
assert px is not None
for y in range(5): for y in range(5):
for x in range(256): for x in range(256):
px[x, y] = x % 128 px[x, y] = x % 128
@ -140,6 +146,7 @@ def test_break_one_at_end(tmp_path: Path) -> None:
def test_break_many_at_end(tmp_path: Path) -> None: def test_break_many_at_end(tmp_path: Path) -> None:
im = Image.new("L", (256, 5)) im = Image.new("L", (256, 5))
px = im.load() px = im.load()
assert px is not None
for y in range(5): for y in range(5):
for x in range(256): for x in range(256):
px[x, y] = x % 128 px[x, y] = x % 128
@ -152,6 +159,7 @@ def test_break_many_at_end(tmp_path: Path) -> None:
def test_break_padding(tmp_path: Path) -> None: def test_break_padding(tmp_path: Path) -> None:
im = Image.new("L", (257, 5)) im = Image.new("L", (257, 5))
px = im.load() px = im.load()
assert px is not None
for y in range(5): for y in range(5):
for x in range(257): for x in range(257):
px[x, y] = x % 128 px[x, y] = x % 128

View File

@ -575,6 +575,7 @@ class TestImage:
for mode in ("I", "F", "L"): for mode in ("I", "F", "L"):
im = Image.new(mode, (100, 100), (5,)) im = Image.new(mode, (100, 100), (5,))
px = im.load() px = im.load()
assert px is not None
assert px[0, 0] == 5 assert px[0, 0] == 5
def test_linear_gradient_wrong_mode(self) -> None: def test_linear_gradient_wrong_mode(self) -> None:

View File

@ -47,6 +47,8 @@ class TestImagePutPixel:
pix1 = im1.load() pix1 = im1.load()
pix2 = im2.load() pix2 = im2.load()
assert pix1 is not None
assert pix2 is not None
with pytest.raises(TypeError): with pytest.raises(TypeError):
pix1[0, "0"] pix1[0, "0"]
with pytest.raises(TypeError): with pytest.raises(TypeError):
@ -89,6 +91,8 @@ class TestImagePutPixel:
pix1 = im1.load() pix1 = im1.load()
pix2 = im2.load() pix2 = im2.load()
assert pix1 is not None
assert pix2 is not None
for y in range(-1, -im1.size[1] - 1, -1): for y in range(-1, -im1.size[1] - 1, -1):
for x in range(-1, -im1.size[0] - 1, -1): for x in range(-1, -im1.size[0] - 1, -1):
pix2[x, y] = pix1[x, y] pix2[x, y] = pix1[x, y]
@ -98,10 +102,11 @@ class TestImagePutPixel:
@pytest.mark.skipif(numpy is None, reason="NumPy not installed") @pytest.mark.skipif(numpy is None, reason="NumPy not installed")
def test_numpy(self) -> None: def test_numpy(self) -> None:
im = hopper() im = hopper()
pix = im.load() px = im.load()
assert px is not None
assert numpy is not None assert numpy is not None
assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59) assert px[numpy.int32(1), numpy.int32(2)] == (18, 20, 59)
class TestImageGetPixel: class TestImageGetPixel:

View File

@ -222,8 +222,10 @@ def test_l_macro_rounding(convert_mode: str) -> None:
converted_im = im.convert(convert_mode) converted_im = im.convert(convert_mode)
px = converted_im.load() px = converted_im.load()
assert px is not None
converted_color = px[0, 0] converted_color = px[0, 0]
if convert_mode == "LA": if convert_mode == "LA":
assert converted_color is not None
converted_color = converted_color[0] converted_color = converted_color[0]
assert converted_color == 1 assert converted_color == 1

View File

@ -12,9 +12,10 @@ from .helper import hopper
def test_sanity() -> None: def test_sanity() -> None:
im = hopper() im = hopper()
pix = im.load() px = im.load()
assert pix[0, 0] == (20, 20, 70) assert px is not None
assert px[0, 0] == (20, 20, 70)
def test_close() -> None: def test_close() -> None:

View File

@ -14,6 +14,7 @@ class TestImagingPaste:
self, im: Image.Image, expected: list[tuple[int, int, int, int]] self, im: Image.Image, expected: list[tuple[int, int, int, int]]
) -> None: ) -> None:
px = im.load() px = im.load()
assert px is not None
actual = [ actual = [
px[0, 0], px[0, 0],
px[self.size // 2, 0], px[self.size // 2, 0],
@ -48,6 +49,7 @@ class TestImagingPaste:
def mask_1(self) -> Image.Image: def mask_1(self) -> Image.Image:
mask = Image.new("1", (self.size, self.size)) mask = Image.new("1", (self.size, self.size))
px = mask.load() px = mask.load()
assert px is not None
for y in range(mask.height): for y in range(mask.height):
for x in range(mask.width): for x in range(mask.width):
px[y, x] = (x + y) % 2 px[y, x] = (x + y) % 2
@ -61,6 +63,7 @@ class TestImagingPaste:
def gradient_L(self) -> Image.Image: def gradient_L(self) -> Image.Image:
gradient = Image.new("L", (self.size, self.size)) gradient = Image.new("L", (self.size, self.size))
px = gradient.load() px = gradient.load()
assert px is not None
for y in range(gradient.height): for y in range(gradient.height):
for x in range(gradient.width): for x in range(gradient.width):
px[y, x] = (x + y) % 255 px[y, x] = (x + y) % 255

View File

@ -80,6 +80,7 @@ def test_quantize_no_dither2() -> None:
assert tuple(quantized.palette.palette) == data assert tuple(quantized.palette.palette) == data
px = quantized.load() px = quantized.load()
assert px is not None
for x in range(9): for x in range(9):
assert px[x, 0] == (0 if x < 5 else 1) assert px[x, 0] == (0 if x < 5 else 1)
@ -118,10 +119,12 @@ def test_colors() -> None:
def test_transparent_colors_equal() -> None: 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()
assert px is not None
px[0, 1] = (255, 255, 255, 0) px[0, 1] = (255, 255, 255, 0)
converted = im.quantize() converted = im.quantize()
converted_px = converted.load() converted_px = converted.load()
assert converted_px is not None
assert converted_px[0, 0] == converted_px[0, 1] assert converted_px[0, 0] == converted_px[0, 1]
@ -139,6 +142,7 @@ def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None:
converted = im.quantize(method=method) converted = im.quantize(method=method)
converted_px = converted.load() converted_px = converted.load()
assert converted_px is not None
assert converted_px[0, 0] == converted.palette.colors[color] assert converted_px[0, 0] == converted.palette.colors[color]

View File

@ -74,6 +74,7 @@ class TestImagingCoreResampleAccuracy:
data = data.replace(" ", "") data = data.replace(" ", "")
sample = Image.new("L", size) sample = Image.new("L", size)
s_px = sample.load() s_px = sample.load()
assert s_px is not None
w, h = size[0] // 2, size[1] // 2 w, h = size[0] // 2, size[1] // 2
for y in range(h): for y in range(h):
for x in range(w): for x in range(w):
@ -87,6 +88,8 @@ class TestImagingCoreResampleAccuracy:
def check_case(self, case: Image.Image, sample: Image.Image) -> None: def check_case(self, case: Image.Image, sample: Image.Image) -> None:
s_px = sample.load() s_px = sample.load()
c_px = case.load() c_px = case.load()
assert s_px is not None
assert c_px is not None
for y in range(case.size[1]): for y in range(case.size[1]):
for x in range(case.size[0]): for x in range(case.size[0]):
if c_px[x, y] != s_px[x, y]: if c_px[x, y] != s_px[x, y]:
@ -98,6 +101,7 @@ class TestImagingCoreResampleAccuracy:
def serialize_image(self, image: Image.Image) -> str: def serialize_image(self, image: Image.Image) -> str:
s_px = image.load() s_px = image.load()
assert s_px is not None
return "\n".join( return "\n".join(
" ".join(f"{s_px[x, y]:02x}" for x in range(image.size[0])) " ".join(f"{s_px[x, y]:02x}" for x in range(image.size[0]))
for y in range(image.size[1]) for y in range(image.size[1])
@ -235,11 +239,14 @@ class TestCoreResampleConsistency:
self, mode: str, fill: tuple[int, int, int] | float self, mode: str, fill: tuple[int, int, int] | float
) -> tuple[Image.Image, tuple[int, ...]]: ) -> tuple[Image.Image, tuple[int, ...]]:
im = Image.new(mode, (512, 9), fill) im = Image.new(mode, (512, 9), fill)
return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0] px = im.load()
assert px is not None
return im.resize((9, 512), Image.Resampling.LANCZOS), px[0, 0]
def run_case(self, case: tuple[Image.Image, int | tuple[int, ...]]) -> None: def run_case(self, case: tuple[Image.Image, int | tuple[int, ...]]) -> None:
channel, color = case channel, color = case
px = channel.load() px = channel.load()
assert px is not None
for x in range(channel.size[0]): for x in range(channel.size[0]):
for y in range(channel.size[1]): for y in range(channel.size[1]):
if px[x, y] != color: if px[x, y] != color:
@ -271,6 +278,7 @@ class TestCoreResampleAlphaCorrect:
def make_levels_case(self, mode: str) -> Image.Image: def make_levels_case(self, mode: str) -> Image.Image:
i = Image.new(mode, (256, 16)) i = Image.new(mode, (256, 16))
px = i.load() px = i.load()
assert px is not None
for y in range(i.size[1]): for y in range(i.size[1]):
for x in range(i.size[0]): for x in range(i.size[0]):
pix = [x] * len(mode) pix = [x] * len(mode)
@ -280,6 +288,7 @@ class TestCoreResampleAlphaCorrect:
def run_levels_case(self, i: Image.Image) -> None: def run_levels_case(self, i: Image.Image) -> None:
px = i.load() px = i.load()
assert px is not None
for y in range(i.size[1]): for y in range(i.size[1]):
used_colors = {px[x, y][0] for x in range(i.size[0])} used_colors = {px[x, y][0] for x in range(i.size[0])}
assert 256 == len(used_colors), ( assert 256 == len(used_colors), (
@ -310,6 +319,7 @@ class TestCoreResampleAlphaCorrect:
) -> Image.Image: ) -> Image.Image:
i = Image.new(mode, (64, 64), dirty_pixel) i = Image.new(mode, (64, 64), dirty_pixel)
px = i.load() px = i.load()
assert px is not None
xdiv4 = i.size[0] // 4 xdiv4 = i.size[0] // 4
ydiv4 = i.size[1] // 4 ydiv4 = i.size[1] // 4
for y in range(ydiv4 * 2): for y in range(ydiv4 * 2):
@ -319,6 +329,7 @@ class TestCoreResampleAlphaCorrect:
def run_dirty_case(self, i: Image.Image, clean_pixel: tuple[int, ...]) -> None: def run_dirty_case(self, i: Image.Image, clean_pixel: tuple[int, ...]) -> None:
px = i.load() px = i.load()
assert px is not None
for y in range(i.size[1]): for y in range(i.size[1]):
for x in range(i.size[0]): for x in range(i.size[0]):
if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel: if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel:
@ -406,6 +417,7 @@ class TestCoreResampleCoefficients:
draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color) draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color)
px = i.resize((5, i.size[1]), Image.Resampling.BICUBIC).load() px = i.resize((5, i.size[1]), Image.Resampling.BICUBIC).load()
assert px is not None
if px[2, 0] != test_color // 2: if px[2, 0] != test_color // 2:
assert test_color // 2 == px[2, 0] assert test_color // 2 == px[2, 0]

View File

@ -165,10 +165,14 @@ def test_pad() -> None:
def test_pad_round() -> None: def test_pad_round() -> None:
im = Image.new("1", (1, 1), 1) im = Image.new("1", (1, 1), 1)
new_im = ImageOps.pad(im, (4, 1)) new_im = ImageOps.pad(im, (4, 1))
assert new_im.load()[2, 0] == 1 px = new_im.load()
assert px is not None
assert px[2, 0] == 1
new_im = ImageOps.pad(im, (1, 4)) new_im = ImageOps.pad(im, (1, 4))
assert new_im.load()[0, 2] == 1 px = new_im.load()
assert px is not None
assert px[0, 2] == 1
@pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("mode", ("P", "PA"))
@ -223,6 +227,7 @@ def test_expand_palette(border: int | tuple[int, int, int, int]) -> None:
else: else:
left, top, right, bottom = border left, top, right, bottom = border
px = im_expanded.convert("RGB").load() px = im_expanded.convert("RGB").load()
assert px is not None
for x in range(im_expanded.width): for x in range(im_expanded.width):
for b in range(top): for b in range(top):
assert px[x, b] == (255, 0, 0) assert px[x, b] == (255, 0, 0)

View File

@ -70,6 +70,11 @@ def test_photoimage(mode: str) -> None:
reloaded = ImageTk.getimage(im_tk) reloaded = ImageTk.getimage(im_tk)
assert_image_equal(reloaded, im.convert("RGBA")) assert_image_equal(reloaded, im.convert("RGBA"))
with pytest.raises(ValueError):
ImageTk.PhotoImage()
with pytest.raises(ValueError):
ImageTk.PhotoImage(mode)
def test_photoimage_apply_transparency() -> None: def test_photoimage_apply_transparency() -> None:
with Image.open("Tests/images/pil123p.png") as im: with Image.open("Tests/images/pil123p.png") as im:

View File

@ -16,6 +16,8 @@ def verify(im1: Image.Image) -> None:
assert im1.size == im2.size assert im1.size == im2.size
pix1 = im1.load() pix1 = im1.load()
pix2 = im2.load() pix2 = im2.load()
assert pix1 is not None
assert pix2 is not None
for y in range(im1.size[1]): for y in range(im1.size[1]):
for x in range(im1.size[0]): for x in range(im1.size[0]):
xy = x, y xy = x, y

View File

@ -109,6 +109,7 @@ def _test_img_equals_nparray(img: Image.Image, np_img: _typing.NumpyArray) -> No
np_size = np_img.shape[1], np_img.shape[0] np_size = np_img.shape[1], np_img.shape[0]
assert img.size == np_size assert img.size == np_size
px = img.load() px = img.load()
assert px is not None
for x in range(0, img.size[0], int(img.size[0] / 10)): for x in range(0, img.size[0], int(img.size[0] / 10)):
for y in range(0, img.size[1], int(img.size[1] / 10)): for y in range(0, img.size[1], int(img.size[1] / 10)):
assert_deep_equal(px[x, y], np_img[y, x]) assert_deep_equal(px[x, y], np_img[y, x])
@ -141,6 +142,7 @@ def test_save_tiff_uint16() -> None:
img = Image.fromarray(a) img = Image.fromarray(a)
img_px = img.load() img_px = img.load()
assert img_px is not None
assert img_px[0, 0] == pixel_value assert img_px[0, 0] == pixel_value

View File

@ -53,7 +53,7 @@ class FpxImageFile(ImageFile.ImageFile):
format = "FPX" format = "FPX"
format_description = "FlashPix" format_description = "FlashPix"
def _open(self): def _open(self) -> None:
# #
# read the OLE directory and see if this is a likely # read the OLE directory and see if this is a likely
# to be a FlashPix file # to be a FlashPix file
@ -64,7 +64,8 @@ class FpxImageFile(ImageFile.ImageFile):
msg = "not an FPX file; invalid OLE file" msg = "not an FPX file; invalid OLE file"
raise SyntaxError(msg) from e raise SyntaxError(msg) from e
if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B": root = self.ole.root
if not root or root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
msg = "not an FPX file; bad root CLSID" msg = "not an FPX file; bad root CLSID"
raise SyntaxError(msg) raise SyntaxError(msg)
@ -99,8 +100,7 @@ class FpxImageFile(ImageFile.ImageFile):
s = prop[0x2000002 | id] s = prop[0x2000002 | id]
bands = i32(s, 4) if not isinstance(s, bytes) or (bands := i32(s, 4)) > 4:
if bands > 4:
msg = "Invalid number of bands" msg = "Invalid number of bands"
raise OSError(msg) raise OSError(msg)

View File

@ -221,7 +221,7 @@ if hasattr(core, "DEFAULT_STRATEGY"):
# Registries # Registries
if TYPE_CHECKING: if TYPE_CHECKING:
from . import ImageFile from . import ImageFile, ImagePalette
ID: list[str] = [] ID: list[str] = []
OPEN: dict[ OPEN: dict[
str, str,
@ -1167,7 +1167,7 @@ class Image:
colors: int = 256, colors: int = 256,
method: int | None = None, method: int | None = None,
kmeans: int = 0, kmeans: int = 0,
palette=None, palette: Image | None = None,
dither: Dither = Dither.FLOYDSTEINBERG, dither: Dither = Dither.FLOYDSTEINBERG,
) -> Image: ) -> Image:
""" """
@ -1239,8 +1239,8 @@ class Image:
from . import ImagePalette from . import ImagePalette
mode = im.im.getpalettemode() mode = im.im.getpalettemode()
palette = im.im.getpalette(mode, mode)[: colors * len(mode)] palette_data = im.im.getpalette(mode, mode)[: colors * len(mode)]
im.palette = ImagePalette.ImagePalette(mode, palette) im.palette = ImagePalette.ImagePalette(mode, palette_data)
return im return im
@ -1395,7 +1395,9 @@ class Image:
self.load() self.load()
return self.im.getbbox(alpha_only) return self.im.getbbox(alpha_only)
def getcolors(self, maxcolors: int = 256): def getcolors(
self, maxcolors: int = 256
) -> list[tuple[int, int]] | list[tuple[int, float]] | None:
""" """
Returns a list of colors used in this image. Returns a list of colors used in this image.
@ -1456,7 +1458,7 @@ class Image:
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands)) return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
return self.im.getextrema() return self.im.getextrema()
def getxmp(self): def getxmp(self) -> dict[str, Any]:
""" """
Returns a dictionary containing the XMP tags. Returns a dictionary containing the XMP tags.
Requires defusedxml to be installed. Requires defusedxml to be installed.
@ -2017,7 +2019,11 @@ class Image:
self.im.putdata(data, scale, offset) self.im.putdata(data, scale, offset)
def putpalette(self, data, rawmode="RGB") -> None: def putpalette(
self,
data: ImagePalette.ImagePalette | bytes | Sequence[int],
rawmode: str = "RGB",
) -> None:
""" """
Attaches a palette to this image. The image must be a "P", "PA", "L" Attaches a palette to this image. The image must be a "P", "PA", "L"
or "LA" image. or "LA" image.
@ -2093,7 +2099,9 @@ class Image:
value = (palette_index, alpha) if self.mode == "PA" else palette_index value = (palette_index, alpha) if self.mode == "PA" else palette_index
return self.im.putpixel(xy, value) return self.im.putpixel(xy, value)
def remap_palette(self, dest_map, source_palette=None): def remap_palette(
self, dest_map: list[int], source_palette: bytes | bytearray | None = None
) -> Image:
""" """
Rewrites the image to reorder the palette. Rewrites the image to reorder the palette.
@ -3532,7 +3540,7 @@ def composite(image1: Image, image2: Image, mask: Image) -> Image:
return image return image
def eval(image, *args): def eval(image: Image, *args: Callable[[int], float]) -> Image:
""" """
Applies the function (which should take one argument) to each pixel Applies the function (which should take one argument) to each pixel
in the given image. If the image has more than one band, the same in the given image. If the image has more than one band, the same

View File

@ -361,7 +361,9 @@ def pad(
else: else:
out = Image.new(image.mode, size, color) out = Image.new(image.mode, size, color)
if resized.palette: if resized.palette:
out.putpalette(resized.getpalette()) palette = resized.getpalette()
if palette is not None:
out.putpalette(palette)
if resized.width != size[0]: if resized.width != size[0]:
x = round((size[0] - resized.width) * max(0, min(centering[0], 1))) x = round((size[0] - resized.width) * max(0, min(centering[0], 1)))
out.paste(resized, (x, 0)) out.paste(resized, (x, 0))

View File

@ -28,8 +28,9 @@ from __future__ import annotations
import tkinter import tkinter
from io import BytesIO from io import BytesIO
from typing import Any
from . import Image from . import Image, ImageFile
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Check for Tkinter interface hooks # Check for Tkinter interface hooks
@ -49,14 +50,15 @@ def _pilbitmap_check() -> int:
return _pilbitmap_ok return _pilbitmap_ok
def _get_image_from_kw(kw): def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None:
source = None source = None
if "file" in kw: if "file" in kw:
source = kw.pop("file") source = kw.pop("file")
elif "data" in kw: elif "data" in kw:
source = BytesIO(kw.pop("data")) source = BytesIO(kw.pop("data"))
if source: if not source:
return Image.open(source) return None
return Image.open(source)
def _pyimagingtkcall(command, photo, id): def _pyimagingtkcall(command, photo, id):
@ -96,12 +98,27 @@ class PhotoImage:
image file). image file).
""" """
def __init__(self, image=None, size=None, **kw): def __init__(
self,
image: Image.Image | str | None = None,
size: tuple[int, int] | None = None,
**kw: Any,
) -> None:
# Tk compatibility: file or data # Tk compatibility: file or data
if image is None: if image is None:
image = _get_image_from_kw(kw) image = _get_image_from_kw(kw)
if hasattr(image, "mode") and hasattr(image, "size"): if image is None:
msg = "Image is required"
raise ValueError(msg)
elif isinstance(image, str):
mode = image
image = None
if size is None:
msg = "If first argument is mode, size is required"
raise ValueError(msg)
else:
# got an image instead of a mode # got an image instead of a mode
mode = image.mode mode = image.mode
if mode == "P": if mode == "P":
@ -114,9 +131,6 @@ class PhotoImage:
mode = "RGB" # default mode = "RGB" # default
size = image.size size = image.size
kw["width"], kw["height"] = size kw["width"], kw["height"] = size
else:
mode = image
image = None
if mode not in ["1", "L", "RGB", "RGBA"]: if mode not in ["1", "L", "RGB", "RGBA"]:
mode = Image.getmodebase(mode) mode = Image.getmodebase(mode)

View File

@ -70,7 +70,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self.__fp = self.fp self.__fp = self.fp
self.seek(0) self.seek(0)
def seek(self, frame): def seek(self, frame: int) -> None:
if not self._seek_check(frame): if not self._seek_check(frame):
return return
try: try: