Skip to content

Commit

Permalink
Merge pull request python-pillow#8267 from radarhere/type_hint
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Jul 28, 2024
2 parents 8f62fbd + c85eb0c commit 7b8a031
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 42 deletions.
2 changes: 2 additions & 0 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,10 @@ def test_roundtrip_itxt(self) -> None:
im = roundtrip(im, pnginfo=info)
assert im.info == {"spam": "Eggs", "eggs": "Spam"}
assert im.text == {"spam": "Eggs", "eggs": "Spam"}
assert isinstance(im.text["spam"], PngImagePlugin.iTXt)
assert im.text["spam"].lang == "en"
assert im.text["spam"].tkey == "Spam"
assert isinstance(im.text["eggs"], PngImagePlugin.iTXt)
assert im.text["eggs"].lang == "en"
assert im.text["eggs"].tkey == "Eggs"

Expand Down
30 changes: 17 additions & 13 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import sys
import tempfile
import warnings
from collections.abc import Callable, MutableMapping, Sequence
from collections.abc import Callable, Iterator, MutableMapping, Sequence
from enum import IntEnum
from types import ModuleType
from typing import (
Expand Down Expand Up @@ -744,11 +744,11 @@ def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]:
new["shape"], new["typestr"] = _conv_type_shape(self)
return new

def __getstate__(self):
def __getstate__(self) -> list[Any]:
im_data = self.tobytes() # load image first
return [self.info, self.mode, self.size, self.getpalette(), im_data]

def __setstate__(self, state) -> None:
def __setstate__(self, state: list[Any]) -> None:
Image.__init__(self)
info, mode, size, palette, data = state
self.info = info
Expand Down Expand Up @@ -1683,7 +1683,9 @@ def getprojection(self) -> tuple[list[int], list[int]]:
x, y = self.im.getprojection()
return list(x), list(y)

def histogram(self, mask: Image | None = None, extrema=None) -> list[int]:
def histogram(
self, mask: Image | None = None, extrema: tuple[float, float] | None = None
) -> list[int]:
"""
Returns a histogram for the image. The histogram is returned as a
list of pixel counts, one for each pixel value in the source
Expand All @@ -1709,12 +1711,14 @@ def histogram(self, mask: Image | None = None, extrema=None) -> list[int]:
mask.load()
return self.im.histogram((0, 0), mask.im)
if self.mode in ("I", "F"):
if extrema is None:
extrema = self.getextrema()
return self.im.histogram(extrema)
return self.im.histogram(
extrema if extrema is not None else self.getextrema()
)
return self.im.histogram()

def entropy(self, mask: Image | None = None, extrema=None):
def entropy(
self, mask: Image | None = None, extrema: tuple[float, float] | None = None
) -> float:
"""
Calculates and returns the entropy for the image.
Expand All @@ -1735,9 +1739,9 @@ def entropy(self, mask: Image | None = None, extrema=None):
mask.load()
return self.im.entropy((0, 0), mask.im)
if self.mode in ("I", "F"):
if extrema is None:
extrema = self.getextrema()
return self.im.entropy(extrema)
return self.im.entropy(
extrema if extrema is not None else self.getextrema()
)
return self.im.entropy()

