mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-30 23:47:27 +03:00 
			
		
		
		
	Merge pull request #7786 from radarhere/type_hints_imageops
Added type hints to ImageOps
This commit is contained in:
		
						commit
						80d4fc14b8
					
				|  | @ -1430,7 +1430,7 @@ class Image: | ||||||
|             root = ElementTree.fromstring(xmp_tags) |             root = ElementTree.fromstring(xmp_tags) | ||||||
|             return {get_name(root.tag): get_value(root)} |             return {get_name(root.tag): get_value(root)} | ||||||
| 
 | 
 | ||||||
|     def getexif(self): |     def getexif(self) -> Exif: | ||||||
|         """ |         """ | ||||||
|         Gets EXIF data from the image. |         Gets EXIF data from the image. | ||||||
| 
 | 
 | ||||||
|  | @ -1438,7 +1438,6 @@ class Image: | ||||||
|         """ |         """ | ||||||
|         if self._exif is None: |         if self._exif is None: | ||||||
|             self._exif = Exif() |             self._exif = Exif() | ||||||
|             self._exif._loaded = False |  | ||||||
|         elif self._exif._loaded: |         elif self._exif._loaded: | ||||||
|             return self._exif |             return self._exif | ||||||
|         self._exif._loaded = True |         self._exif._loaded = True | ||||||
|  | @ -1525,7 +1524,7 @@ class Image: | ||||||
|         self.load() |         self.load() | ||||||
|         return self.im.ptr |         return self.im.ptr | ||||||
| 
 | 
 | ||||||
|     def getpalette(self, rawmode="RGB"): |     def getpalette(self, rawmode: str | None = "RGB") -> list[int] | None: | ||||||
|         """ |         """ | ||||||
|         Returns the image palette as a list. |         Returns the image palette as a list. | ||||||
| 
 | 
 | ||||||
|  | @ -1615,7 +1614,7 @@ class Image: | ||||||
|         x, y = self.im.getprojection() |         x, y = self.im.getprojection() | ||||||
|         return list(x), list(y) |         return list(x), list(y) | ||||||
| 
 | 
 | ||||||
|     def histogram(self, mask=None, extrema=None): |     def histogram(self, mask=None, extrema=None) -> list[int]: | ||||||
|         """ |         """ | ||||||
|         Returns a histogram for the image. The histogram is returned as a |         Returns a histogram for the image. The histogram is returned as a | ||||||
|         list of pixel counts, one for each pixel value in the source |         list of pixel counts, one for each pixel value in the source | ||||||
|  | @ -1804,7 +1803,7 @@ class Image: | ||||||
|         result = alpha_composite(background, overlay) |         result = alpha_composite(background, overlay) | ||||||
|         self.paste(result, box) |         self.paste(result, box) | ||||||
| 
 | 
 | ||||||
|     def point(self, lut, mode=None): |     def point(self, lut, mode: str | None = None) -> Image: | ||||||
|         """ |         """ | ||||||
|         Maps this image through a lookup table or function. |         Maps this image through a lookup table or function. | ||||||
| 
 | 
 | ||||||
|  | @ -1928,7 +1927,7 @@ class Image: | ||||||
| 
 | 
 | ||||||
|         self.im.putdata(data, scale, offset) |         self.im.putdata(data, scale, offset) | ||||||
| 
 | 
 | ||||||
|     def putpalette(self, data, rawmode="RGB"): |     def putpalette(self, data, rawmode="RGB") -> None: | ||||||
|         """ |         """ | ||||||
|         Attaches a palette to this image.  The image must be a "P", "PA", "L" |         Attaches a palette to this image.  The image must be a "P", "PA", "L" | ||||||
|         or "LA" image. |         or "LA" image. | ||||||
|  | @ -2108,7 +2107,7 @@ class Image: | ||||||
|             min(self.size[1], math.ceil(box[3] + support_y)), |             min(self.size[1], math.ceil(box[3] + support_y)), | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def resize(self, size, resample=None, box=None, reducing_gap=None): |     def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image: | ||||||
|         """ |         """ | ||||||
|         Returns a resized copy of this image. |         Returns a resized copy of this image. | ||||||
| 
 | 
 | ||||||
