Added type hints

This commit is contained in:
Andrew Murray 2024-07-26 16:42:28 +10:00
parent 7bd28952f1
commit 046285ac5d
6 changed files with 53 additions and 41 deletions

View File

@ -57,6 +57,7 @@ def test_getiptcinfo_fotostation() -> None:
iptc = IptcImagePlugin.getiptcinfo(im)
# Assert
assert iptc is not None
for tag in iptc.keys():
if tag[0] == 240:
return

View File

@ -468,40 +468,40 @@ def _getencoder(
class _E:
def __init__(self, scale, offset) -> None:
def __init__(self, scale: float, offset: float) -> None:
self.scale = scale
self.offset = offset
def __neg__(self) -> _E:
return _E(-self.scale, -self.offset)
def __add__(self, other) -> _E:
def __add__(self, other: _E | float) -> _E:
if isinstance(other, _E):
return _E(self.scale + other.scale, self.offset + other.offset)
return _E(self.scale, self.offset + other)
__radd__ = __add__
def __sub__(self, other):
def __sub__(self, other: _E | float) -> _E:
return self + -other
def __rsub__(self, other):
def __rsub__(self, other: _E | float) -> _E:
return other + -self
def __mul__(self, other) -> _E:
def __mul__(self, other: _E | float) -> _E:
if isinstance(other, _E):
return NotImplemented
return _E(self.scale * other, self.offset * other)
__rmul__ = __mul__
def __truediv__(self, other) -> _E:
def __truediv__(self, other: _E | float) -> _E:
if isinstance(other, _E):
return NotImplemented
return _E(self.scale / other, self.offset / other)
def _getscaleoffset(expr):
def _getscaleoffset(expr) -> tuple[float, float]:
a = expr(_E(1, 0))
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)

View File

@ -24,10 +24,10 @@
"""
from __future__ import annotations
from typing import BinaryIO
from typing import AnyStr, BinaryIO
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from ._typing import StrOrBytesPath
from ._typing import Coords, StrOrBytesPath
class Pen:
@ -74,12 +74,14 @@ class Draw:
image = Image.new(image, size, color)
self.draw = ImageDraw.Draw(image)
self.image = image
self.transform = None
self.transform: tuple[float, float, float, float, float, float] | None = None
def flush(self) -> Image.Image:
return self.image
def render(self, op, xy, pen, brush=None):
def render(
self, op: str, xy: Coords, pen: Pen | Brush, brush: Brush | Pen | None = None
) -> None:
# handle color arguments
outline = fill = None
width = 1
@ -95,20 +97,21 @@ class Draw:
fill = pen.color
# handle transformation
if self.transform:
xy = ImagePath.Path(xy)
xy.transform(self.transform)
path = ImagePath.Path(xy)
path.transform(self.transform)
xy = path
# render the item
if op == "line":
self.draw.line(xy, fill=outline, width=width)
else:
getattr(self.draw, op)(xy, fill=fill, outline=outline)
def settransform(self, offset):
def settransform(self, offset: tuple[float, float]) -> None:
"""Sets a transformation offset."""
(xoffset, yoffset) = offset
self.transform = (1, 0, xoffset, 0, 1, yoffset)
def arc(self, xy, start, end, *options):
def arc(self, xy: Coords, start, end, *options) -> None:
"""
Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box.
@ -117,7 +120,7 @@ class Draw:
"""
self.render("arc", xy, start, end, *options)
def chord(self, xy, start, end, *options):
def chord(self, xy: Coords, start, end, *options) -> None:
"""
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
with a straight line.
@ -126,7 +129,7 @@ class Draw:
"""
self.render("chord", xy, start, end, *options)
def ellipse(self, xy, *options):
def ellipse(self, xy: Coords, *options) -> None:
"""
Draws an ellipse inside the given bounding box.
@ -134,7 +137,7 @@ class Draw:
"""
self.render("ellipse", xy, *options)
def line(self, xy, *options):
def line(self, xy: Coords, *options) -> None:
"""
Draws a line between the coordinates in the ``xy`` list.
@ -142,7 +145,7 @@ class Draw:
"""
self.render("line", xy, *options)
def pieslice(self, xy, start, end, *options):
def pieslice(self, xy: Coords, start, end, *options) -> None:
"""
Same as arc, but also draws straight lines between the end points and the
center of the bounding box.
@ -151,7 +154,7 @@ class Draw:
"""
self.render("pieslice", xy, start, end, *options)
def polygon(self, xy, *options):
def polygon(self, xy: Coords, *options) -> None:
"""
Draws a polygon.
@ -164,7 +167,7 @@ class Draw:
"""
self.render("polygon", xy, *options)
def rectangle(self, xy, *options):
def rectangle(self, xy: Coords, *options) -> None:
"""
Draws a rectangle.
@ -172,18 +175,21 @@ class Draw:
"""
self.render("rectangle", xy, *options)
def text(self, xy, text, font):
def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None:
"""
Draws the string at the given position.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
"""
if self.transform:
xy = ImagePath.Path(xy)
xy.transform(self.transform)
path = ImagePath.Path(xy)
path.transform(self.transform)
xy = path
self.draw.text(xy, text, font=font.font, fill=font.color)
def textbbox(self, xy, text, font):
def textbbox(
self, xy: tuple[float, float], text: AnyStr, font: Font
) -> tuple[float, float, float, float]:
"""
Returns bounding box (in pixels) of given text.
@ -192,11 +198,12 @@ class Draw:
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
"""
if self.transform:
xy = ImagePath.Path(xy)
xy.transform(self.transform)
path = ImagePath.Path(xy)
path.transform(self.transform)
xy = path
return self.draw.textbbox(xy, text, font=font.font)
def textlength(self, text, font):
def textlength(self, text: AnyStr, font: Font) -> float:
"""
Returns length (in pixels) of given text.
This is the amount by which following text should be offset.

