Skip to content

Commit

Permalink
Add type hints
Browse files Browse the repository at this point in the history
Co-authored-by: Ondrej Baranovič <ondreko.tiba@gmail.com>
  • Loading branch information
hugovk and nulano committed Mar 31, 2024
1 parent a6c7a04 commit b6c755d
Showing 1 changed file with 54 additions and 46 deletions.
100 changes: 54 additions & 46 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
_plugins,
)
from ._binary import i32le, o32be, o32le
from ._typing import TypeGuard
from ._util import DeferredError, is_path

ElementTree: ModuleType | None
Expand Down Expand Up @@ -120,7 +121,7 @@ class DecompressionBombError(Exception):
cffi = None


def isImageType(t):
def isImageType(t: Any) -> TypeGuard[Image]:
"""
Checks if an object is an image object.
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -508,22 +509,22 @@ def __init__(self):
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
Expand Down Expand Up @@ -556,7 +557,7 @@ def __exit__(self, *args):
self._close_fp()
self.fp = None

def close(self):
def close(self) -> None:
"""
Closes the file pointer, if possible.
Expand Down Expand Up @@ -589,7 +590,7 @@ def _copy(self) -> None:
self.pyaccess = None
self.readonly = 0

def _ensure_mutable(self):
def _ensure_mutable(self) -> None:
if self.readonly:
self._copy()
else:
Expand Down Expand Up @@ -629,7 +630,7 @@ def __eq__(self, other):
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__,
Expand All @@ -639,7 +640,7 @@ def __repr__(self):
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),
Expand Down Expand Up @@ -711,7 +712,7 @@ def __getstate__(self):
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
Expand Down Expand Up @@ -774,7 +775,7 @@ def tobytes(self, encoder_name: str = "raw", *args) -> bytes:

return b"".join(output)

def tobitmap(self, name="image"):
def tobitmap(self, name: str = "image") -> bytes:
"""
Returns the image converted to an X11 bitmap.
Expand Down Expand Up @@ -886,7 +887,12 @@ def verify(self):
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
Expand Down Expand Up @@ -1117,12 +1123,12 @@ def convert_transparency(m, v):

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.
Expand Down Expand Up @@ -1210,7 +1216,7 @@ def copy(self) -> 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
Expand Down Expand Up @@ -1341,7 +1347,7 @@ def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int]:
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.
Expand All @@ -1364,7 +1370,7 @@ def getcolors(self, maxcolors=256):
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
Expand All @@ -1387,7 +1393,7 @@ def getdata(self, band=None):
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.
Expand Down Expand Up @@ -1468,7 +1474,7 @@ def getexif(self) -> Exif:

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
Expand Down Expand Up @@ -1605,7 +1611,7 @@ def getpixel(self, xy):
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
Expand All @@ -1617,7 +1623,7 @@ def getprojection(self):
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
Expand Down Expand Up @@ -2463,7 +2469,7 @@ def save(self, fp, format=None, **params) -> None:
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
Expand All @@ -2485,7 +2491,7 @@ def seek(self, frame) -> None:
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.
Expand Down Expand Up @@ -2526,7 +2532,7 @@ def split(self) -> tuple[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.
Expand Down Expand Up @@ -2601,13 +2607,13 @@ def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0):

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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -3193,7 +3201,7 @@ def fromqpixmap(im):
}


def _decompression_bomb_check(size):
def _decompression_bomb_check(size: tuple[int, int]) -> None:
if MAX_IMAGE_PIXELS is None:
return

Expand Down Expand Up @@ -3335,7 +3343,7 @@ def _open_core(fp, filename, prefix, formats):
# Image processing.


def alpha_composite(im1, im2):
def alpha_composite(im1: Image, im2: Image) -> Image:
"""
Alpha composite im2 over im1.
Expand All @@ -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::
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -3928,21 +3936,21 @@ def get_ifd(self, tag):
}
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:
self[tag]

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)
Expand All @@ -3954,10 +3962,10 @@ def __getitem__(self, tag):
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
Expand Down

0 comments on commit b6c755d

Please sign in to comment.