Skip to content

Commit

Permalink
Merge pull request python-pillow#8270 from radarhere/type_hint
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Jul 30, 2024
2 parents 5833a8b + accfaf1 commit 4df4df2
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 53 deletions.
2 changes: 1 addition & 1 deletion Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def test_cmyk(self) -> None:
assert k > 0.9

def test_rgb(self) -> None:
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, int, int]:
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]:
return tuple(v[0] for v in im.layer)

im = hopper()
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def text(self, text: str) -> None:
im = Image.new("L", (100, 100))

p = Pretty()
im._repr_pretty_(p, None)
im._repr_pretty_(p, False)
assert p.pretty_output == "<PIL.Image.Image image mode=L size=100x100>"

def test_open_formats(self) -> None:
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ Plugin reference
:undoc-members:
:show-inheritance:

:mod:`~PIL.MpoImagePlugin` Module
----------------------------------

.. automodule:: PIL.MpoImagePlugin
:members:
:undoc-members:
:show-inheritance:

:mod:`~PIL.MspImagePlugin` Module
---------------------------------

Expand Down
20 changes: 16 additions & 4 deletions src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,24 @@ def has_ghostscript() -> bool:
return gs_binary is not False


def Ghostscript(tile, size, fp, scale=1, transparency: bool = False) -> Image.Image:
def Ghostscript(
tile: list[ImageFile._Tile],
size: tuple[int, int],
fp: IO[bytes],
scale: int = 1,
transparency: bool = False,
) -> Image.Image:
"""Render an image using Ghostscript"""
global gs_binary
if not has_ghostscript():
msg = "Unable to locate Ghostscript on paths"
raise OSError(msg)
assert isinstance(gs_binary, str)

# Unpack decoder tile
decoder, tile, offset, data = tile[0]
length, bbox = data
args = tile[0].args
assert isinstance(args, tuple)
length, bbox = args

# Hack to support hi-res rendering
scale = int(scale) or 1
Expand Down Expand Up @@ -227,7 +235,11 @@ def _read_comment(s: str) -> bool:
# put floating point values there anyway.
box = [int(float(i)) for i in v.split()]
self._size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0, 0) + self.size, offset, (length, box))]
self.tile = [
ImageFile._Tile(
"eps", (0, 0) + self.size, offset, (length, box)
)
]
except Exception:
pass
return True
Expand Down
48 changes: 26 additions & 22 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class Quantize(IntEnum):
if TYPE_CHECKING:
from xml.etree.ElementTree import Element

from . import ImageFile, ImagePalette
from . import ImageFile, ImagePalette, TiffImagePlugin
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
ID: list[str] = []
OPEN: dict[
Expand Down Expand Up @@ -676,7 +676,7 @@ def __repr__(self) -> str:
id(self),
)

def _repr_pretty_(self, p, cycle) -> None:
def _repr_pretty_(self, p, cycle: bool) -> None:
"""IPython plain text display support"""

# Same as __repr__ but without unpredictable id(self),
Expand Down Expand Up @@ -1551,6 +1551,7 @@ def get_child_images(self) -> list[ImageFile.ImageFile]:
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
if ifd1 and ifd1.get(513):
assert exif._info is not None
ifds.append((ifd1, exif._info.next))

offset = None
Expand All @@ -1560,12 +1561,13 @@ def get_child_images(self) -> list[ImageFile.ImageFile]:
offset = current_offset

fp = self.fp
thumbnail_offset = ifd.get(513)
if thumbnail_offset is not None:
thumbnail_offset += getattr(self, "_exif_offset", 0)
self.fp.seek(thumbnail_offset)
data = self.fp.read(ifd.get(514))
fp = io.BytesIO(data)
if ifd is not None:
thumbnail_offset = ifd.get(513)
if thumbnail_offset is not None:
thumbnail_offset += getattr(self, "_exif_offset", 0)
self.fp.seek(thumbnail_offset)
data = self.fp.read(ifd.get(514))
fp = io.BytesIO(data)