|  | @ -2200,10 +2199,11 @@ class Image: | ||||||
|             if factor_x > 1 or factor_y > 1: |             if factor_x > 1 or factor_y > 1: | ||||||
|                 reduce_box = self._get_safe_box(size, resample, box) |                 reduce_box = self._get_safe_box(size, resample, box) | ||||||
|                 factor = (factor_x, factor_y) |                 factor = (factor_x, factor_y) | ||||||
|                 if callable(self.reduce): |                 self = ( | ||||||
|                     self = self.reduce(factor, box=reduce_box) |                     self.reduce(factor, box=reduce_box) | ||||||
|                 else: |                     if callable(self.reduce) | ||||||
|                     self = Image.reduce(self, factor, box=reduce_box) |                     else Image.reduce(self, factor, box=reduce_box) | ||||||
|  |                 ) | ||||||
|                 box = ( |                 box = ( | ||||||
|                     (box[0] - reduce_box[0]) / factor_x, |                     (box[0] - reduce_box[0]) / factor_x, | ||||||
|                     (box[1] - reduce_box[1]) / factor_y, |                     (box[1] - reduce_box[1]) / factor_y, | ||||||
|  | @ -2818,7 +2818,7 @@ class Image: | ||||||
| 
 | 
 | ||||||
|         self.im.transform2(box, image.im, method, data, resample, fill) |         self.im.transform2(box, image.im, method, data, resample, fill) | ||||||
| 
 | 
 | ||||||
|     def transpose(self, method): |     def transpose(self, method: Transpose) -> Image: | ||||||
|         """ |         """ | ||||||
|         Transpose image (flip or rotate in 90 degree steps) |         Transpose image (flip or rotate in 90 degree steps) | ||||||
| 
 | 
 | ||||||
|  | @ -2870,7 +2870,9 @@ class ImagePointHandler: | ||||||
|     (for use with :py:meth:`~PIL.Image.Image.point`) |     (for use with :py:meth:`~PIL.Image.Image.point`) | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     pass |     @abc.abstractmethod | ||||||
|  |     def point(self, im: Image) -> Image: | ||||||
|  |         pass | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ImageTransformHandler: | class ImageTransformHandler: | ||||||
|  | @ -3690,6 +3692,7 @@ class Exif(_ExifBase): | ||||||
| 
 | 
 | ||||||
|     endian = None |     endian = None | ||||||
|     bigtiff = False |     bigtiff = False | ||||||
|  |     _loaded = False | ||||||
| 
 | 
 | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self._data = {} |         self._data = {} | ||||||
|  | @ -3805,7 +3808,7 @@ class Exif(_ExifBase): | ||||||
| 
 | 
 | ||||||
|         return merged_dict |         return merged_dict | ||||||
| 
 | 
 | ||||||
|     def tobytes(self, offset=8): |     def tobytes(self, offset: int = 8) -> bytes: | ||||||
|         from . import TiffImagePlugin |         from . import TiffImagePlugin | ||||||
| 
 | 
 | ||||||
|         head = self._get_head() |         head = self._get_head() | ||||||
|  | @ -3960,7 +3963,7 @@ class Exif(_ExifBase): | ||||||
|             del self._info[tag] |             del self._info[tag] | ||||||
|         self._data[tag] = value |         self._data[tag] = value | ||||||
| 
 | 
 | ||||||
|     def __delitem__(self, tag): |     def __delitem__(self, tag: int) -> None: | ||||||
|         if self._info is not None and tag in self._info: |         if self._info is not None and tag in self._info: | ||||||
|             del self._info[tag] |             del self._info[tag] | ||||||
|         else: |         else: | ||||||
|  |  | ||||||
|  | @ -124,7 +124,7 @@ def getrgb(color): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @lru_cache | @lru_cache | ||||||
| def getcolor(color, mode): | def getcolor(color, mode: str) -> tuple[int, ...]: | ||||||
|     """ |     """ | ||||||
|     Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if |     Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if | ||||||
|     ``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is |     ``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ from __future__ import annotations | ||||||
| import functools | import functools | ||||||
| import operator | import operator | ||||||
| import re | import re | ||||||
|  | from typing import Protocol, Sequence, cast | ||||||
| 
 | 
 | ||||||
| from . import ExifTags, Image, ImagePalette | from . import ExifTags, Image, ImagePalette | ||||||
| 
 | 
 | ||||||
|  | @ -28,7 +29,7 @@ from . import ExifTags, Image, ImagePalette | ||||||
| # helpers | # helpers | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _border(border): | def _border(border: int | tuple[int, ...]) -> tuple[int, int, int, int]: | ||||||
|     if isinstance(border, tuple): |     if isinstance(border, tuple): | ||||||
|         if len(border) == 2: |         if len(border) == 2: | ||||||
|             left, top = right, bottom = border |             left, top = right, bottom = border | ||||||
|  | @ -39,7 +40,7 @@ def _border(border): | ||||||
|     return left, top, right, bottom |     return left, top, right, bottom | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _color(color, mode): | def _color(color: str | int | tuple[int, ...], mode: str) -> int | tuple[int, ...]: | ||||||
|     if isinstance(color, str): |     if isinstance(color, str): | ||||||
|         from . import ImageColor |         from . import ImageColor | ||||||
| 
 | 
 | ||||||
|  | @ -47,7 +48,7 @@ def _color(color, mode): | ||||||
|     return color |     return color | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _lut(image, lut): | def _lut(image: Image.Image, lut: list[int]) -> Image.Image: | ||||||
|     if image.mode == "P": |     if image.mode == "P": | ||||||
|         # FIXME: apply to lookup table, not image data |         # FIXME: apply to lookup table, not image data | ||||||
|         msg = "mode P support coming soon" |         msg = "mode P support coming soon" | ||||||
|  | @ -65,7 +66,13 @@ def _lut(image, lut): | ||||||
| # actions | # actions | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): | def autocontrast( | ||||||
|  |     image: Image.Image, | ||||||
|  |     cutoff: float | tuple[float, float] = 0, | ||||||
|  |     ignore: int | Sequence[int] | None = None, | ||||||
|  |     mask: Image.Image | None = None, | ||||||
|  |     preserve_tone: bool = False, | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Maximize (normalize) image contrast. This function calculates a |     Maximize (normalize) image contrast. This function calculates a | ||||||
|     histogram of the input image (or mask region), removes ``cutoff`` percent of the |     histogram of the input image (or mask region), removes ``cutoff`` percent of the | ||||||
|  | @ -97,10 +104,9 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): | ||||||
|         h = histogram[layer : layer + 256] |         h = histogram[layer : layer + 256] | ||||||
|         if ignore is not None: |         if ignore is not None: | ||||||
|             # get rid of outliers |             # get rid of outliers | ||||||
|             try: |             if isinstance(ignore, int): | ||||||
|                 h[ignore] = 0 |                 h[ignore] = 0 | ||||||
|             except TypeError: |             else: | ||||||
|                 # assume sequence |  | ||||||
|                 for ix in ignore: |                 for ix in ignore: | ||||||
|                     h[ix] = 0 |                     h[ix] = 0 | ||||||
|         if cutoff: |         if cutoff: | ||||||
|  | @ -112,7 +118,7 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): | ||||||
|             for ix in range(256): |             for ix in range(256): | ||||||
|                 n = n + h[ix] |                 n = n + h[ix] | ||||||
|             # remove cutoff% pixels from the low end |             # remove cutoff% pixels from the low end | ||||||
|             cut = n * cutoff[0] // 100 |             cut = int(n * cutoff[0] // 100) | ||||||
|             for lo in range(256): |             for lo in range(256): | ||||||
|                 if cut > h[lo]: |                 if cut > h[lo]: | ||||||
|                     cut = cut - h[lo] |                     cut = cut - h[lo] | ||||||
|  | @ -123,7 +129,7 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): | ||||||
|                 if cut <= 0: |                 if cut <= 0: | ||||||
|                     break |                     break | ||||||
|             # remove cutoff% samples from the high end |             # remove cutoff% samples from the high end | ||||||
|             cut = n * cutoff[1] // 100 |             cut = int(n * cutoff[1] // 100) | ||||||
|             for hi in range(255, -1, -1): |             for hi in range(255, -1, -1): | ||||||
|                 if cut > h[hi]: |                 if cut > h[hi]: | ||||||
|                     cut = cut - h[hi] |                     cut = cut - h[hi] | ||||||
|  | @ -156,7 +162,15 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): | ||||||
|     return _lut(image, lut) |     return _lut(image, lut) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoint=127): | def colorize( | ||||||
|  |     image: Image.Image, | ||||||
|  |     black: str | tuple[int, ...], | ||||||
|  |     white: str | tuple[int, ...], | ||||||
|  |     mid: str | int | tuple[int, ...] | None = None, | ||||||
|  |     blackpoint: int = 0, | ||||||
|  |     whitepoint: int = 255, | ||||||
|  |     midpoint: int = 127, | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Colorize grayscale image. |     Colorize grayscale image. | ||||||
|     This function calculates a color wedge which maps all black pixels in |     This function calculates a color wedge which maps all black pixels in | ||||||
|  | @ -188,10 +202,9 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi | ||||||
|         assert 0 <= blackpoint <= midpoint <= whitepoint <= 255 |         assert 0 <= blackpoint <= midpoint <= whitepoint <= 255 | ||||||
| 
 | 
 | ||||||
|     # Define colors from arguments |     # Define colors from arguments | ||||||
|     black = _color(black, "RGB") |     rgb_black = cast(Sequence[int], _color(black, "RGB")) | ||||||
|     white = _color(white, "RGB") |     rgb_white = cast(Sequence[int], _color(white, "RGB")) | ||||||
|     if mid is not None: |     rgb_mid = cast(Sequence[int], _color(mid, "RGB")) if mid is not None else None | ||||||
|         mid = _color(mid, "RGB") |  | ||||||
| 
 | 
 | ||||||
|     # Empty lists for the mapping |     # Empty lists for the mapping | ||||||
|     red = [] |     red = [] | ||||||
|  | @ -200,18 +213,24 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi | ||||||
| 
 | 
 | ||||||
|     # Create the low-end values |     # Create the low-end values | ||||||
|     for i in range(0, blackpoint): |     for i in range(0, blackpoint): | ||||||
|         red.append(black[0]) |         red.append(rgb_black[0]) | ||||||
|         green.append(black[1]) |         green.append(rgb_black[1]) | ||||||
|         blue.append(black[2]) |         blue.append(rgb_black[2]) | ||||||
| 
 | 
 | ||||||
|     # Create the mapping (2-color) |     # Create the mapping (2-color) | ||||||
|     if mid is None: |     if rgb_mid is None: | ||||||
|         range_map = range(0, whitepoint - blackpoint) |         range_map = range(0, whitepoint - blackpoint) | ||||||
| 
 | 
 | ||||||
|         for i in range_map: |         for i in range_map: | ||||||
|             red.append(black[0] + i * (white[0] - black[0]) // len(range_map)) |             red.append( | ||||||
|             green.append(black[1] + i * (white[1] - black[1]) // len(range_map)) |                 rgb_black[0] + i * (rgb_white[0] - rgb_black[0]) // len(range_map) | ||||||
|             blue.append(black[2] + i * (white[2] - black[2]) // len(range_map)) |             ) | ||||||
|  |             green.append( | ||||||
|  |                 rgb_black[1] + i * (rgb_white[1] - rgb_black[1]) // len(range_map) | ||||||
|  |             ) | ||||||
|  |             blue.append( | ||||||
|  |                 rgb_black[2] + i * (rgb_white[2] - rgb_black[2]) // len(range_map) | ||||||
|  |             ) | ||||||
| 
 | 
 | ||||||
|     # Create the mapping (3-color) |     # Create the mapping (3-color) | ||||||
|     else: |     else: | ||||||
|  | @ -219,26 +238,36 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi | ||||||
|         range_map2 = range(0, whitepoint - midpoint) |         range_map2 = range(0, whitepoint - midpoint) | ||||||
| 
 | 
 | ||||||
|         for i in range_map1: |         for i in range_map1: | ||||||
|             red.append(black[0] + i * (mid[0] - black[0]) // len(range_map1)) |             red.append( | ||||||
|             green.append(black[1] + i * (mid[1] - black[1]) // len(range_map1)) |                 rgb_black[0] + i * (rgb_mid[0] - rgb_black[0]) // len(range_map1) | ||||||
|             blue.append(black[2] + i * (mid[2] - black[2]) // len(range_map1)) |             ) | ||||||
|  |             green.append( | ||||||
|  |                 rgb_black[1] + i * (rgb_mid[1] - rgb_black[1]) // len(range_map1) | ||||||
|  |             ) | ||||||
|  |             blue.append( | ||||||
|  |                 rgb_black[2] + i * (rgb_mid[2] - rgb_black[2]) // len(range_map1) | ||||||
|  |             ) | ||||||
|         for i in range_map2: |         for i in range_map2: | ||||||
|             red.append(mid[0] + i * (white[0] - mid[0]) // len(range_map2)) |             red.append(rgb_mid[0] + i * (rgb_white[0] - rgb_mid[0]) // len(range_map2)) | ||||||
|             green.append(mid[1] + i * (white[1] - mid[1]) // len(range_map2)) |             green.append( | ||||||
|             blue.append(mid[2] + i * (white[2] - mid[2]) // len(range_map2)) |                 rgb_mid[1] + i * (rgb_white[1] - rgb_mid[1]) // len(range_map2) | ||||||
|  |             ) | ||||||
|  |             blue.append(rgb_mid[2] + i * (rgb_white[2] - rgb_mid[2]) // len(range_map2)) | ||||||
| 
 | 
 | ||||||
|     # Create the high-end values |     # Create the high-end values | ||||||
|     for i in range(0, 256 - whitepoint): |     for i in range(0, 256 - whitepoint): | ||||||
|         red.append(white[0]) |         red.append(rgb_white[0]) | ||||||
|         green.append(white[1]) |         green.append(rgb_white[1]) | ||||||
|         blue.append(white[2]) |         blue.append(rgb_white[2]) | ||||||
| 
 | 
 | ||||||
|     # Return converted image |     # Return converted image | ||||||
|     image = image.convert("RGB") |     image = image.convert("RGB") | ||||||
|     return _lut(image, red + green + blue) |     return _lut(image, red + green + blue) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def contain(image, size, method=Image.Resampling.BICUBIC): | def contain( | ||||||
|  |     image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Returns a resized version of the image, set to the maximum width and height |     Returns a resized version of the image, set to the maximum width and height | ||||||
|     within the requested size, while maintaining the original aspect ratio. |     within the requested size, while maintaining the original aspect ratio. | ||||||
|  | @ -267,7 +296,9 @@ def contain(image, size, method=Image.Resampling.BICUBIC): | ||||||
|     return image.resize(size, resample=method) |     return image.resize(size, resample=method) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def cover(image, size, method=Image.Resampling.BICUBIC): | def cover( | ||||||
|  |     image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Returns a resized version of the image, so that the requested size is |     Returns a resized version of the image, so that the requested size is | ||||||
|     covered, while maintaining the original aspect ratio. |     covered, while maintaining the original aspect ratio. | ||||||
|  | @ -296,7 +327,13 @@ def cover(image, size, method=Image.Resampling.BICUBIC): | ||||||
|     return image.resize(size, resample=method) |     return image.resize(size, resample=method) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5, 0.5)): | def pad( | ||||||
|  |     image: Image.Image, | ||||||
|  |     size: tuple[int, int], | ||||||
|  |     method: int = Image.Resampling.BICUBIC, | ||||||
|  |     color: str | int | tuple[int, ...] | None = None, | ||||||
|  |     centering: tuple[float, float] = (0.5, 0.5), | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Returns a resized and padded version of the image, expanded to fill the |     Returns a resized and padded version of the image, expanded to fill the | ||||||
|     requested aspect ratio and size. |     requested aspect ratio and size. | ||||||
|  | @ -334,7 +371,7 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5 | ||||||
|     return out |     return out | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def crop(image, border=0): | def crop(image: Image.Image, border: int = 0) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Remove border from image.  The same amount of pixels are removed |     Remove border from image.  The same amount of pixels are removed | ||||||
|     from all four sides.  This function works on all image modes. |     from all four sides.  This function works on all image modes. | ||||||
|  | @ -349,7 +386,9 @@ def crop(image, border=0): | ||||||
|     return image.crop((left, top, image.size[0] - right, image.size[1] - bottom)) |     return image.crop((left, top, image.size[0] - right, image.size[1] - bottom)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def scale(image, factor, resample=Image.Resampling.BICUBIC): | def scale( | ||||||
|  |     image: Image.Image, factor: float, resample: int = Image.Resampling.BICUBIC | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Returns a rescaled image by a specific factor given in parameter. |     Returns a rescaled image by a specific factor given in parameter. | ||||||
|     A factor greater than 1 expands the image, between 0 and 1 contracts the |     A factor greater than 1 expands the image, between 0 and 1 contracts the | ||||||
|  | @ -372,7 +411,19 @@ def scale(image, factor, resample=Image.Resampling.BICUBIC): | ||||||
|         return image.resize(size, resample) |         return image.resize(size, resample) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def deform(image, deformer, resample=Image.Resampling.BILINEAR): | class _SupportsGetMesh(Protocol): | ||||||
|  |     def getmesh( | ||||||
|  |         self, image: Image.Image | ||||||
|  |     ) -> list[ | ||||||
|  |         tuple[tuple[int, int, int, int], tuple[int, int, int, int, int, int, int, int]] | ||||||
|  |     ]: ... | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def deform( | ||||||
|  |     image: Image.Image, | ||||||
|  |     deformer: _SupportsGetMesh, | ||||||
|  |     resample: int = Image.Resampling.BILINEAR, | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Deform the image. |     Deform the image. | ||||||
| 
 | 
 | ||||||
|  | @ -388,7 +439,7 @@ def deform(image, deformer, resample=Image.Resampling.BILINEAR): | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def equalize(image, mask=None): | def equalize(image: Image.Image, mask: Image.Image | None = None) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Equalize the image histogram. This function applies a non-linear |     Equalize the image histogram. This function applies a non-linear | ||||||
|     mapping to the input image, in order to create a uniform |     mapping to the input image, in order to create a uniform | ||||||
|  | @ -419,7 +470,11 @@ def equalize(image, mask=None): | ||||||
|     return _lut(image, lut) |     return _lut(image, lut) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def expand(image, border=0, fill=0): | def expand( | ||||||
|  |     image: Image.Image, | ||||||
|  |     border: int | tuple[int, ...] = 0, | ||||||
|  |     fill: str | int | tuple[int, ...] = 0, | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Add border to the image |     Add border to the image | ||||||
| 
 | 
 | ||||||
|  | @ -445,7 +500,13 @@ def expand(image, border=0, fill=0): | ||||||
|     return out |     return out | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): | def fit( | ||||||
|  |     image: Image.Image, | ||||||
|  |     size: tuple[int, int], | ||||||
|  |     method: int = Image.Resampling.BICUBIC, | ||||||
|  |     bleed: float = 0.0, | ||||||
|  |     centering: tuple[float, float] = (0.5, 0.5), | ||||||
|  | ) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Returns a resized and cropped version of the image, cropped to the |     Returns a resized and cropped version of the image, cropped to the | ||||||
|     requested aspect ratio and size. |     requested aspect ratio and size. | ||||||
|  | @ -479,13 +540,12 @@ def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, | ||||||
|     # kevin@cazabon.com |     # kevin@cazabon.com | ||||||
|     # https://www.cazabon.com |     # https://www.cazabon.com | ||||||
| 
 | 
 | ||||||
|     # ensure centering is mutable |     centering_x, centering_y = centering | ||||||
|     centering = list(centering) |  | ||||||
| 
 | 
 | ||||||
|     if not 0.0 <= centering[0] <= 1.0: |     if not 0.0 <= centering_x <= 1.0: | ||||||
|         centering[0] = 0.5 |         centering_x = 0.5 | ||||||
|     if not 0.0 <= centering[1] <= 1.0: |     if not 0.0 <= centering_y <= 1.0: | ||||||
|         centering[1] = 0.5 |         centering_y = 0.5 | ||||||
| 
 | 
 | ||||||
|     if not 0.0 <= bleed < 0.5: |     if not 0.0 <= bleed < 0.5: | ||||||
|         bleed = 0.0 |         bleed = 0.0 | ||||||
|  | @ -522,8 +582,8 @@ def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, | ||||||
|         crop_height = live_size[0] / output_ratio |         crop_height = live_size[0] / output_ratio | ||||||
| 
 | 
 | ||||||
|     # make the crop |     # make the crop | ||||||
|     crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering[0] |     crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering_x | ||||||
|     crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering[1] |     crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering_y | ||||||
| 
 | 
 | ||||||
|     crop = (crop_left, crop_top, crop_left + crop_width, crop_top + crop_height) |     crop = (crop_left, crop_top, crop_left + crop_width, crop_top + crop_height) | ||||||
| 
 | 
 | ||||||
|  | @ -531,7 +591,7 @@ def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, | ||||||
|     return image.resize(size, method, box=crop) |     return image.resize(size, method, box=crop) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def flip(image): | def flip(image: Image.Image) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Flip the image vertically (top to bottom). |     Flip the image vertically (top to bottom). | ||||||
| 
 | 
 | ||||||
|  | @ -541,7 +601,7 @@ def flip(image): | ||||||
|     return image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) |     return image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def grayscale(image): | def grayscale(image: Image.Image) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Convert the image to grayscale. |     Convert the image to grayscale. | ||||||
| 
 | 
 | ||||||
|  | @ -551,7 +611,7 @@ def grayscale(image): | ||||||
|     return image.convert("L") |     return image.convert("L") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def invert(image): | def invert(image: Image.Image) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Invert (negate) the image. |     Invert (negate) the image. | ||||||
| 
 | 
 | ||||||
|  | @ -562,7 +622,7 @@ def invert(image): | ||||||
|     return image.point(lut) if image.mode == "1" else _lut(image, lut) |     return image.point(lut) if image.mode == "1" else _lut(image, lut) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def mirror(image): | def mirror(image: Image.Image) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Flip image horizontally (left to right). |     Flip image horizontally (left to right). | ||||||
| 
 | 
 | ||||||
|  | @ -572,7 +632,7 @@ def mirror(image): | ||||||
|     return image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) |     return image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def posterize(image, bits): | def posterize(image: Image.Image, bits: int) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Reduce the number of bits for each color channel. |     Reduce the number of bits for each color channel. | ||||||
| 
 | 
 | ||||||
|  | @ -585,7 +645,7 @@ def posterize(image, bits): | ||||||
|     return _lut(image, lut) |     return _lut(image, lut) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def solarize(image, threshold=128): | def solarize(image: Image.Image, threshold: int = 128) -> Image.Image: | ||||||
|     """ |     """ | ||||||
|     Invert all pixel values above a threshold. |     Invert all pixel values above a threshold. | ||||||
| 
 | 
 | ||||||
|  | @ -602,7 +662,7 @@ def solarize(image, threshold=128): | ||||||
|     return _lut(image, lut) |     return _lut(image, lut) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def exif_transpose(image, *, in_place=False): | def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image | None: | ||||||
|     """ |     """ | ||||||
|     If an image has an EXIF Orientation tag, other than 1, transpose the image |     If an image has an EXIF Orientation tag, other than 1, transpose the image | ||||||
|     accordingly, and remove the orientation data. |     accordingly, and remove the orientation data. | ||||||
|  | @ -616,7 +676,7 @@ def exif_transpose(image, *, in_place=False): | ||||||
|     """ |     """ | ||||||
|     image.load() |     image.load() | ||||||
|     image_exif = image.getexif() |     image_exif = image.getexif() | ||||||
|     orientation = image_exif.get(ExifTags.Base.Orientation) |     orientation = image_exif.get(ExifTags.Base.Orientation, 1) | ||||||
|     method = { |     method = { | ||||||
|         2: Image.Transpose.FLIP_LEFT_RIGHT, |         2: Image.Transpose.FLIP_LEFT_RIGHT, | ||||||
|         3: Image.Transpose.ROTATE_180, |         3: Image.Transpose.ROTATE_180, | ||||||
|  | @ -653,3 +713,4 @@ def exif_transpose(image, *, in_place=False): | ||||||
|             return transposed_image |             return transposed_image | ||||||
|     elif not in_place: |     elif not in_place: | ||||||
|         return image.copy() |         return image.copy() | ||||||
|  |     return None | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
| 
 | 
 | ||||||
| import array | import array | ||||||
|  | from typing import Sequence | ||||||
| 
 | 
 | ||||||
| from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile | from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile | ||||||
| 
 | 
 | ||||||
|  | @ -34,11 +35,11 @@ class ImagePalette: | ||||||
|         Defaults to an empty palette. |         Defaults to an empty palette. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def __init__(self, mode="RGB", palette=None): |     def __init__(self, mode: str = "RGB", palette: Sequence[int] | None = None) -> None: | ||||||
|         self.mode = mode |         self.mode = mode | ||||||
|         self.rawmode = None  # if set, palette contains raw data |         self.rawmode = None  # if set, palette contains raw data | ||||||
|         self.palette = palette or bytearray() |         self.palette = palette or bytearray() | ||||||
|         self.dirty = None |         self.dirty: int | None = None | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def palette(self): |     def palette(self): | ||||||
|  | @ -127,7 +128,7 @@ class ImagePalette: | ||||||
|                 raise ValueError(msg) from e |                 raise ValueError(msg) from e | ||||||
|         return index |         return index | ||||||
| 
 | 
 | ||||||
|     def getcolor(self, color, image=None): |     def getcolor(self, color, image=None) -> int: | ||||||
|         """Given an rgb tuple, allocate palette entry. |         """Given an rgb tuple, allocate palette entry. | ||||||
| 
 | 
 | ||||||
|         .. warning:: This method is experimental. |         .. warning:: This method is experimental. | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user