Skip to content

Commit

Permalink
Merge pull request #8250 from radarhere/type_hint
Browse files Browse the repository at this point in the history
Added type hints
  • Loading branch information
mergify[bot] committed Jul 20, 2024
2 parents 906b8af + 1cf887d commit 8405412
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 65 deletions.
3 changes: 2 additions & 1 deletion Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,10 @@ def test_additional_metadata(

new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
for tag, info in core_items.items():
assert info.type is not None
if info.length == 1:
new_ifd[tag] = values[info.type]
if info.length == 0:
elif not info.length:
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
else:
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/ImageFile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ Example: Parse an image
Classes
-------

.. autoclass:: PIL.ImageFile._Tile()
:member-order: bysource
:members:
:show-inheritance:

.. autoclass:: PIL.ImageFile.Parser()
:members:

Expand Down
38 changes: 18 additions & 20 deletions src/PIL/ImageWin.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def __init__(
assert not isinstance(image, str)
self.paste(image)

def expose(self, handle):
def expose(self, handle: int | HDC | HWND) -> None:
"""
Copy the bitmap contents to a device context.
Expand All @@ -101,19 +101,18 @@ def expose(self, handle):
if isinstance(handle, HWND):
dc = self.image.getdc(handle)
try:
result = self.image.expose(dc)
self.image.expose(dc)
finally:
self.image.releasedc(handle, dc)
else:
result = self.image.expose(handle)
return result
self.image.expose(handle)

def draw(
self,
handle,
handle: int | HDC | HWND,
dst: tuple[int, int, int, int],
src: tuple[int, int, int, int] | None = None,
):
) -> None:
"""
Same as expose, but allows you to specify where to draw the image, and
what part of it to draw.
Expand All @@ -128,14 +127,13 @@ def draw(
if isinstance(handle, HWND):
dc = self.image.getdc(handle)
try:
result = self.image.draw(dc, dst, src)
self.image.draw(dc, dst, src)
finally:
self.image.releasedc(handle, dc)
else:
result = self.image.draw(handle, dst, src)
return result
self.image.draw(handle, dst, src)

def query_palette(self, handle):
def query_palette(self, handle: int | HDC | HWND) -> int:
"""
Installs the palette associated with the image in the given device
context.
Expand All @@ -147,8 +145,8 @@ def query_palette(self, handle):
:param handle: Device context (HDC), cast to a Python integer, or an
HDC or HWND instance.
:return: A true value if one or more entries were changed (this
indicates that the image should be redrawn).
:return: The number of entries that were changed (if one or more entries,
this indicates that the image should be redrawn).
"""
if isinstance(handle, HWND):
handle = self.image.getdc(handle)
Expand Down Expand Up @@ -210,22 +208,22 @@ def __init__(
title, self.__dispatcher, width or 0, height or 0
)

def __dispatcher(self, action: str, *args):
return getattr(self, f"ui_handle_{action}")(*args)
def __dispatcher(self, action: str, *args: int) -> None:
getattr(self, f"ui_handle_{action}")(*args)

def ui_handle_clear(self, dc, x0, y0, x1, y1) -> None:
def ui_handle_clear(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
pass

def ui_handle_damage(self, x0, y0, x1, y1) -> None:
def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None:
pass

def ui_handle_destroy(self) -> None:
pass

def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
pass

def ui_handle_resize(self, width, height) -> None:
def ui_handle_resize(self, width: int, height: int) -> None:
pass

def mainloop(self) -> None:
Expand All @@ -235,12 +233,12 @@ def mainloop(self) -> None:
class ImageWindow(Window):
"""Create an image window which displays the given image."""

def __init__(self, image, title: str = "PIL") -> None:
def __init__(self, image: Image.Image | Dib, title: str = "PIL") -> None:
if not isinstance(image, Dib):
image = Dib(image)
self.image = image
width, height = image.size
super().__init__(title, width=width, height=height)

def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
self.image.draw(dc, (x0, y0, x1, y1))
48 changes: 28 additions & 20 deletions src/PIL/PsdImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import io
from functools import cached_property
from typing import IO

from . import Image, ImageFile, ImagePalette
from ._binary import i8
Expand Down Expand Up @@ -142,7 +143,9 @@ def _open(self) -> None:
self._min_frame = 1

@cached_property
def layers(self):
def layers(
self,
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
layers = []
if self._layers_position is not None:
self._fp.seek(self._layers_position)
Expand Down Expand Up @@ -181,7 +184,9 @@ def tell(self) -> int:
return self.frame


def _layerinfo(fp, ct_bytes):
def _layerinfo(
fp: IO[bytes], ct_bytes: int
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
# read layerinfo block
layers = []

Expand All @@ -203,7 +208,7 @@ def read(size: int) -> bytes:
x1 = si32(read(4))

# image info
mode = []
bands = []
ct_types = i16(read(2))
if ct_types > 4:
fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
Expand All @@ -215,23 +220,23 @@ def read(size: int) -> bytes:
type = i16(read(2))

if type == 65535:
m = "A"
b = "A"
else:
m = "RGBA"[type]
b = "RGBA"[type]

mode.append(m)
bands.append(b)
read(4) # size

# figure out the image mode
mode.sort()
if mode == ["R"]:
bands.sort()
if bands == ["R"]:
mode = "L"
elif mode == ["B", "G", "R"]:
elif bands == ["B", "G", "R"]:
mode = "RGB"
elif mode == ["A", "B", "G", "R"]:
elif bands == ["A", "B", "G", "R"]:
mode = "RGBA"
else:
mode = None # unknown
mode = "" # unknown

# skip over blend flags and extra information
read(12) # filler
Expand All @@ -258,19 +263,22 @@ def read(size: int) -> bytes:
layers.append((name, mode, (x0, y0, x1, y1)))

# get tiles
layerinfo = []
for i, (name, mode, bbox) in enumerate(layers):
tile = []
for m in mode:
t = _maketile(fp, m, bbox, 1)
if t:
tile.extend(t)
layers[i] = name, mode, bbox, tile
layerinfo.append((name, mode, bbox, tile))

return layers
return layerinfo


def _maketile(file, mode, bbox, channels):
tile = None
def _maketile(
file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
) -> list[ImageFile._Tile] | None:
tiles = None
read = file.read

compression = i16(read(2))
Expand All @@ -283,26 +291,26 @@ def _maketile(file, mode, bbox, channels):
if compression == 0:
#
# raw compression
tile = []
tiles = []
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer += ";I"
tile.append(("raw", bbox, offset, layer))
tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
offset = offset + xsize * ysize

elif compression == 1:
#
# packbits compression
i = 0
tile = []
tiles = []
bytecount = read(channels * ysize * 2)
offset = file.tell()
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer += ";I"
tile.append(("packbits", bbox, offset, layer))
tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
for y in range(ysize):
offset = offset + i16(bytecount, i)
i += 2
Expand All @@ -312,7 +320,7 @@ def _maketile(file, mode, bbox, channels):
if offset & 1:
read(1) # padding

return tile
return tiles


# --------------------------------------------------------------------
Expand Down
28 changes: 14 additions & 14 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ def __setstate__(self, state):
__int__ = _delegate("__int__")


def _register_loader(idx, size):
def _register_loader(idx: int, size: int):
def decorator(func):
from .TiffTags import TYPES

Expand All @@ -457,15 +457,15 @@ def decorator(func):
return decorator


def _register_writer(idx):
def _register_writer(idx: int):
def decorator(func):
_write_dispatch[idx] = func # noqa: F821
return func

return decorator


def _register_basic(idx_fmt_name):
def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None:
from .TiffTags import TYPES

idx, fmt, name = idx_fmt_name
Expand Down Expand Up @@ -640,7 +640,7 @@ def __getitem__(self, tag):
def __contains__(self, tag: object) -> bool:
return tag in self._tags_v2 or tag in self._tagdata

def __setitem__(self, tag, value) -> None:
def __setitem__(self, tag: int, value) -> None:
self._setitem(tag, value, self.legacy_api)

def _setitem(self, tag, value, legacy_api) -> None:
Expand Down Expand Up @@ -731,10 +731,10 @@ def __delitem__(self, tag: int) -> None:
def __iter__(self):
return iter(set(self._tagdata) | set(self._tags_v2))

def _unpack(self, fmt, data):
def _unpack(self, fmt: str, data):
return struct.unpack(self._endian + fmt, data)

def _pack(self, fmt, *values):
def _pack(self, fmt: str, *values):
return struct.pack(self._endian + fmt, *values)

list(
Expand All @@ -755,7 +755,7 @@ def _pack(self, fmt, *values):
)

@_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api=True):
def load_byte(self, data, legacy_api: bool = True):
return data

@_register_writer(1) # Basic type, except for the legacy API.
Expand All @@ -767,7 +767,7 @@ def write_byte(self, data) -> bytes:
return data

@_register_loader(2, 1)
def load_string(self, data, legacy_api=True):
def load_string(self, data: bytes, legacy_api: bool = True) -> str:
if data.endswith(b"\0"):
data = data[:-1]
return data.decode("latin-1", "replace")
Expand Down Expand Up @@ -797,7 +797,7 @@ def write_rational(self, *values) -> bytes:
)

@_register_loader(7, 1)
def load_undefined(self, data, legacy_api=True):
def load_undefined(self, data, legacy_api: bool = True):
return data

@_register_writer(7)
Expand All @@ -809,7 +809,7 @@ def write_undefined(self, value) -> bytes:
return value

@_register_loader(10, 8)
def load_signed_rational(self, data, legacy_api=True):
def load_signed_rational(self, data, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}l", data)

def combine(a, b):
Expand Down Expand Up @@ -1030,7 +1030,7 @@ def __init__(self, *args, **kwargs) -> None:
"""Dictionary of tag types"""

@classmethod
def from_v2(cls, original) -> ImageFileDirectory_v1:
def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1:
"""Returns an
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
instance with the same data as is contained in the original
Expand Down Expand Up @@ -1073,7 +1073,7 @@ def __len__(self) -> int:
def __iter__(self):
return iter(set(self._tagdata) | set(self._tags_v1))

def __setitem__(self, tag, value) -> None:
def __setitem__(self, tag: int, value) -> None:
for legacy_api in (False, True):
self._setitem(tag, value, legacy_api)

Expand Down Expand Up @@ -1212,7 +1212,7 @@ def tell(self) -> int:
"""Return the current frame number"""
return self.__frame

def get_photoshop_blocks(self):
def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]:
"""
Returns a dictionary of Photoshop "Image Resource Blocks".
The keys are the image resource ID. For more information, see
Expand Down Expand Up @@ -1259,7 +1259,7 @@ def load_end(self) -> None:
if ExifTags.Base.Orientation in self.tag_v2:
del self.tag_v2[ExifTags.Base.Orientation]

def _load_libtiff(self):
def _load_libtiff(self) -> Image.core.PixelAccess | None:
"""Overload method triggered when we detect a compressed tiff
Calls out to libtiff"""

Expand Down
Loading

0 comments on commit 8405412

Please sign in to comment.