with open(fp) as im:
from . import TiffImagePlugin
Expand Down Expand Up @@ -3869,39 +3871,41 @@ class Exif(_ExifBase):
bigtiff = False
_loaded = False

def __init__(self):
self._data = {}
self._hidden_data = {}
self._ifds = {}
self._info = None
self._loaded_exif = None
def __init__(self) -> None:
self._data: dict[int, Any] = {}
self._hidden_data: dict[int, Any] = {}
self._ifds: dict[int, dict[int, Any]] = {}
self._info: TiffImagePlugin.ImageFileDirectory_v2 | None = None
self._loaded_exif: bytes | None = None

def _fixup(self, value):
def _fixup(self, value: Any) -> Any:
try:
if len(value) == 1 and isinstance(value, tuple):
return value[0]
except Exception:
pass
return value

def _fixup_dict(self, src_dict):
def _fixup_dict(self, src_dict: dict[int, Any]) -> dict[int, Any]:
# Helper function
# 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: int | None = None):
def _get_ifd_dict(
self, offset: int, group: int | None = None
) -> dict[int, Any] | None:
try:
# an offset pointer to the location of the nested embedded IFD.
# It should be a long, but may be corrupted.
self.fp.seek(offset)
except (KeyError, TypeError):
pass
return None
else:
from . import TiffImagePlugin

info = TiffImagePlugin.ImageFileDirectory_v2(self.head, group=group)
info.load(self.fp)
return self._fixup_dict(info)
return self._fixup_dict(dict(info))

def _get_head(self) -> bytes:
version = b"\x2B" if self.bigtiff else b"\x2A"
Expand Down Expand Up @@ -3966,7 +3970,7 @@ def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
self.fp.seek(offset)
self._info.load(self.fp)

def _get_merged_dict(self):
def _get_merged_dict(self) -> dict[int, Any]:
merged_dict = dict(self)

# get EXIF extension
Expand Down Expand Up @@ -4124,7 +4128,7 @@ def __len__(self) -> int:
keys.update(self._info)
return len(keys)

def __getitem__(self, tag: int):
def __getitem__(self, tag: int) -> Any:
if self._info is not None and tag not in self._data and tag in self._info:
self._data[tag] = self._fixup(self._info[tag])
del self._info[tag]
Expand All @@ -4133,7 +4137,7 @@ def __getitem__(self, tag: int):
def __contains__(self, tag: object) -> bool:
return tag in self._data or (self._info is not None and tag in self._info)

def __setitem__(self, tag: int, value) -> None:
def __setitem__(self, tag: int, value: Any) -> None:
if self._info is not None and tag in self._info:
del self._info[tag]
self._data[tag] = value
Expand Down
19 changes: 14 additions & 5 deletions src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def raise_oserror(error: int) -> OSError:
raise _get_oserror(error, encoder=False)


def _tilesort(t) -> int:
def _tilesort(t: _Tile) -> int:
# sort on offset
return t[2]

Expand Down Expand Up @@ -161,7 +161,7 @@ def get_format_mimetype(self) -> str | None:
return Image.MIME.get(self.format.upper())
return None

def __setstate__(self, state) -> None:
def __setstate__(self, state: list[Any]) -> None:
self.tile = []
super().__setstate__(state)

Expand Down Expand Up @@ -525,7 +525,7 @@ def close(self) -> Image.Image:
# --------------------------------------------------------------------


def _save(im, fp, tile, bufsize: int = 0) -> None:
def _save(im: Image.Image, fp: IO[bytes], tile, bufsize: int = 0) -> None:
"""Helper to save image based on tile list
:param im: Image object.
Expand Down Expand Up @@ -554,7 +554,12 @@ def _save(im, fp, tile, bufsize: int = 0) -> None:


def _encode_tile(
im, fp: IO[bytes], tile: list[_Tile], bufsize: int, fh, exc=None
im: Image.Image,
fp: IO[bytes],
tile: list[_Tile],
bufsize: int,
fh,
exc: BaseException | None = None,
) -> None:
for encoder_name, extents, offset, args in tile:
if offset > 0:
Expand Down Expand Up @@ -664,7 +669,11 @@ def setfd(self, fd: IO[bytes]) -> None:
"""
self.fd = fd

