Added type hints (#8204)

Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
This commit is contained in:
Andrew Murray 2024-07-06 03:55:23 +10:00 committed by GitHub
parent 7e1a6be767
commit f3c3e52797
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 67 additions and 50 deletions

View File

@ -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()

View File

@ -338,7 +338,7 @@ class EpsImageFile(ImageFile.ImageFile):
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":
@ -361,7 +361,9 @@ class EpsImageFile(ImageFile.ImageFile):
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)

View File

@ -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"):
@ -83,7 +83,7 @@ class FliImageFile(ImageFile.ImageFile):
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)
@ -96,8 +96,9 @@ class FliImageFile(ImageFile.ImageFile):
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
@ -105,7 +106,7 @@ class FliImageFile(ImageFile.ImageFile):
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

View File

@ -231,7 +231,7 @@ class FpxImageFile(ImageFile.ImageFile):
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"])

View File

@ -88,7 +88,7 @@ class GbrImageFile(ImageFile.ImageFile):
# 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))

View File

@ -34,11 +34,13 @@ MAGIC = b"icns"
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)
@ -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.
@ -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:
@ -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)
@ -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)
@ -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):
@ -183,11 +193,11 @@ class IcnsFile:
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:
@ -196,14 +206,14 @@ class IcnsFile:
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
@ -216,18 +226,20 @@ class IcnsFile:
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:
@ -268,7 +280,7 @@ class IcnsImageFile(ImageFile.ImageFile):
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)
@ -287,7 +299,7 @@ class IcnsImageFile(ImageFile.ImageFile):
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 = (

View File

@ -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
"""
@ -177,19 +177,19 @@ class IcoFile:
# 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
"""
@ -321,7 +321,7 @@ class IcoImageFile(ImageFile.ImageFile):
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)
@ -341,6 +341,7 @@ class IcoImageFile(ImageFile.ImageFile):
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

View File

@ -1395,7 +1395,7 @@ class Image:
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.
@ -1412,7 +1412,7 @@ class Image:
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

View File

@ -26,7 +26,7 @@ from . import Image
_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::
@ -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:

View File

@ -148,7 +148,7 @@ class IptcImageFile(ImageFile.ImageFile):
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)
@ -176,6 +176,7 @@ class IptcImageFile(ImageFile.ImageFile):
with Image.open(o) as _im:
_im.load()
self.im = _im.im
return None
Image.register_open(IptcImageFile.format, IptcImageFile)

View File

@ -299,7 +299,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
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

View File

@ -1232,7 +1232,7 @@ class TiffImageFile(ImageFile.ImageFile):
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()

View File

@ -50,7 +50,7 @@ class WalImageFile(ImageFile.ImageFile):
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]))

View File

@ -144,7 +144,7 @@ class WebPImageFile(ImageFile.ImageFile):
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)