Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/example/anchors.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations

from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw, ImageFont, ImageType

font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16)


def test(anchor: str) -> Image.Image:
def test(anchor: str) -> ImageType:
im = Image.new("RGBA", (200, 100), "white")
d = ImageDraw.Draw(im)
d.line(((100, 0), (100, 100)), "gray")
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/Image.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ used to represent a PIL image. The module also provides a number of factory
functions, including functions to load images from files, and to create new
images.

To assist with type hints, ``ImageType`` has been added a simpler way to refer
to the class within the module. In the following example, the two type
annotations are equivalent::

from PIL import Image, ImageType
im1: Image.Image = Image.new("RGB", (1, 1))
im2: ImageType = Image.new("RGB", (1, 1))

Examples
--------

Expand Down
56 changes: 30 additions & 26 deletions src/PIL/ImageChops.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@

from __future__ import annotations

from . import Image
from . import Image, ImageType


def constant(image: Image.Image, value: int) -> Image.Image:
def constant(image: ImageType, value: int) -> ImageType:
"""Fill a channel with a given gray level.

:rtype: :py:class:`~PIL.Image.Image`
Expand All @@ -29,7 +29,7 @@ def constant(image: Image.Image, value: int) -> Image.Image:
return Image.new("L", image.size, value)


def duplicate(image: Image.Image) -> Image.Image:
def duplicate(image: ImageType) -> ImageType:
"""Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`.

:rtype: :py:class:`~PIL.Image.Image`
Expand All @@ -38,7 +38,7 @@ def duplicate(image: Image.Image) -> Image.Image:
return image.copy()


def invert(image: Image.Image) -> Image.Image:
def invert(image: ImageType) -> ImageType:
"""
Invert an image (channel). ::

Expand All @@ -51,7 +51,7 @@ def invert(image: Image.Image) -> Image.Image:
return image._new(image.im.chop_invert())


def lighter(image1: Image.Image, image2: Image.Image) -> Image.Image:
def lighter(image1: ImageType, image2: ImageType) -> ImageType:
"""
Compares the two images, pixel by pixel, and returns a new image containing
the lighter values. ::
Expand All @@ -66,7 +66,7 @@ def lighter(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_lighter(image2.im))


def darker(image1: Image.Image, image2: Image.Image) -> Image.Image:
def darker(image1: ImageType, image2: ImageType) -> ImageType:
"""
Compares the two images, pixel by pixel, and returns a new image containing
the darker values. ::
Expand All @@ -81,7 +81,7 @@ def darker(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_darker(image2.im))


def difference(image1: Image.Image, image2: Image.Image) -> Image.Image:
def difference(image1: ImageType, image2: ImageType) -> ImageType:
"""
Returns the absolute value of the pixel-by-pixel difference between the two
images. ::
Expand All @@ -96,7 +96,7 @@ def difference(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_difference(image2.im))


def multiply(image1: Image.Image, image2: Image.Image) -> Image.Image:
def multiply(image1: ImageType, image2: ImageType) -> ImageType:
"""
Superimposes two images on top of each other.

Expand All @@ -113,7 +113,7 @@ def multiply(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_multiply(image2.im))


def screen(image1: Image.Image, image2: Image.Image) -> Image.Image:
def screen(image1: ImageType, image2: ImageType) -> ImageType:
"""
Superimposes two inverted images on top of each other. ::

Expand All @@ -127,7 +127,7 @@ def screen(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_screen(image2.im))


def soft_light(image1: Image.Image, image2: Image.Image) -> Image.Image:
def soft_light(image1: ImageType, image2: ImageType) -> ImageType:
"""
Superimposes two images on top of each other using the Soft Light algorithm

Expand All @@ -139,7 +139,7 @@ def soft_light(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_soft_light(image2.im))


def hard_light(image1: Image.Image, image2: Image.Image) -> Image.Image:
def hard_light(image1: ImageType, image2: ImageType) -> ImageType:
"""
Superimposes two images on top of each other using the Hard Light algorithm

Expand All @@ -151,7 +151,7 @@ def hard_light(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_hard_light(image2.im))


def overlay(image1: Image.Image, image2: Image.Image) -> Image.Image:
def overlay(image1: ImageType, image2: ImageType) -> ImageType:
"""
Superimposes two images on top of each other using the Overlay algorithm

Expand All @@ -164,8 +164,11 @@ def overlay(image1: Image.Image, image2: Image.Image) -> Image.Image:


def add(
image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0
) -> Image.Image:
image1: ImageType,
image2: ImageType,
scale: float = 1.0,
offset: float = 0,
) -> ImageType:
"""
Adds two images, dividing the result by scale and adding the
offset. If omitted, scale defaults to 1.0, and offset to 0.0. ::
Expand All @@ -181,8 +184,11 @@ def add(


def subtract(
image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0
) -> Image.Image:
image1: ImageType,
image2: ImageType,
scale: float = 1.0,
offset: float = 0,
) -> ImageType:
"""
Subtracts two images, dividing the result by scale and adding the offset.
If omitted, scale defaults to 1.0, and offset to 0.0. ::
Expand All @@ -197,7 +203,7 @@ def subtract(
return image1._new(image1.im.chop_subtract(image2.im, scale, offset))


def add_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image:
def add_modulo(image1: ImageType, image2: ImageType) -> ImageType:
"""Add two images, without clipping the result. ::

out = ((image1 + image2) % MAX)
Expand All @@ -210,7 +216,7 @@ def add_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_add_modulo(image2.im))


def subtract_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image:
def subtract_modulo(image1: ImageType, image2: ImageType) -> ImageType:
"""Subtract two images, without clipping the result. ::

out = ((image1 - image2) % MAX)
Expand All @@ -223,7 +229,7 @@ def subtract_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_subtract_modulo(image2.im))


def logical_and(image1: Image.Image, image2: Image.Image) -> Image.Image:
def logical_and(image1: ImageType, image2: ImageType) -> ImageType:
"""Logical AND between two images.

Both of the images must have mode "1". If you would like to perform a
Expand All @@ -241,7 +247,7 @@ def logical_and(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_and(image2.im))


def logical_or(image1: Image.Image, image2: Image.Image) -> Image.Image:
def logical_or(image1: ImageType, image2: ImageType) -> ImageType:
"""Logical OR between two images.

Both of the images must have mode "1". ::
Expand All @@ -256,7 +262,7 @@ def logical_or(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_or(image2.im))


def logical_xor(image1: Image.Image, image2: Image.Image) -> Image.Image:
def logical_xor(image1: ImageType, image2: ImageType) -> ImageType:
"""Logical XOR between two images.

Both of the images must have mode "1". ::
Expand All @@ -271,7 +277,7 @@ def logical_xor(image1: Image.Image, image2: Image.Image) -> Image.Image:
return image1._new(image1.im.chop_xor(image2.im))


def blend(image1: Image.Image, image2: Image.Image, alpha: float) -> Image.Image:
def blend(image1: ImageType, image2: ImageType, alpha: float) -> ImageType:
"""Blend images using constant transparency weight. Alias for
:py:func:`PIL.Image.blend`.

Expand All @@ -281,9 +287,7 @@ def blend(image1: Image.Image, image2: Image.Image, alpha: float) -> Image.Image
return Image.blend(image1, image2, alpha)


def composite(
image1: Image.Image, image2: Image.Image, mask: Image.Image
) -> Image.Image:
def composite(image1: ImageType, image2: ImageType, mask: ImageType) -> ImageType:
"""Create composite using transparency mask. Alias for
:py:func:`PIL.Image.composite`.

Expand All @@ -293,7 +297,7 @@ def composite(
return Image.composite(image1, image2, mask)


def offset(image: Image.Image, xoffset: int, yoffset: int | None = None) -> Image.Image:
def offset(image: ImageType, xoffset: int, yoffset: int | None = None) -> ImageType:
"""Returns a copy of the image where data has been offset by the given
distances. Data wraps around the edges. If ``yoffset`` is omitted, it
is assumed to be equal to ``xoffset``.
Expand Down
14 changes: 7 additions & 7 deletions src/PIL/ImageSequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from typing import Callable

from . import Image
from . import ImageType


class Iterator:
Expand All @@ -33,14 +33,14 @@ class Iterator:
:param im: An image object.
"""

def __init__(self, im: Image.Image):
def __init__(self, im: ImageType):
if not hasattr(im, "seek"):
msg = "im must have seek method"
raise AttributeError(msg)
self.im = im
self.position = getattr(self.im, "_min_frame", 0)

def __getitem__(self, ix: int) -> Image.Image:
def __getitem__(self, ix: int) -> ImageType:
try:
self.im.seek(ix)
return self.im
Expand All @@ -51,7 +51,7 @@ def __getitem__(self, ix: int) -> Image.Image:
def __iter__(self) -> Iterator:
return self

def __next__(self) -> Image.Image:
def __next__(self) -> ImageType:
try:
self.im.seek(self.position)
self.position += 1
Expand All @@ -62,9 +62,9 @@ def __next__(self) -> Image.Image:


def all_frames(
im: Image.Image | list[Image.Image],
func: Callable[[Image.Image], Image.Image] | None = None,
) -> list[Image.Image]:
im: ImageType | list[ImageType],
func: Callable[[ImageType], ImageType] | None = None,
) -> list[ImageType]:
"""
Applies a given function to all frames in an image or a list of images.
The frames are returned as a list of separate images.
Expand Down
9 changes: 9 additions & 0 deletions src/PIL/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,12 @@ class UnidentifiedImageError(OSError):
"""

pass


def __getattr__(name: str):
if name == "ImageType":
from . import Image

return Image.Image
msg = f"module 'PIL' has no attribute '{name}'"
raise AttributeError(msg)