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: def test_register() -> None:
# Test registering a viewer that is not a class # Test registering a viewer that is an instance
ImageShow.register("not a class") class TestViewer(ImageShow.Viewer):
pass
ImageShow.register(TestViewer())
# Restore original state # Restore original state
ImageShow._viewers.pop() ImageShow._viewers.pop()

View File

@ -338,7 +338,7 @@ class EpsImageFile(ImageFile.ImageFile):
msg = "cannot determine EPS bounding box" msg = "cannot determine EPS bounding box"
raise OSError(msg) raise OSError(msg)
def _find_offset(self, fp): def _find_offset(self, fp: IO[bytes]) -> tuple[int, int]:
s = fp.read(4) s = fp.read(4)
if s == b"%!PS": if s == b"%!PS":
@ -361,7 +361,9 @@ class EpsImageFile(ImageFile.ImageFile):
return length, offset 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 # Load EPS via Ghostscript
if self.tile: if self.tile:
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) 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" format_description = "Autodesk FLI/FLC Animation"
_close_exclusive_fp_after_loading = False _close_exclusive_fp_after_loading = False
def _open(self): def _open(self) -> None:
# HEAD # HEAD
s = self.fp.read(128) s = self.fp.read(128)
if not (_accept(s) and s[20:22] == b"\x00\x00"): if not (_accept(s) and s[20:22] == b"\x00\x00"):
@ -83,7 +83,7 @@ class FliImageFile(ImageFile.ImageFile):
if i16(s, 4) == 0xF1FA: if i16(s, 4) == 0xF1FA:
# look for palette chunk # look for palette chunk
number_of_subchunks = i16(s, 6) number_of_subchunks = i16(s, 6)
chunk_size = None chunk_size: int | None = None
for _ in range(number_of_subchunks): for _ in range(number_of_subchunks):
if chunk_size is not None: if chunk_size is not None:
self.fp.seek(chunk_size - 6, os.SEEK_CUR) self.fp.seek(chunk_size - 6, os.SEEK_CUR)
@ -96,8 +96,9 @@ class FliImageFile(ImageFile.ImageFile):
if not chunk_size: if not chunk_size:
break break
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette] self.palette = ImagePalette.raw(
self.palette = ImagePalette.raw("RGB", b"".join(palette)) "RGB", b"".join(o8(r) + o8(g) + o8(b) for (r, g, b) in palette)
)
# set things up to decode first frame # set things up to decode first frame
self.__frame = -1 self.__frame = -1
@ -105,7 +106,7 @@ class FliImageFile(ImageFile.ImageFile):
self.__rewind = self.fp.tell() self.__rewind = self.fp.tell()
self.seek(0) self.seek(0)
def _palette(self, palette, shift): def _palette(self, palette: list[tuple[int, int, int]], shift: int) -> None:
# load palette # load palette
i = 0 i = 0

View File

@ -231,7 +231,7 @@ class FpxImageFile(ImageFile.ImageFile):
self._fp = self.fp self._fp = self.fp
self.fp = None self.fp = None
def load(self): def load(self) -> Image.core.PixelAccess | None:
if not self.fp: if not self.fp:
self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) 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 # Data is an uncompressed block of w * h * bytes/pixel
self._data_size = width * height * color_depth self._data_size = width * height * color_depth
def load(self): def load(self) -> Image.core.PixelAccess | None:
if not self.im: if not self.im:
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self._data_size)) self.frombytes(self.fp.read(self._data_size))

View File

