Merge pull request #8250 from radarhere/type_hint

Added type hints
This commit is contained in:
mergify[bot] 2024-07-20 19:48:05 +00:00 committed by GitHub
commit 8405412b76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 84 additions and 65 deletions

View File

@ -240,9 +240,10 @@ class TestFileLibTiff(LibTiffTestCase):
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))

View File

@ -37,6 +37,11 @@ Example: Parse an image
Classes
-------
.. autoclass:: PIL.ImageFile._Tile()
:member-order: bysource
:members:
:show-inheritance:
.. autoclass:: PIL.ImageFile.Parser()
:members:

View File

@ -90,7 +90,7 @@ class Dib:
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.
@ -101,19 +101,18 @@ class Dib:
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.
@ -128,14 +127,13 @@ class Dib:
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.
@ -147,8 +145,8 @@ class Dib:
: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)
@ -210,22 +208,22 @@ class Window:
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:
@ -235,12 +233,12 @@ class Window:
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))

View File

@ -19,6 +19,7 @@ from __future__ import annotations
import io
from functools import cached_property
from typing import IO
from . import Image, ImageFile, ImagePalette
from ._binary import i8
@ -142,7 +143,9 @@ class PsdImageFile(ImageFile.ImageFile):
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)
@ -181,7 +184,9 @@ class PsdImageFile(ImageFile.ImageFile):
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 = []
@ -203,7 +208,7 @@ def _layerinfo(fp, ct_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)
@ -215,23 +220,23 @@ def _layerinfo(fp, ct_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
@ -258,19 +263,22 @@ def _layerinfo(fp, ct_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))
@ -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
@ -312,7 +320,7 @@ def _maketile(file, mode, bbox, channels):
if offset & 1:
read(1) # padding
return tile
return tiles
# --------------------------------------------------------------------

View File

@ -445,7 +445,7 @@ class IFDRational(Rational):
__int__ = _delegate("__int__")
def _register_loader(idx, size):
def _register_loader(idx: int, size: int):
def decorator(func):
from .TiffTags import TYPES
@ -457,7 +457,7 @@ def _register_loader(idx, size):
return decorator
def _register_writer(idx):
def _register_writer(idx: int):
def decorator(func):
_write_dispatch[idx] = func # noqa: F821
return func
@ -465,7 +465,7 @@ def _register_writer(idx):
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
@ -640,7 +640,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
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:
@ -731,10 +731,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
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(
@ -755,7 +755,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
)
@_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.
@ -767,7 +767,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
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")
@ -797,7 +797,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
)
@_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)
@ -809,7 +809,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
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):
@ -1030,7 +1030,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
"""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
@ -1073,7 +1073,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
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)
@ -1212,7 +1212,7 @@ class TiffImageFile(ImageFile.ImageFile):
"""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
@ -1259,7 +1259,7 @@ class TiffImageFile(ImageFile.ImageFile):
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"""

View File

@ -32,17 +32,24 @@ class _TagInfo(NamedTuple):
class TagInfo(_TagInfo):
__slots__: list[str] = []
def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None):
def __new__(
cls,
value: int | None = None,
name: str = "unknown",
type: int | None = None,
length: int | None = None,
enum: dict[str, int] | None = None,
) -> TagInfo:
return super().__new__(cls, value, name, type, length, enum or {})
def cvt_enum(self, value):
def cvt_enum(self, value: str) -> int | str:
# Using get will call hash(value), which can be expensive
# for some types (e.g. Fraction). Since self.enum is rarely
# used, it's usually better to test it first.
return self.enum.get(value, value) if self.enum else value
def lookup(tag, group=None):
def lookup(tag: int, group: int | None = None) -> TagInfo:
"""
:param tag: Integer tag number
:param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
@ -89,7 +96,7 @@ DOUBLE = 12
IFD = 13
LONG8 = 16
_tags_v2 = {
_tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] = {
254: ("NewSubfileType", LONG, 1),
255: ("SubfileType", SHORT, 1),
256: ("ImageWidth", LONG, 1),
@ -233,7 +240,7 @@ _tags_v2 = {
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006
}
TAGS_V2_GROUPS = {
_tags_v2_groups = {
# ExifIFD
34665: {
36864: ("ExifVersion", UNDEFINED, 1),
@ -281,7 +288,7 @@ TAGS_V2_GROUPS = {
# Legacy Tags structure
# these tags aren't included above, but were in the previous versions
TAGS = {
TAGS: dict[int | tuple[int, int], str] = {
347: "JPEGTables",
700: "XMP",
# Additional Exif Info
@ -426,9 +433,10 @@ TAGS = {
}
TAGS_V2: dict[int, TagInfo] = {}
TAGS_V2_GROUPS: dict[int, dict[int, TagInfo]] = {}
def _populate():
def _populate() -> None:
for k, v in _tags_v2.items():
# Populate legacy structure.
TAGS[k] = v[0]
@ -438,9 +446,8 @@ def _populate():
TAGS_V2[k] = TagInfo(k, *v)
for tags in TAGS_V2_GROUPS.values():
for k, v in tags.items():
tags[k] = TagInfo(k, *v)
for group, tags in _tags_v2_groups.items():
TAGS_V2_GROUPS[group] = {k: TagInfo(k, *v) for k, v in tags.items()}
_populate()