Skip to content

Commit

Permalink
Added type hints (#8204)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
  • Loading branch information
radarhere and radarhere authored Jul 5, 2024
1 parent 7e1a6be commit f3c3e52
Show file tree
Hide file tree
Showing 14 changed files with 67 additions and 50 deletions.
7 changes: 5 additions & 2 deletions Tests/test_imageshow.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ def test_sanity() -> None:


def test_register() -> None:
# Test registering a viewer that is not a class
ImageShow.register("not a class")
# Test registering a viewer that is an instance
class TestViewer(ImageShow.Viewer):
pass

ImageShow.register(TestViewer())

# Restore original state
ImageShow._viewers.pop()
Expand Down
6 changes: 4 additions & 2 deletions src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def _read_comment(s: str) -> bool:
msg = "cannot determine EPS bounding box"
raise OSError(msg)

def _find_offset(self, fp):
def _find_offset(self, fp: IO[bytes]) -> tuple[int, int]:
s = fp.read(4)

if s == b"%!PS":
Expand All @@ -361,7 +361,9 @@ def _find_offset(self, fp):

return length, offset

def load(self, scale=1, transparency=False):
def load(
self, scale: int = 1, transparency: bool = False
) -> Image.core.PixelAccess | None:
# Load EPS via Ghostscript
if self.tile:
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
Expand Down
11 changes: 6 additions & 5 deletions src/PIL/FliImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class FliImageFile(ImageFile.ImageFile):
format_description = "Autodesk FLI/FLC Animation"
_close_exclusive_fp_after_loading = False

def _open(self):
def _open(self) -> None:
# HEAD
s = self.fp.read(128)
if not (_accept(s) and s[20:22] == b"\x00\x00"):
Expand Down Expand Up @@ -83,7 +83,7 @@ def _open(self):
if i16(s, 4) == 0xF1FA:
# look for palette chunk
number_of_subchunks = i16(s, 6)
chunk_size = None
chunk_size: int | None = None
for _ in range(number_of_subchunks):
if chunk_size is not None:
self.fp.seek(chunk_size - 6, os.SEEK_CUR)
Expand All @@ -96,16 +96,17 @@ def _open(self):
if not chunk_size:
break

palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
self.palette = ImagePalette.raw("RGB", b"".join(palette))
self.palette = ImagePalette.raw(
"RGB", b"".join(o8(r) + o8(g) + o8(b) for (r, g, b) in palette)
)

# set things up to decode first frame
self.__frame = -1
self._fp = self.fp
self.__rewind = self.fp.tell()
self.seek(0)

def _palette(self, palette, shift):
def _palette(self, palette: list[tuple[int, int, int]], shift: int) -> None:
# load palette

i = 0
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/FpxImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def _open_subimage(self, index: int = 1, subimage: int = 0) -> None:
self._fp = self.fp
self.fp = None

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if not self.fp:
self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])

Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GbrImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def _open(self) -> None:
# Data is an uncompressed block of w * h * bytes/pixel
self._data_size = width * height * color_depth

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if not self.im:
self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self._data_size))
Expand Down
54 changes: 33 additions & 21 deletions src/PIL/IcnsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
HEADERSIZE = 8


def nextheader(fobj):
def nextheader(fobj: IO[bytes]) -> tuple[bytes, int]:
return struct.unpack(">4sI", fobj.read(HEADERSIZE))


def read_32t(fobj, start_length, size):
def read_32t(
fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int]
) -> dict[str, Image.Image]:
# The 128x128 icon seems to have an extra header for some reason.
(start, length) = start_length
fobj.seek(start)
Expand All @@ -49,7 +51,9 @@ def read_32t(fobj, start_length, size):
return read_32(fobj, (start + 4, length - 4), size)


