From b6c755df3204f625cb9761edea98a59d604aa153 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 31 Mar 2024 17:28:00 +0300 Subject: [PATCH 1/3] Add type hints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- src/PIL/Image.py | 100 +++++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 41981d77c..ccbfe2a6f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -55,6 +55,7 @@ from . import ( _plugins, ) from ._binary import i32le, o32be, o32le +from ._typing import TypeGuard from ._util import DeferredError, is_path ElementTree: ModuleType | None @@ -120,7 +121,7 @@ except ImportError: cffi = None -def isImageType(t): +def isImageType(t: Any) -> TypeGuard[Image]: """ Checks if an object is an image object. @@ -267,7 +268,7 @@ def getmodebase(mode: str) -> str: return ImageMode.getmode(mode).basemode -def getmodetype(mode): +def getmodetype(mode: str) -> str: """ Gets the storage type mode. Given a mode, this function returns a single-layer mode suitable for storing individual bands. @@ -279,7 +280,7 @@ def getmodetype(mode): return ImageMode.getmode(mode).basetype -def getmodebandnames(mode): +def getmodebandnames(mode: str) -> tuple[str, ...]: """ Gets a list of individual band names. Given a mode, this function returns a tuple containing the names of individual bands (use @@ -311,7 +312,7 @@ def getmodebands(mode: str) -> int: _initialized = 0 -def preinit(): +def preinit() -> None: """ Explicitly loads BMP, GIF, JPEG, PPM and PPM file format drivers. @@ -437,7 +438,7 @@ def _getencoder(mode, encoder_name, args, extra=()): class _E: - def __init__(self, scale, offset): + def __init__(self, scale, offset) -> None: self.scale = scale self.offset = offset @@ -508,22 +509,22 @@ class Image: self._exif = None @property - def width(self): + def width(self) -> int: return self.size[0] @property - def height(self): + def height(self) -> int: return self.size[1] @property - def size(self): + def size(self) -> tuple[int, int]: return self._size @property def mode(self): return self._mode - def _new(self, im): + def _new(self, im: Image) -> Image: new = Image() new.im = im new._mode = im.mode @@ -556,7 +557,7 @@ class Image: self._close_fp() self.fp = None - def close(self): + def close(self) -> None: """ Closes the file pointer, if possible. @@ -589,7 +590,7 @@ class Image: self.pyaccess = None self.readonly = 0 - def _ensure_mutable(self): + def _ensure_mutable(self) -> None: if self.readonly: self._copy() else: @@ -629,7 +630,7 @@ class Image: and self.tobytes() == other.tobytes() ) - def __repr__(self): + def __repr__(self) -> str: return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % ( self.__class__.__module__, self.__class__.__name__, @@ -639,7 +640,7 @@ class Image: id(self), ) - def _repr_pretty_(self, p, cycle): + def _repr_pretty_(self, p, cycle) -> None: """IPython plain text display support""" # Same as __repr__ but without unpredictable id(self), @@ -711,7 +712,7 @@ class Image: im_data = self.tobytes() # load image first return [self.info, self.mode, self.size, self.getpalette(), im_data] - def __setstate__(self, state): + def __setstate__(self, state) -> None: Image.__init__(self) info, mode, size, palette, data = state self.info = info @@ -774,7 +775,7 @@ class Image: return b"".join(output) - def tobitmap(self, name="image"): + def tobitmap(self, name: str = "image") -> bytes: """ Returns the image converted to an X11 bitmap. @@ -886,7 +887,12 @@ class Image: pass def convert( - self, mode=None, matrix=None, dither=None, palette=Palette.WEB, colors=256 + self, + mode: str | None = None, + matrix: tuple[float, ...] | None = None, + dither: Dither | None = None, + palette: Palette = Palette.WEB, + colors: int = 256, ) -> Image: """ Returns a converted copy of this image. For the "P" mode, this @@ -1117,12 +1123,12 @@ class Image: def quantize( self, - colors=256, - method=None, - kmeans=0, + colors: int = 256, + method: Quantize | None = None, + kmeans: int = 0, palette=None, - dither=Dither.FLOYDSTEINBERG, - ): + dither: Dither = Dither.FLOYDSTEINBERG, + ) -> Image: """ Convert the image to 'P' mode with the specified number of colors. @@ -1210,7 +1216,7 @@ class Image: __copy__ = copy - def crop(self, box=None) -> Image: + def crop(self, box: tuple[int, int, int, int] | None = None) -> Image: """ Returns a rectangular region from this image. The box is a 4-tuple defining the left, upper, right, and lower pixel @@ -1341,7 +1347,7 @@ class Image: self.load() return self.im.getbbox(alpha_only) - def getcolors(self, maxcolors=256): + def getcolors(self, maxcolors: int = 256) -> list[tuple[int, int]] | None: """ Returns a list of colors used in this image. @@ -1364,7 +1370,7 @@ class Image: return out return self.im.getcolors(maxcolors) - def getdata(self, band=None): + def getdata(self, band: int | None = None) -> Image: """ Returns the contents of this image as a sequence object containing pixel values. The sequence object is flattened, so @@ -1387,7 +1393,7 @@ class Image: return self.im.getband(band) return self.im # could be abused - def getextrema(self): + def getextrema(self) -> tuple[float, float] | tuple[tuple[float, float], ...]: """ Gets the minimum and maximum pixel values for each band in the image. @@ -1468,7 +1474,7 @@ class Image: return self._exif - def _reload_exif(self): + def _reload_exif(self) -> None: if self._exif is None or not self._exif._loaded: return self._exif._loaded = False @@ -1605,7 +1611,7 @@ class Image: return self.pyaccess.getpixel(xy) return self.im.getpixel(tuple(xy)) - def getprojection(self): + def getprojection(self) -> tuple[list[int], list[int]]: """ Get projection to x and y axes @@ -1617,7 +1623,7 @@ class Image: x, y = self.im.getprojection() return list(x), list(y) - def histogram(self, mask=None, extrema=None) -> list[int]: + def histogram(self, mask: Image | None = None, extrema=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 @@ -2463,7 +2469,7 @@ class Image: if open_fp: fp.close() - def seek(self, frame) -> None: + def seek(self, frame: int) -> None: """ Seeks to the given frame in this sequence file. If you seek beyond the end of the sequence, the method raises an @@ -2485,7 +2491,7 @@ class Image: msg = "no more images in file" raise EOFError(msg) - def show(self, title=None): + def show(self, title: str | None = None) -> None: """ Displays this image. This method is mainly intended for debugging purposes. @@ -2526,7 +2532,7 @@ class Image: return (self.copy(),) return tuple(map(self._new, self.im.split())) - def getchannel(self, channel): + def getchannel(self, channel: int | str) -> Image: """ Returns an image containing a single channel of the source image. @@ -2601,13 +2607,13 @@ class Image: provided_size = tuple(map(math.floor, size)) - def preserve_aspect_ratio(): + def preserve_aspect_ratio() -> tuple[int, int] | None: def round_aspect(number, key): return max(min(math.floor(number), math.ceil(number), key=key), 1) x, y = provided_size if x >= self.width and y >= self.height: - return + return None aspect = self.width / self.height if x / y >= aspect: @@ -2927,7 +2933,9 @@ def _check_size(size): return True -def new(mode, size, color=0) -> Image: +def new( + mode: str, size: tuple[int, int], color: float | tuple[float, ...] | str | None = 0 +) -> Image: """ Creates a new image with the given mode and size. @@ -3193,7 +3201,7 @@ _fromarray_typemap = { } -def _decompression_bomb_check(size): +def _decompression_bomb_check(size: tuple[int, int]) -> None: if MAX_IMAGE_PIXELS is None: return @@ -3335,7 +3343,7 @@ def open(fp, mode="r", formats=None) -> Image: # Image processing. -def alpha_composite(im1, im2): +def alpha_composite(im1: Image, im2: Image) -> Image: """ Alpha composite im2 over im1. @@ -3350,7 +3358,7 @@ def alpha_composite(im1, im2): return im1._new(core.alpha_composite(im1.im, im2.im)) -def blend(im1, im2, alpha): +def blend(im1: Image, im2: Image, alpha: float) -> Image: """ Creates a new image by interpolating between two input images, using a constant alpha:: @@ -3373,7 +3381,7 @@ def blend(im1, im2, alpha): return im1._new(core.blend(im1.im, im2.im, alpha)) -def composite(image1, image2, mask): +def composite(image1: Image, image2: Image, mask: Image) -> Image: """ Create composite image by blending images using a transparency mask. @@ -3483,7 +3491,7 @@ def register_save(id: str, driver) -> None: SAVE[id.upper()] = driver -def register_save_all(id, driver): +def register_save_all(id, driver) -> None: """ Registers an image function to save all the frames of a multiframe format. This function should not be @@ -3557,7 +3565,7 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None: # Simple display support. -def _show(image, **options): +def _show(image, **options) -> None: from . import ImageShow ImageShow.show(image, **options) @@ -3613,7 +3621,7 @@ def radial_gradient(mode): # Resources -def _apply_env_variables(env=None): +def _apply_env_variables(env=None) -> None: if env is None: env = os.environ @@ -3928,13 +3936,13 @@ class Exif(_ExifBase): } return ifd - def hide_offsets(self): + def hide_offsets(self) -> None: for tag in (ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo): if tag in self: self._hidden_data[tag] = self[tag] del self[tag] - def __str__(self): + def __str__(self) -> str: if self._info is not None: # Load all keys into self._data for tag in self._info: @@ -3942,7 +3950,7 @@ class Exif(_ExifBase): return str(self._data) - def __len__(self): + def __len__(self) -> int: keys = set(self._data) if self._info is not None: keys.update(self._info) @@ -3954,10 +3962,10 @@ class Exif(_ExifBase): del self._info[tag] return self._data[tag] - def __contains__(self, tag): + def __contains__(self, tag) -> bool: return tag in self._data or (self._info is not None and tag in self._info) - def __setitem__(self, tag, value): + def __setitem__(self, tag, value) -> None: if self._info is not None and tag in self._info: del self._info[tag] self._data[tag] = value From 008b28eb0caffb8a54fd043686cae996f0a842b6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 31 Mar 2024 23:20:11 -0600 Subject: [PATCH 2/3] Add type hints Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/Image.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ccbfe2a6f..b92c0bb43 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -524,7 +524,7 @@ class Image: def mode(self): return self._mode - def _new(self, im: Image) -> Image: + def _new(self, im: Image): new = Image() new.im = im new._mode = im.mode @@ -1347,7 +1347,9 @@ class Image: self.load() return self.im.getbbox(alpha_only) - def getcolors(self, maxcolors: int = 256) -> list[tuple[int, int]] | None: + def getcolors( + self, maxcolors: int = 256 + ) -> list[tuple[int, int | tuple[int, ...]]] | None: """ Returns a list of colors used in this image. @@ -1370,7 +1372,7 @@ class Image: return out return self.im.getcolors(maxcolors) - def getdata(self, band: int | None = None) -> Image: + def getdata(self, band: int | None = None): """ Returns the contents of this image as a sequence object containing pixel values. The sequence object is flattened, so @@ -1393,7 +1395,7 @@ class Image: return self.im.getband(band) return self.im # could be abused - def getextrema(self) -> tuple[float, float] | tuple[tuple[float, float], ...]: + def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]: """ Gets the minimum and maximum pixel values for each band in the image. From 7c5d0b9284144e785ee17cb9395063747d4ab180 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 31 Mar 2024 23:44:27 -0600 Subject: [PATCH 3/3] Add type hints Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/Image.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b92c0bb43..baef0aa11 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -524,7 +524,7 @@ class Image: def mode(self): return self._mode - def _new(self, im: Image): + def _new(self, im) -> Image: new = Image() new.im = im new._mode = im.mode @@ -1347,9 +1347,7 @@ class Image: self.load() return self.im.getbbox(alpha_only) - def getcolors( - self, maxcolors: int = 256 - ) -> list[tuple[int, int | tuple[int, ...]]] | None: + def getcolors(self, maxcolors: int = 256): """ Returns a list of colors used in this image.