def setimage(self, im, extents=None):
def setimage(
self,
im: Image.core.ImagingCore,
extents: tuple[int, int, int, int] | None = None,
) -> None:
"""
Called from ImageFile to set the core output image for the codec
Expand Down
25 changes: 15 additions & 10 deletions src/PIL/JpegImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import sys
import tempfile
import warnings
from typing import IO, Any
from typing import IO, TYPE_CHECKING, Any

from . import Image, ImageFile
from ._binary import i16be as i16
Expand All @@ -51,6 +51,9 @@
from ._binary import o16be as o16
from .JpegPresets import presets

if TYPE_CHECKING:
from .MpoImagePlugin import MpoImageFile

#
# Parser

Expand Down Expand Up @@ -329,7 +332,7 @@ class JpegImageFile(ImageFile.ImageFile):
format = "JPEG"
format_description = "JPEG (ISO 10918)"

def _open(self):
def _open(self) -> None:
s = self.fp.read(3)

if not _accept(s):
Expand All @@ -342,13 +345,13 @@ def _open(self):
self._exif_offset = 0

# JPEG specifics (internal)
self.layer = []
self.huffman_dc = {}
self.huffman_ac = {}
self.quantization = {}
self.app = {} # compatibility
self.applist = []
self.icclist = []
self.layer: list[tuple[int, int, int, int]] = []
self.huffman_dc: dict[Any, Any] = {}
self.huffman_ac: dict[Any, Any] = {}
self.quantization: dict[int, list[int]] = {}
self.app: dict[str, bytes] = {} # compatibility
self.applist: list[tuple[str, bytes]] = []
self.icclist: list[bytes] = []

while True:
i = s[0]
Expand Down Expand Up @@ -831,7 +834,9 @@ def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:

##
# Factory for making JPEG and MPO instances
def jpeg_factory(fp: IO[bytes] | None = None, filename: str | bytes | None = None):
def jpeg_factory(
fp: IO[bytes] | None = None, filename: str | bytes | None = None
) -> JpegImageFile | MpoImageFile:
im = JpegImageFile(fp, filename)
try:
mpheader = im._getmp()
Expand Down
16 changes: 11 additions & 5 deletions src/PIL/PngImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
import zlib
from collections.abc import Callable
from enum import IntEnum
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn, cast

from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16
Expand Down Expand Up @@ -1223,7 +1223,11 @@ def _write_multiple_frames(
if default_image:
if im.mode != mode:
im = im.convert(mode)
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
ImageFile._save(
im,
cast(IO[bytes], _idat(fp, chunk)),
[("zip", (0, 0) + im.size, 0, rawmode)],
)

seq_num = 0
for frame, frame_data in enumerate(im_frames):
Expand Down Expand Up @@ -1258,14 +1262,14 @@ def _write_multiple_frames(
# first frame must be in IDAT chunks for backwards compatibility
ImageFile._save(
im_frame,
_idat(fp, chunk),
cast(IO[bytes], _idat(fp, chunk)),
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
)
else:
fdat_chunks = _fdat(fp, chunk, seq_num)
ImageFile._save(
im_frame,
fdat_chunks,
cast(IO[bytes], fdat_chunks),
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
)
seq_num = fdat_chunks.seq_num
Expand Down Expand Up @@ -1465,7 +1469,9 @@ def _save(
)
if single_im:
ImageFile._save(
single_im, _idat(fp, chunk), [("zip", (0, 0) + single_im.size, 0, rawmode)]
single_im,
cast(IO[bytes], _idat(fp, chunk)),
[("zip", (0, 0) + single_im.size, 0, rawmode)],
)

if info:
Expand Down
Loading

0 comments on commit 4df4df2

Please sign in to comment.