mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
Added type hints
This commit is contained in:
parent
912a33f5e9
commit
5c858d75e4
|
@ -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