mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
Merge pull request #8150 from radarhere/type_hint_image
Added type hints to Image
This commit is contained in:
commit
b1d5d7f6f9
|
@ -115,7 +115,11 @@ class FitsImageFile(ImageFile.ImageFile):
|
|||
elif number_of_bits in (-32, -64):
|
||||
self._mode = "F"
|
||||
|
||||
args = (self.mode, 0, -1) if decoder_name == "raw" else (number_of_bits,)
|
||||
args: tuple[str | int, ...]
|
||||
if decoder_name == "raw":
|
||||
args = (self.mode, 0, -1)
|
||||
else:
|
||||
args = (number_of_bits,)
|
||||
return decoder_name, offset, args
|
||||
|
||||
|
||||
|
|
|
@ -458,6 +458,8 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
frame_im = self.im.convert("RGBA")
|
||||
else:
|
||||
frame_im = self.im.convert("RGB")
|
||||
|
||||
assert self.dispose_extent is not None
|
||||
frame_im = self._crop(frame_im, self.dispose_extent)
|
||||
|
||||
self.im = self._prev_im
|
||||
|
|
173
src/PIL/Image.py
173
src/PIL/Image.py
|
@ -410,7 +410,9 @@ def init() -> bool:
|
|||
# Codec factories (used by tobytes/frombytes and ImageFile.load)
|
||||
|
||||
|
||||
def _getdecoder(mode, decoder_name, args, extra=()):
|
||||
def _getdecoder(
|
||||
mode: str, decoder_name: str, args: Any, extra: tuple[Any, ...] = ()
|
||||
) -> core.ImagingDecoder | ImageFile.PyDecoder:
|
||||
# tweak arguments
|
||||
if args is None:
|
||||
args = ()
|
||||
|
@ -433,7 +435,9 @@ def _getdecoder(mode, decoder_name, args, extra=()):
|
|||
return decoder(mode, *args + extra)
|
||||
|
||||
|
||||
def _getencoder(mode, encoder_name, args, extra=()):
|
||||
def _getencoder(
|
||||
mode: str, encoder_name: str, args: Any, extra: tuple[Any, ...] = ()
|
||||
) -> core.ImagingEncoder | ImageFile.PyEncoder:
|
||||
# tweak arguments
|
||||
if args is None:
|
||||
args = ()
|
||||
|
@ -550,10 +554,10 @@ class Image:
|
|||
return self._size
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
def mode(self) -> str:
|
||||
return self._mode
|
||||
|
||||
def _new(self, im) -> Image:
|
||||
def _new(self, im: core.ImagingCore) -> Image:
|
||||
new = Image()
|
||||
new.im = im
|
||||
new._mode = im.mode
|
||||
|
@ -687,7 +691,7 @@ class Image:
|
|||
)
|
||||
)
|
||||
|
||||
def _repr_image(self, image_format, **kwargs):
|
||||
def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None:
|
||||
"""Helper function for iPython display hook.
|
||||
|
||||
:param image_format: Image format.
|
||||
|
@ -700,14 +704,14 @@ class Image:
|
|||
return None
|
||||
return b.getvalue()
|
||||
|
||||
def _repr_png_(self):
|
||||
def _repr_png_(self) -> bytes | None:
|
||||
"""iPython display hook support for PNG format.
|
||||
|
||||
:returns: PNG version of the image as bytes
|
||||
"""
|
||||
return self._repr_image("PNG", compress_level=1)
|
||||
|
||||
def _repr_jpeg_(self):
|
||||
def _repr_jpeg_(self) -> bytes | None:
|
||||
"""iPython display hook support for JPEG format.
|
||||
|
||||
:returns: JPEG version of the image as bytes
|
||||
|
@ -754,7 +758,7 @@ class Image:
|
|||
self.putpalette(palette)
|
||||
self.frombytes(data)
|
||||
|
||||
def tobytes(self, encoder_name: str = "raw", *args) -> bytes:
|
||||
def tobytes(self, encoder_name: str = "raw", *args: Any) -> bytes:
|
||||
"""
|
||||
Return image as a bytes object.
|
||||
|
||||
|
@ -776,12 +780,13 @@ class Image:
|
|||
:returns: A :py:class:`bytes` object.
|
||||
"""
|
||||
|
||||
# may pass tuple instead of argument list
|
||||
if len(args) == 1 and isinstance(args[0], tuple):
|
||||
args = args[0]
|
||||
encoder_args: Any = args
|
||||
if len(encoder_args) == 1 and isinstance(encoder_args[0], tuple):
|
||||
# may pass tuple instead of argument list
|
||||
encoder_args = encoder_args[0]
|
||||
|
||||
if encoder_name == "raw" and args == ():
|
||||
args = self.mode
|
||||
if encoder_name == "raw" and encoder_args == ():
|
||||
encoder_args = self.mode
|
||||
|
||||
self.load()
|
||||
|
||||
|
@ -789,7 +794,7 @@ class Image:
|
|||
return b""
|
||||
|
||||
# unpack data
|
||||
e = _getencoder(self.mode, encoder_name, args)
|
||||
e = _getencoder(self.mode, encoder_name, encoder_args)
|
||||
e.setimage(self.im)
|
||||
|
||||
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
|
||||
|
@ -832,7 +837,9 @@ class Image:
|
|||
]
|
||||
)
|
||||
|
||||
def frombytes(self, data: bytes, decoder_name: str = "raw", *args) -> None:
|
||||
def frombytes(
|
||||
self, data: bytes | bytearray, decoder_name: str = "raw", *args: Any
|
||||
) -> None:
|
||||
"""
|
||||
Loads this image with pixel data from a bytes object.
|
||||
|
||||
|
@ -843,16 +850,17 @@ class Image:
|
|||
if self.width == 0 or self.height == 0:
|
||||
return
|
||||
|
||||
# may pass tuple instead of argument list
|
||||
if len(args) == 1 and isinstance(args[0], tuple):
|
||||
args = args[0]
|
||||
decoder_args: Any = args
|
||||
if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple):
|
||||
# may pass tuple instead of argument list
|
||||
decoder_args = decoder_args[0]
|
||||
|
||||
# default format
|
||||
if decoder_name == "raw" and args == ():
|
||||
args = self.mode
|
||||
if decoder_name == "raw" and decoder_args == ():
|
||||
decoder_args = self.mode
|
||||
|
||||
# unpack data
|
||||
d = _getdecoder(self.mode, decoder_name, args)
|
||||
d = _getdecoder(self.mode, decoder_name, decoder_args)
|
||||
d.setimage(self.im)
|
||||
s = d.decode(data)
|
||||
|
||||
|
@ -996,9 +1004,11 @@ class Image:
|
|||
if has_transparency and self.im.bands == 3:
|
||||
transparency = new_im.info["transparency"]
|
||||
|
||||
def convert_transparency(m, v):
|
||||
v = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5
|
||||
return max(0, min(255, int(v)))
|
||||
def convert_transparency(
|
||||
m: tuple[float, ...], v: tuple[int, int, int]
|
||||
) -> int:
|
||||
value = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5
|
||||
return max(0, min(255, int(value)))
|
||||
|
||||
if mode == "L":
|
||||
transparency = convert_transparency(matrix, transparency)
|
||||
|
@ -1250,7 +1260,7 @@ class Image:
|
|||
|
||||
__copy__ = copy
|
||||
|
||||
def crop(self, box: tuple[int, int, int, int] | None = None) -> Image:
|
||||
def crop(self, box: tuple[float, float, float, float] | None = None) -> Image:
|
||||
"""
|
||||
Returns a rectangular region from this image. The box is a
|
||||
4-tuple defining the left, upper, right, and lower pixel
|
||||
|
@ -1276,7 +1286,9 @@ class Image:
|
|||
self.load()
|
||||
return self._new(self._crop(self.im, box))
|
||||
|
||||
def _crop(self, im, box):
|
||||
def _crop(
|
||||
self, im: core.ImagingCore, box: tuple[float, float, float, float]
|
||||
) -> core.ImagingCore:
|
||||
"""
|
||||
Returns a rectangular region from the core image object im.
|
||||
|
||||
|
@ -1448,7 +1460,7 @@ class Image:
|
|||
return self.im.getextrema()
|
||||
|
||||
def _getxmp(self, xmp_tags):
|
||||
def get_name(tag):
|
||||
def get_name(tag: str) -> str:
|
||||
return re.sub("^{[^}]+}", "", tag)
|
||||
|
||||
def get_value(element):
|
||||
|
@ -1549,7 +1561,11 @@ class Image:
|
|||
fp = io.BytesIO(data)
|
||||
|
||||
with open(fp) as im:
|
||||
if thumbnail_offset is None:
|
||||
from . import TiffImagePlugin
|
||||
|
||||
if thumbnail_offset is None and isinstance(
|
||||
im, TiffImagePlugin.TiffImageFile
|
||||
):
|
||||
im._frame_pos = [ifd_offset]
|
||||
im._seek(0)
|
||||
im.load()
|
||||
|
@ -1803,7 +1819,9 @@ class Image:
|
|||
else:
|
||||
self.im.paste(im, box)
|
||||
|
||||
def alpha_composite(self, im, dest=(0, 0), source=(0, 0)):
|
||||
def alpha_composite(
|
||||
self, im: Image, dest: Sequence[int] = (0, 0), source: Sequence[int] = (0, 0)
|
||||
) -> None:
|
||||
"""'In-place' analog of Image.alpha_composite. Composites an image
|
||||
onto this image.
|
||||
|
||||
|
@ -1818,32 +1836,35 @@ class Image:
|
|||
"""
|
||||
|
||||
if not isinstance(source, (list, tuple)):
|
||||
msg = "Source must be a tuple"
|
||||
msg = "Source must be a list or tuple"
|
||||
raise ValueError(msg)
|
||||
if not isinstance(dest, (list, tuple)):
|
||||
msg = "Destination must be a tuple"
|
||||
msg = "Destination must be a list or tuple"
|
||||
raise ValueError(msg)
|
||||
if len(source) not in (2, 4):
|
||||
msg = "Source must be a 2 or 4-tuple"
|
||||
|
||||
if len(source) == 4:
|
||||
overlay_crop_box = tuple(source)
|
||||
elif len(source) == 2:
|
||||
overlay_crop_box = tuple(source) + im.size
|
||||
else:
|
||||
msg = "Source must be a sequence of length 2 or 4"
|
||||
raise ValueError(msg)
|
||||
|
||||
if not len(dest) == 2:
|
||||
msg = "Destination must be a 2-tuple"
|
||||
msg = "Destination must be a sequence of length 2"
|
||||
raise ValueError(msg)
|
||||
if min(source) < 0:
|
||||
msg = "Source must be non-negative"
|
||||
raise ValueError(msg)
|
||||
|
||||
if len(source) == 2:
|
||||
source = source + im.size
|
||||
|
||||
# over image, crop if it's not the whole thing.
|
||||
if source == (0, 0) + im.size:
|
||||
# over image, crop if it's not the whole image.
|
||||
if overlay_crop_box == (0, 0) + im.size:
|
||||
overlay = im
|
||||
else:
|
||||
overlay = im.crop(source)
|
||||
overlay = im.crop(overlay_crop_box)
|
||||
|
||||
# target for the paste
|
||||
box = dest + (dest[0] + overlay.width, dest[1] + overlay.height)
|
||||
box = tuple(dest) + (dest[0] + overlay.width, dest[1] + overlay.height)
|
||||
|
||||
# destination image. don't copy if we're using the whole image.
|
||||
if box == (0, 0) + self.size:
|
||||
|
@ -1854,7 +1875,11 @@ class Image:
|
|||
result = alpha_composite(background, overlay)
|
||||
self.paste(result, box)
|
||||
|
||||
def point(self, lut, mode: str | None = None) -> Image:
|
||||
def point(
|
||||
self,
|
||||
lut: Sequence[float] | Callable[[int], float] | ImagePointHandler,
|
||||
mode: str | None = None,
|
||||
) -> Image:
|
||||
"""
|
||||
Maps this image through a lookup table or function.
|
||||
|
||||
|
@ -1891,7 +1916,9 @@ class Image:
|
|||
scale, offset = _getscaleoffset(lut)
|
||||
return self._new(self.im.point_transform(scale, offset))
|
||||
# for other modes, convert the function to a table
|
||||
lut = [lut(i) for i in range(256)] * self.im.bands
|
||||
flatLut = [lut(i) for i in range(256)] * self.im.bands
|
||||
else:
|
||||
flatLut = lut
|
||||
|
||||
if self.mode == "F":
|
||||
# FIXME: _imaging returns a confusing error message for this case
|
||||
|
@ -1899,8 +1926,8 @@ class Image:
|
|||
raise ValueError(msg)
|
||||
|
||||
if mode != "F":
|
||||
lut = [round(i) for i in lut]
|
||||
return self._new(self.im.point(lut, mode))
|
||||
flatLut = [round(i) for i in flatLut]
|
||||
return self._new(self.im.point(flatLut, mode))
|
||||
|
||||
def putalpha(self, alpha):
|
||||
"""
|
||||
|
@ -2973,29 +3000,29 @@ def _wedge() -> Image:
|
|||
return Image()._new(core.wedge("L"))
|
||||
|
||||
|
||||
def _check_size(size):
|
||||
def _check_size(size: Any) -> None:
|
||||
"""
|
||||
Common check to enforce type and sanity check on size tuples
|
||||
|
||||
:param size: Should be a 2 tuple of (width, height)
|
||||
:returns: True, or raises a ValueError
|
||||
:returns: None, or raises a ValueError
|
||||
"""
|
||||
|
||||
if not isinstance(size, (list, tuple)):
|
||||
msg = "Size must be a tuple"
|
||||
msg = "Size must be a list or tuple"
|
||||
raise ValueError(msg)
|
||||
if len(size) != 2:
|
||||
msg = "Size must be a tuple of length 2"
|
||||
msg = "Size must be a sequence of length 2"
|
||||
raise ValueError(msg)
|
||||
if size[0] < 0 or size[1] < 0:
|
||||
msg = "Width and height must be >= 0"
|
||||
raise ValueError(msg)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def new(
|
||||
mode: str, size: tuple[int, int], color: float | tuple[float, ...] | str | None = 0
|
||||
mode: str,
|
||||
size: tuple[int, int] | list[int],
|
||||
color: float | tuple[float, ...] | str | None = 0,
|
||||
) -> Image:
|
||||
"""
|
||||
Creates a new image with the given mode and size.
|
||||
|
@ -3044,7 +3071,13 @@ def new(
|
|||
return im._new(core.fill(mode, size, color))
|
||||
|
||||
|
||||
def frombytes(mode, size, data, decoder_name: str = "raw", *args) -> Image:
|
||||
def frombytes(
|
||||
mode: str,
|
||||
size: tuple[int, int],
|
||||
data: bytes | bytearray,
|
||||
decoder_name: str = "raw",
|
||||
*args: Any,
|
||||
) -> Image:
|
||||
"""
|
||||
Creates a copy of an image memory from pixel data in a buffer.
|
||||
|
||||
|
@ -3072,18 +3105,21 @@ def frombytes(mode, size, data, decoder_name: str = "raw", *args) -> Image:
|
|||
|
||||
im = new(mode, size)
|
||||
if im.width != 0 and im.height != 0:
|
||||
# may pass tuple instead of argument list
|
||||
if len(args) == 1 and isinstance(args[0], tuple):
|
||||
args = args[0]
|
||||
decoder_args: Any = args
|
||||
if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple):
|
||||
# may pass tuple instead of argument list
|
||||
decoder_args = decoder_args[0]
|
||||
|
||||
if decoder_name == "raw" and args == ():
|
||||
args = mode
|
||||
if decoder_name == "raw" and decoder_args == ():
|
||||
decoder_args = mode
|
||||
|
||||
im.frombytes(data, decoder_name, args)
|
||||
im.frombytes(data, decoder_name, decoder_args)
|
||||
return im
|
||||
|
||||
|
||||
def frombuffer(mode: str, size, data, decoder_name: str = "raw", *args) -> Image:
|
||||
def frombuffer(
|
||||
mode: str, size: tuple[int, int], data, decoder_name: str = "raw", *args: Any
|
||||
) -> Image:
|
||||
"""
|
||||
Creates an image memory referencing pixel data in a byte buffer.
|
||||
|
||||
|
@ -3540,7 +3576,7 @@ def merge(mode: str, bands: Sequence[Image]) -> Image:
|
|||
|
||||
|
||||
def register_open(
|
||||
id,
|
||||
id: str,
|
||||
factory: Callable[[IO[bytes], str | bytes], ImageFile.ImageFile],
|
||||
accept: Callable[[bytes], bool | str] | None = None,
|
||||
) -> None:
|
||||
|
@ -3674,7 +3710,7 @@ def _show(image: Image, **options: Any) -> None:
|
|||
|
||||
|
||||
def effect_mandelbrot(
|
||||
size: tuple[int, int], extent: tuple[int, int, int, int], quality: int
|
||||
size: tuple[int, int], extent: tuple[float, float, float, float], quality: int
|
||||
) -> Image:
|
||||
"""
|
||||
Generate a Mandelbrot set covering the given extent.
|
||||
|
@ -3721,19 +3757,18 @@ def radial_gradient(mode: str) -> Image:
|
|||
# Resources
|
||||
|
||||
|
||||
def _apply_env_variables(env=None) -> None:
|
||||
if env is None:
|
||||
env = os.environ
|
||||
def _apply_env_variables(env: dict[str, str] | None = None) -> None:
|
||||
env_dict = env if env is not None else os.environ
|
||||
|
||||
for var_name, setter in [
|
||||
("PILLOW_ALIGNMENT", core.set_alignment),
|
||||
("PILLOW_BLOCK_SIZE", core.set_block_size),
|
||||
("PILLOW_BLOCKS_MAX", core.set_blocks_max),
|
||||
]:
|
||||
if var_name not in env:
|
||||
if var_name not in env_dict:
|
||||
continue
|
||||
|
||||
var = env[var_name].lower()
|
||||
var = env_dict[var_name].lower()
|
||||
|
||||
units = 1
|
||||
for postfix, mul in [("k", 1024), ("m", 1024 * 1024)]:
|
||||
|
@ -3742,13 +3777,13 @@ def _apply_env_variables(env=None) -> None:
|
|||
var = var[: -len(postfix)]
|
||||
|
||||
try:
|
||||
var = int(var) * units
|
||||
var_int = int(var) * units
|
||||
except ValueError:
|
||||
warnings.warn(f"{var_name} is not int")
|
||||
continue
|
||||
|
||||
try:
|
||||
setter(var)
|
||||
setter(var_int)
|
||||
except ValueError as e:
|
||||
warnings.warn(f"{var_name}: {e}")
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ def _parse_codestream(fp):
|
|||
elif csiz == 4:
|
||||
mode = "RGBA"
|
||||
else:
|
||||
mode = None
|
||||
mode = ""
|
||||
|
||||
return size, mode
|
||||
|
||||
|
@ -237,7 +237,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
|||
msg = "not a JPEG 2000 file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
if self.size is None or self.mode is None:
|
||||
if self.size is None or not self.mode:
|
||||
msg = "unable to determine size/mode"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
|
|
|
@ -129,15 +129,16 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
# and invert it because
|
||||
# Palm does grayscale from white (0) to black (1)
|
||||
bpp = im.encoderinfo["bpp"]
|
||||
im = im.point(
|
||||
lambda x, shift=8 - bpp, maxval=(1 << bpp) - 1: maxval - (x >> shift)
|
||||
)
|
||||
maxval = (1 << bpp) - 1
|
||||
shift = 8 - bpp
|
||||
im = im.point(lambda x: maxval - (x >> shift))
|
||||
elif im.info.get("bpp") in (1, 2, 4):
|
||||
# here we assume that even though the inherent mode is 8-bit grayscale,
|
||||
# only the lower bpp bits are significant.
|
||||
# We invert them to match the Palm.
|
||||
bpp = im.info["bpp"]
|
||||
im = im.point(lambda x, maxval=(1 << bpp) - 1: maxval - (x & maxval))
|
||||
maxval = (1 << bpp) - 1
|
||||
im = im.point(lambda x: maxval - (x & maxval))
|
||||
else:
|
||||
msg = f"cannot write mode {im.mode} as Palm"
|
||||
raise OSError(msg)
|
||||
|
|
|
@ -12,5 +12,11 @@ class ImagingDraw:
|
|||
class PixelAccess:
|
||||
def __getattr__(self, name: str) -> Any: ...
|
||||
|
||||
class ImagingDecoder:
|
||||
def __getattr__(self, name: str) -> Any: ...
|
||||
|
||||
class ImagingEncoder:
|
||||
def __getattr__(self, name: str) -> Any: ...
|
||||
|
||||
def font(image: ImagingCore, glyphdata: bytes) -> ImagingFont: ...
|
||||
def __getattr__(name: str) -> Any: ...
|
||||
|
|
Loading…
Reference in New Issue
Block a user