def paste(
Expand Down Expand Up @@ -3881,7 +3885,7 @@ def _fixup_dict(self, src_dict):
# returns a dict with any single item tuples/lists as individual values
return {k: self._fixup(v) for k, v in src_dict.items()}

def _get_ifd_dict(self, offset: int, group=None):
def _get_ifd_dict(self, offset: int, group: int | None = None):
try:
# an offset pointer to the location of the nested embedded IFD.
# It should be a long, but may be corrupted.
Expand Down Expand Up @@ -4136,7 +4140,7 @@ def __delitem__(self, tag: int) -> None:
else:
del self._data[tag]

def __iter__(self):
def __iter__(self) -> Iterator[int]:
keys = set(self._data)
if self._info is not None:
keys.update(self._info)
Expand Down
14 changes: 7 additions & 7 deletions src/PIL/ImageDraw2.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"""
from __future__ import annotations

from typing import AnyStr, BinaryIO
from typing import Any, AnyStr, BinaryIO

from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from ._typing import Coords, StrOrBytesPath
Expand Down Expand Up @@ -111,7 +111,7 @@ def settransform(self, offset: tuple[float, float]) -> None:
(xoffset, yoffset) = offset
self.transform = (1, 0, xoffset, 0, 1, yoffset)

def arc(self, xy: Coords, start, end, *options) -> None:
def arc(self, xy: Coords, start, end, *options: Any) -> None:
"""
Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box.
Expand All @@ -120,7 +120,7 @@ def arc(self, xy: Coords, start, end, *options) -> None:
"""
self.render("arc", xy, start, end, *options)

def chord(self, xy: Coords, start, end, *options) -> None:
def chord(self, xy: Coords, start, end, *options: Any) -> None:
"""
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
with a straight line.
Expand All @@ -129,23 +129,23 @@ def chord(self, xy: Coords, start, end, *options) -> None:
"""
self.render("chord", xy, start, end, *options)

def ellipse(self, xy: Coords, *options) -> None:
def ellipse(self, xy: Coords, *options: Any) -> None:
"""
Draws an ellipse inside the given bounding box.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse`
"""
self.render("ellipse", xy, *options)

def line(self, xy: Coords, *options) -> None:
def line(self, xy: Coords, *options: Any) -> None:
"""
Draws a line between the coordinates in the ``xy`` list.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line`
"""
self.render("line", xy, *options)

def pieslice(self, xy: Coords, start, end, *options) -> None:
def pieslice(self, xy: Coords, start, end, *options: Any) -> None:
"""
Same as arc, but also draws straight lines between the end points and the
center of the bounding box.
Expand All @@ -154,7 +154,7 @@ def pieslice(self, xy: Coords, start, end, *options) -> None:
"""
self.render("pieslice", xy, start, end, *options)

def polygon(self, xy: Coords, *options) -> None:
def polygon(self, xy: Coords, *options: Any) -> None:
"""
Draws a polygon.
Expand Down
38 changes: 19 additions & 19 deletions src/PIL/PngImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import struct
import warnings
import zlib
from collections.abc import Callable
from enum import IntEnum
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn

Expand Down Expand Up @@ -135,7 +136,7 @@ class Blend(IntEnum):
"""


def _safe_zlib_decompress(s):
def _safe_zlib_decompress(s: bytes) -> bytes:
dobj = zlib.decompressobj()
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
if dobj.unconsumed_tail:
Expand Down Expand Up @@ -783,7 +784,7 @@ def _open(self) -> None:
self._mode = self.png.im_mode
self._size = self.png.im_size
self.info = self.png.im_info
self._text = None
self._text: dict[str, str | iTXt] | None = None
self.tile = self.png.im_tile
self.custom_mimetype = self.png.im_custom_mimetype
self.n_frames = self.png.im_n_frames or 1
Expand All @@ -810,7 +811,7 @@ def _open(self) -> None:
self.is_animated = self.n_frames > 1

@property
def text(self):
def text(self) -> dict[str, str | iTXt]:
# experimental
if self._text is None:
# iTxt, tEXt and zTXt chunks may appear at the end of the file
Expand All @@ -822,6 +823,7 @@ def text(self):
self.load()
if self.is_animated:
self.seek(frame)
assert self._text is not None
return self._text

def verify(self) -> None:
Expand Down Expand Up @@ -1105,7 +1107,7 @@ def putchunk(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
class _idat:
# wrap output from the encoder in IDAT chunks

def __init__(self, fp, chunk) -> None:
def __init__(self, fp: IO[bytes], chunk: Callable[..., None]) -> None:
self.fp = fp
self.chunk = chunk

Expand All @@ -1116,7 +1118,7 @@ def write(self, data: bytes) -> None:
class _fdat:
# wrap encoder output in fdAT chunks

def __init__(self, fp: IO[bytes], chunk, seq_num: int) -> None:
def __init__(self, fp: IO[bytes], chunk: Callable[..., None], seq_num: int) -> None:
self.fp = fp
self.chunk = chunk
self.seq_num = seq_num
Expand All @@ -1135,7 +1137,7 @@ class _Frame(NamedTuple):
def _write_multiple_frames(
im: Image.Image,
fp: IO[bytes],
chunk,
chunk: Callable[..., None],
mode: str,
rawmode: str,
default_image: Image.Image | None,
Expand Down Expand Up @@ -1275,7 +1277,11 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:


def _save(
im: Image.Image, fp, filename: str | bytes, chunk=putchunk, save_all: bool = False
im: Image.Image,
fp: IO[bytes],
filename: str | bytes,
chunk: Callable[..., None] = putchunk,
save_all: bool = False,
) -> None:
# save an image to disk (called by the save method)

Expand Down Expand Up @@ -1483,30 +1489,24 @@ def _save(

def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]:
"""Return a list of PNG chunks representing this image."""
from io import BytesIO

class collector:
data = []

def write(self, data: bytes) -> None:
pass

def append(self, chunk: tuple[bytes, bytes, bytes]) -> None:
self.data.append(chunk)
chunks = []

def append(fp: collector, cid: bytes, *data: bytes) -> None:
def append(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
byte_data = b"".join(data)
crc = o32(_crc32(byte_data, _crc32(cid)))
fp.append((cid, byte_data, crc))
chunks.append((cid, byte_data, crc))

fp = collector()
fp = BytesIO()

try:
im.encoderinfo = params
_save(im, fp, "", append)
finally:
del im.encoderinfo

return fp.data
return chunks


# --------------------------------------------------------------------
Expand Down
8 changes: 5 additions & 3 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
.. deprecated:: 3.0.0
"""

def __init__(self, *args, **kwargs) -> None:
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self._legacy_api = True

Expand Down Expand Up @@ -1142,13 +1142,15 @@ def _open(self) -> None:
self._seek(0)

@property
def n_frames(self):
if self._n_frames is None:
def n_frames(self) -> int:
current_n_frames = self._n_frames
if current_n_frames is None:
current = self.tell()
self._seek(len(self._frame_pos))
while self._n_frames is None:
self._seek(self.tell() + 1)
self.seek(current)
assert self._n_frames is not None
return self._n_frames

def seek(self, frame: int) -> None:
Expand Down

0 comments on commit 7b8a031

Please sign in to comment.