View File

@ -58,7 +58,7 @@ else:
qt_version = None
def rgb(r, g, b, a=255):
def rgb(r: int, g: int, b: int, a: int = 255) -> int:
"""(Internal) Turns an RGB color into a Qt compatible color integer."""
# use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern.

View File

@ -185,7 +185,9 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
Image.register_extension(IptcImageFile.format, ".iim")
def getiptcinfo(im: ImageFile.ImageFile):
def getiptcinfo(
im: ImageFile.ImageFile,
) -> dict[tuple[int, int], bytes | list[bytes]] | None:
"""
Get IPTC information from TIFF, JPEG, or IPTC file.

View File

@ -584,8 +584,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
self.tagtype: dict[int, int] = {}
""" Dictionary of tag types """
self.reset()
(self.next,) = (
self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:])
self.next = (
self._unpack("Q", ifh[8:])[0]
if self._bigtiff
else self._unpack("L", ifh[4:])[0]
)
self._legacy_api = False
@ -643,7 +645,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
def __setitem__(self, tag: int, value) -> None:
self._setitem(tag, value, self.legacy_api)
def _setitem(self, tag, value, legacy_api) -> None:
def _setitem(self, tag: int, value, legacy_api: bool) -> None:
basetypes = (Number, bytes, str)
info = TiffTags.lookup(tag, self.group)
@ -731,7 +733,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
def __iter__(self):
return iter(set(self._tagdata) | set(self._tags_v2))
def _unpack(self, fmt: str, data):
def _unpack(self, fmt: str, data: bytes):
return struct.unpack(self._endian + fmt, data)
def _pack(self, fmt: str, *values):
@ -755,11 +757,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
)
@_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api: bool = True):
def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes:
return data
@_register_writer(1) # Basic type, except for the legacy API.
def write_byte(self, data) -> bytes:
def write_byte(self, data: bytes | int | IFDRational) -> bytes:
if isinstance(data, IFDRational):
data = int(data)
if isinstance(data, int):
@ -773,7 +775,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return data.decode("latin-1", "replace")
@_register_writer(2)
def write_string(self, value) -> bytes:
def write_string(self, value: str | bytes | int) -> bytes:
# remerge of https://github.com/python-pillow/Pillow/pull/1416
if isinstance(value, int):
value = str(value)
@ -782,7 +784,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value + b"\0"
@_register_loader(5, 8)
def load_rational(self, data, legacy_api=True):
def load_rational(self, data, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}L", data)
def combine(a, b):
@ -797,11 +799,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
)
@_register_loader(7, 1)
def load_undefined(self, data, legacy_api: bool = True):
def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes:
return data
@_register_writer(7)
def write_undefined(self, value) -> bytes:
def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
if isinstance(value, IFDRational):
value = int(value)
if isinstance(value, int):
@ -809,7 +811,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value
@_register_loader(10, 8)
def load_signed_rational(self, data, legacy_api: bool = True):
def load_signed_rational(self, data: bytes, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}l", data)
def combine(a, b):