def read_32(fobj, start_length, size):
def read_32(
fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int]
) -> dict[str, Image.Image]:
"""
Read a 32bit RGB icon resource. Seems to be either uncompressed or
an RLE packbits-like scheme.
Expand All @@ -72,14 +76,14 @@ def read_32(fobj, start_length, size):
byte = fobj.read(1)
if not byte:
break
byte = byte[0]
if byte & 0x80:
blocksize = byte - 125
byte_int = byte[0]
if byte_int & 0x80:
blocksize = byte_int - 125
byte = fobj.read(1)
for i in range(blocksize):
data.append(byte)
else:
blocksize = byte + 1
blocksize = byte_int + 1
data.append(fobj.read(blocksize))
bytesleft -= blocksize
if bytesleft <= 0:
Expand All @@ -92,7 +96,9 @@ def read_32(fobj, start_length, size):
return {"RGB": im}


def read_mk(fobj, start_length, size):
def read_mk(
fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int]
) -> dict[str, Image.Image]:
# Alpha masks seem to be uncompressed
start = start_length[0]
fobj.seek(start)
Expand All @@ -102,10 +108,14 @@ def read_mk(fobj, start_length, size):
return {"A": band}


def read_png_or_jpeg2000(fobj, start_length, size):
def read_png_or_jpeg2000(
fobj: IO[bytes], start_length: tuple[int, int], size: tuple[int, int, int]
) -> dict[str, Image.Image]:
(start, length) = start_length
fobj.seek(start)
sig = fobj.read(12)

im: Image.Image
if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
fobj.seek(start)
im = PngImagePlugin.PngImageFile(fobj)
Expand Down Expand Up @@ -164,12 +174,12 @@ class IcnsFile:
],
}

def __init__(self, fobj):
def __init__(self, fobj: IO[bytes]) -> None:
"""
fobj is a file-like object as an icns resource
"""
# signature : (start, length)
self.dct = dct = {}
self.dct = {}
self.fobj = fobj
sig, filesize = nextheader(fobj)
if not _accept(sig):
Expand All @@ -183,11 +193,11 @@ def __init__(self, fobj):
raise SyntaxError(msg)
i += HEADERSIZE
blocksize -= HEADERSIZE
dct[sig] = (i, blocksize)
self.dct[sig] = (i, blocksize)
fobj.seek(blocksize, io.SEEK_CUR)
i += blocksize

def itersizes(self):
def itersizes(self) -> list[tuple[int, int, int]]:
sizes = []
for size, fmts in self.SIZES.items():
for fmt, reader in fmts:
Expand All @@ -196,14 +206,14 @@ def itersizes(self):
break
return sizes

def bestsize(self):
def bestsize(self) -> tuple[int, int, int]:
sizes = self.itersizes()
if not sizes:
msg = "No 32bit icon resources found"
raise SyntaxError(msg)
return max(sizes)

def dataforsize(self, size):
def dataforsize(self, size: tuple[int, int, int]) -> dict[str, Image.Image]:
"""
Get an icon resource as {channel: array}. Note that
the arrays are bottom-up like windows bitmaps and will likely
Expand All @@ -216,18 +226,20 @@ def dataforsize(self, size):
dct.update(reader(self.fobj, desc, size))
return dct

def getimage(self, size=None):
def getimage(
self, size: tuple[int, int] | tuple[int, int, int] | None = None
) -> Image.Image:
if size is None:
size = self.bestsize()
if len(size) == 2:
elif len(size) == 2:
size = (size[0], size[1], 1)
channels = self.dataforsize(size)

im = channels.get("RGBA", None)
im = channels.get("RGBA")
if im:
return im

im = channels.get("RGB").copy()
im = channels["RGB"].copy()
try:
im.putalpha(channels["A"])
except KeyError:
Expand Down Expand Up @@ -268,7 +280,7 @@ def size(self):
return self._size

@size.setter
def size(self, value):
def size(self, value) -> None:
info_size = value
if info_size not in self.info["sizes"] and len(info_size) == 2:
info_size = (info_size[0], info_size[1], 1)
Expand All @@ -287,7 +299,7 @@ def size(self, value):
raise ValueError(msg)
self._size = value

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if len(self.size) == 3:
self.best_size = self.size
self.size = (
Expand Down
11 changes: 6 additions & 5 deletions src/PIL/IcoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def _accept(prefix: bytes) -> bool:


class IcoFile:
def __init__(self, buf):
def __init__(self, buf) -> None:
"""
Parse image from file-like object containing ico file data
"""
Expand Down Expand Up @@ -177,19 +177,19 @@ def __init__(self, buf):
# ICO images are usually squares
self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True)