@ -34,11 +34,13 @@ MAGIC = b"icns"
HEADERSIZE = 8 HEADERSIZE = 8
def nextheader(fobj): def nextheader(fobj: IO[bytes]) -> tuple[bytes, int]:
return struct.unpack(">4sI", fobj.read(HEADERSIZE)) 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. # The 128x128 icon seems to have an extra header for some reason.
(start, length) = start_length (start, length) = start_length
fobj.seek(start) fobj.seek(start)
@ -49,7 +51,9 @@ def read_32t(fobj, start_length, size):
return read_32(fobj, (start + 4, length - 4), 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 Read a 32bit RGB icon resource. Seems to be either uncompressed or
an RLE packbits-like scheme. an RLE packbits-like scheme.
@ -72,14 +76,14 @@ def read_32(fobj, start_length, size):
byte = fobj.read(1) byte = fobj.read(1)
if not byte: if not byte:
break break
byte = byte[0] byte_int = byte[0]
if byte & 0x80: if byte_int & 0x80:
blocksize = byte - 125 blocksize = byte_int - 125
byte = fobj.read(1) byte = fobj.read(1)
for i in range(blocksize): for i in range(blocksize):
data.append(byte) data.append(byte)
else: else:
blocksize = byte + 1 blocksize = byte_int + 1
data.append(fobj.read(blocksize)) data.append(fobj.read(blocksize))
bytesleft -= blocksize bytesleft -= blocksize
if bytesleft <= 0: if bytesleft <= 0:
@ -92,7 +96,9 @@ def read_32(fobj, start_length, size):
return {"RGB": im} 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 # Alpha masks seem to be uncompressed
start = start_length[0] start = start_length[0]
fobj.seek(start) fobj.seek(start)
@ -102,10 +108,14 @@ def read_mk(fobj, start_length, size):
return {"A": band} 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 (start, length) = start_length
fobj.seek(start) fobj.seek(start)
sig = fobj.read(12) sig = fobj.read(12)
im: Image.Image
if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a": if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
fobj.seek(start) fobj.seek(start)
im = PngImagePlugin.PngImageFile(fobj) 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 fobj is a file-like object as an icns resource
""" """
# signature : (start, length) # signature : (start, length)
self.dct = dct = {} self.dct = {}
self.fobj = fobj self.fobj = fobj
sig, filesize = nextheader(fobj) sig, filesize = nextheader(fobj)
if not _accept(sig): if not _accept(sig):
@ -183,11 +193,11 @@ class IcnsFile:
raise SyntaxError(msg) raise SyntaxError(msg)
i += HEADERSIZE i += HEADERSIZE
blocksize -= HEADERSIZE blocksize -= HEADERSIZE
dct[sig] = (i, blocksize) self.dct[sig] = (i, blocksize)
fobj.seek(blocksize, io.SEEK_CUR) fobj.seek(blocksize, io.SEEK_CUR)
i += blocksize i += blocksize
def itersizes(self): def itersizes(self) -> list[tuple[int, int, int]]:
sizes = [] sizes = []
for size, fmts in self.SIZES.items(): for size, fmts in self.SIZES.items():
for fmt, reader in fmts: for fmt, reader in fmts:
@ -196,14 +206,14 @@ class IcnsFile:
break break
return sizes return sizes
def bestsize(self): def bestsize(self) -> tuple[int, int, int]:
sizes = self.itersizes() sizes = self.itersizes()
if not sizes: if not sizes:
msg = "No 32bit icon resources found" msg = "No 32bit icon resources found"
raise SyntaxError(msg) raise SyntaxError(msg)
return max(sizes) 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 Get an icon resource as {channel: array}. Note that
the arrays are bottom-up like windows bitmaps and will likely the arrays are bottom-up like windows bitmaps and will likely
@ -216,18 +226,20 @@ class IcnsFile:
dct.update(reader(self.fobj, desc, size)) dct.update(reader(self.fobj, desc, size))
return dct 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: if size is None:
size = self.bestsize() size = self.bestsize()
if len(size) == 2: elif len(size) == 2:
size = (size[0], size[1], 1) size = (size[0], size[1], 1)
channels = self.dataforsize(size) channels = self.dataforsize(size)
im = channels.get("RGBA", None) im = channels.get("RGBA")
if im: if im:
return im return im
im = channels.get("RGB").copy() im = channels["RGB"].copy()
try: try:
im.putalpha(channels["A"]) im.putalpha(channels["A"])
except KeyError: except KeyError:
@ -268,7 +280,7 @@ class IcnsImageFile(ImageFile.ImageFile):
return self._size return self._size
@size.setter @size.setter
def size(self, value): def size(self, value) -> None:
info_size = value info_size = value
if info_size not in self.info["sizes"] and len(info_size) == 2: if info_size not in self.info["sizes"] and len(info_size) == 2:
info_size = (info_size[0], info_size[1], 1) info_size = (info_size[0], info_size[1], 1)
@ -287,7 +299,7 @@ class IcnsImageFile(ImageFile.ImageFile):
raise ValueError(msg) raise ValueError(msg)
self._size = value self._size = value
def load(self): def load(self) -> Image.core.PixelAccess | None:
if len(self.size) == 3: if len(self.size) == 3:
self.best_size = self.size self.best_size = self.size
self.size = ( self.size = (

View File

@ -120,7 +120,7 @@ def _accept(prefix: bytes) -> bool:
class IcoFile: class IcoFile:
def __init__(self, buf): def __init__(self, buf) -> None:
""" """
Parse image from file-like object containing ico file data Parse image from file-like object containing ico file data
""" """
@ -177,19 +177,19 @@ class IcoFile:
# ICO images are usually squares # ICO images are usually squares
self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True) 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. Get a list of all available icon sizes and color depths.
""" """
return {(h["width"], h["height"]) for h in self.entry} 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): for i, h in enumerate(self.entry):
if size == h["dim"] and (bpp is False or bpp == h["color_depth"]): if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
return i return i
return 0 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 Get an image from the icon
""" """
@ -321,7 +321,7 @@ class IcoImageFile(ImageFile.ImageFile):
raise ValueError(msg) raise ValueError(msg)
self._size = value 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: if self.im is not None and self.im.size == self.size:
# Already loaded # Already loaded
return Image.Image.load(self) return Image.Image.load(self)
@ -341,6 +341,7 @@ class IcoImageFile(ImageFile.ImageFile):
self.info["sizes"] = set(sizes) self.info["sizes"] = set(sizes)
self.size = im.size self.size = im.size
return None
def load_seek(self, pos: int) -> None: def load_seek(self, pos: int) -> None:
# Flag the ImageFile.Parser so that it # Flag the ImageFile.Parser so that it

View File

@ -1395,7 +1395,7 @@ class Image:
def getcolors( def getcolors(
self, maxcolors: int = 256 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. Returns a list of colors used in this image.
@ -1412,7 +1412,7 @@ class Image:
self.load() self.load()
if self.mode in ("1", "L", "P"): if self.mode in ("1", "L", "P"):
h = self.im.histogram() 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: if len(out) > maxcolors:
return None return None
return out return out

View File

@ -26,7 +26,7 @@ from . import Image
_viewers = [] _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:: 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, Zero or a negative integer to prepend this viewer to the list,
a positive integer to append it. a positive integer to append it.
""" """
try: if isinstance(viewer, type) and issubclass(viewer, Viewer):
if issubclass(viewer, Viewer):
viewer = viewer() viewer = viewer()
except TypeError:
pass # raised if viewer wasn't a class
if order > 0: if order > 0:
_viewers.append(viewer) _viewers.append(viewer)
else: else:

View File

@ -148,7 +148,7 @@ class IptcImageFile(ImageFile.ImageFile):
if tag == (8, 10): if tag == (8, 10):
self.tile = [("iptc", (0, 0) + self.size, offset, compression)] 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": if len(self.tile) != 1 or self.tile[0][0] != "iptc":
return ImageFile.ImageFile.load(self) return ImageFile.ImageFile.load(self)
@ -176,6 +176,7 @@ class IptcImageFile(ImageFile.ImageFile):
with Image.open(o) as _im: with Image.open(o) as _im:
_im.load() _im.load()
self.im = _im.im self.im = _im.im
return None
Image.register_open(IptcImageFile.format, IptcImageFile) Image.register_open(IptcImageFile.format, IptcImageFile)

View File

@ -299,7 +299,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def reduce(self, value): def reduce(self, value):
self._reduce = value self._reduce = value
def load(self): def load(self) -> Image.core.PixelAccess | None:
if self.tile and self._reduce: if self.tile and self._reduce:
power = 1 << self._reduce power = 1 << self._reduce
adjust = power >> 1 adjust = power >> 1

View File

@ -1232,7 +1232,7 @@ class TiffImageFile(ImageFile.ImageFile):
val = val[math.ceil((10 + n + size) / 2) * 2 :] val = val[math.ceil((10 + n + size) / 2) * 2 :]
return blocks return blocks
def load(self): def load(self) -> Image.core.PixelAccess | None:
if self.tile and self.use_load_libtiff: if self.tile and self.use_load_libtiff:
return self._load_libtiff() return self._load_libtiff()
return super().load() return super().load()

View File

@ -50,7 +50,7 @@ class WalImageFile(ImageFile.ImageFile):
if next_name: if next_name:
self.info["next_name"] = next_name self.info["next_name"] = next_name
def load(self): def load(self) -> Image.core.PixelAccess | None:
if not self.im: if not self.im:
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self.size[0] * self.size[1])) 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: while self.__physical_frame < frame:
self._get_next() # Advance to the requested frame self._get_next() # Advance to the requested frame
def load(self): def load(self) -> Image.core.PixelAccess | None:
if _webp.HAVE_WEBPANIM: if _webp.HAVE_WEBPANIM:
if self.__loaded != self.__logical_frame: if self.__loaded != self.__logical_frame:
self._seek(self.__logical_frame) self._seek(self.__logical_frame)