mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-13 18:56:17 +03:00
Merge pull request #8032 from nulano/type_hints
Added type hints for PixelAccess related methods and others
This commit is contained in:
commit
d2b5e11d2b
|
@ -89,6 +89,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200
|
||||||
p.communicate()
|
p.communicate()
|
||||||
|
|
||||||
im = ImageGrab.grabclipboard()
|
im = ImageGrab.grabclipboard()
|
||||||
|
assert isinstance(im, list)
|
||||||
assert len(im) == 1
|
assert len(im) == 1
|
||||||
assert os.path.samefile(im[0], "Tests/images/hopper.gif")
|
assert os.path.samefile(im[0], "Tests/images/hopper.gif")
|
||||||
|
|
||||||
|
@ -105,6 +106,7 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
|
||||||
p.communicate()
|
p.communicate()
|
||||||
|
|
||||||
im = ImageGrab.grabclipboard()
|
im = ImageGrab.grabclipboard()
|
||||||
|
assert isinstance(im, Image.Image)
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
|
@ -120,6 +122,7 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
|
||||||
with open(image_path, "rb") as fp:
|
with open(image_path, "rb") as fp:
|
||||||
subprocess.call(["wl-copy"], stdin=fp)
|
subprocess.call(["wl-copy"], stdin=fp)
|
||||||
im = ImageGrab.grabclipboard()
|
im = ImageGrab.grabclipboard()
|
||||||
|
assert isinstance(im, Image.Image)
|
||||||
assert_image_equal_tofile(im, image_path)
|
assert_image_equal_tofile(im, image_path)
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
|
|
|
@ -691,23 +691,7 @@ Methods
|
||||||
:param hints: An optional list of hints.
|
:param hints: An optional list of hints.
|
||||||
:returns: A (drawing context, drawing resource factory) tuple.
|
:returns: A (drawing context, drawing resource factory) tuple.
|
||||||
|
|
||||||
.. py:method:: floodfill(image, xy, value, border=None, thresh=0)
|
.. autofunction:: PIL.ImageDraw.floodfill
|
||||||
|
|
||||||
.. warning:: This method is experimental.
|
|
||||||
|
|
||||||
Fills a bounded region with a given color.
|
|
||||||
|
|
||||||
:param image: Target image.
|
|
||||||
:param xy: Seed position (a 2-item coordinate tuple).
|
|
||||||
:param value: Fill color.
|
|
||||||
:param border: Optional border value. If given, the region consists of
|
|
||||||
pixels with a color different from the border color. If not given,
|
|
||||||
the region consists of pixels having the same color as the seed
|
|
||||||
pixel.
|
|
||||||
:param thresh: Optional threshold value which specifies a maximum
|
|
||||||
tolerable difference of a pixel value from the 'background' in
|
|
||||||
order for it to be replaced. Useful for filling regions of non-
|
|
||||||
homogeneous, but similar, colors.
|
|
||||||
|
|
||||||
.. _BCP 47 language code: https://www.w3.org/International/articles/language-tags/
|
.. _BCP 47 language code: https://www.w3.org/International/articles/language-tags/
|
||||||
.. _OpenType docs: https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
|
.. _OpenType docs: https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
|
||||||
|
|
|
@ -44,42 +44,23 @@ Access using negative indexes is also possible. ::
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
.. class:: PixelAccess
|
.. class:: PixelAccess
|
||||||
|
:canonical: PIL.Image.core.PixelAccess
|
||||||
|
|
||||||
.. method:: __setitem__(self, xy, color):
|
.. method:: __getitem__(self, xy: tuple[int, int]) -> float | tuple[int, ...]
|
||||||
|
|
||||||
Modifies the pixel at x,y. The color is given as a single
|
Returns the pixel at x,y. The pixel is returned as a single
|
||||||
numerical value for single band images, and a tuple for
|
value for single band images or a tuple for multi-band images.
|
||||||
multi-band images
|
|
||||||
|
|
||||||
:param xy: The pixel coordinate, given as (x, y).
|
|
||||||
:param color: The pixel value according to its mode. e.g. tuple (r, g, b) for RGB mode)
|
|
||||||
|
|
||||||
.. method:: __getitem__(self, xy):
|
|
||||||
|
|
||||||
Returns the pixel at x,y. The pixel is returned as a single
|
|
||||||
value for single band images or a tuple for multiple band
|
|
||||||
images
|
|
||||||
|
|
||||||
:param xy: The pixel coordinate, given as (x, y).
|
:param xy: The pixel coordinate, given as (x, y).
|
||||||
:returns: a pixel value for single band images, a tuple of
|
:returns: a pixel value for single band images, a tuple of
|
||||||
pixel values for multiband images.
|
pixel values for multiband images.
|
||||||
|
|
||||||
.. method:: putpixel(self, xy, color):
|
.. method:: __setitem__(self, xy: tuple[int, int], color: float | tuple[int, ...]) -> None
|
||||||
|
|
||||||
Modifies the pixel at x,y. The color is given as a single
|
Modifies the pixel at x,y. The color is given as a single
|
||||||
numerical value for single band images, and a tuple for
|
numerical value for single band images, and a tuple for
|
||||||
multi-band images. In addition to this, RGB and RGBA tuples
|
multi-band images.
|
||||||
are accepted for P and PA images.
|
|
||||||
|
|
||||||
:param xy: The pixel coordinate, given as (x, y).
|
:param xy: The pixel coordinate, given as (x, y).
|
||||||
:param color: The pixel value according to its mode. e.g. tuple (r, g, b) for RGB mode)
|
:param color: The pixel value according to its mode,
|
||||||
|
e.g. tuple (r, g, b) for RGB mode.
|
||||||
.. method:: getpixel(self, xy):
|
|
||||||
|
|
||||||
Returns the pixel at x,y. The pixel is returned as a single
|
|
||||||
value for single band images or a tuple for multiple band
|
|
||||||
images
|
|
||||||
|
|
||||||
:param xy: The pixel coordinate, given as (x, y).
|
|
||||||
:returns: a pixel value for single band images, a tuple of
|
|
||||||
pixel values for multiband images.
|
|
||||||
|
|
|
@ -44,3 +44,4 @@ Access using negative indexes is also possible. ::
|
||||||
|
|
||||||
.. autoclass:: PIL.PyAccess.PyAccess()
|
.. autoclass:: PIL.PyAccess.PyAccess()
|
||||||
:members:
|
:members:
|
||||||
|
:special-members: __getitem__, __setitem__
|
||||||
|
|
|
@ -41,7 +41,16 @@ import warnings
|
||||||
from collections.abc import Callable, MutableMapping
|
from collections.abc import Callable, MutableMapping
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, Tuple, cast
|
from typing import (
|
||||||
|
IO,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Literal,
|
||||||
|
Protocol,
|
||||||
|
Sequence,
|
||||||
|
Tuple,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
|
||||||
# VERSION was removed in Pillow 6.0.0.
|
# VERSION was removed in Pillow 6.0.0.
|
||||||
# PILLOW_VERSION was removed in Pillow 9.0.0.
|
# PILLOW_VERSION was removed in Pillow 9.0.0.
|
||||||
|
@ -218,7 +227,7 @@ if hasattr(core, "DEFAULT_STRATEGY"):
|
||||||
# Registries
|
# Registries
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import ImageFile
|
from . import ImageFile, PyAccess
|
||||||
ID: list[str] = []
|
ID: list[str] = []
|
||||||
OPEN: dict[
|
OPEN: dict[
|
||||||
str,
|
str,
|
||||||
|
@ -871,7 +880,7 @@ class Image:
|
||||||
msg = "cannot decode image data"
|
msg = "cannot decode image data"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
def load(self):
|
def load(self) -> core.PixelAccess | PyAccess.PyAccess | None:
|
||||||
"""
|
"""
|
||||||
Allocates storage for the image and loads the pixel data. In
|
Allocates storage for the image and loads the pixel data. In
|
||||||
normal cases, you don't need to call this method, since the
|
normal cases, you don't need to call this method, since the
|
||||||
|
@ -884,7 +893,7 @@ class Image:
|
||||||
operations. See :ref:`file-handling` for more information.
|
operations. See :ref:`file-handling` for more information.
|
||||||
|
|
||||||
:returns: An image access object.
|
:returns: An image access object.
|
||||||
:rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
|
:rtype: :py:class:`.PixelAccess` or :py:class:`.PyAccess`
|
||||||
"""
|
"""
|
||||||
if self.im is not None and self.palette and self.palette.dirty:
|
if self.im is not None and self.palette and self.palette.dirty:
|
||||||
# realize palette
|
# realize palette
|
||||||
|
@ -913,6 +922,7 @@ class Image:
|
||||||
if self.pyaccess:
|
if self.pyaccess:
|
||||||
return self.pyaccess
|
return self.pyaccess
|
||||||
return self.im.pixel_access(self.readonly)
|
return self.im.pixel_access(self.readonly)
|
||||||
|
return None
|
||||||
|
|
||||||
def verify(self) -> None:
|
def verify(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1102,7 +1112,10 @@ class Image:
|
||||||
del new_im.info["transparency"]
|
del new_im.info["transparency"]
|
||||||
if trns is not None:
|
if trns is not None:
|
||||||
try:
|
try:
|
||||||
new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
|
new_im.info["transparency"] = new_im.palette.getcolor(
|
||||||
|
cast(Tuple[int, int, int], trns), # trns was converted to RGB
|
||||||
|
new_im,
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
# if we can't make a transparent color, don't leave the old
|
# if we can't make a transparent color, don't leave the old
|
||||||
# transparency hanging around to mess us up.
|
# transparency hanging around to mess us up.
|
||||||
|
@ -1152,7 +1165,10 @@ class Image:
|
||||||
if trns is not None:
|
if trns is not None:
|
||||||
if new_im.mode == "P":
|
if new_im.mode == "P":
|
||||||
try:
|
try:
|
||||||
new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
|
new_im.info["transparency"] = new_im.palette.getcolor(
|
||||||
|
cast(Tuple[int, int, int], trns), # trns was converted to RGB
|
||||||
|
new_im,
|
||||||
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
del new_im.info["transparency"]
|
del new_im.info["transparency"]
|
||||||
if str(e) != "cannot allocate more than 256 colors":
|
if str(e) != "cannot allocate more than 256 colors":
|
||||||
|
@ -1657,7 +1673,9 @@ class Image:
|
||||||
|
|
||||||
del self.info["transparency"]
|
del self.info["transparency"]
|
||||||
|
|
||||||
def getpixel(self, xy):
|
def getpixel(
|
||||||
|
self, xy: tuple[int, int] | list[int]
|
||||||
|
) -> float | tuple[int, ...] | None:
|
||||||
"""
|
"""
|
||||||
Returns the pixel value at a given position.
|
Returns the pixel value at a given position.
|
||||||
|
|
||||||
|
@ -1941,15 +1959,14 @@ class Image:
|
||||||
flatLut = [round(i) for i in flatLut]
|
flatLut = [round(i) for i in flatLut]
|
||||||
return self._new(self.im.point(flatLut, mode))
|
return self._new(self.im.point(flatLut, mode))
|
||||||
|
|
||||||
def putalpha(self, alpha):
|
def putalpha(self, alpha: Image | int) -> None:
|
||||||
"""
|
"""
|
||||||
Adds or replaces the alpha layer in this image. If the image
|
Adds or replaces the alpha layer in this image. If the image
|
||||||
does not have an alpha layer, it's converted to "LA" or "RGBA".
|
does not have an alpha layer, it's converted to "LA" or "RGBA".
|
||||||
The new layer must be either "L" or "1".
|
The new layer must be either "L" or "1".
|
||||||
|
|
||||||
:param alpha: The new alpha layer. This can either be an "L" or "1"
|
:param alpha: The new alpha layer. This can either be an "L" or "1"
|
||||||
image having the same size as this image, or an integer or
|
image having the same size as this image, or an integer.
|
||||||
other color value.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._ensure_mutable()
|
self._ensure_mutable()
|
||||||
|
@ -1988,6 +2005,7 @@ class Image:
|
||||||
alpha = alpha.convert("L")
|
alpha = alpha.convert("L")
|
||||||
else:
|
else:
|
||||||
# constant alpha
|
# constant alpha
|
||||||
|
alpha = cast(int, alpha) # see python/typing#1013
|
||||||
try:
|
try:
|
||||||
self.im.fillband(band, alpha)
|
self.im.fillband(band, alpha)
|
||||||
except (AttributeError, ValueError):
|
except (AttributeError, ValueError):
|
||||||
|
@ -2056,7 +2074,9 @@ class Image:
|
||||||
self.palette.mode = "RGBA" if "A" in rawmode else "RGB"
|
self.palette.mode = "RGBA" if "A" in rawmode else "RGB"
|
||||||
self.load() # install new palette
|
self.load() # install new palette
|
||||||
|
|
||||||
def putpixel(self, xy, value):
|
def putpixel(
|
||||||
|
self, xy: tuple[int, int], value: float | tuple[int, ...] | list[int]
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Modifies the pixel at the given position. The color is given as
|
Modifies the pixel at the given position. The color is given as
|
||||||
a single numerical value for single-band images, and a tuple for
|
a single numerical value for single-band images, and a tuple for
|
||||||
|
@ -2094,9 +2114,8 @@ class Image:
|
||||||
if self.mode == "PA":
|
if self.mode == "PA":
|
||||||
alpha = value[3] if len(value) == 4 else 255
|
alpha = value[3] if len(value) == 4 else 255
|
||||||
value = value[:3]
|
value = value[:3]
|
||||||
value = self.palette.getcolor(value, self)
|
palette_index = self.palette.getcolor(value, self)
|
||||||
if self.mode == "PA":
|
value = (palette_index, alpha) if self.mode == "PA" else palette_index
|
||||||
value = (value, alpha)
|
|
||||||
return self.im.putpixel(xy, value)
|
return self.im.putpixel(xy, value)
|
||||||
|
|
||||||
def remap_palette(self, dest_map, source_palette=None):
|
def remap_palette(self, dest_map, source_palette=None):
|
||||||
|
|
|
@ -1007,7 +1007,9 @@ def floodfill(
|
||||||
thresh: float = 0,
|
thresh: float = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
(experimental) Fills a bounded region with a given color.
|
.. warning:: This method is experimental.
|
||||||
|
|
||||||
|
Fills a bounded region with a given color.
|
||||||
|
|
||||||
:param image: Target image.
|
:param image: Target image.
|
||||||
:param xy: Seed position (a 2-item coordinate tuple). See
|
:param xy: Seed position (a 2-item coordinate tuple). See
|
||||||
|
@ -1025,6 +1027,7 @@ def floodfill(
|
||||||
# based on an implementation by Eric S. Raymond
|
# based on an implementation by Eric S. Raymond
|
||||||
# amended by yo1995 @20180806
|
# amended by yo1995 @20180806
|
||||||
pixel = image.load()
|
pixel = image.load()
|
||||||
|
assert pixel is not None
|
||||||
x, y = xy
|
x, y = xy
|
||||||
try:
|
try:
|
||||||
background = pixel[x, y]
|
background = pixel[x, y]
|
||||||
|
|
|
@ -26,7 +26,13 @@ import tempfile
|
||||||
from . import Image
|
from . import Image
|
||||||
|
|
||||||
|
|
||||||
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
|
def grab(
|
||||||
|
bbox: tuple[int, int, int, int] | None = None,
|
||||||
|
include_layered_windows: bool = False,
|
||||||
|
all_screens: bool = False,
|
||||||
|
xdisplay: str | None = None,
|
||||||
|
) -> Image.Image:
|
||||||
|
im: Image.Image
|
||||||
if xdisplay is None:
|
if xdisplay is None:
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
fh, filepath = tempfile.mkstemp(".png")
|
fh, filepath = tempfile.mkstemp(".png")
|
||||||
|
@ -63,14 +69,16 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
|
||||||
left, top, right, bottom = bbox
|
left, top, right, bottom = bbox
|
||||||
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
||||||
return im
|
return im
|
||||||
|
# Cast to Optional[str] needed for Windows and macOS.
|
||||||
|
display_name: str | None = xdisplay
|
||||||
try:
|
try:
|
||||||
if not Image.core.HAVE_XCB:
|
if not Image.core.HAVE_XCB:
|
||||||
msg = "Pillow was built without XCB support"
|
msg = "Pillow was built without XCB support"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
size, data = Image.core.grabscreen_x11(xdisplay)
|
size, data = Image.core.grabscreen_x11(display_name)
|
||||||
except OSError:
|
except OSError:
|
||||||
if (
|
if (
|
||||||
xdisplay is None
|
display_name is None
|
||||||
and sys.platform not in ("darwin", "win32")
|
and sys.platform not in ("darwin", "win32")
|
||||||
and shutil.which("gnome-screenshot")
|
and shutil.which("gnome-screenshot")
|
||||||
):
|
):
|
||||||
|
@ -94,7 +102,7 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
def grabclipboard():
|
def grabclipboard() -> Image.Image | list[str] | None:
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
fh, filepath = tempfile.mkstemp(".png")
|
fh, filepath = tempfile.mkstemp(".png")
|
||||||
os.close(fh)
|
os.close(fh)
|
||||||
|
|
|
@ -77,7 +77,11 @@ class PyAccess:
|
||||||
def _post_init(self) -> None:
|
def _post_init(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __setitem__(self, xy, color):
|
def __setitem__(
|
||||||
|
self,
|
||||||
|
xy: tuple[int, int] | list[int],
|
||||||
|
color: float | tuple[int, ...] | list[int],
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Modifies the pixel at x,y. The color is given as a single
|
Modifies the pixel at x,y. The color is given as a single
|
||||||
numerical value for single band images, and a tuple for
|
numerical value for single band images, and a tuple for
|
||||||
|
@ -107,13 +111,12 @@ class PyAccess:
|
||||||
if self._im.mode == "PA":
|
if self._im.mode == "PA":
|
||||||
alpha = color[3] if len(color) == 4 else 255
|
alpha = color[3] if len(color) == 4 else 255
|
||||||
color = color[:3]
|
color = color[:3]
|
||||||
color = self._palette.getcolor(color, self._img)
|
palette_index = self._palette.getcolor(color, self._img)
|
||||||
if self._im.mode == "PA":
|
color = (palette_index, alpha) if self._im.mode == "PA" else palette_index
|
||||||
color = (color, alpha)
|
|
||||||
|
|
||||||
return self.set_pixel(x, y, color)
|
return self.set_pixel(x, y, color)
|
||||||
|
|
||||||
def __getitem__(self, xy: tuple[int, int]) -> float | tuple[int, ...]:
|
def __getitem__(self, xy: tuple[int, int] | list[int]) -> float | tuple[int, ...]:
|
||||||
"""
|
"""
|
||||||
Returns the pixel at x,y. The pixel is returned as a single
|
Returns the pixel at x,y. The pixel is returned as a single
|
||||||
value for single band images or a tuple for multiple band
|
value for single band images or a tuple for multiple band
|
||||||
|
@ -145,7 +148,9 @@ class PyAccess:
|
||||||
def get_pixel(self, x: int, y: int) -> float | tuple[int, ...]:
|
def get_pixel(self, x: int, y: int) -> float | tuple[int, ...]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_pixel(self, x: int, y: int, color: float | tuple[int, ...]) -> None:
|
def set_pixel(
|
||||||
|
self, x: int, y: int, color: float | tuple[int, ...] | list[int]
|
||||||
|
) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,10 @@ class ImagingDraw:
|
||||||
def __getattr__(self, name: str) -> Any: ...
|
def __getattr__(self, name: str) -> Any: ...
|
||||||
|
|
||||||
class PixelAccess:
|
class PixelAccess:
|
||||||
def __getattr__(self, name: str) -> Any: ...
|
def __getitem__(self, xy: tuple[int, int]) -> float | tuple[int, ...]: ...
|
||||||
|
def __setitem__(
|
||||||
|
self, xy: tuple[int, int], color: float | tuple[int, ...]
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
class ImagingDecoder:
|
class ImagingDecoder:
|
||||||
def __getattr__(self, name: str) -> Any: ...
|
def __getattr__(self, name: str) -> Any: ...
|
||||||
|
|
Loading…
Reference in New Issue
Block a user