mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +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 |  | ||||||
|         numerical value for single band images, and a tuple for |  | ||||||
|         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 |         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 multi-band images. | ||||||
|         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