mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
Merge pull request #8263 from radarhere/type_hint
This commit is contained in:
commit
8f62fbdf44
|
@ -57,6 +57,7 @@ def test_getiptcinfo_fotostation() -> None:
|
||||||
iptc = IptcImagePlugin.getiptcinfo(im)
|
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
assert iptc is not None
|
||||||
for tag in iptc.keys():
|
for tag in iptc.keys():
|
||||||
if tag[0] == 240:
|
if tag[0] == 240:
|
||||||
return
|
return
|
||||||
|
|
|
@ -468,40 +468,40 @@ def _getencoder(
|
||||||
|
|
||||||
|
|
||||||
class _E:
|
class _E:
|
||||||
def __init__(self, scale, offset) -> None:
|
def __init__(self, scale: float, offset: float) -> None:
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
|
||||||
def __neg__(self) -> _E:
|
def __neg__(self) -> _E:
|
||||||
return _E(-self.scale, -self.offset)
|
return _E(-self.scale, -self.offset)
|
||||||
|
|
||||||
def __add__(self, other) -> _E:
|
def __add__(self, other: _E | float) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return _E(self.scale + other.scale, self.offset + other.offset)
|
return _E(self.scale + other.scale, self.offset + other.offset)
|
||||||
return _E(self.scale, self.offset + other)
|
return _E(self.scale, self.offset + other)
|
||||||
|
|
||||||
__radd__ = __add__
|
__radd__ = __add__
|
||||||
|
|
||||||
def __sub__(self, other):
|
def __sub__(self, other: _E | float) -> _E:
|
||||||
return self + -other
|
return self + -other
|
||||||
|
|
||||||
def __rsub__(self, other):
|
def __rsub__(self, other: _E | float) -> _E:
|
||||||
return other + -self
|
return other + -self
|
||||||
|
|
||||||
def __mul__(self, other) -> _E:
|
def __mul__(self, other: _E | float) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return _E(self.scale * other, self.offset * other)
|
return _E(self.scale * other, self.offset * other)
|
||||||
|
|
||||||
__rmul__ = __mul__
|
__rmul__ = __mul__
|
||||||
|
|
||||||
def __truediv__(self, other) -> _E:
|
def __truediv__(self, other: _E | float) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return _E(self.scale / other, self.offset / other)
|
return _E(self.scale / other, self.offset / other)
|
||||||
|
|
||||||
|
|
||||||
def _getscaleoffset(expr):
|
def _getscaleoffset(expr) -> tuple[float, float]:
|
||||||
a = expr(_E(1, 0))
|
a = expr(_E(1, 0))
|
||||||
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
|
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,10 @@
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import BinaryIO
|
from typing import AnyStr, BinaryIO
|
||||||
|
|
||||||
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||||
from ._typing import StrOrBytesPath
|
from ._typing import Coords, StrOrBytesPath
|
||||||
|
|
||||||
|
|
||||||
class Pen:
|
class Pen:
|
||||||
|
@ -74,12 +74,14 @@ class Draw:
|
||||||
image = Image.new(image, size, color)
|
image = Image.new(image, size, color)
|
||||||
self.draw = ImageDraw.Draw(image)
|
self.draw = ImageDraw.Draw(image)
|
||||||
self.image = image
|
self.image = image
|
||||||
self.transform = None
|
self.transform: tuple[float, float, float, float, float, float] | None = None
|
||||||
|
|
||||||
def flush(self) -> Image.Image:
|
def flush(self) -> Image.Image:
|
||||||
return self.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
|
# handle color arguments
|
||||||
outline = fill = None
|
outline = fill = None
|
||||||
width = 1
|
width = 1
|
||||||
|
@ -95,20 +97,21 @@ class Draw:
|
||||||
fill = pen.color
|
fill = pen.color
|
||||||
# handle transformation
|
# handle transformation
|
||||||
if self.transform:
|
if self.transform:
|
||||||
xy = ImagePath.Path(xy)
|
path = ImagePath.Path(xy)
|
||||||
xy.transform(self.transform)
|
path.transform(self.transform)
|
||||||
|
xy = path
|
||||||
# render the item
|
# render the item
|
||||||
if op == "line":
|
if op == "line":
|
||||||
self.draw.line(xy, fill=outline, width=width)
|
self.draw.line(xy, fill=outline, width=width)
|
||||||
else:
|
else:
|
||||||
getattr(self.draw, op)(xy, fill=fill, outline=outline)
|
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."""
|
"""Sets a transformation offset."""
|
||||||
(xoffset, yoffset) = offset
|
(xoffset, yoffset) = offset
|
||||||
self.transform = (1, 0, xoffset, 0, 1, yoffset)
|
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
|
Draws an arc (a portion of a circle outline) between the start and end
|
||||||
angles, inside the given bounding box.
|
angles, inside the given bounding box.
|
||||||
|
@ -117,7 +120,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("arc", xy, start, end, *options)
|
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
|
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
|
||||||
with a straight line.
|
with a straight line.
|
||||||
|
@ -126,7 +129,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("chord", xy, start, end, *options)
|
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.
|
Draws an ellipse inside the given bounding box.
|
||||||
|
|
||||||
|
@ -134,7 +137,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("ellipse", xy, *options)
|
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.
|
Draws a line between the coordinates in the ``xy`` list.
|
||||||
|
|
||||||
|
@ -142,7 +145,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("line", xy, *options)
|
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
|
Same as arc, but also draws straight lines between the end points and the
|
||||||
center of the bounding box.
|
center of the bounding box.
|
||||||
|
@ -151,7 +154,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("pieslice", xy, start, end, *options)
|
self.render("pieslice", xy, start, end, *options)
|
||||||
|
|
||||||
def polygon(self, xy, *options):
|
def polygon(self, xy: Coords, *options) -> None:
|
||||||
"""
|
"""
|
||||||
Draws a polygon.
|
Draws a polygon.
|
||||||
|
|
||||||
|
@ -164,7 +167,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("polygon", xy, *options)
|
self.render("polygon", xy, *options)
|
||||||
|
|
||||||
def rectangle(self, xy, *options):
|
def rectangle(self, xy: Coords, *options) -> None:
|
||||||
"""
|
"""
|
||||||
Draws a rectangle.
|
Draws a rectangle.
|
||||||
|
|
||||||
|
@ -172,18 +175,21 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("rectangle", xy, *options)
|
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.
|
Draws the string at the given position.
|
||||||
|
|
||||||
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
|
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
|
||||||
"""
|
"""
|
||||||
if self.transform:
|
if self.transform:
|
||||||
xy = ImagePath.Path(xy)
|
path = ImagePath.Path(xy)
|
||||||
xy.transform(self.transform)
|
path.transform(self.transform)
|
||||||
|
xy = path
|
||||||
self.draw.text(xy, text, font=font.font, fill=font.color)
|
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.
|
Returns bounding box (in pixels) of given text.
|
||||||
|
|
||||||
|
@ -192,11 +198,12 @@ class Draw:
|
||||||
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
|
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
|
||||||
"""
|
"""
|
||||||
if self.transform:
|
if self.transform:
|
||||||
xy = ImagePath.Path(xy)
|
path = ImagePath.Path(xy)
|
||||||
xy.transform(self.transform)
|
path.transform(self.transform)
|
||||||
|
xy = path
|
||||||
return self.draw.textbbox(xy, text, font=font.font)
|
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.
|
Returns length (in pixels) of given text.
|
||||||
This is the amount by which following text should be offset.
|
This is the amount by which following text should be offset.
|
||||||
|
|
|
@ -58,7 +58,7 @@ else:
|
||||||
qt_version = None
|
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."""
|
"""(Internal) Turns an RGB color into a Qt compatible color integer."""
|
||||||
# use qRgb to pack the colors, and then turn the resulting long
|
# use qRgb to pack the colors, and then turn the resulting long
|
||||||
# into a negative integer with the same bitpattern.
|
# into a negative integer with the same bitpattern.
|
||||||
|
|
|
@ -185,7 +185,9 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
|
||||||
Image.register_extension(IptcImageFile.format, ".iim")
|
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.
|
Get IPTC information from TIFF, JPEG, or IPTC file.
|
||||||
|
|
||||||
|
|
|
@ -584,8 +584,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
self.tagtype: dict[int, int] = {}
|
self.tagtype: dict[int, int] = {}
|
||||||
""" Dictionary of tag types """
|
""" Dictionary of tag types """
|
||||||
self.reset()
|
self.reset()
|
||||||
(self.next,) = (
|
self.next = (
|
||||||
self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:])
|
self._unpack("Q", ifh[8:])[0]
|
||||||
|
if self._bigtiff
|
||||||
|
else self._unpack("L", ifh[4:])[0]
|
||||||
)
|
)
|
||||||
self._legacy_api = False
|
self._legacy_api = False
|
||||||
|
|
||||||
|
@ -643,7 +645,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
def __setitem__(self, tag: int, value) -> None:
|
def __setitem__(self, tag: int, value) -> None:
|
||||||
self._setitem(tag, value, self.legacy_api)
|
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)
|
basetypes = (Number, bytes, str)
|
||||||
|
|
||||||
info = TiffTags.lookup(tag, self.group)
|
info = TiffTags.lookup(tag, self.group)
|
||||||
|
@ -731,7 +733,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(set(self._tagdata) | set(self._tags_v2))
|
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)
|
return struct.unpack(self._endian + fmt, data)
|
||||||
|
|
||||||
def _pack(self, fmt: str, *values):
|
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.
|
@_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
|
return data
|
||||||
|
|
||||||
@_register_writer(1) # Basic type, except for the legacy API.
|
@_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):
|
if isinstance(data, IFDRational):
|
||||||
data = int(data)
|
data = int(data)
|
||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
|
@ -773,7 +775,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
return data.decode("latin-1", "replace")
|
return data.decode("latin-1", "replace")
|
||||||
|
|
||||||
@_register_writer(2)
|
@_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
|
# remerge of https://github.com/python-pillow/Pillow/pull/1416
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
|
@ -782,7 +784,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
return value + b"\0"
|
return value + b"\0"
|
||||||
|
|
||||||
@_register_loader(5, 8)
|
@_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)
|
vals = self._unpack(f"{len(data) // 4}L", data)
|
||||||
|
|
||||||
def combine(a, b):
|
def combine(a, b):
|
||||||
|
@ -797,11 +799,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
@_register_loader(7, 1)
|
@_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
|
return data
|
||||||
|
|
||||||
@_register_writer(7)
|
@_register_writer(7)
|
||||||
def write_undefined(self, value) -> bytes:
|
def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
|
||||||
if isinstance(value, IFDRational):
|
if isinstance(value, IFDRational):
|
||||||
value = int(value)
|
value = int(value)
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
|
@ -809,7 +811,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@_register_loader(10, 8)
|
@_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)
|
vals = self._unpack(f"{len(data) // 4}l", data)
|
||||||
|
|
||||||
def combine(a, b):
|
def combine(a, b):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user