def sizes(self):
def sizes(self) -> set[tuple[int, int]]:
"""
Get a list of all available icon sizes and color depths.
"""
return {(h["width"], h["height"]) for h in self.entry}

def getentryindex(self, size, bpp=False):
def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int:
for i, h in enumerate(self.entry):
if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
return i
return 0

def getimage(self, size, bpp=False):
def getimage(self, size: tuple[int, int], bpp: int | bool = False) -> Image.Image:
"""
Get an image from the icon
"""
Expand Down Expand Up @@ -321,7 +321,7 @@ def size(self, value):
raise ValueError(msg)
self._size = value

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if self.im is not None and self.im.size == self.size:
# Already loaded
return Image.Image.load(self)
Expand All @@ -341,6 +341,7 @@ def load(self):
self.info["sizes"] = set(sizes)

self.size = im.size
return None

def load_seek(self, pos: int) -> None:
# Flag the ImageFile.Parser so that it
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1395,7 +1395,7 @@ def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int] | Non

def getcolors(
self, maxcolors: int = 256
) -> list[tuple[int, int]] | list[tuple[int, float]] | None:
) -> list[tuple[int, tuple[int, ...]]] | list[tuple[int, float]] | None:
"""
Returns a list of colors used in this image.
Expand All @@ -1412,7 +1412,7 @@ def getcolors(
self.load()
if self.mode in ("1", "L", "P"):
h = self.im.histogram()
out = [(h[i], i) for i in range(256) if h[i]]
out: list[tuple[int, float]] = [(h[i], i) for i in range(256) if h[i]]
if len(out) > maxcolors:
return None
return out
Expand Down
9 changes: 3 additions & 6 deletions src/PIL/ImageShow.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
_viewers = []


def register(viewer, order: int = 1) -> None:
def register(viewer: type[Viewer] | Viewer, order: int = 1) -> None:
"""
The :py:func:`register` function is used to register additional viewers::
Expand All @@ -40,11 +40,8 @@ def register(viewer, order: int = 1) -> None:
Zero or a negative integer to prepend this viewer to the list,
a positive integer to append it.
"""
try:
if issubclass(viewer, Viewer):
viewer = viewer()
except TypeError:
pass # raised if viewer wasn't a class
if isinstance(viewer, type) and issubclass(viewer, Viewer):
viewer = viewer()
if order > 0:
_viewers.append(viewer)
else:
Expand Down
3 changes: 2 additions & 1 deletion src/PIL/IptcImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def _open(self) -> None:
if tag == (8, 10):
self.tile = [("iptc", (0, 0) + self.size, offset, compression)]

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if len(self.tile) != 1 or self.tile[0][0] != "iptc":
return ImageFile.ImageFile.load(self)

Expand Down Expand Up @@ -176,6 +176,7 @@ def load(self):
with Image.open(o) as _im:
_im.load()
self.im = _im.im
return None


Image.register_open(IptcImageFile.format, IptcImageFile)
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/Jpeg2KImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def reduce(self):
def reduce(self, value):
self._reduce = value

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if self.tile and self._reduce:
power = 1 << self._reduce
adjust = power >> 1
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,7 @@ def get_photoshop_blocks(self):
val = val[math.ceil((10 + n + size) / 2) * 2 :]
return blocks

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if self.tile and self.use_load_libtiff:
return self._load_libtiff()
return super().load()
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/WalImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def _open(self) -> None:
if next_name:
self.info["next_name"] = next_name

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if not self.im:
self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self.size[0] * self.size[1]))
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/WebPImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def _seek(self, frame: int) -> None:
while self.__physical_frame < frame:
self._get_next() # Advance to the requested frame

def load(self):
def load(self) -> Image.core.PixelAccess | None:
if _webp.HAVE_WEBPANIM:
if self.__loaded != self.__logical_frame:
self._seek(self.__logical_frame)
Expand Down

0 comments on commit f3c3e52

Please sign in to comment.