|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| from __future__ import annotations
|
|
|
| import abc
|
| import atexit
|
| import builtins
|
| import io
|
| import logging
|
| import math
|
| import os
|
| import re
|
| import struct
|
| import sys
|
| import tempfile
|
| import warnings
|
| from collections.abc import MutableMapping
|
| from enum import IntEnum
|
| from typing import IO, Protocol, cast
|
|
|
|
|
|
|
|
|
| from . import (
|
| ExifTags,
|
| ImageMode,
|
| TiffTags,
|
| UnidentifiedImageError,
|
| __version__,
|
| _plugins,
|
| )
|
| from ._binary import i32le, o32be, o32le
|
| from ._deprecate import deprecate
|
| from ._util import DeferredError, is_path
|
|
|
| ElementTree: ModuleType | None
|
| try:
|
| from defusedxml import ElementTree
|
| except ImportError:
|
| ElementTree = None
|
|
|
| TYPE_CHECKING = False
|
| if TYPE_CHECKING:
|
| from collections.abc import Callable, Iterator, Sequence
|
| from types import ModuleType
|
| from typing import Any, Literal
|
|
|
| logger = logging.getLogger(__name__)
|
|
|
|
|
| class DecompressionBombWarning(RuntimeWarning):
|
| pass
|
|
|
|
|
| class DecompressionBombError(Exception):
|
| pass
|
|
|
|
|
| WARN_POSSIBLE_FORMATS: bool = False
|
|
|
|
|
| MAX_IMAGE_PIXELS: int | None = int(1024 * 1024 * 1024 // 4 // 3)
|
|
|
|
|
| try:
|
|
|
|
|
|
|
|
|
|
|
| from . import _imaging as core
|
|
|
| if __version__ != getattr(core, "PILLOW_VERSION", None):
|
| msg = (
|
| "The _imaging extension was built for another version of Pillow or PIL:\n"
|
| f"Core version: {getattr(core, 'PILLOW_VERSION', None)}\n"
|
| f"Pillow version: {__version__}"
|
| )
|
| raise ImportError(msg)
|
|
|
| except ImportError as v:
|
|
|
| if str(v).startswith("Module use of python"):
|
|
|
|
|
|
|
| warnings.warn(
|
| "The _imaging extension was built for another version of Python.",
|
| RuntimeWarning,
|
| )
|
| elif str(v).startswith("The _imaging extension"):
|
| warnings.warn(str(v), RuntimeWarning)
|
|
|
|
|
| raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| class Transpose(IntEnum):
|
| FLIP_LEFT_RIGHT = 0
|
| FLIP_TOP_BOTTOM = 1
|
| ROTATE_90 = 2
|
| ROTATE_180 = 3
|
| ROTATE_270 = 4
|
| TRANSPOSE = 5
|
| TRANSVERSE = 6
|
|
|
|
|
|
|
| class Transform(IntEnum):
|
| AFFINE = 0
|
| EXTENT = 1
|
| PERSPECTIVE = 2
|
| QUAD = 3
|
| MESH = 4
|
|
|
|
|
|
|
| class Resampling(IntEnum):
|
| NEAREST = 0
|
| BOX = 4
|
| BILINEAR = 2
|
| HAMMING = 5
|
| BICUBIC = 3
|
| LANCZOS = 1
|
|
|
|
|
| _filters_support = {
|
| Resampling.BOX: 0.5,
|
| Resampling.BILINEAR: 1.0,
|
| Resampling.HAMMING: 1.0,
|
| Resampling.BICUBIC: 2.0,
|
| Resampling.LANCZOS: 3.0,
|
| }
|
|
|
|
|
|
|
| class Dither(IntEnum):
|
| NONE = 0
|
| ORDERED = 1
|
| RASTERIZE = 2
|
| FLOYDSTEINBERG = 3
|
|
|
|
|
|
|
| class Palette(IntEnum):
|
| WEB = 0
|
| ADAPTIVE = 1
|
|
|
|
|
| class Quantize(IntEnum):
|
| MEDIANCUT = 0
|
| MAXCOVERAGE = 1
|
| FASTOCTREE = 2
|
| LIBIMAGEQUANT = 3
|
|
|
|
|
| module = sys.modules[__name__]
|
| for enum in (Transpose, Transform, Resampling, Dither, Palette, Quantize):
|
| for item in enum:
|
| setattr(module, item.name, item.value)
|
|
|
|
|
| if hasattr(core, "DEFAULT_STRATEGY"):
|
| DEFAULT_STRATEGY = core.DEFAULT_STRATEGY
|
| FILTERED = core.FILTERED
|
| HUFFMAN_ONLY = core.HUFFMAN_ONLY
|
| RLE = core.RLE
|
| FIXED = core.FIXED
|
|
|
|
|
|
|
|
|
|
|
| TYPE_CHECKING = False
|
| if TYPE_CHECKING:
|
| import mmap
|
| from xml.etree.ElementTree import Element
|
|
|
| from IPython.lib.pretty import PrettyPrinter
|
|
|
| from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin
|
| from ._typing import CapsuleType, NumpyArray, StrOrBytesPath
|
| ID: list[str] = []
|
| OPEN: dict[
|
| str,
|
| tuple[
|
| Callable[[IO[bytes], str | bytes], ImageFile.ImageFile],
|
| Callable[[bytes], bool | str] | None,
|
| ],
|
| ] = {}
|
| MIME: dict[str, str] = {}
|
| SAVE: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {}
|
| SAVE_ALL: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {}
|
| EXTENSION: dict[str, str] = {}
|
| DECODERS: dict[str, type[ImageFile.PyDecoder]] = {}
|
| ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {}
|
|
|
|
|
|
|
|
|
| _ENDIAN = "<" if sys.byteorder == "little" else ">"
|
|
|
|
|
| def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]:
|
| m = ImageMode.getmode(im.mode)
|
| shape: tuple[int, ...] = (im.height, im.width)
|
| extra = len(m.bands)
|
| if extra != 1:
|
| shape += (extra,)
|
| return shape, m.typestr
|
|
|
|
|
| MODES = [
|
| "1",
|
| "CMYK",
|
| "F",
|
| "HSV",
|
| "I",
|
| "I;16",
|
| "I;16B",
|
| "I;16L",
|
| "I;16N",
|
| "L",
|
| "LA",
|
| "La",
|
| "LAB",
|
| "P",
|
| "PA",
|
| "RGB",
|
| "RGBA",
|
| "RGBa",
|
| "RGBX",
|
| "YCbCr",
|
| ]
|
|
|
|
|
|
|
| _MAPMODES = ("L", "P", "RGBX", "RGBA", "CMYK", "I;16", "I;16L", "I;16B")
|
|
|
|
|
| def getmodebase(mode: str) -> str:
|
| """
|
| Gets the "base" mode for given mode. This function returns "L" for
|
| images that contain grayscale data, and "RGB" for images that
|
| contain color data.
|
|
|
| :param mode: Input mode.
|
| :returns: "L" or "RGB".
|
| :exception KeyError: If the input mode was not a standard mode.
|
| """
|
| return ImageMode.getmode(mode).basemode
|
|
|
|
|
| def getmodetype(mode: str) -> str:
|
| """
|
| Gets the storage type mode. Given a mode, this function returns a
|
| single-layer mode suitable for storing individual bands.
|
|
|
| :param mode: Input mode.
|
| :returns: "L", "I", or "F".
|
| :exception KeyError: If the input mode was not a standard mode.
|
| """
|
| return ImageMode.getmode(mode).basetype
|
|
|
|
|
| def getmodebandnames(mode: str) -> tuple[str, ...]:
|
| """
|
| Gets a list of individual band names. Given a mode, this function returns
|
| a tuple containing the names of individual bands (use
|
| :py:method:`~PIL.Image.getmodetype` to get the mode used to store each
|
| individual band.
|
|
|
| :param mode: Input mode.
|
| :returns: A tuple containing band names. The length of the tuple
|
| gives the number of bands in an image of the given mode.
|
| :exception KeyError: If the input mode was not a standard mode.
|
| """
|
| return ImageMode.getmode(mode).bands
|
|
|
|
|
| def getmodebands(mode: str) -> int:
|
| """
|
| Gets the number of individual bands for this mode.
|
|
|
| :param mode: Input mode.
|
| :returns: The number of bands in this mode.
|
| :exception KeyError: If the input mode was not a standard mode.
|
| """
|
| return len(ImageMode.getmode(mode).bands)
|
|
|
|
|
|
|
|
|
|
|
| _initialized = 0
|
|
|
|
|
| def preinit() -> None:
|
| """
|
| Explicitly loads BMP, GIF, JPEG, PPM and PPM file format drivers.
|
|
|
| It is called when opening or saving images.
|
| """
|
|
|
| global _initialized
|
| if _initialized >= 1:
|
| return
|
|
|
| try:
|
| from . import BmpImagePlugin
|
|
|
| assert BmpImagePlugin
|
| except ImportError:
|
| pass
|
| try:
|
| from . import GifImagePlugin
|
|
|
| assert GifImagePlugin
|
| except ImportError:
|
| pass
|
| try:
|
| from . import JpegImagePlugin
|
|
|
| assert JpegImagePlugin
|
| except ImportError:
|
| pass
|
| try:
|
| from . import PpmImagePlugin
|
|
|
| assert PpmImagePlugin
|
| except ImportError:
|
| pass
|
| try:
|
| from . import PngImagePlugin
|
|
|
| assert PngImagePlugin
|
| except ImportError:
|
| pass
|
|
|
| _initialized = 1
|
|
|
|
|
| def init() -> bool:
|
| """
|
| Explicitly initializes the Python Imaging Library. This function
|
| loads all available file format drivers.
|
|
|
| It is called when opening or saving images if :py:meth:`~preinit()` is
|
| insufficient, and by :py:meth:`~PIL.features.pilinfo`.
|
| """
|
|
|
| global _initialized
|
| if _initialized >= 2:
|
| return False
|
|
|
| parent_name = __name__.rpartition(".")[0]
|
| for plugin in _plugins:
|
| try:
|
| logger.debug("Importing %s", plugin)
|
| __import__(f"{parent_name}.{plugin}", globals(), locals(), [])
|
| except ImportError as e:
|
| logger.debug("Image: failed to import %s: %s", plugin, e)
|
|
|
| if OPEN or SAVE:
|
| _initialized = 2
|
| return True
|
| return False
|
|
|
|
|
|
|
|
|
|
|
|
|
| def _getdecoder(
|
| mode: str, decoder_name: str, args: Any, extra: tuple[Any, ...] = ()
|
| ) -> core.ImagingDecoder | ImageFile.PyDecoder:
|
|
|
| if args is None:
|
| args = ()
|
| elif not isinstance(args, tuple):
|
| args = (args,)
|
|
|
| try:
|
| decoder = DECODERS[decoder_name]
|
| except KeyError:
|
| pass
|
| else:
|
| return decoder(mode, *args + extra)
|
|
|
| try:
|
|
|
| decoder = getattr(core, f"{decoder_name}_decoder")
|
| except AttributeError as e:
|
| msg = f"decoder {decoder_name} not available"
|
| raise OSError(msg) from e
|
| return decoder(mode, *args + extra)
|
|
|
|
|
| def _getencoder(
|
| mode: str, encoder_name: str, args: Any, extra: tuple[Any, ...] = ()
|
| ) -> core.ImagingEncoder | ImageFile.PyEncoder:
|
|
|
| if args is None:
|
| args = ()
|
| elif not isinstance(args, tuple):
|
| args = (args,)
|
|
|
| try:
|
| encoder = ENCODERS[encoder_name]
|
| except KeyError:
|
| pass
|
| else:
|
| return encoder(mode, *args + extra)
|
|
|
| try:
|
|
|
| encoder = getattr(core, f"{encoder_name}_encoder")
|
| except AttributeError as e:
|
| msg = f"encoder {encoder_name} not available"
|
| raise OSError(msg) from e
|
| return encoder(mode, *args + extra)
|
|
|
|
|
|
|
|
|
|
|
|
|
| class ImagePointTransform:
|
| """
|
| Used with :py:meth:`~PIL.Image.Image.point` for single band images with more than
|
| 8 bits, this represents an affine transformation, where the value is multiplied by
|
| ``scale`` and ``offset`` is added.
|
| """
|
|
|
| def __init__(self, scale: float, offset: float) -> None:
|
| self.scale = scale
|
| self.offset = offset
|
|
|
| def __neg__(self) -> ImagePointTransform:
|
| return ImagePointTransform(-self.scale, -self.offset)
|
|
|
| def __add__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
| if isinstance(other, ImagePointTransform):
|
| return ImagePointTransform(
|
| self.scale + other.scale, self.offset + other.offset
|
| )
|
| return ImagePointTransform(self.scale, self.offset + other)
|
|
|
| __radd__ = __add__
|
|
|
| def __sub__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
| return self + -other
|
|
|
| def __rsub__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
| return other + -self
|
|
|
| def __mul__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
| if isinstance(other, ImagePointTransform):
|
| return NotImplemented
|
| return ImagePointTransform(self.scale * other, self.offset * other)
|
|
|
| __rmul__ = __mul__
|
|
|
| def __truediv__(self, other: ImagePointTransform | float) -> ImagePointTransform:
|
| if isinstance(other, ImagePointTransform):
|
| return NotImplemented
|
| return ImagePointTransform(self.scale / other, self.offset / other)
|
|
|
|
|
| def _getscaleoffset(
|
| expr: Callable[[ImagePointTransform], ImagePointTransform | float],
|
| ) -> tuple[float, float]:
|
| a = expr(ImagePointTransform(1, 0))
|
| return (a.scale, a.offset) if isinstance(a, ImagePointTransform) else (0, a)
|
|
|
|
|
|
|
|
|
|
|
|
|
| class SupportsGetData(Protocol):
|
| def getdata(
|
| self,
|
| ) -> tuple[Transform, Sequence[int]]: ...
|
|
|
|
|
| class Image:
|
| """
|
| This class represents an image object. To create
|
| :py:class:`~PIL.Image.Image` objects, use the appropriate factory
|
| functions. There's hardly ever any reason to call the Image constructor
|
| directly.
|
|
|
| * :py:func:`~PIL.Image.open`
|
| * :py:func:`~PIL.Image.new`
|
| * :py:func:`~PIL.Image.frombytes`
|
| """
|
|
|
| format: str | None = None
|
| format_description: str | None = None
|
| _close_exclusive_fp_after_loading = True
|
|
|
| def __init__(self) -> None:
|
|
|
| self._im: core.ImagingCore | DeferredError | None = None
|
| self._mode = ""
|
| self._size = (0, 0)
|
| self.palette: ImagePalette.ImagePalette | None = None
|
| self.info: dict[str | tuple[int, int], Any] = {}
|
| self.readonly = 0
|
| self._exif: Exif | None = None
|
|
|
| @property
|
| def im(self) -> core.ImagingCore:
|
| if isinstance(self._im, DeferredError):
|
| raise self._im.ex
|
| assert self._im is not None
|
| return self._im
|
|
|
| @im.setter
|
| def im(self, im: core.ImagingCore) -> None:
|
| self._im = im
|
|
|
| @property
|
| def width(self) -> int:
|
| return self.size[0]
|
|
|
| @property
|
| def height(self) -> int:
|
| return self.size[1]
|
|
|
| @property
|
| def size(self) -> tuple[int, int]:
|
| return self._size
|
|
|
| @property
|
| def mode(self) -> str:
|
| return self._mode
|
|
|
| @property
|
| def readonly(self) -> int:
|
| return (self._im and self._im.readonly) or self._readonly
|
|
|
| @readonly.setter
|
| def readonly(self, readonly: int) -> None:
|
| self._readonly = readonly
|
|
|
| def _new(self, im: core.ImagingCore) -> Image:
|
| new = Image()
|
| new.im = im
|
| new._mode = im.mode
|
| new._size = im.size
|
| if im.mode in ("P", "PA"):
|
| if self.palette:
|
| new.palette = self.palette.copy()
|
| else:
|
| from . import ImagePalette
|
|
|
| new.palette = ImagePalette.ImagePalette()
|
| new.info = self.info.copy()
|
| return new
|
|
|
|
|
| def __enter__(self):
|
| return self
|
|
|
| def __exit__(self, *args):
|
| from . import ImageFile
|
|
|
| if isinstance(self, ImageFile.ImageFile):
|
| if getattr(self, "_exclusive_fp", False):
|
| self._close_fp()
|
| self.fp = None
|
|
|
| def close(self) -> None:
|
| """
|
| This operation will destroy the image core and release its memory.
|
| The image data will be unusable afterward.
|
|
|
| This function is required to close images that have multiple frames or
|
| have not had their file read and closed by the
|
| :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for
|
| more information.
|
| """
|
| if getattr(self, "map", None):
|
| if sys.platform == "win32" and hasattr(sys, "pypy_version_info"):
|
| self.map.close()
|
| self.map: mmap.mmap | None = None
|
|
|
|
|
|
|
|
|
| self._im = DeferredError(ValueError("Operation on closed image"))
|
|
|
| def _copy(self) -> None:
|
| self.load()
|
| self.im = self.im.copy()
|
| self.readonly = 0
|
|
|
| def _ensure_mutable(self) -> None:
|
| if self.readonly:
|
| self._copy()
|
| else:
|
| self.load()
|
|
|
| def _dump(
|
| self, file: str | None = None, format: str | None = None, **options: Any
|
| ) -> str:
|
| suffix = ""
|
| if format:
|
| suffix = f".{format}"
|
|
|
| if not file:
|
| f, filename = tempfile.mkstemp(suffix)
|
| os.close(f)
|
| else:
|
| filename = file
|
| if not filename.endswith(suffix):
|
| filename = filename + suffix
|
|
|
| self.load()
|
|
|
| if not format or format == "PPM":
|
| self.im.save_ppm(filename)
|
| else:
|
| self.save(filename, format, **options)
|
|
|
| return filename
|
|
|
| def __eq__(self, other: object) -> bool:
|
| if self.__class__ is not other.__class__:
|
| return False
|
| assert isinstance(other, Image)
|
| return (
|
| self.mode == other.mode
|
| and self.size == other.size
|
| and self.info == other.info
|
| and self.getpalette() == other.getpalette()
|
| and self.tobytes() == other.tobytes()
|
| )
|
|
|
| def __repr__(self) -> str:
|
| return (
|
| f"<{self.__class__.__module__}.{self.__class__.__name__} "
|
| f"image mode={self.mode} size={self.size[0]}x{self.size[1]} "
|
| f"at 0x{id(self):X}>"
|
| )
|
|
|
| def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None:
|
| """IPython plain text display support"""
|
|
|
|
|
|
|
| p.text(
|
| f"<{self.__class__.__module__}.{self.__class__.__name__} "
|
| f"image mode={self.mode} size={self.size[0]}x{self.size[1]}>"
|
| )
|
|
|
| def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None:
|
| """Helper function for iPython display hook.
|
|
|
| :param image_format: Image format.
|
| :returns: image as bytes, saved into the given format.
|
| """
|
| b = io.BytesIO()
|
| try:
|
| self.save(b, image_format, **kwargs)
|
| except Exception:
|
| return None
|
| return b.getvalue()
|
|
|
| def _repr_png_(self) -> bytes | None:
|
| """iPython display hook support for PNG format.
|
|
|
| :returns: PNG version of the image as bytes
|
| """
|
| return self._repr_image("PNG", compress_level=1)
|
|
|
| def _repr_jpeg_(self) -> bytes | None:
|
| """iPython display hook support for JPEG format.
|
|
|
| :returns: JPEG version of the image as bytes
|
| """
|
| return self._repr_image("JPEG")
|
|
|
| @property
|
| def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]:
|
|
|
| new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3}
|
| if self.mode == "1":
|
|
|
|
|
| new["data"] = self.tobytes("raw", "L")
|
| else:
|
| new["data"] = self.tobytes()
|
| new["shape"], new["typestr"] = _conv_type_shape(self)
|
| return new
|
|
|
| def __arrow_c_schema__(self) -> object:
|
| self.load()
|
| return self.im.__arrow_c_schema__()
|
|
|
| def __arrow_c_array__(
|
| self, requested_schema: object | None = None
|
| ) -> tuple[object, object]:
|
| self.load()
|
| return (self.im.__arrow_c_schema__(), self.im.__arrow_c_array__())
|
|
|
| def __getstate__(self) -> list[Any]:
|
| im_data = self.tobytes()
|
| return [self.info, self.mode, self.size, self.getpalette(), im_data]
|
|
|
| def __setstate__(self, state: list[Any]) -> None:
|
| Image.__init__(self)
|
| info, mode, size, palette, data = state[:5]
|
| self.info = info
|
| self._mode = mode
|
| self._size = size
|
| self.im = core.new(mode, size)
|
| if mode in ("L", "LA", "P", "PA") and palette:
|
| self.putpalette(palette)
|
| self.frombytes(data)
|
|
|
| def tobytes(self, encoder_name: str = "raw", *args: Any) -> bytes:
|
| """
|
| Return image as a bytes object.
|
|
|
| .. warning::
|
|
|
| This method returns raw image data derived from Pillow's internal
|
| storage. For compressed image data (e.g. PNG, JPEG) use
|
| :meth:`~.save`, with a BytesIO parameter for in-memory data.
|
|
|
| :param encoder_name: What encoder to use.
|
|
|
| The default is to use the standard "raw" encoder.
|
| To see how this packs pixel data into the returned
|
| bytes, see :file:`libImaging/Pack.c`.
|
|
|
| A list of C encoders can be seen under codecs
|
| section of the function array in
|
| :file:`_imaging.c`. Python encoders are registered
|
| within the relevant plugins.
|
| :param args: Extra arguments to the encoder.
|
| :returns: A :py:class:`bytes` object.
|
| """
|
|
|
| encoder_args: Any = args
|
| if len(encoder_args) == 1 and isinstance(encoder_args[0], tuple):
|
|
|
| encoder_args = encoder_args[0]
|
|
|
| if encoder_name == "raw" and encoder_args == ():
|
| encoder_args = self.mode
|
|
|
| self.load()
|
|
|
| if self.width == 0 or self.height == 0:
|
| return b""
|
|
|
|
|
| e = _getencoder(self.mode, encoder_name, encoder_args)
|
| e.setimage(self.im)
|
|
|
| from . import ImageFile
|
|
|
| bufsize = max(ImageFile.MAXBLOCK, self.size[0] * 4)
|
|
|
| output = []
|
| while True:
|
| bytes_consumed, errcode, data = e.encode(bufsize)
|
| output.append(data)
|
| if errcode:
|
| break
|
| if errcode < 0:
|
| msg = f"encoder error {errcode} in tobytes"
|
| raise RuntimeError(msg)
|
|
|
| return b"".join(output)
|
|
|
| def tobitmap(self, name: str = "image") -> bytes:
|
| """
|
| Returns the image converted to an X11 bitmap.
|
|
|
| .. note:: This method only works for mode "1" images.
|
|
|
| :param name: The name prefix to use for the bitmap variables.
|
| :returns: A string containing an X11 bitmap.
|
| :raises ValueError: If the mode is not "1"
|
| """
|
|
|
| self.load()
|
| if self.mode != "1":
|
| msg = "not a bitmap"
|
| raise ValueError(msg)
|
| data = self.tobytes("xbm")
|
| return b"".join(
|
| [
|
| f"#define {name}_width {self.size[0]}\n".encode("ascii"),
|
| f"#define {name}_height {self.size[1]}\n".encode("ascii"),
|
| f"static char {name}_bits[] = {{\n".encode("ascii"),
|
| data,
|
| b"};",
|
| ]
|
| )
|
|
|
| def frombytes(
|
| self,
|
| data: bytes | bytearray | SupportsArrayInterface,
|
| decoder_name: str = "raw",
|
| *args: Any,
|
| ) -> None:
|
| """
|
| Loads this image with pixel data from a bytes object.
|
|
|
| This method is similar to the :py:func:`~PIL.Image.frombytes` function,
|
| but loads data into this image instead of creating a new image object.
|
| """
|
|
|
| if self.width == 0 or self.height == 0:
|
| return
|
|
|
| decoder_args: Any = args
|
| if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple):
|
|
|
| decoder_args = decoder_args[0]
|
|
|
|
|
| if decoder_name == "raw" and decoder_args == ():
|
| decoder_args = self.mode
|
|
|
|
|
| d = _getdecoder(self.mode, decoder_name, decoder_args)
|
| d.setimage(self.im)
|
| s = d.decode(data)
|
|
|
| if s[0] >= 0:
|
| msg = "not enough image data"
|
| raise ValueError(msg)
|
| if s[1] != 0:
|
| msg = "cannot decode image data"
|
| raise ValueError(msg)
|
|
|
| def load(self) -> core.PixelAccess | None:
|
| """
|
| Allocates storage for the image and loads the pixel data. In
|
| normal cases, you don't need to call this method, since the
|
| Image class automatically loads an opened image when it is
|
| accessed for the first time.
|
|
|
| If the file associated with the image was opened by Pillow, then this
|
| method will close it. The exception to this is if the image has
|
| multiple frames, in which case the file will be left open for seek
|
| operations. See :ref:`file-handling` for more information.
|
|
|
| :returns: An image access object.
|
| :rtype: :py:class:`.PixelAccess`
|
| """
|
| if self._im is not None and self.palette and self.palette.dirty:
|
|
|
| mode, arr = self.palette.getdata()
|
| self.im.putpalette(self.palette.mode, mode, arr)
|
| self.palette.dirty = 0
|
| self.palette.rawmode = None
|
| if "transparency" in self.info and mode in ("LA", "PA"):
|
| if isinstance(self.info["transparency"], int):
|
| self.im.putpalettealpha(self.info["transparency"], 0)
|
| else:
|
| self.im.putpalettealphas(self.info["transparency"])
|
| self.palette.mode = "RGBA"
|
| else:
|
| self.palette.palette = self.im.getpalette(
|
| self.palette.mode, self.palette.mode
|
| )
|
|
|
| if self._im is not None:
|
| return self.im.pixel_access(self.readonly)
|
| return None
|
|
|
| def verify(self) -> None:
|
| """
|
| Verifies the contents of a file. For data read from a file, this
|
| method attempts to determine if the file is broken, without
|
| actually decoding the image data. If this method finds any
|
| problems, it raises suitable exceptions. If you need to load
|
| the image after using this method, you must reopen the image
|
| file.
|
| """
|
| pass
|
|
|
| def convert(
|
| self,
|
| mode: str | None = None,
|
| matrix: tuple[float, ...] | None = None,
|
| dither: Dither | None = None,
|
| palette: Palette = Palette.WEB,
|
| colors: int = 256,
|
| ) -> Image:
|
| """
|
| Returns a converted copy of this image. For the "P" mode, this
|
| method translates pixels through the palette. If mode is
|
| omitted, a mode is chosen so that all information in the image
|
| and the palette can be represented without a palette.
|
|
|
| This supports all possible conversions between "L", "RGB" and "CMYK". The
|
| ``matrix`` argument only supports "L" and "RGB".
|
|
|
| When translating a color image to grayscale (mode "L"),
|
| the library uses the ITU-R 601-2 luma transform::
|
|
|
| L = R * 299/1000 + G * 587/1000 + B * 114/1000
|
|
|
| The default method of converting a grayscale ("L") or "RGB"
|
| image into a bilevel (mode "1") image uses Floyd-Steinberg
|
| dither to approximate the original image luminosity levels. If
|
| dither is ``None``, all values larger than 127 are set to 255 (white),
|
| all other values to 0 (black). To use other thresholds, use the
|
| :py:meth:`~PIL.Image.Image.point` method.
|
|
|
| When converting from "RGBA" to "P" without a ``matrix`` argument,
|
| this passes the operation to :py:meth:`~PIL.Image.Image.quantize`,
|
| and ``dither`` and ``palette`` are ignored.
|
|
|
| When converting from "PA", if an "RGBA" palette is present, the alpha
|
| channel from the image will be used instead of the values from the palette.
|
|
|
| :param mode: The requested mode. See: :ref:`concept-modes`.
|
| :param matrix: An optional conversion matrix. If given, this
|
| should be 4- or 12-tuple containing floating point values.
|
| :param dither: Dithering method, used when converting from
|
| mode "RGB" to "P" or from "RGB" or "L" to "1".
|
| Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG`
|
| (default). Note that this is not used when ``matrix`` is supplied.
|
| :param palette: Palette to use when converting from mode "RGB"
|
| to "P". Available palettes are :data:`Palette.WEB` or
|
| :data:`Palette.ADAPTIVE`.
|
| :param colors: Number of colors to use for the :data:`Palette.ADAPTIVE`
|
| palette. Defaults to 256.
|
| :rtype: :py:class:`~PIL.Image.Image`
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| self.load()
|
|
|
| has_transparency = "transparency" in self.info
|
| if not mode and self.mode == "P":
|
|
|
| if self.palette:
|
| mode = self.palette.mode
|
| else:
|
| mode = "RGB"
|
| if mode == "RGB" and has_transparency:
|
| mode = "RGBA"
|
| if not mode or (mode == self.mode and not matrix):
|
| return self.copy()
|
|
|
| if matrix:
|
|
|
| if mode not in ("L", "RGB"):
|
| msg = "illegal conversion"
|
| raise ValueError(msg)
|
| im = self.im.convert_matrix(mode, matrix)
|
| new_im = self._new(im)
|
| if has_transparency and self.im.bands == 3:
|
| transparency = new_im.info["transparency"]
|
|
|
| def convert_transparency(
|
| m: tuple[float, ...], v: tuple[int, int, int]
|
| ) -> int:
|
| value = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5
|
| return max(0, min(255, int(value)))
|
|
|
| if mode == "L":
|
| transparency = convert_transparency(matrix, transparency)
|
| elif len(mode) == 3:
|
| transparency = tuple(
|
| convert_transparency(matrix[i * 4 : i * 4 + 4], transparency)
|
| for i in range(len(transparency))
|
| )
|
| new_im.info["transparency"] = transparency
|
| return new_im
|
|
|
| if self.mode == "RGBA":
|
| if mode == "P":
|
| return self.quantize(colors)
|
| elif mode == "PA":
|
| r, g, b, a = self.split()
|
| rgb = merge("RGB", (r, g, b))
|
| p = rgb.quantize(colors)
|
| return merge("PA", (p, a))
|
|
|
| trns = None
|
| delete_trns = False
|
|
|
| if has_transparency:
|
| if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or (
|
| self.mode == "RGB" and mode in ("La", "LA", "RGBa", "RGBA")
|
| ):
|
|
|
|
|
| new_im = self._new(
|
| self.im.convert_transparent(mode, self.info["transparency"])
|
| )
|
| del new_im.info["transparency"]
|
| return new_im
|
| elif self.mode in ("L", "RGB", "P") and mode in ("L", "RGB", "P"):
|
| t = self.info["transparency"]
|
| if isinstance(t, bytes):
|
|
|
| warnings.warn(
|
| "Palette images with Transparency expressed in bytes should be "
|
| "converted to RGBA images"
|
| )
|
| delete_trns = True
|
| else:
|
|
|
|
|
| trns_im = new(self.mode, (1, 1))
|
| if self.mode == "P":
|
| assert self.palette is not None
|
| trns_im.putpalette(self.palette, self.palette.mode)
|
| if isinstance(t, tuple):
|
| err = "Couldn't allocate a palette color for transparency"
|
| assert trns_im.palette is not None
|
| try:
|
| t = trns_im.palette.getcolor(t, self)
|
| except ValueError as e:
|
| if str(e) == "cannot allocate more than 256 colors":
|
|
|
|
|
| t = None
|
| else:
|
| raise ValueError(err) from e
|
| if t is None:
|
| trns = None
|
| else:
|
| trns_im.putpixel((0, 0), t)
|
|
|
| if mode in ("L", "RGB"):
|
| trns_im = trns_im.convert(mode)
|
| else:
|
|
|
|
|
| trns_im = trns_im.convert("RGB")
|
| trns = trns_im.getpixel((0, 0))
|
|
|
| elif self.mode == "P" and mode in ("LA", "PA", "RGBA"):
|
| t = self.info["transparency"]
|
| delete_trns = True
|
|
|
| if isinstance(t, bytes):
|
| self.im.putpalettealphas(t)
|
| elif isinstance(t, int):
|
| self.im.putpalettealpha(t, 0)
|
| else:
|
| msg = "Transparency for P mode should be bytes or int"
|
| raise ValueError(msg)
|
|
|
| if mode == "P" and palette == Palette.ADAPTIVE:
|
| im = self.im.quantize(colors)
|
| new_im = self._new(im)
|
| from . import ImagePalette
|
|
|
| new_im.palette = ImagePalette.ImagePalette(
|
| "RGB", new_im.im.getpalette("RGB")
|
| )
|
| if delete_trns:
|
|
|
|
|
| del new_im.info["transparency"]
|
| if trns is not None:
|
| try:
|
| new_im.info["transparency"] = new_im.palette.getcolor(
|
| cast(tuple[int, ...], trns),
|
| new_im,
|
| )
|
| except Exception:
|
|
|
|
|
| del new_im.info["transparency"]
|
| warnings.warn("Couldn't allocate palette entry for transparency")
|
| return new_im
|
|
|
| if "LAB" in (self.mode, mode):
|
| im = self
|
| if mode == "LAB":
|
| if im.mode not in ("RGB", "RGBA", "RGBX"):
|
| im = im.convert("RGBA")
|
| other_mode = im.mode
|
| else:
|
| other_mode = mode
|
| if other_mode in ("RGB", "RGBA", "RGBX"):
|
| from . import ImageCms
|
|
|
| srgb = ImageCms.createProfile("sRGB")
|
| lab = ImageCms.createProfile("LAB")
|
| profiles = [lab, srgb] if im.mode == "LAB" else [srgb, lab]
|
| transform = ImageCms.buildTransform(
|
| profiles[0], profiles[1], im.mode, mode
|
| )
|
| return transform.apply(im)
|
|
|
|
|
| if dither is None:
|
| dither = Dither.FLOYDSTEINBERG
|
|
|
| try:
|
| im = self.im.convert(mode, dither)
|
| except ValueError:
|
| try:
|
|
|
| modebase = getmodebase(self.mode)
|
| if modebase == self.mode:
|
| raise
|
| im = self.im.convert(modebase)
|
| im = im.convert(mode, dither)
|
| except KeyError as e:
|
| msg = "illegal conversion"
|
| raise ValueError(msg) from e
|
|
|
| new_im = self._new(im)
|
| if mode in ("P", "PA") and palette != Palette.ADAPTIVE:
|
| from . import ImagePalette
|
|
|
| new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB"))
|
| if delete_trns:
|
|
|
| del new_im.info["transparency"]
|
| if trns is not None:
|
| if new_im.mode == "P" and new_im.palette:
|
| try:
|
| new_im.info["transparency"] = new_im.palette.getcolor(
|
| cast(tuple[int, ...], trns), new_im
|
| )
|
| except ValueError as e:
|
| del new_im.info["transparency"]
|
| if str(e) != "cannot allocate more than 256 colors":
|
|
|
|
|
| warnings.warn(
|
| "Couldn't allocate palette entry for transparency"
|
| )
|
| else:
|
| new_im.info["transparency"] = trns
|
| return new_im
|
|
|
| def quantize(
|
| self,
|
| colors: int = 256,
|
| method: int | None = None,
|
| kmeans: int = 0,
|
| palette: Image | None = None,
|
| dither: Dither = Dither.FLOYDSTEINBERG,
|
| ) -> Image:
|
| """
|
| Convert the image to 'P' mode with the specified number
|
| of colors.
|
|
|
| :param colors: The desired number of colors, <= 256
|
| :param method: :data:`Quantize.MEDIANCUT` (median cut),
|
| :data:`Quantize.MAXCOVERAGE` (maximum coverage),
|
| :data:`Quantize.FASTOCTREE` (fast octree),
|
| :data:`Quantize.LIBIMAGEQUANT` (libimagequant; check support
|
| using :py:func:`PIL.features.check_feature` with
|
| ``feature="libimagequant"``).
|
|
|
| By default, :data:`Quantize.MEDIANCUT` will be used.
|
|
|
| The exception to this is RGBA images. :data:`Quantize.MEDIANCUT`
|
| and :data:`Quantize.MAXCOVERAGE` do not support RGBA images, so
|
| :data:`Quantize.FASTOCTREE` is used by default instead.
|
| :param kmeans: Integer greater than or equal to zero.
|
| :param palette: Quantize to the palette of given
|
| :py:class:`PIL.Image.Image`.
|
| :param dither: Dithering method, used when converting from
|
| mode "RGB" to "P" or from "RGB" or "L" to "1".
|
| Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG`
|
| (default).
|
| :returns: A new image
|
| """
|
|
|
| self.load()
|
|
|
| if method is None:
|
|
|
| method = Quantize.MEDIANCUT
|
| if self.mode == "RGBA":
|
| method = Quantize.FASTOCTREE
|
|
|
| if self.mode == "RGBA" and method not in (
|
| Quantize.FASTOCTREE,
|
| Quantize.LIBIMAGEQUANT,
|
| ):
|
|
|
| msg = (
|
| "Fast Octree (method == 2) and libimagequant (method == 3) "
|
| "are the only valid methods for quantizing RGBA images"
|
| )
|
| raise ValueError(msg)
|
|
|
| if palette:
|
|
|
| palette.load()
|
| if palette.mode != "P":
|
| msg = "bad mode for palette image"
|
| raise ValueError(msg)
|
| if self.mode not in {"RGB", "L"}:
|
| msg = "only RGB or L mode images can be quantized to a palette"
|
| raise ValueError(msg)
|
| im = self.im.convert("P", dither, palette.im)
|
| new_im = self._new(im)
|
| assert palette.palette is not None
|
| new_im.palette = palette.palette.copy()
|
| return new_im
|
|
|
| if kmeans < 0:
|
| msg = "kmeans must not be negative"
|
| raise ValueError(msg)
|
|
|
| im = self._new(self.im.quantize(colors, method, kmeans))
|
|
|
| from . import ImagePalette
|
|
|
| mode = im.im.getpalettemode()
|
| palette_data = im.im.getpalette(mode, mode)[: colors * len(mode)]
|
| im.palette = ImagePalette.ImagePalette(mode, palette_data)
|
|
|
| return im
|
|
|
| def copy(self) -> Image:
|
| """
|
| Copies this image. Use this method if you wish to paste things
|
| into an image, but still retain the original.
|
|
|
| :rtype: :py:class:`~PIL.Image.Image`
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
| self.load()
|
| return self._new(self.im.copy())
|
|
|
| __copy__ = copy
|
|
|
| def crop(self, box: tuple[float, float, float, float] | None = None) -> Image:
|
| """
|
| Returns a rectangular region from this image. The box is a
|
| 4-tuple defining the left, upper, right, and lower pixel
|
| coordinate. See :ref:`coordinate-system`.
|
|
|
| Note: Prior to Pillow 3.4.0, this was a lazy operation.
|
|
|
| :param box: The crop rectangle, as a (left, upper, right, lower)-tuple.
|
| :rtype: :py:class:`~PIL.Image.Image`
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| if box is None:
|
| return self.copy()
|
|
|
| if box[2] < box[0]:
|
| msg = "Coordinate 'right' is less than 'left'"
|
| raise ValueError(msg)
|
| elif box[3] < box[1]:
|
| msg = "Coordinate 'lower' is less than 'upper'"
|
| raise ValueError(msg)
|
|
|
| self.load()
|
| return self._new(self._crop(self.im, box))
|
|
|
| def _crop(
|
| self, im: core.ImagingCore, box: tuple[float, float, float, float]
|
| ) -> core.ImagingCore:
|
| """
|
| Returns a rectangular region from the core image object im.
|
|
|
| This is equivalent to calling im.crop((x0, y0, x1, y1)), but
|
| includes additional sanity checks.
|
|
|
| :param im: a core image object
|
| :param box: The crop rectangle, as a (left, upper, right, lower)-tuple.
|
| :returns: A core image object.
|
| """
|
|
|
| x0, y0, x1, y1 = map(int, map(round, box))
|
|
|
| absolute_values = (abs(x1 - x0), abs(y1 - y0))
|
|
|
| _decompression_bomb_check(absolute_values)
|
|
|
| return im.crop((x0, y0, x1, y1))
|
|
|
| def draft(
|
| self, mode: str | None, size: tuple[int, int] | None
|
| ) -> tuple[str, tuple[int, int, float, float]] | None:
|
| """
|
| Configures the image file loader so it returns a version of the
|
| image that as closely as possible matches the given mode and
|
| size. For example, you can use this method to convert a color
|
| JPEG to grayscale while loading it.
|
|
|
| If any changes are made, returns a tuple with the chosen ``mode`` and
|
| ``box`` with coordinates of the original image within the altered one.
|
|
|
| Note that this method modifies the :py:class:`~PIL.Image.Image` object
|
| in place. If the image has already been loaded, this method has no
|
| effect.
|
|
|
| Note: This method is not implemented for most images. It is
|
| currently implemented only for JPEG and MPO images.
|
|
|
| :param mode: The requested mode.
|
| :param size: The requested size in pixels, as a 2-tuple:
|
| (width, height).
|
| """
|
| pass
|
|
|
| def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
|
| """
|
| Filters this image using the given filter. For a list of
|
| available filters, see the :py:mod:`~PIL.ImageFilter` module.
|
|
|
| :param filter: Filter kernel.
|
| :returns: An :py:class:`~PIL.Image.Image` object."""
|
|
|
| from . import ImageFilter
|
|
|
| self.load()
|
|
|
| if callable(filter):
|
| filter = filter()
|
| if not hasattr(filter, "filter"):
|
| msg = "filter argument should be ImageFilter.Filter instance or class"
|
| raise TypeError(msg)
|
|
|
| multiband = isinstance(filter, ImageFilter.MultibandFilter)
|
| if self.im.bands == 1 or multiband:
|
| return self._new(filter.filter(self.im))
|
|
|
| ims = [
|
| self._new(filter.filter(self.im.getband(c))) for c in range(self.im.bands)
|
| ]
|
| return merge(self.mode, ims)
|
|
|
| def getbands(self) -> tuple[str, ...]:
|
| """
|
| Returns a tuple containing the name of each band in this image.
|
| For example, ``getbands`` on an RGB image returns ("R", "G", "B").
|
|
|
| :returns: A tuple containing band names.
|
| :rtype: tuple
|
| """
|
| return ImageMode.getmode(self.mode).bands
|
|
|
| def getbbox(self, *, alpha_only: bool = True) -> tuple[int, int, int, int] | None:
|
| """
|
| Calculates the bounding box of the non-zero regions in the
|
| image.
|
|
|
| :param alpha_only: Optional flag, defaulting to ``True``.
|
| If ``True`` and the image has an alpha channel, trim transparent pixels.
|
| Otherwise, trim pixels when all channels are zero.
|
| Keyword-only argument.
|
| :returns: The bounding box is returned as a 4-tuple defining the
|
| left, upper, right, and lower pixel coordinate. See
|
| :ref:`coordinate-system`. If the image is completely empty, this
|
| method returns None.
|
|
|
| """
|
|
|
| self.load()
|
| return self.im.getbbox(alpha_only)
|
|
|
| def getcolors(
|
| self, maxcolors: int = 256
|
| ) -> list[tuple[int, tuple[int, ...]]] | list[tuple[int, float]] | None:
|
| """
|
| Returns a list of colors used in this image.
|
|
|
| The colors will be in the image's mode. For example, an RGB image will
|
| return a tuple of (red, green, blue) color values, and a P image will
|
| return the index of the color in the palette.
|
|
|
| :param maxcolors: Maximum number of colors. If this number is
|
| exceeded, this method returns None. The default limit is
|
| 256 colors.
|
| :returns: An unsorted list of (count, pixel) values.
|
| """
|
|
|
| self.load()
|
| if self.mode in ("1", "L", "P"):
|
| h = self.im.histogram()
|
| out: list[tuple[int, float]] = [(h[i], i) for i in range(256) if h[i]]
|
| if len(out) > maxcolors:
|
| return None
|
| return out
|
| return self.im.getcolors(maxcolors)
|
|
|
| def getdata(self, band: int | None = None) -> core.ImagingCore:
|
| """
|
| Returns the contents of this image as a sequence object
|
| containing pixel values. The sequence object is flattened, so
|
| that values for line one follow directly after the values of
|
| line zero, and so on.
|
|
|
| Note that the sequence object returned by this method is an
|
| internal PIL data type, which only supports certain sequence
|
| operations. To convert it to an ordinary sequence (e.g. for
|
| printing), use ``list(im.getdata())``.
|
|
|
| :param band: What band to return. The default is to return
|
| all bands. To return a single band, pass in the index
|
| value (e.g. 0 to get the "R" band from an "RGB" image).
|
| :returns: A sequence-like object.
|
| """
|
|
|
| self.load()
|
| if band is not None:
|
| return self.im.getband(band)
|
| return self.im
|
|
|
| def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]:
|
| """
|
| Gets the minimum and maximum pixel values for each band in
|
| the image.
|
|
|
| :returns: For a single-band image, a 2-tuple containing the
|
| minimum and maximum pixel value. For a multi-band image,
|
| a tuple containing one 2-tuple for each band.
|
| """
|
|
|
| self.load()
|
| if self.im.bands > 1:
|
| return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
|
| return self.im.getextrema()
|
|
|
| def getxmp(self) -> dict[str, Any]:
|
| """
|
| Returns a dictionary containing the XMP tags.
|
| Requires defusedxml to be installed.
|
|
|
| :returns: XMP tags in a dictionary.
|
| """
|
|
|
| def get_name(tag: str) -> str:
|
| return re.sub("^{[^}]+}", "", tag)
|
|
|
| def get_value(element: Element) -> str | dict[str, Any] | None:
|
| value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()}
|
| children = list(element)
|
| if children:
|
| for child in children:
|
| name = get_name(child.tag)
|
| child_value = get_value(child)
|
| if name in value:
|
| if not isinstance(value[name], list):
|
| value[name] = [value[name]]
|
| value[name].append(child_value)
|
| else:
|
| value[name] = child_value
|
| elif value:
|
| if element.text:
|
| value["text"] = element.text
|
| else:
|
| return element.text
|
| return value
|
|
|
| if ElementTree is None:
|
| warnings.warn("XMP data cannot be read without defusedxml dependency")
|
| return {}
|
| if "xmp" not in self.info:
|
| return {}
|
| root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00 "))
|
| return {get_name(root.tag): get_value(root)}
|
|
|
| def getexif(self) -> Exif:
|
| """
|
| Gets EXIF data from the image.
|
|
|
| :returns: an :py:class:`~PIL.Image.Exif` object.
|
| """
|
| if self._exif is None:
|
| self._exif = Exif()
|
| elif self._exif._loaded:
|
| return self._exif
|
| self._exif._loaded = True
|
|
|
| exif_info = self.info.get("exif")
|
| if exif_info is None:
|
| if "Raw profile type exif" in self.info:
|
| exif_info = bytes.fromhex(
|
| "".join(self.info["Raw profile type exif"].split("\n")[3:])
|
| )
|
| elif hasattr(self, "tag_v2"):
|
| self._exif.bigtiff = self.tag_v2._bigtiff
|
| self._exif.endian = self.tag_v2._endian
|
| self._exif.load_from_fp(self.fp, self.tag_v2._offset)
|
| if exif_info is not None:
|
| self._exif.load(exif_info)
|
|
|
|
|
| if ExifTags.Base.Orientation not in self._exif:
|
| xmp_tags = self.info.get("XML:com.adobe.xmp")
|
| pattern: str | bytes = r'tiff:Orientation(="|>)([0-9])'
|
| if not xmp_tags and (xmp_tags := self.info.get("xmp")):
|
| pattern = rb'tiff:Orientation(="|>)([0-9])'
|
| if xmp_tags:
|
| match = re.search(pattern, xmp_tags)
|
| if match:
|
| self._exif[ExifTags.Base.Orientation] = int(match[2])
|
|
|
| return self._exif
|
|
|
| def _reload_exif(self) -> None:
|
| if self._exif is None or not self._exif._loaded:
|
| return
|
| self._exif._loaded = False
|
| self.getexif()
|
|
|
| def get_child_images(self) -> list[ImageFile.ImageFile]:
|
| from . import ImageFile
|
|
|
| deprecate("Image.Image.get_child_images", 13)
|
| return ImageFile.ImageFile.get_child_images(self)
|
|
|
| def getim(self) -> CapsuleType:
|
| """
|
| Returns a capsule that points to the internal image memory.
|
|
|
| :returns: A capsule object.
|
| """
|
|
|
| self.load()
|
| return self.im.ptr
|
|
|
| def getpalette(self, rawmode: str | None = "RGB") -> list[int] | None:
|
| """
|
| Returns the image palette as a list.
|
|
|
| :param rawmode: The mode in which to return the palette. ``None`` will
|
| return the palette in its current mode.
|
|
|
| .. versionadded:: 9.1.0
|
|
|
| :returns: A list of color values [r, g, b, ...], or None if the
|
| image has no palette.
|
| """
|
|
|
| self.load()
|
| try:
|
| mode = self.im.getpalettemode()
|
| except ValueError:
|
| return None
|
| if rawmode is None:
|
| rawmode = mode
|
| return list(self.im.getpalette(mode, rawmode))
|
|
|
| @property
|
| def has_transparency_data(self) -> bool:
|
| """
|
| Determine if an image has transparency data, whether in the form of an
|
| alpha channel, a palette with an alpha channel, or a "transparency" key
|
| in the info dictionary.
|
|
|
| Note the image might still appear solid, if all of the values shown
|
| within are opaque.
|
|
|
| :returns: A boolean.
|
| """
|
| if (
|
| self.mode in ("LA", "La", "PA", "RGBA", "RGBa")
|
| or "transparency" in self.info
|
| ):
|
| return True
|
| if self.mode == "P":
|
| assert self.palette is not None
|
| return self.palette.mode.endswith("A")
|
| return False
|
|
|
| def apply_transparency(self) -> None:
|
| """
|
| If a P mode image has a "transparency" key in the info dictionary,
|
| remove the key and instead apply the transparency to the palette.
|
| Otherwise, the image is unchanged.
|
| """
|
| if self.mode != "P" or "transparency" not in self.info:
|
| return
|
|
|
| from . import ImagePalette
|
|
|
| palette = self.getpalette("RGBA")
|
| assert palette is not None
|
| transparency = self.info["transparency"]
|
| if isinstance(transparency, bytes):
|
| for i, alpha in enumerate(transparency):
|
| palette[i * 4 + 3] = alpha
|
| else:
|
| palette[transparency * 4 + 3] = 0
|
| self.palette = ImagePalette.ImagePalette("RGBA", bytes(palette))
|
| self.palette.dirty = 1
|
|
|
| del self.info["transparency"]
|
|
|
| def getpixel(
|
| self, xy: tuple[int, int] | list[int]
|
| ) -> float | tuple[int, ...] | None:
|
| """
|
| Returns the pixel value at a given position.
|
|
|
| :param xy: The coordinate, given as (x, y). See
|
| :ref:`coordinate-system`.
|
| :returns: The pixel value. If the image is a multi-layer image,
|
| this method returns a tuple.
|
| """
|
|
|
| self.load()
|
| return self.im.getpixel(tuple(xy))
|
|
|
| def getprojection(self) -> tuple[list[int], list[int]]:
|
| """
|
| Get projection to x and y axes
|
|
|
| :returns: Two sequences, indicating where there are non-zero
|
| pixels along the X-axis and the Y-axis, respectively.
|
| """
|
|
|
| self.load()
|
| x, y = self.im.getprojection()
|
| return list(x), list(y)
|
|
|
| def histogram(
|
| self, mask: Image | None = None, extrema: tuple[float, float] | None = None
|
| ) -> list[int]:
|
| """
|
| Returns a histogram for the image. The histogram is returned as a
|
| list of pixel counts, one for each pixel value in the source
|
| image. Counts are grouped into 256 bins for each band, even if
|
| the image has more than 8 bits per band. If the image has more
|
| than one band, the histograms for all bands are concatenated (for
|
| example, the histogram for an "RGB" image contains 768 values).
|
|
|
| A bilevel image (mode "1") is treated as a grayscale ("L") image
|
| by this method.
|
|
|
| If a mask is provided, the method returns a histogram for those
|
| parts of the image where the mask image is non-zero. The mask
|
| image must have the same size as the image, and be either a
|
| bi-level image (mode "1") or a grayscale image ("L").
|
|
|
| :param mask: An optional mask.
|
| :param extrema: An optional tuple of manually-specified extrema.
|
| :returns: A list containing pixel counts.
|
| """
|
| self.load()
|
| if mask:
|
| mask.load()
|
| return self.im.histogram((0, 0), mask.im)
|
| if self.mode in ("I", "F"):
|
| return self.im.histogram(
|
| extrema if extrema is not None else self.getextrema()
|
| )
|
| return self.im.histogram()
|
|
|
| def entropy(
|
| self, mask: Image | None = None, extrema: tuple[float, float] | None = None
|
| ) -> float:
|
| """
|
| Calculates and returns the entropy for the image.
|
|
|
| A bilevel image (mode "1") is treated as a grayscale ("L")
|
| image by this method.
|
|
|
| If a mask is provided, the method employs the histogram for
|
| those parts of the image where the mask image is non-zero.
|
| The mask image must have the same size as the image, and be
|
| either a bi-level image (mode "1") or a grayscale image ("L").
|
|
|
| :param mask: An optional mask.
|
| :param extrema: An optional tuple of manually-specified extrema.
|
| :returns: A float value representing the image entropy
|
| """
|
| self.load()
|
| if mask:
|
| mask.load()
|
| return self.im.entropy((0, 0), mask.im)
|
| if self.mode in ("I", "F"):
|
| return self.im.entropy(
|
| extrema if extrema is not None else self.getextrema()
|
| )
|
| return self.im.entropy()
|
|
|
| def paste(
|
| self,
|
| im: Image | str | float | tuple[float, ...],
|
| box: Image | tuple[int, int, int, int] | tuple[int, int] | None = None,
|
| mask: Image | None = None,
|
| ) -> None:
|
| """
|
| Pastes another image into this image. The box argument is either
|
| a 2-tuple giving the upper left corner, a 4-tuple defining the
|
| left, upper, right, and lower pixel coordinate, or None (same as
|
| (0, 0)). See :ref:`coordinate-system`. If a 4-tuple is given, the size
|
| of the pasted image must match the size of the region.
|
|
|
| If the modes don't match, the pasted image is converted to the mode of
|
| this image (see the :py:meth:`~PIL.Image.Image.convert` method for
|
| details).
|
|
|
| Instead of an image, the source can be a integer or tuple
|
| containing pixel values. The method then fills the region
|
| with the given color. When creating RGB images, you can
|
| also use color strings as supported by the ImageColor module. See
|
| :ref:`colors` for more information.
|
|
|
| If a mask is given, this method updates only the regions
|
| indicated by the mask. You can use either "1", "L", "LA", "RGBA"
|
| or "RGBa" images (if present, the alpha band is used as mask).
|
| Where the mask is 255, the given image is copied as is. Where
|
| the mask is 0, the current value is preserved. Intermediate
|
| values will mix the two images together, including their alpha
|
| channels if they have them.
|
|
|
| See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
|
| combine images with respect to their alpha channels.
|
|
|
| :param im: Source image or pixel value (integer, float or tuple).
|
| :param box: An optional 4-tuple giving the region to paste into.
|
| If a 2-tuple is used instead, it's treated as the upper left
|
| corner. If omitted or None, the source is pasted into the
|
| upper left corner.
|
|
|
| If an image is given as the second argument and there is no
|
| third, the box defaults to (0, 0), and the second argument
|
| is interpreted as a mask image.
|
| :param mask: An optional mask image.
|
| """
|
|
|
| if isinstance(box, Image):
|
| if mask is not None:
|
| msg = "If using second argument as mask, third argument must be None"
|
| raise ValueError(msg)
|
|
|
| mask = box
|
| box = None
|
|
|
| if box is None:
|
| box = (0, 0)
|
|
|
| if len(box) == 2:
|
|
|
| if isinstance(im, Image):
|
| size = im.size
|
| elif isinstance(mask, Image):
|
| size = mask.size
|
| else:
|
|
|
| msg = "cannot determine region size; use 4-item box"
|
| raise ValueError(msg)
|
| box += (box[0] + size[0], box[1] + size[1])
|
|
|
| source: core.ImagingCore | str | float | tuple[float, ...]
|
| if isinstance(im, str):
|
| from . import ImageColor
|
|
|
| source = ImageColor.getcolor(im, self.mode)
|
| elif isinstance(im, Image):
|
| im.load()
|
| if self.mode != im.mode:
|
| if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"):
|
|
|
| im = im.convert(self.mode)
|
| source = im.im
|
| else:
|
| source = im
|
|
|
| self._ensure_mutable()
|
|
|
| if mask:
|
| mask.load()
|
| self.im.paste(source, box, mask.im)
|
| else:
|
| self.im.paste(source, box)
|
|
|
| def alpha_composite(
|
| self, im: Image, dest: Sequence[int] = (0, 0), source: Sequence[int] = (0, 0)
|
| ) -> None:
|
| """'In-place' analog of Image.alpha_composite. Composites an image
|
| onto this image.
|
|
|
| :param im: image to composite over this one
|
| :param dest: Optional 2 tuple (left, top) specifying the upper
|
| left corner in this (destination) image.
|
| :param source: Optional 2 (left, top) tuple for the upper left
|
| corner in the overlay source image, or 4 tuple (left, top, right,
|
| bottom) for the bounds of the source rectangle
|
|
|
| Performance Note: Not currently implemented in-place in the core layer.
|
| """
|
|
|
| if not isinstance(source, (list, tuple)):
|
| msg = "Source must be a list or tuple"
|
| raise ValueError(msg)
|
| if not isinstance(dest, (list, tuple)):
|
| msg = "Destination must be a list or tuple"
|
| raise ValueError(msg)
|
|
|
| if len(source) == 4:
|
| overlay_crop_box = tuple(source)
|
| elif len(source) == 2:
|
| overlay_crop_box = tuple(source) + im.size
|
| else:
|
| msg = "Source must be a sequence of length 2 or 4"
|
| raise ValueError(msg)
|
|
|
| if not len(dest) == 2:
|
| msg = "Destination must be a sequence of length 2"
|
| raise ValueError(msg)
|
| if min(source) < 0:
|
| msg = "Source must be non-negative"
|
| raise ValueError(msg)
|
|
|
|
|
| if overlay_crop_box == (0, 0) + im.size:
|
| overlay = im
|
| else:
|
| overlay = im.crop(overlay_crop_box)
|
|
|
|
|
| box = tuple(dest) + (dest[0] + overlay.width, dest[1] + overlay.height)
|
|
|
|
|
| if box == (0, 0) + self.size:
|
| background = self
|
| else:
|
| background = self.crop(box)
|
|
|
| result = alpha_composite(background, overlay)
|
| self.paste(result, box)
|
|
|
| def point(
|
| self,
|
| lut: (
|
| Sequence[float]
|
| | NumpyArray
|
| | Callable[[int], float]
|
| | Callable[[ImagePointTransform], ImagePointTransform | float]
|
| | ImagePointHandler
|
| ),
|
| mode: str | None = None,
|
| ) -> Image:
|
| """
|
| Maps this image through a lookup table or function.
|
|
|
| :param lut: A lookup table, containing 256 (or 65536 if
|
| self.mode=="I" and mode == "L") values per band in the
|
| image. A function can be used instead, it should take a
|
| single argument. The function is called once for each
|
| possible pixel value, and the resulting table is applied to
|
| all bands of the image.
|
|
|
| It may also be an :py:class:`~PIL.Image.ImagePointHandler`
|
| object::
|
|
|
| class Example(Image.ImagePointHandler):
|
| def point(self, im: Image) -> Image:
|
| # Return result
|
| :param mode: Output mode (default is same as input). This can only be used if
|
| the source image has mode "L" or "P", and the output has mode "1" or the
|
| source image mode is "I" and the output mode is "L".
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| self.load()
|
|
|
| if isinstance(lut, ImagePointHandler):
|
| return lut.point(self)
|
|
|
| if callable(lut):
|
|
|
| if self.mode in ("I", "I;16", "F"):
|
|
|
|
|
|
|
| scale, offset = _getscaleoffset(lut)
|
| return self._new(self.im.point_transform(scale, offset))
|
|
|
| flatLut = [lut(i) for i in range(256)] * self.im.bands
|
| else:
|
| flatLut = lut
|
|
|
| if self.mode == "F":
|
|
|
| msg = "point operation not supported for this mode"
|
| raise ValueError(msg)
|
|
|
| if mode != "F":
|
| flatLut = [round(i) for i in flatLut]
|
| return self._new(self.im.point(flatLut, mode))
|
|
|
| def putalpha(self, alpha: Image | int) -> None:
|
| """
|
| 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".
|
| The new layer must be either "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.
|
| """
|
|
|
| self._ensure_mutable()
|
|
|
| if self.mode not in ("LA", "PA", "RGBA"):
|
|
|
| try:
|
| mode = getmodebase(self.mode) + "A"
|
| try:
|
| self.im.setmode(mode)
|
| except (AttributeError, ValueError) as e:
|
|
|
| im = self.im.convert(mode)
|
| if im.mode not in ("LA", "PA", "RGBA"):
|
| msg = "alpha channel could not be added"
|
| raise ValueError(msg) from e
|
| self.im = im
|
| self._mode = self.im.mode
|
| except KeyError as e:
|
| msg = "illegal image mode"
|
| raise ValueError(msg) from e
|
|
|
| if self.mode in ("LA", "PA"):
|
| band = 1
|
| else:
|
| band = 3
|
|
|
| if isinstance(alpha, Image):
|
|
|
| if alpha.mode not in ("1", "L"):
|
| msg = "illegal image mode"
|
| raise ValueError(msg)
|
| alpha.load()
|
| if alpha.mode == "1":
|
| alpha = alpha.convert("L")
|
| else:
|
|
|
| try:
|
| self.im.fillband(band, alpha)
|
| except (AttributeError, ValueError):
|
|
|
| alpha = new("L", self.size, alpha)
|
| else:
|
| return
|
|
|
| self.im.putband(alpha.im, band)
|
|
|
| def putdata(
|
| self,
|
| data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray,
|
| scale: float = 1.0,
|
| offset: float = 0.0,
|
| ) -> None:
|
| """
|
| Copies pixel data from a flattened sequence object into the image. The
|
| values should start at the upper left corner (0, 0), continue to the
|
| end of the line, followed directly by the first value of the second
|
| line, and so on. Data will be read until either the image or the
|
| sequence ends. The scale and offset values are used to adjust the
|
| sequence values: **pixel = value*scale + offset**.
|
|
|
| :param data: A flattened sequence object. See :ref:`colors` for more
|
| information about values.
|
| :param scale: An optional scale value. The default is 1.0.
|
| :param offset: An optional offset value. The default is 0.0.
|
| """
|
|
|
| self._ensure_mutable()
|
|
|
| self.im.putdata(data, scale, offset)
|
|
|
| def putpalette(
|
| self,
|
| data: ImagePalette.ImagePalette | bytes | Sequence[int],
|
| rawmode: str = "RGB",
|
| ) -> None:
|
| """
|
| Attaches a palette to this image. The image must be a "P", "PA", "L"
|
| or "LA" image.
|
|
|
| The palette sequence must contain at most 256 colors, made up of one
|
| integer value for each channel in the raw mode.
|
| For example, if the raw mode is "RGB", then it can contain at most 768
|
| values, made up of red, green and blue values for the corresponding pixel
|
| index in the 256 colors.
|
| If the raw mode is "RGBA", then it can contain at most 1024 values,
|
| containing red, green, blue and alpha values.
|
|
|
| Alternatively, an 8-bit string may be used instead of an integer sequence.
|
|
|
| :param data: A palette sequence (either a list or a string).
|
| :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode
|
| that can be transformed to "RGB" or "RGBA" (e.g. "R", "BGR;15", "RGBA;L").
|
| """
|
| from . import ImagePalette
|
|
|
| if self.mode not in ("L", "LA", "P", "PA"):
|
| msg = "illegal image mode"
|
| raise ValueError(msg)
|
| if isinstance(data, ImagePalette.ImagePalette):
|
| if data.rawmode is not None:
|
| palette = ImagePalette.raw(data.rawmode, data.palette)
|
| else:
|
| palette = ImagePalette.ImagePalette(palette=data.palette)
|
| palette.dirty = 1
|
| else:
|
| if not isinstance(data, bytes):
|
| data = bytes(data)
|
| palette = ImagePalette.raw(rawmode, data)
|
| self._mode = "PA" if "A" in self.mode else "P"
|
| self.palette = palette
|
| self.palette.mode = "RGBA" if "A" in rawmode else "RGB"
|
| self.load()
|
|
|
| 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
|
| a single numerical value for single-band images, and a tuple for
|
| multi-band images. In addition to this, RGB and RGBA tuples are
|
| accepted for P and PA images. See :ref:`colors` for more information.
|
|
|
| Note that this method is relatively slow. For more extensive changes,
|
| use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw`
|
| module instead.
|
|
|
| See:
|
|
|
| * :py:meth:`~PIL.Image.Image.paste`
|
| * :py:meth:`~PIL.Image.Image.putdata`
|
| * :py:mod:`~PIL.ImageDraw`
|
|
|
| :param xy: The pixel coordinate, given as (x, y). See
|
| :ref:`coordinate-system`.
|
| :param value: The pixel value.
|
| """
|
|
|
| self._ensure_mutable()
|
|
|
| if (
|
| self.mode in ("P", "PA")
|
| and isinstance(value, (list, tuple))
|
| and len(value) in [3, 4]
|
| ):
|
|
|
| if self.mode == "PA":
|
| alpha = value[3] if len(value) == 4 else 255
|
| value = value[:3]
|
| assert self.palette is not None
|
| palette_index = self.palette.getcolor(tuple(value), self)
|
| value = (palette_index, alpha) if self.mode == "PA" else palette_index
|
| return self.im.putpixel(xy, value)
|
|
|
| def remap_palette(
|
| self, dest_map: list[int], source_palette: bytes | bytearray | None = None
|
| ) -> Image:
|
| """
|
| Rewrites the image to reorder the palette.
|
|
|
| :param dest_map: A list of indexes into the original palette.
|
| e.g. ``[1,0]`` would swap a two item palette, and ``list(range(256))``
|
| is the identity transform.
|
| :param source_palette: Bytes or None.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
|
|
| """
|
| from . import ImagePalette
|
|
|
| if self.mode not in ("L", "P"):
|
| msg = "illegal image mode"
|
| raise ValueError(msg)
|
|
|
| bands = 3
|
| palette_mode = "RGB"
|
| if source_palette is None:
|
| if self.mode == "P":
|
| self.load()
|
| palette_mode = self.im.getpalettemode()
|
| if palette_mode == "RGBA":
|
| bands = 4
|
| source_palette = self.im.getpalette(palette_mode, palette_mode)
|
| else:
|
| source_palette = bytearray(i // 3 for i in range(768))
|
| elif len(source_palette) > 768:
|
| bands = 4
|
| palette_mode = "RGBA"
|
|
|
| palette_bytes = b""
|
| new_positions = [0] * 256
|
|
|
|
|
| for i, oldPosition in enumerate(dest_map):
|
| palette_bytes += source_palette[
|
| oldPosition * bands : oldPosition * bands + bands
|
| ]
|
| new_positions[oldPosition] = i
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| mapping_palette = bytearray(new_positions)
|
|
|
| m_im = self.copy()
|
| m_im._mode = "P"
|
|
|
| m_im.palette = ImagePalette.ImagePalette(
|
| palette_mode, palette=mapping_palette * bands
|
| )
|
|
|
|
|
|
|
|
|
| m_im.im.putpalette(palette_mode, palette_mode + ";L", m_im.palette.tobytes())
|
|
|
| m_im = m_im.convert("L")
|
|
|
| m_im.putpalette(palette_bytes, palette_mode)
|
| m_im.palette = ImagePalette.ImagePalette(palette_mode, palette=palette_bytes)
|
|
|
| if "transparency" in self.info:
|
| try:
|
| m_im.info["transparency"] = dest_map.index(self.info["transparency"])
|
| except ValueError:
|
| if "transparency" in m_im.info:
|
| del m_im.info["transparency"]
|
|
|
| return m_im
|
|
|
| def _get_safe_box(
|
| self,
|
| size: tuple[int, int],
|
| resample: Resampling,
|
| box: tuple[float, float, float, float],
|
| ) -> tuple[int, int, int, int]:
|
| """Expands the box so it includes adjacent pixels
|
| that may be used by resampling with the given resampling filter.
|
| """
|
| filter_support = _filters_support[resample] - 0.5
|
| scale_x = (box[2] - box[0]) / size[0]
|
| scale_y = (box[3] - box[1]) / size[1]
|
| support_x = filter_support * scale_x
|
| support_y = filter_support * scale_y
|
|
|
| return (
|
| max(0, int(box[0] - support_x)),
|
| max(0, int(box[1] - support_y)),
|
| min(self.size[0], math.ceil(box[2] + support_x)),
|
| min(self.size[1], math.ceil(box[3] + support_y)),
|
| )
|
|
|
| def resize(
|
| self,
|
| size: tuple[int, int] | list[int] | NumpyArray,
|
| resample: int | None = None,
|
| box: tuple[float, float, float, float] | None = None,
|
| reducing_gap: float | None = None,
|
| ) -> Image:
|
| """
|
| Returns a resized copy of this image.
|
|
|
| :param size: The requested size in pixels, as a tuple or array:
|
| (width, height).
|
| :param resample: An optional resampling filter. This can be
|
| one of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`,
|
| :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
|
| :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
|
| If the image has mode "1" or "P", it is always set to
|
| :py:data:`Resampling.NEAREST`. Otherwise, the default filter is
|
| :py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`.
|
| :param box: An optional 4-tuple of floats providing
|
| the source image region to be scaled.
|
| The values must be within (0, 0, width, height) rectangle.
|
| If omitted or None, the entire source is used.
|
| :param reducing_gap: Apply optimization by resizing the image
|
| in two steps. First, reducing the image by integer times
|
| using :py:meth:`~PIL.Image.Image.reduce`.
|
| Second, resizing using regular resampling. The last step
|
| changes size no less than by ``reducing_gap`` times.
|
| ``reducing_gap`` may be None (no first step is performed)
|
| or should be greater than 1.0. The bigger ``reducing_gap``,
|
| the closer the result to the fair resampling.
|
| The smaller ``reducing_gap``, the faster resizing.
|
| With ``reducing_gap`` greater or equal to 3.0, the result is
|
| indistinguishable from fair resampling in most cases.
|
| The default value is None (no optimization).
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| if resample is None:
|
| resample = Resampling.BICUBIC
|
| elif resample not in (
|
| Resampling.NEAREST,
|
| Resampling.BILINEAR,
|
| Resampling.BICUBIC,
|
| Resampling.LANCZOS,
|
| Resampling.BOX,
|
| Resampling.HAMMING,
|
| ):
|
| msg = f"Unknown resampling filter ({resample})."
|
|
|
| filters = [
|
| f"{filter[1]} ({filter[0]})"
|
| for filter in (
|
| (Resampling.NEAREST, "Image.Resampling.NEAREST"),
|
| (Resampling.LANCZOS, "Image.Resampling.LANCZOS"),
|
| (Resampling.BILINEAR, "Image.Resampling.BILINEAR"),
|
| (Resampling.BICUBIC, "Image.Resampling.BICUBIC"),
|
| (Resampling.BOX, "Image.Resampling.BOX"),
|
| (Resampling.HAMMING, "Image.Resampling.HAMMING"),
|
| )
|
| ]
|
| msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}"
|
| raise ValueError(msg)
|
|
|
| if reducing_gap is not None and reducing_gap < 1.0:
|
| msg = "reducing_gap must be 1.0 or greater"
|
| raise ValueError(msg)
|
|
|
| if box is None:
|
| box = (0, 0) + self.size
|
|
|
| size = tuple(size)
|
| if self.size == size and box == (0, 0) + self.size:
|
| return self.copy()
|
|
|
| if self.mode in ("1", "P"):
|
| resample = Resampling.NEAREST
|
|
|
| if self.mode in ["LA", "RGBA"] and resample != Resampling.NEAREST:
|
| im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
|
| im = im.resize(size, resample, box)
|
| return im.convert(self.mode)
|
|
|
| self.load()
|
|
|
| if reducing_gap is not None and resample != Resampling.NEAREST:
|
| factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
|
| factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
|
| if factor_x > 1 or factor_y > 1:
|
| reduce_box = self._get_safe_box(size, cast(Resampling, resample), box)
|
| factor = (factor_x, factor_y)
|
| self = (
|
| self.reduce(factor, box=reduce_box)
|
| if callable(self.reduce)
|
| else Image.reduce(self, factor, box=reduce_box)
|
| )
|
| box = (
|
| (box[0] - reduce_box[0]) / factor_x,
|
| (box[1] - reduce_box[1]) / factor_y,
|
| (box[2] - reduce_box[0]) / factor_x,
|
| (box[3] - reduce_box[1]) / factor_y,
|
| )
|
|
|
| return self._new(self.im.resize(size, resample, box))
|
|
|
| def reduce(
|
| self,
|
| factor: int | tuple[int, int],
|
| box: tuple[int, int, int, int] | None = None,
|
| ) -> Image:
|
| """
|
| Returns a copy of the image reduced ``factor`` times.
|
| If the size of the image is not dividable by ``factor``,
|
| the resulting size will be rounded up.
|
|
|
| :param factor: A greater than 0 integer or tuple of two integers
|
| for width and height separately.
|
| :param box: An optional 4-tuple of ints providing
|
| the source image region to be reduced.
|
| The values must be within ``(0, 0, width, height)`` rectangle.
|
| If omitted or ``None``, the entire source is used.
|
| """
|
| if not isinstance(factor, (list, tuple)):
|
| factor = (factor, factor)
|
|
|
| if box is None:
|
| box = (0, 0) + self.size
|
|
|
| if factor == (1, 1) and box == (0, 0) + self.size:
|
| return self.copy()
|
|
|
| if self.mode in ["LA", "RGBA"]:
|
| im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
|
| im = im.reduce(factor, box)
|
| return im.convert(self.mode)
|
|
|
| self.load()
|
|
|
| return self._new(self.im.reduce(factor, box))
|
|
|
| def rotate(
|
| self,
|
| angle: float,
|
| resample: Resampling = Resampling.NEAREST,
|
| expand: int | bool = False,
|
| center: tuple[float, float] | None = None,
|
| translate: tuple[int, int] | None = None,
|
| fillcolor: float | tuple[float, ...] | str | None = None,
|
| ) -> Image:
|
| """
|
| Returns a rotated copy of this image. This method returns a
|
| copy of this image, rotated the given number of degrees counter
|
| clockwise around its centre.
|
|
|
| :param angle: In degrees counter clockwise.
|
| :param resample: An optional resampling filter. This can be
|
| one of :py:data:`Resampling.NEAREST` (use nearest neighbour),
|
| :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2
|
| environment), or :py:data:`Resampling.BICUBIC` (cubic spline
|
| interpolation in a 4x4 environment). If omitted, or if the image has
|
| mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`.
|
| See :ref:`concept-filters`.
|
| :param expand: Optional expansion flag. If true, expands the output
|
| image to make it large enough to hold the entire rotated image.
|
| If false or omitted, make the output image the same size as the
|
| input image. Note that the expand flag assumes rotation around
|
| the center and no translation.
|
| :param center: Optional center of rotation (a 2-tuple). Origin is
|
| the upper left corner. Default is the center of the image.
|
| :param translate: An optional post-rotate translation (a 2-tuple).
|
| :param fillcolor: An optional color for area outside the rotated image.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| angle = angle % 360.0
|
|
|
|
|
|
|
| if not (center or translate):
|
| if angle == 0:
|
| return self.copy()
|
| if angle == 180:
|
| return self.transpose(Transpose.ROTATE_180)
|
| if angle in (90, 270) and (expand or self.width == self.height):
|
| return self.transpose(
|
| Transpose.ROTATE_90 if angle == 90 else Transpose.ROTATE_270
|
| )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| w, h = self.size
|
|
|
| if translate is None:
|
| post_trans = (0, 0)
|
| else:
|
| post_trans = translate
|
| if center is None:
|
| center = (w / 2, h / 2)
|
|
|
| angle = -math.radians(angle)
|
| matrix = [
|
| round(math.cos(angle), 15),
|
| round(math.sin(angle), 15),
|
| 0.0,
|
| round(-math.sin(angle), 15),
|
| round(math.cos(angle), 15),
|
| 0.0,
|
| ]
|
|
|
| def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]:
|
| (a, b, c, d, e, f) = matrix
|
| return a * x + b * y + c, d * x + e * y + f
|
|
|
| matrix[2], matrix[5] = transform(
|
| -center[0] - post_trans[0], -center[1] - post_trans[1], matrix
|
| )
|
| matrix[2] += center[0]
|
| matrix[5] += center[1]
|
|
|
| if expand:
|
|
|
| xx = []
|
| yy = []
|
| for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
|
| transformed_x, transformed_y = transform(x, y, matrix)
|
| xx.append(transformed_x)
|
| yy.append(transformed_y)
|
| nw = math.ceil(max(xx)) - math.floor(min(xx))
|
| nh = math.ceil(max(yy)) - math.floor(min(yy))
|
|
|
|
|
|
|
|
|
| matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix)
|
| w, h = nw, nh
|
|
|
| return self.transform(
|
| (w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor
|
| )
|
|
|
| def save(
|
| self, fp: StrOrBytesPath | IO[bytes], format: str | None = None, **params: Any
|
| ) -> None:
|
| """
|
| Saves this image under the given filename. If no format is
|
| specified, the format to use is determined from the filename
|
| extension, if possible.
|
|
|
| Keyword options can be used to provide additional instructions
|
| to the writer. If a writer doesn't recognise an option, it is
|
| silently ignored. The available options are described in the
|
| :doc:`image format documentation
|
| <../handbook/image-file-formats>` for each writer.
|
|
|
| You can use a file object instead of a filename. In this case,
|
| you must always specify the format. The file object must
|
| implement the ``seek``, ``tell``, and ``write``
|
| methods, and be opened in binary mode.
|
|
|
| :param fp: A filename (string), os.PathLike object or file object.
|
| :param format: Optional format override. If omitted, the
|
| format to use is determined from the filename extension.
|
| If a file object was used instead of a filename, this
|
| parameter should always be used.
|
| :param params: Extra parameters to the image writer. These can also be
|
| set on the image itself through ``encoderinfo``. This is useful when
|
| saving multiple images::
|
|
|
| # Saving XMP data to a single image
|
| from PIL import Image
|
| red = Image.new("RGB", (1, 1), "#f00")
|
| red.save("out.mpo", xmp=b"test")
|
|
|
| # Saving XMP data to the second frame of an image
|
| from PIL import Image
|
| black = Image.new("RGB", (1, 1))
|
| red = Image.new("RGB", (1, 1), "#f00")
|
| red.encoderinfo = {"xmp": b"test"}
|
| black.save("out.mpo", save_all=True, append_images=[red])
|
| :returns: None
|
| :exception ValueError: If the output format could not be determined
|
| from the file name. Use the format option to solve this.
|
| :exception OSError: If the file could not be written. The file
|
| may have been created, and may contain partial data.
|
| """
|
|
|
| filename: str | bytes = ""
|
| open_fp = False
|
| if is_path(fp):
|
| filename = os.fspath(fp)
|
| open_fp = True
|
| elif fp == sys.stdout:
|
| try:
|
| fp = sys.stdout.buffer
|
| except AttributeError:
|
| pass
|
| if not filename and hasattr(fp, "name") and is_path(fp.name):
|
|
|
| filename = os.fspath(fp.name)
|
|
|
| preinit()
|
|
|
| filename_ext = os.path.splitext(filename)[1].lower()
|
| ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext
|
|
|
| if not format:
|
| if ext not in EXTENSION:
|
| init()
|
| try:
|
| format = EXTENSION[ext]
|
| except KeyError as e:
|
| msg = f"unknown file extension: {ext}"
|
| raise ValueError(msg) from e
|
|
|
| from . import ImageFile
|
|
|
|
|
| if isinstance(self, ImageFile.ImageFile) and os.path.abspath(
|
| filename
|
| ) == os.path.abspath(self.filename):
|
| self._ensure_mutable()
|
| else:
|
| self.load()
|
|
|
| save_all = params.pop("save_all", None)
|
| self._default_encoderinfo = params
|
| encoderinfo = getattr(self, "encoderinfo", {})
|
| self._attach_default_encoderinfo(self)
|
| self.encoderconfig: tuple[Any, ...] = ()
|
|
|
| if format.upper() not in SAVE:
|
| init()
|
| if save_all or (
|
| save_all is None
|
| and params.get("append_images")
|
| and format.upper() in SAVE_ALL
|
| ):
|
| save_handler = SAVE_ALL[format.upper()]
|
| else:
|
| save_handler = SAVE[format.upper()]
|
|
|
| created = False
|
| if open_fp:
|
| created = not os.path.exists(filename)
|
| if params.get("append", False):
|
|
|
|
|
| fp = builtins.open(filename, "r+b")
|
| else:
|
| fp = builtins.open(filename, "w+b")
|
| else:
|
| fp = cast(IO[bytes], fp)
|
|
|
| try:
|
| save_handler(self, fp, filename)
|
| except Exception:
|
| if open_fp:
|
| fp.close()
|
| if created:
|
| try:
|
| os.remove(filename)
|
| except PermissionError:
|
| pass
|
| raise
|
| finally:
|
| self.encoderinfo = encoderinfo
|
| if open_fp:
|
| fp.close()
|
|
|
| def _attach_default_encoderinfo(self, im: Image) -> dict[str, Any]:
|
| encoderinfo = getattr(self, "encoderinfo", {})
|
| self.encoderinfo = {**im._default_encoderinfo, **encoderinfo}
|
| return encoderinfo
|
|
|
| def seek(self, frame: int) -> None:
|
| """
|
| Seeks to the given frame in this sequence file. If you seek
|
| beyond the end of the sequence, the method raises an
|
| ``EOFError`` exception. When a sequence file is opened, the
|
| library automatically seeks to frame 0.
|
|
|
| See :py:meth:`~PIL.Image.Image.tell`.
|
|
|
| If defined, :attr:`~PIL.Image.Image.n_frames` refers to the
|
| number of available frames.
|
|
|
| :param frame: Frame number, starting at 0.
|
| :exception EOFError: If the call attempts to seek beyond the end
|
| of the sequence.
|
| """
|
|
|
|
|
| if frame != 0:
|
| msg = "no more images in file"
|
| raise EOFError(msg)
|
|
|
| def show(self, title: str | None = None) -> None:
|
| """
|
| Displays this image. This method is mainly intended for debugging purposes.
|
|
|
| This method calls :py:func:`PIL.ImageShow.show` internally. You can use
|
| :py:func:`PIL.ImageShow.register` to override its default behaviour.
|
|
|
| The image is first saved to a temporary file. By default, it will be in
|
| PNG format.
|
|
|
| On Unix, the image is then opened using the **xdg-open**, **display**,
|
| **gm**, **eog** or **xv** utility, depending on which one can be found.
|
|
|
| On macOS, the image is opened with the native Preview application.
|
|
|
| On Windows, the image is opened with the standard PNG display utility.
|
|
|
| :param title: Optional title to use for the image window, where possible.
|
| """
|
|
|
| from . import ImageShow
|
|
|
| ImageShow.show(self, title)
|
|
|
| def split(self) -> tuple[Image, ...]:
|
| """
|
| Split this image into individual bands. This method returns a
|
| tuple of individual image bands from an image. For example,
|
| splitting an "RGB" image creates three new images each
|
| containing a copy of one of the original bands (red, green,
|
| blue).
|
|
|
| If you need only one band, :py:meth:`~PIL.Image.Image.getchannel`
|
| method can be more convenient and faster.
|
|
|
| :returns: A tuple containing bands.
|
| """
|
|
|
| self.load()
|
| if self.im.bands == 1:
|
| return (self.copy(),)
|
| return tuple(map(self._new, self.im.split()))
|
|
|
| def getchannel(self, channel: int | str) -> Image:
|
| """
|
| Returns an image containing a single channel of the source image.
|
|
|
| :param channel: What channel to return. Could be index
|
| (0 for "R" channel of "RGB") or channel name
|
| ("A" for alpha channel of "RGBA").
|
| :returns: An image in "L" mode.
|
|
|
| .. versionadded:: 4.3.0
|
| """
|
| self.load()
|
|
|
| if isinstance(channel, str):
|
| try:
|
| channel = self.getbands().index(channel)
|
| except ValueError as e:
|
| msg = f'The image has no channel "{channel}"'
|
| raise ValueError(msg) from e
|
|
|
| return self._new(self.im.getband(channel))
|
|
|
| def tell(self) -> int:
|
| """
|
| Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`.
|
|
|
| If defined, :attr:`~PIL.Image.Image.n_frames` refers to the
|
| number of available frames.
|
|
|
| :returns: Frame number, starting with 0.
|
| """
|
| return 0
|
|
|
| def thumbnail(
|
| self,
|
| size: tuple[float, float],
|
| resample: Resampling = Resampling.BICUBIC,
|
| reducing_gap: float | None = 2.0,
|
| ) -> None:
|
| """
|
| Make this image into a thumbnail. This method modifies the
|
| image to contain a thumbnail version of itself, no larger than
|
| the given size. This method calculates an appropriate thumbnail
|
| size to preserve the aspect of the image, calls the
|
| :py:meth:`~PIL.Image.Image.draft` method to configure the file reader
|
| (where applicable), and finally resizes the image.
|
|
|
| Note that this function modifies the :py:class:`~PIL.Image.Image`
|
| object in place. If you need to use the full resolution image as well,
|
| apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
|
| image.
|
|
|
| :param size: The requested size in pixels, as a 2-tuple:
|
| (width, height).
|
| :param resample: Optional resampling filter. This can be one
|
| of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`,
|
| :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
|
| :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
|
| If omitted, it defaults to :py:data:`Resampling.BICUBIC`.
|
| (was :py:data:`Resampling.NEAREST` prior to version 2.5.0).
|
| See: :ref:`concept-filters`.
|
| :param reducing_gap: Apply optimization by resizing the image
|
| in two steps. First, reducing the image by integer times
|
| using :py:meth:`~PIL.Image.Image.reduce` or
|
| :py:meth:`~PIL.Image.Image.draft` for JPEG images.
|
| Second, resizing using regular resampling. The last step
|
| changes size no less than by ``reducing_gap`` times.
|
| ``reducing_gap`` may be None (no first step is performed)
|
| or should be greater than 1.0. The bigger ``reducing_gap``,
|
| the closer the result to the fair resampling.
|
| The smaller ``reducing_gap``, the faster resizing.
|
| With ``reducing_gap`` greater or equal to 3.0, the result is
|
| indistinguishable from fair resampling in most cases.
|
| The default value is 2.0 (very close to fair resampling
|
| while still being faster in many cases).
|
| :returns: None
|
| """
|
|
|
| provided_size = tuple(map(math.floor, size))
|
|
|
| def preserve_aspect_ratio() -> tuple[int, int] | None:
|
| def round_aspect(number: float, key: Callable[[int], float]) -> int:
|
| return max(min(math.floor(number), math.ceil(number), key=key), 1)
|
|
|
| x, y = provided_size
|
| if x >= self.width and y >= self.height:
|
| return None
|
|
|
| aspect = self.width / self.height
|
| if x / y >= aspect:
|
| x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y))
|
| else:
|
| y = round_aspect(
|
| x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n)
|
| )
|
| return x, y
|
|
|
| preserved_size = preserve_aspect_ratio()
|
| if preserved_size is None:
|
| return
|
| final_size = preserved_size
|
|
|
| box = None
|
| if reducing_gap is not None:
|
| res = self.draft(
|
| None, (int(size[0] * reducing_gap), int(size[1] * reducing_gap))
|
| )
|
| if res is not None:
|
| box = res[1]
|
|
|
| if self.size != final_size:
|
| im = self.resize(final_size, resample, box=box, reducing_gap=reducing_gap)
|
|
|
| self.im = im.im
|
| self._size = final_size
|
| self._mode = self.im.mode
|
|
|
| self.readonly = 0
|
|
|
|
|
|
|
| def transform(
|
| self,
|
| size: tuple[int, int],
|
| method: Transform | ImageTransformHandler | SupportsGetData,
|
| data: Sequence[Any] | None = None,
|
| resample: int = Resampling.NEAREST,
|
| fill: int = 1,
|
| fillcolor: float | tuple[float, ...] | str | None = None,
|
| ) -> Image:
|
| """
|
| Transforms this image. This method creates a new image with the
|
| given size, and the same mode as the original, and copies data
|
| to the new image using the given transform.
|
|
|
| :param size: The output size in pixels, as a 2-tuple:
|
| (width, height).
|
| :param method: The transformation method. This is one of
|
| :py:data:`Transform.EXTENT` (cut out a rectangular subregion),
|
| :py:data:`Transform.AFFINE` (affine transform),
|
| :py:data:`Transform.PERSPECTIVE` (perspective transform),
|
| :py:data:`Transform.QUAD` (map a quadrilateral to a rectangle), or
|
| :py:data:`Transform.MESH` (map a number of source quadrilaterals
|
| in one operation).
|
|
|
| It may also be an :py:class:`~PIL.Image.ImageTransformHandler`
|
| object::
|
|
|
| class Example(Image.ImageTransformHandler):
|
| def transform(self, size, data, resample, fill=1):
|
| # Return result
|
|
|
| Implementations of :py:class:`~PIL.Image.ImageTransformHandler`
|
| for some of the :py:class:`Transform` methods are provided
|
| in :py:mod:`~PIL.ImageTransform`.
|
|
|
| It may also be an object with a ``method.getdata`` method
|
| that returns a tuple supplying new ``method`` and ``data`` values::
|
|
|
| class Example:
|
| def getdata(self):
|
| method = Image.Transform.EXTENT
|
| data = (0, 0, 100, 100)
|
| return method, data
|
| :param data: Extra data to the transformation method.
|
| :param resample: Optional resampling filter. It can be one of
|
| :py:data:`Resampling.NEAREST` (use nearest neighbour),
|
| :py:data:`Resampling.BILINEAR` (linear interpolation in a 2x2
|
| environment), or :py:data:`Resampling.BICUBIC` (cubic spline
|
| interpolation in a 4x4 environment). If omitted, or if the image
|
| has mode "1" or "P", it is set to :py:data:`Resampling.NEAREST`.
|
| See: :ref:`concept-filters`.
|
| :param fill: If ``method`` is an
|
| :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of
|
| the arguments passed to it. Otherwise, it is unused.
|
| :param fillcolor: Optional fill color for the area outside the
|
| transform in the output image.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| if self.mode in ("LA", "RGBA") and resample != Resampling.NEAREST:
|
| return (
|
| self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode])
|
| .transform(size, method, data, resample, fill, fillcolor)
|
| .convert(self.mode)
|
| )
|
|
|
| if isinstance(method, ImageTransformHandler):
|
| return method.transform(size, self, resample=resample, fill=fill)
|
|
|
| if hasattr(method, "getdata"):
|
|
|
| method, data = method.getdata()
|
|
|
| if data is None:
|
| msg = "missing method data"
|
| raise ValueError(msg)
|
|
|
| im = new(self.mode, size, fillcolor)
|
| if self.mode == "P" and self.palette:
|
| im.palette = self.palette.copy()
|
| im.info = self.info.copy()
|
| if method == Transform.MESH:
|
|
|
| for box, quad in data:
|
| im.__transformer(
|
| box, self, Transform.QUAD, quad, resample, fillcolor is None
|
| )
|
| else:
|
| im.__transformer(
|
| (0, 0) + size, self, method, data, resample, fillcolor is None
|
| )
|
|
|
| return im
|
|
|
| def __transformer(
|
| self,
|
| box: tuple[int, int, int, int],
|
| image: Image,
|
| method: Transform,
|
| data: Sequence[float],
|
| resample: int = Resampling.NEAREST,
|
| fill: bool = True,
|
| ) -> None:
|
| w = box[2] - box[0]
|
| h = box[3] - box[1]
|
|
|
| if method == Transform.AFFINE:
|
| data = data[:6]
|
|
|
| elif method == Transform.EXTENT:
|
|
|
| x0, y0, x1, y1 = data
|
| xs = (x1 - x0) / w
|
| ys = (y1 - y0) / h
|
| method = Transform.AFFINE
|
| data = (xs, 0, x0, 0, ys, y0)
|
|
|
| elif method == Transform.PERSPECTIVE:
|
| data = data[:8]
|
|
|
| elif method == Transform.QUAD:
|
|
|
|
|
| nw = data[:2]
|
| sw = data[2:4]
|
| se = data[4:6]
|
| ne = data[6:8]
|
| x0, y0 = nw
|
| As = 1.0 / w
|
| At = 1.0 / h
|
| data = (
|
| x0,
|
| (ne[0] - x0) * As,
|
| (sw[0] - x0) * At,
|
| (se[0] - sw[0] - ne[0] + x0) * As * At,
|
| y0,
|
| (ne[1] - y0) * As,
|
| (sw[1] - y0) * At,
|
| (se[1] - sw[1] - ne[1] + y0) * As * At,
|
| )
|
|
|
| else:
|
| msg = "unknown transformation method"
|
| raise ValueError(msg)
|
|
|
| if resample not in (
|
| Resampling.NEAREST,
|
| Resampling.BILINEAR,
|
| Resampling.BICUBIC,
|
| ):
|
| if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
|
| unusable: dict[int, str] = {
|
| Resampling.BOX: "Image.Resampling.BOX",
|
| Resampling.HAMMING: "Image.Resampling.HAMMING",
|
| Resampling.LANCZOS: "Image.Resampling.LANCZOS",
|
| }
|
| msg = unusable[resample] + f" ({resample}) cannot be used."
|
| else:
|
| msg = f"Unknown resampling filter ({resample})."
|
|
|
| filters = [
|
| f"{filter[1]} ({filter[0]})"
|
| for filter in (
|
| (Resampling.NEAREST, "Image.Resampling.NEAREST"),
|
| (Resampling.BILINEAR, "Image.Resampling.BILINEAR"),
|
| (Resampling.BICUBIC, "Image.Resampling.BICUBIC"),
|
| )
|
| ]
|
| msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}"
|
| raise ValueError(msg)
|
|
|
| image.load()
|
|
|
| self.load()
|
|
|
| if image.mode in ("1", "P"):
|
| resample = Resampling.NEAREST
|
|
|
| self.im.transform(box, image.im, method, data, resample, fill)
|
|
|
| def transpose(self, method: Transpose) -> Image:
|
| """
|
| Transpose image (flip or rotate in 90 degree steps)
|
|
|
| :param method: One of :py:data:`Transpose.FLIP_LEFT_RIGHT`,
|
| :py:data:`Transpose.FLIP_TOP_BOTTOM`, :py:data:`Transpose.ROTATE_90`,
|
| :py:data:`Transpose.ROTATE_180`, :py:data:`Transpose.ROTATE_270`,
|
| :py:data:`Transpose.TRANSPOSE` or :py:data:`Transpose.TRANSVERSE`.
|
| :returns: Returns a flipped or rotated copy of this image.
|
| """
|
|
|
| self.load()
|
| return self._new(self.im.transpose(method))
|
|
|
| def effect_spread(self, distance: int) -> Image:
|
| """
|
| Randomly spread pixels in an image.
|
|
|
| :param distance: Distance to spread pixels.
|
| """
|
| self.load()
|
| return self._new(self.im.effect_spread(distance))
|
|
|
| def toqimage(self) -> ImageQt.ImageQt:
|
| """Returns a QImage copy of this image"""
|
| from . import ImageQt
|
|
|
| if not ImageQt.qt_is_installed:
|
| msg = "Qt bindings are not installed"
|
| raise ImportError(msg)
|
| return ImageQt.toqimage(self)
|
|
|
| def toqpixmap(self) -> ImageQt.QPixmap:
|
| """Returns a QPixmap copy of this image"""
|
| from . import ImageQt
|
|
|
| if not ImageQt.qt_is_installed:
|
| msg = "Qt bindings are not installed"
|
| raise ImportError(msg)
|
| return ImageQt.toqpixmap(self)
|
|
|
|
|
|
|
|
|
|
|
|
|
| class ImagePointHandler(abc.ABC):
|
| """
|
| Used as a mixin by point transforms
|
| (for use with :py:meth:`~PIL.Image.Image.point`)
|
| """
|
|
|
| @abc.abstractmethod
|
| def point(self, im: Image) -> Image:
|
| pass
|
|
|
|
|
| class ImageTransformHandler(abc.ABC):
|
| """
|
| Used as a mixin by geometry transforms
|
| (for use with :py:meth:`~PIL.Image.Image.transform`)
|
| """
|
|
|
| @abc.abstractmethod
|
| def transform(
|
| self,
|
| size: tuple[int, int],
|
| image: Image,
|
| **options: Any,
|
| ) -> Image:
|
| pass
|
|
|
|
|
|
|
|
|
|
|
|
|
| def _check_size(size: Any) -> None:
|
| """
|
| Common check to enforce type and sanity check on size tuples
|
|
|
| :param size: Should be a 2 tuple of (width, height)
|
| :returns: None, or raises a ValueError
|
| """
|
|
|
| if not isinstance(size, (list, tuple)):
|
| msg = "Size must be a list or tuple"
|
| raise ValueError(msg)
|
| if len(size) != 2:
|
| msg = "Size must be a sequence of length 2"
|
| raise ValueError(msg)
|
| if size[0] < 0 or size[1] < 0:
|
| msg = "Width and height must be >= 0"
|
| raise ValueError(msg)
|
|
|
|
|
| def new(
|
| mode: str,
|
| size: tuple[int, int] | list[int],
|
| color: float | tuple[float, ...] | str | None = 0,
|
| ) -> Image:
|
| """
|
| Creates a new image with the given mode and size.
|
|
|
| :param mode: The mode to use for the new image. See:
|
| :ref:`concept-modes`.
|
| :param size: A 2-tuple, containing (width, height) in pixels.
|
| :param color: What color to use for the image. Default is black. If given,
|
| this should be a single integer or floating point value for single-band
|
| modes, and a tuple for multi-band modes (one value per band). When
|
| creating RGB or HSV images, you can also use color strings as supported
|
| by the ImageColor module. See :ref:`colors` for more information. If the
|
| color is None, the image is not initialised.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| _check_size(size)
|
|
|
| if color is None:
|
|
|
| return Image()._new(core.new(mode, size))
|
|
|
| if isinstance(color, str):
|
|
|
|
|
| from . import ImageColor
|
|
|
| color = ImageColor.getcolor(color, mode)
|
|
|
| im = Image()
|
| if (
|
| mode == "P"
|
| and isinstance(color, (list, tuple))
|
| and all(isinstance(i, int) for i in color)
|
| ):
|
| color_ints: tuple[int, ...] = cast(tuple[int, ...], tuple(color))
|
| if len(color_ints) == 3 or len(color_ints) == 4:
|
|
|
| from . import ImagePalette
|
|
|
| im.palette = ImagePalette.ImagePalette()
|
| color = im.palette.getcolor(color_ints)
|
| return im._new(core.fill(mode, size, color))
|
|
|
|
|
| def frombytes(
|
| mode: str,
|
| size: tuple[int, int],
|
| data: bytes | bytearray | SupportsArrayInterface,
|
| decoder_name: str = "raw",
|
| *args: Any,
|
| ) -> Image:
|
| """
|
| Creates a copy of an image memory from pixel data in a buffer.
|
|
|
| In its simplest form, this function takes three arguments
|
| (mode, size, and unpacked pixel data).
|
|
|
| You can also use any pixel decoder supported by PIL. For more
|
| information on available decoders, see the section
|
| :ref:`Writing Your Own File Codec <file-codecs>`.
|
|
|
| Note that this function decodes pixel data only, not entire images.
|
| If you have an entire image in a string, wrap it in a
|
| :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load
|
| it.
|
|
|
| :param mode: The image mode. See: :ref:`concept-modes`.
|
| :param size: The image size.
|
| :param data: A byte buffer containing raw data for the given mode.
|
| :param decoder_name: What decoder to use.
|
| :param args: Additional parameters for the given decoder.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| _check_size(size)
|
|
|
| im = new(mode, size)
|
| if im.width != 0 and im.height != 0:
|
| decoder_args: Any = args
|
| if len(decoder_args) == 1 and isinstance(decoder_args[0], tuple):
|
|
|
| decoder_args = decoder_args[0]
|
|
|
| if decoder_name == "raw" and decoder_args == ():
|
| decoder_args = mode
|
|
|
| im.frombytes(data, decoder_name, decoder_args)
|
| return im
|
|
|
|
|
| def frombuffer(
|
| mode: str,
|
| size: tuple[int, int],
|
| data: bytes | SupportsArrayInterface,
|
| decoder_name: str = "raw",
|
| *args: Any,
|
| ) -> Image:
|
| """
|
| Creates an image memory referencing pixel data in a byte buffer.
|
|
|
| This function is similar to :py:func:`~PIL.Image.frombytes`, but uses data
|
| in the byte buffer, where possible. This means that changes to the
|
| original buffer object are reflected in this image). Not all modes can
|
| share memory; supported modes include "L", "RGBX", "RGBA", and "CMYK".
|
|
|
| Note that this function decodes pixel data only, not entire images.
|
| If you have an entire image file in a string, wrap it in a
|
| :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it.
|
|
|
| The default parameters used for the "raw" decoder differs from that used for
|
| :py:func:`~PIL.Image.frombytes`. This is a bug, and will probably be fixed in a
|
| future release. The current release issues a warning if you do this; to disable
|
| the warning, you should provide the full set of parameters. See below for details.
|
|
|
| :param mode: The image mode. See: :ref:`concept-modes`.
|
| :param size: The image size.
|
| :param data: A bytes or other buffer object containing raw
|
| data for the given mode.
|
| :param decoder_name: What decoder to use.
|
| :param args: Additional parameters for the given decoder. For the
|
| default encoder ("raw"), it's recommended that you provide the
|
| full set of parameters::
|
|
|
| frombuffer(mode, size, data, "raw", mode, 0, 1)
|
|
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
|
|
| .. versionadded:: 1.1.4
|
| """
|
|
|
| _check_size(size)
|
|
|
|
|
| if len(args) == 1 and isinstance(args[0], tuple):
|
| args = args[0]
|
|
|
| if decoder_name == "raw":
|
| if args == ():
|
| args = mode, 0, 1
|
| if args[0] in _MAPMODES:
|
| im = new(mode, (0, 0))
|
| im = im._new(core.map_buffer(data, size, decoder_name, 0, args))
|
| if mode == "P":
|
| from . import ImagePalette
|
|
|
| im.palette = ImagePalette.ImagePalette("RGB", im.im.getpalette("RGB"))
|
| im.readonly = 1
|
| return im
|
|
|
| return frombytes(mode, size, data, decoder_name, args)
|
|
|
|
|
| class SupportsArrayInterface(Protocol):
|
| """
|
| An object that has an ``__array_interface__`` dictionary.
|
| """
|
|
|
| @property
|
| def __array_interface__(self) -> dict[str, Any]:
|
| raise NotImplementedError()
|
|
|
|
|
| class SupportsArrowArrayInterface(Protocol):
|
| """
|
| An object that has an ``__arrow_c_array__`` method corresponding to the arrow c
|
| data interface.
|
| """
|
|
|
| def __arrow_c_array__(
|
| self, requested_schema: "PyCapsule" = None
|
| ) -> tuple["PyCapsule", "PyCapsule"]:
|
| raise NotImplementedError()
|
|
|
|
|
| def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
|
| """
|
| Creates an image memory from an object exporting the array interface
|
| (using the buffer protocol)::
|
|
|
| from PIL import Image
|
| import numpy as np
|
| a = np.zeros((5, 5))
|
| im = Image.fromarray(a)
|
|
|
| If ``obj`` is not contiguous, then the ``tobytes`` method is called
|
| and :py:func:`~PIL.Image.frombuffer` is used.
|
|
|
| In the case of NumPy, be aware that Pillow modes do not always correspond
|
| to NumPy dtypes. Pillow modes only offer 1-bit pixels, 8-bit pixels,
|
| 32-bit signed integer pixels, and 32-bit floating point pixels.
|
|
|
| Pillow images can also be converted to arrays::
|
|
|
| from PIL import Image
|
| import numpy as np
|
| im = Image.open("hopper.jpg")
|
| a = np.asarray(im)
|
|
|
| When converting Pillow images to arrays however, only pixel values are
|
| transferred. This means that P and PA mode images will lose their palette.
|
|
|
| :param obj: Object with array interface
|
| :param mode: Optional mode to use when reading ``obj``. Since pixel values do not
|
| contain information about palettes or color spaces, this can be used to place
|
| grayscale L mode data within a P mode image, or read RGB data as YCbCr for
|
| example.
|
|
|
| See: :ref:`concept-modes` for general information about modes.
|
| :returns: An image object.
|
|
|
| .. versionadded:: 1.1.6
|
| """
|
| arr = obj.__array_interface__
|
| shape = arr["shape"]
|
| ndim = len(shape)
|
| strides = arr.get("strides", None)
|
| try:
|
| typekey = (1, 1) + shape[2:], arr["typestr"]
|
| except KeyError as e:
|
| if mode is not None:
|
| typekey = None
|
| color_modes: list[str] = []
|
| else:
|
| msg = "Cannot handle this data type"
|
| raise TypeError(msg) from e
|
| if typekey is not None:
|
| try:
|
| typemode, rawmode, color_modes = _fromarray_typemap[typekey]
|
| except KeyError as e:
|
| typekey_shape, typestr = typekey
|
| msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
|
| raise TypeError(msg) from e
|
| if mode is not None:
|
| if mode != typemode and mode not in color_modes:
|
| deprecate("'mode' parameter for changing data types", 13)
|
| rawmode = mode
|
| else:
|
| mode = typemode
|
| if mode in ["1", "L", "I", "P", "F"]:
|
| ndmax = 2
|
| elif mode == "RGB":
|
| ndmax = 3
|
| else:
|
| ndmax = 4
|
| if ndim > ndmax:
|
| msg = f"Too many dimensions: {ndim} > {ndmax}."
|
| raise ValueError(msg)
|
|
|
| size = 1 if ndim == 1 else shape[1], shape[0]
|
| if strides is not None:
|
| if hasattr(obj, "tobytes"):
|
| obj = obj.tobytes()
|
| elif hasattr(obj, "tostring"):
|
| obj = obj.tostring()
|
| else:
|
| msg = "'strides' requires either tobytes() or tostring()"
|
| raise ValueError(msg)
|
|
|
| return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
|
|
|
|
|
| def fromarrow(
|
| obj: SupportsArrowArrayInterface, mode: str, size: tuple[int, int]
|
| ) -> Image:
|
| """Creates an image with zero-copy shared memory from an object exporting
|
| the arrow_c_array interface protocol::
|
|
|
| from PIL import Image
|
| import pyarrow as pa
|
| arr = pa.array([0]*(5*5*4), type=pa.uint8())
|
| im = Image.fromarrow(arr, 'RGBA', (5, 5))
|
|
|
| If the data representation of the ``obj`` is not compatible with
|
| Pillow internal storage, a ValueError is raised.
|
|
|
| Pillow images can also be converted to Arrow objects::
|
|
|
| from PIL import Image
|
| import pyarrow as pa
|
| im = Image.open('hopper.jpg')
|
| arr = pa.array(im)
|
|
|
| As with array support, when converting Pillow images to arrays,
|
| only pixel values are transferred. This means that P and PA mode
|
| images will lose their palette.
|
|
|
| :param obj: Object with an arrow_c_array interface
|
| :param mode: Image mode.
|
| :param size: Image size. This must match the storage of the arrow object.
|
| :returns: An Image object
|
|
|
| Note that according to the Arrow spec, both the producer and the
|
| consumer should consider the exported array to be immutable, as
|
| unsynchronized updates will potentially cause inconsistent data.
|
|
|
| See: :ref:`arrow-support` for more detailed information
|
|
|
| .. versionadded:: 11.2.1
|
|
|
| """
|
| if not hasattr(obj, "__arrow_c_array__"):
|
| msg = "arrow_c_array interface not found"
|
| raise ValueError(msg)
|
|
|
| (schema_capsule, array_capsule) = obj.__arrow_c_array__()
|
| _im = core.new_arrow(mode, size, schema_capsule, array_capsule)
|
| if _im:
|
| return Image()._new(_im)
|
|
|
| msg = "new_arrow returned None without an exception"
|
| raise ValueError(msg)
|
|
|
|
|
| def fromqimage(im: ImageQt.QImage) -> ImageFile.ImageFile:
|
| """Creates an image instance from a QImage image"""
|
| from . import ImageQt
|
|
|
| if not ImageQt.qt_is_installed:
|
| msg = "Qt bindings are not installed"
|
| raise ImportError(msg)
|
| return ImageQt.fromqimage(im)
|
|
|
|
|
| def fromqpixmap(im: ImageQt.QPixmap) -> ImageFile.ImageFile:
|
| """Creates an image instance from a QPixmap image"""
|
| from . import ImageQt
|
|
|
| if not ImageQt.qt_is_installed:
|
| msg = "Qt bindings are not installed"
|
| raise ImportError(msg)
|
| return ImageQt.fromqpixmap(im)
|
|
|
|
|
| _fromarray_typemap = {
|
|
|
|
|
| ((1, 1), "|b1"): ("1", "1;8", []),
|
| ((1, 1), "|u1"): ("L", "L", ["P"]),
|
| ((1, 1), "|i1"): ("I", "I;8", []),
|
| ((1, 1), "<u2"): ("I", "I;16", []),
|
| ((1, 1), ">u2"): ("I", "I;16B", []),
|
| ((1, 1), "<i2"): ("I", "I;16S", []),
|
| ((1, 1), ">i2"): ("I", "I;16BS", []),
|
| ((1, 1), "<u4"): ("I", "I;32", []),
|
| ((1, 1), ">u4"): ("I", "I;32B", []),
|
| ((1, 1), "<i4"): ("I", "I;32S", []),
|
| ((1, 1), ">i4"): ("I", "I;32BS", []),
|
| ((1, 1), "<f4"): ("F", "F;32F", []),
|
| ((1, 1), ">f4"): ("F", "F;32BF", []),
|
| ((1, 1), "<f8"): ("F", "F;64F", []),
|
| ((1, 1), ">f8"): ("F", "F;64BF", []),
|
| ((1, 1, 2), "|u1"): ("LA", "LA", ["La", "PA"]),
|
| ((1, 1, 3), "|u1"): ("RGB", "RGB", ["YCbCr", "LAB", "HSV"]),
|
| ((1, 1, 4), "|u1"): ("RGBA", "RGBA", ["RGBa", "RGBX", "CMYK"]),
|
|
|
| ((1, 1), f"{_ENDIAN}i4"): ("I", "I", []),
|
| ((1, 1), f"{_ENDIAN}f4"): ("F", "F", []),
|
| }
|
|
|
|
|
| def _decompression_bomb_check(size: tuple[int, int]) -> None:
|
| if MAX_IMAGE_PIXELS is None:
|
| return
|
|
|
| pixels = max(1, size[0]) * max(1, size[1])
|
|
|
| if pixels > 2 * MAX_IMAGE_PIXELS:
|
| msg = (
|
| f"Image size ({pixels} pixels) exceeds limit of {2 * MAX_IMAGE_PIXELS} "
|
| "pixels, could be decompression bomb DOS attack."
|
| )
|
| raise DecompressionBombError(msg)
|
|
|
| if pixels > MAX_IMAGE_PIXELS:
|
| warnings.warn(
|
| f"Image size ({pixels} pixels) exceeds limit of {MAX_IMAGE_PIXELS} pixels, "
|
| "could be decompression bomb DOS attack.",
|
| DecompressionBombWarning,
|
| )
|
|
|
|
|
| def open(
|
| fp: StrOrBytesPath | IO[bytes],
|
| mode: Literal["r"] = "r",
|
| formats: list[str] | tuple[str, ...] | None = None,
|
| ) -> ImageFile.ImageFile:
|
| """
|
| Opens and identifies the given image file.
|
|
|
| This is a lazy operation; this function identifies the file, but
|
| the file remains open and the actual image data is not read from
|
| the file until you try to process the data (or call the
|
| :py:meth:`~PIL.Image.Image.load` method). See
|
| :py:func:`~PIL.Image.new`. See :ref:`file-handling`.
|
|
|
| :param fp: A filename (string), os.PathLike object or a file object.
|
| The file object must implement ``file.read``,
|
| ``file.seek``, and ``file.tell`` methods,
|
| and be opened in binary mode. The file object will also seek to zero
|
| before reading.
|
| :param mode: The mode. If given, this argument must be "r".
|
| :param formats: A list or tuple of formats to attempt to load the file in.
|
| This can be used to restrict the set of formats checked.
|
| Pass ``None`` to try all supported formats. You can print the set of
|
| available formats by running ``python3 -m PIL`` or using
|
| the :py:func:`PIL.features.pilinfo` function.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| :exception FileNotFoundError: If the file cannot be found.
|
| :exception PIL.UnidentifiedImageError: If the image cannot be opened and
|
| identified.
|
| :exception ValueError: If the ``mode`` is not "r", or if a ``StringIO``
|
| instance is used for ``fp``.
|
| :exception TypeError: If ``formats`` is not ``None``, a list or a tuple.
|
| """
|
|
|
| if mode != "r":
|
| msg = f"bad mode {repr(mode)}"
|
| raise ValueError(msg)
|
| elif isinstance(fp, io.StringIO):
|
| msg = (
|
| "StringIO cannot be used to open an image. "
|
| "Binary data must be used instead."
|
| )
|
| raise ValueError(msg)
|
|
|
| if formats is None:
|
| formats = ID
|
| elif not isinstance(formats, (list, tuple)):
|
| msg = "formats must be a list or tuple"
|
| raise TypeError(msg)
|
|
|
| exclusive_fp = False
|
| filename: str | bytes = ""
|
| if is_path(fp):
|
| filename = os.fspath(fp)
|
| fp = builtins.open(filename, "rb")
|
| exclusive_fp = True
|
| else:
|
| fp = cast(IO[bytes], fp)
|
|
|
| try:
|
| fp.seek(0)
|
| except (AttributeError, io.UnsupportedOperation):
|
| fp = io.BytesIO(fp.read())
|
| exclusive_fp = True
|
|
|
| prefix = fp.read(16)
|
|
|
| preinit()
|
|
|
| warning_messages: list[str] = []
|
|
|
| def _open_core(
|
| fp: IO[bytes],
|
| filename: str | bytes,
|
| prefix: bytes,
|
| formats: list[str] | tuple[str, ...],
|
| ) -> ImageFile.ImageFile | None:
|
| for i in formats:
|
| i = i.upper()
|
| if i not in OPEN:
|
| init()
|
| try:
|
| factory, accept = OPEN[i]
|
| result = not accept or accept(prefix)
|
| if isinstance(result, str):
|
| warning_messages.append(result)
|
| elif result:
|
| fp.seek(0)
|
| im = factory(fp, filename)
|
| _decompression_bomb_check(im.size)
|
| return im
|
| except (SyntaxError, IndexError, TypeError, struct.error) as e:
|
| if WARN_POSSIBLE_FORMATS:
|
| warning_messages.append(i + " opening failed. " + str(e))
|
| except BaseException:
|
| if exclusive_fp:
|
| fp.close()
|
| raise
|
| return None
|
|
|
| im = _open_core(fp, filename, prefix, formats)
|
|
|
| if im is None and formats is ID:
|
| checked_formats = ID.copy()
|
| if init():
|
| im = _open_core(
|
| fp,
|
| filename,
|
| prefix,
|
| tuple(format for format in formats if format not in checked_formats),
|
| )
|
|
|
| if im:
|
| im._exclusive_fp = exclusive_fp
|
| return im
|
|
|
| if exclusive_fp:
|
| fp.close()
|
| for message in warning_messages:
|
| warnings.warn(message)
|
| msg = "cannot identify image file %r" % (filename if filename else fp)
|
| raise UnidentifiedImageError(msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
| def alpha_composite(im1: Image, im2: Image) -> Image:
|
| """
|
| Alpha composite im2 over im1.
|
|
|
| :param im1: The first image. Must have mode RGBA or LA.
|
| :param im2: The second image. Must have the same mode and size as the first image.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| im1.load()
|
| im2.load()
|
| return im1._new(core.alpha_composite(im1.im, im2.im))
|
|
|
|
|
| def blend(im1: Image, im2: Image, alpha: float) -> Image:
|
| """
|
| Creates a new image by interpolating between two input images, using
|
| a constant alpha::
|
|
|
| out = image1 * (1.0 - alpha) + image2 * alpha
|
|
|
| :param im1: The first image.
|
| :param im2: The second image. Must have the same mode and size as
|
| the first image.
|
| :param alpha: The interpolation alpha factor. If alpha is 0.0, a
|
| copy of the first image is returned. If alpha is 1.0, a copy of
|
| the second image is returned. There are no restrictions on the
|
| alpha value. If necessary, the result is clipped to fit into
|
| the allowed output range.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| im1.load()
|
| im2.load()
|
| return im1._new(core.blend(im1.im, im2.im, alpha))
|
|
|
|
|
| def composite(image1: Image, image2: Image, mask: Image) -> Image:
|
| """
|
| Create composite image by blending images using a transparency mask.
|
|
|
| :param image1: The first image.
|
| :param image2: The second image. Must have the same mode and
|
| size as the first image.
|
| :param mask: A mask image. This image can have mode
|
| "1", "L", or "RGBA", and must have the same size as the
|
| other two images.
|
| """
|
|
|
| image = image2.copy()
|
| image.paste(image1, None, mask)
|
| return image
|
|
|
|
|
| def eval(image: Image, *args: Callable[[int], float]) -> Image:
|
| """
|
| Applies the function (which should take one argument) to each pixel
|
| in the given image. If the image has more than one band, the same
|
| function is applied to each band. Note that the function is
|
| evaluated once for each possible pixel value, so you cannot use
|
| random components or other generators.
|
|
|
| :param image: The input image.
|
| :param function: A function object, taking one integer argument.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| return image.point(args[0])
|
|
|
|
|
| def merge(mode: str, bands: Sequence[Image]) -> Image:
|
| """
|
| Merge a set of single band images into a new multiband image.
|
|
|
| :param mode: The mode to use for the output image. See:
|
| :ref:`concept-modes`.
|
| :param bands: A sequence containing one single-band image for
|
| each band in the output image. All bands must have the
|
| same size.
|
| :returns: An :py:class:`~PIL.Image.Image` object.
|
| """
|
|
|
| if getmodebands(mode) != len(bands) or "*" in mode:
|
| msg = "wrong number of bands"
|
| raise ValueError(msg)
|
| for band in bands[1:]:
|
| if band.mode != getmodetype(mode):
|
| msg = "mode mismatch"
|
| raise ValueError(msg)
|
| if band.size != bands[0].size:
|
| msg = "size mismatch"
|
| raise ValueError(msg)
|
| for band in bands:
|
| band.load()
|
| return bands[0]._new(core.merge(mode, *[b.im for b in bands]))
|
|
|
|
|
|
|
|
|
|
|
|
|
| def register_open(
|
| id: str,
|
| factory: (
|
| Callable[[IO[bytes], str | bytes], ImageFile.ImageFile]
|
| | type[ImageFile.ImageFile]
|
| ),
|
| accept: Callable[[bytes], bool | str] | None = None,
|
| ) -> None:
|
| """
|
| Register an image file plugin. This function should not be used
|
| in application code.
|
|
|
| :param id: An image format identifier.
|
| :param factory: An image file factory method.
|
| :param accept: An optional function that can be used to quickly
|
| reject images having another format.
|
| """
|
| id = id.upper()
|
| if id not in ID:
|
| ID.append(id)
|
| OPEN[id] = factory, accept
|
|
|
|
|
| def register_mime(id: str, mimetype: str) -> None:
|
| """
|
| Registers an image MIME type by populating ``Image.MIME``. This function
|
| should not be used in application code.
|
|
|
| ``Image.MIME`` provides a mapping from image format identifiers to mime
|
| formats, but :py:meth:`~PIL.ImageFile.ImageFile.get_format_mimetype` can
|
| provide a different result for specific images.
|
|
|
| :param id: An image format identifier.
|
| :param mimetype: The image MIME type for this format.
|
| """
|
| MIME[id.upper()] = mimetype
|
|
|
|
|
| def register_save(
|
| id: str, driver: Callable[[Image, IO[bytes], str | bytes], None]
|
| ) -> None:
|
| """
|
| Registers an image save function. This function should not be
|
| used in application code.
|
|
|
| :param id: An image format identifier.
|
| :param driver: A function to save images in this format.
|
| """
|
| SAVE[id.upper()] = driver
|
|
|
|
|
| def register_save_all(
|
| id: str, driver: Callable[[Image, IO[bytes], str | bytes], None]
|
| ) -> None:
|
| """
|
| Registers an image function to save all the frames
|
| of a multiframe format. This function should not be
|
| used in application code.
|
|
|
| :param id: An image format identifier.
|
| :param driver: A function to save images in this format.
|
| """
|
| SAVE_ALL[id.upper()] = driver
|
|
|
|
|
| def register_extension(id: str, extension: str) -> None:
|
| """
|
| Registers an image extension. This function should not be
|
| used in application code.
|
|
|
| :param id: An image format identifier.
|
| :param extension: An extension used for this format.
|
| """
|
| EXTENSION[extension.lower()] = id.upper()
|
|
|
|
|
| def register_extensions(id: str, extensions: list[str]) -> None:
|
| """
|
| Registers image extensions. This function should not be
|
| used in application code.
|
|
|
| :param id: An image format identifier.
|
| :param extensions: A list of extensions used for this format.
|
| """
|
| for extension in extensions:
|
| register_extension(id, extension)
|
|
|
|
|
| def registered_extensions() -> dict[str, str]:
|
| """
|
| Returns a dictionary containing all file extensions belonging
|
| to registered plugins
|
| """
|
| init()
|
| return EXTENSION
|
|
|
|
|
| def register_decoder(name: str, decoder: type[ImageFile.PyDecoder]) -> None:
|
| """
|
| Registers an image decoder. This function should not be
|
| used in application code.
|
|
|
| :param name: The name of the decoder
|
| :param decoder: An ImageFile.PyDecoder object
|
|
|
| .. versionadded:: 4.1.0
|
| """
|
| DECODERS[name] = decoder
|
|
|
|
|
| def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None:
|
| """
|
| Registers an image encoder. This function should not be
|
| used in application code.
|
|
|
| :param name: The name of the encoder
|
| :param encoder: An ImageFile.PyEncoder object
|
|
|
| .. versionadded:: 4.1.0
|
| """
|
| ENCODERS[name] = encoder
|
|
|
|
|
|
|
|
|
|
|
|
|
| def _show(image: Image, **options: Any) -> None:
|
| from . import ImageShow
|
|
|
| deprecate("Image._show", 13, "ImageShow.show")
|
| ImageShow.show(image, **options)
|
|
|
|
|
|
|
|
|
|
|
|
|
| def effect_mandelbrot(
|
| size: tuple[int, int], extent: tuple[float, float, float, float], quality: int
|
| ) -> Image:
|
| """
|
| Generate a Mandelbrot set covering the given extent.
|
|
|
| :param size: The requested size in pixels, as a 2-tuple:
|
| (width, height).
|
| :param extent: The extent to cover, as a 4-tuple:
|
| (x0, y0, x1, y1).
|
| :param quality: Quality.
|
| """
|
| return Image()._new(core.effect_mandelbrot(size, extent, quality))
|
|
|
|
|
| def effect_noise(size: tuple[int, int], sigma: float) -> Image:
|
| """
|
| Generate Gaussian noise centered around 128.
|
|
|
| :param size: The requested size in pixels, as a 2-tuple:
|
| (width, height).
|
| :param sigma: Standard deviation of noise.
|
| """
|
| return Image()._new(core.effect_noise(size, sigma))
|
|
|
|
|
| def linear_gradient(mode: str) -> Image:
|
| """
|
| Generate 256x256 linear gradient from black to white, top to bottom.
|
|
|
| :param mode: Input mode.
|
| """
|
| return Image()._new(core.linear_gradient(mode))
|
|
|
|
|
| def radial_gradient(mode: str) -> Image:
|
| """
|
| Generate 256x256 radial gradient from black to white, centre to edge.
|
|
|
| :param mode: Input mode.
|
| """
|
| return Image()._new(core.radial_gradient(mode))
|
|
|
|
|
|
|
|
|
|
|
|
|
| def _apply_env_variables(env: dict[str, str] | None = None) -> None:
|
| env_dict = env if env is not None else os.environ
|
|
|
| for var_name, setter in [
|
| ("PILLOW_ALIGNMENT", core.set_alignment),
|
| ("PILLOW_BLOCK_SIZE", core.set_block_size),
|
| ("PILLOW_BLOCKS_MAX", core.set_blocks_max),
|
| ]:
|
| if var_name not in env_dict:
|
| continue
|
|
|
| var = env_dict[var_name].lower()
|
|
|
| units = 1
|
| for postfix, mul in [("k", 1024), ("m", 1024 * 1024)]:
|
| if var.endswith(postfix):
|
| units = mul
|
| var = var[: -len(postfix)]
|
|
|
| try:
|
| var_int = int(var) * units
|
| except ValueError:
|
| warnings.warn(f"{var_name} is not int")
|
| continue
|
|
|
| try:
|
| setter(var_int)
|
| except ValueError as e:
|
| warnings.warn(f"{var_name}: {e}")
|
|
|
|
|
| _apply_env_variables()
|
| atexit.register(core.clear_cache)
|
|
|
|
|
| if TYPE_CHECKING:
|
| _ExifBase = MutableMapping[int, Any]
|
| else:
|
| _ExifBase = MutableMapping
|
|
|
|
|
| class Exif(_ExifBase):
|
| """
|
| This class provides read and write access to EXIF image data::
|
|
|
| from PIL import Image
|
| im = Image.open("exif.png")
|
| exif = im.getexif() # Returns an instance of this class
|
|
|
| Information can be read and written, iterated over or deleted::
|
|
|
| print(exif[274]) # 1
|
| exif[274] = 2
|
| for k, v in exif.items():
|
| print("Tag", k, "Value", v) # Tag 274 Value 2
|
| del exif[274]
|
|
|
| To access information beyond IFD0, :py:meth:`~PIL.Image.Exif.get_ifd`
|
| returns a dictionary::
|
|
|
| from PIL import ExifTags
|
| im = Image.open("exif_gps.jpg")
|
| exif = im.getexif()
|
| gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
|
| print(gps_ifd)
|
|
|
| Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.MakerNote``,
|
| ``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
|
|
|
| :py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
|
|
|
| print(exif[ExifTags.Base.Software]) # PIL
|
| print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
|
| """
|
|
|
| endian: str | None = None
|
| bigtiff = False
|
| _loaded = False
|
|
|
| def __init__(self) -> None:
|
| self._data: dict[int, Any] = {}
|
| self._hidden_data: dict[int, Any] = {}
|
| self._ifds: dict[int, dict[int, Any]] = {}
|
| self._info: TiffImagePlugin.ImageFileDirectory_v2 | None = None
|
| self._loaded_exif: bytes | None = None
|
|
|
| def _fixup(self, value: Any) -> Any:
|
| try:
|
| if len(value) == 1 and isinstance(value, tuple):
|
| return value[0]
|
| except Exception:
|
| pass
|
| return value
|
|
|
| def _fixup_dict(self, src_dict: dict[int, Any]) -> dict[int, Any]:
|
|
|
|
|
| return {k: self._fixup(v) for k, v in src_dict.items()}
|
|
|
| def _get_ifd_dict(
|
| self, offset: int, group: int | None = None
|
| ) -> dict[int, Any] | None:
|
| try:
|
|
|
|
|
| self.fp.seek(offset)
|
| except (KeyError, TypeError):
|
| return None
|
| else:
|
| from . import TiffImagePlugin
|
|
|
| info = TiffImagePlugin.ImageFileDirectory_v2(self.head, group=group)
|
| info.load(self.fp)
|
| return self._fixup_dict(dict(info))
|
|
|
| def _get_head(self) -> bytes:
|
| version = b"\x2b" if self.bigtiff else b"\x2a"
|
| if self.endian == "<":
|
| head = b"II" + version + b"\x00" + o32le(8)
|
| else:
|
| head = b"MM\x00" + version + o32be(8)
|
| if self.bigtiff:
|
| head += o32le(8) if self.endian == "<" else o32be(8)
|
| head += b"\x00\x00\x00\x00"
|
| return head
|
|
|
| def load(self, data: bytes) -> None:
|
|
|
|
|
|
|
|
|
|
|
|
|
| if data == self._loaded_exif:
|
| return
|
| self._loaded_exif = data
|
| self._data.clear()
|
| self._hidden_data.clear()
|
| self._ifds.clear()
|
| while data and data.startswith(b"Exif\x00\x00"):
|
| data = data[6:]
|
| if not data:
|
| self._info = None
|
| return
|
|
|
| self.fp: IO[bytes] = io.BytesIO(data)
|
| self.head = self.fp.read(8)
|
|
|
| from . import TiffImagePlugin
|
|
|
| self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head)
|
| self.endian = self._info._endian
|
| self.fp.seek(self._info.next)
|
| self._info.load(self.fp)
|
|
|
| def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
|
| self._loaded_exif = None
|
| self._data.clear()
|
| self._hidden_data.clear()
|
| self._ifds.clear()
|
|
|
|
|
| from . import TiffImagePlugin
|
|
|
| self.fp = fp
|
| if offset is not None:
|
| self.head = self._get_head()
|
| else:
|
| self.head = self.fp.read(8)
|
| self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head)
|
| if self.endian is None:
|
| self.endian = self._info._endian
|
| if offset is None:
|
| offset = self._info.next
|
| self.fp.tell()
|
| self.fp.seek(offset)
|
| self._info.load(self.fp)
|
|
|
| def _get_merged_dict(self) -> dict[int, Any]:
|
| merged_dict = dict(self)
|
|
|
|
|
| if ExifTags.IFD.Exif in self:
|
| ifd = self._get_ifd_dict(self[ExifTags.IFD.Exif], ExifTags.IFD.Exif)
|
| if ifd:
|
| merged_dict.update(ifd)
|
|
|
|
|
| if ExifTags.IFD.GPSInfo in self:
|
| merged_dict[ExifTags.IFD.GPSInfo] = self._get_ifd_dict(
|
| self[ExifTags.IFD.GPSInfo], ExifTags.IFD.GPSInfo
|
| )
|
|
|
| return merged_dict
|
|
|
| def tobytes(self, offset: int = 8) -> bytes:
|
| from . import TiffImagePlugin
|
|
|
| head = self._get_head()
|
| ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
| for tag, ifd_dict in self._ifds.items():
|
| if tag not in self:
|
| ifd[tag] = ifd_dict
|
| for tag, value in self.items():
|
| if tag in [
|
| ExifTags.IFD.Exif,
|
| ExifTags.IFD.GPSInfo,
|
| ] and not isinstance(value, dict):
|
| value = self.get_ifd(tag)
|
| if (
|
| tag == ExifTags.IFD.Exif
|
| and ExifTags.IFD.Interop in value
|
| and not isinstance(value[ExifTags.IFD.Interop], dict)
|
| ):
|
| value = value.copy()
|
| value[ExifTags.IFD.Interop] = self.get_ifd(ExifTags.IFD.Interop)
|
| ifd[tag] = value
|
| return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
|
|
| def get_ifd(self, tag: int) -> dict[int, Any]:
|
| if tag not in self._ifds:
|
| if tag == ExifTags.IFD.IFD1:
|
| if self._info is not None and self._info.next != 0:
|
| ifd = self._get_ifd_dict(self._info.next)
|
| if ifd is not None:
|
| self._ifds[tag] = ifd
|
| elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]:
|
| offset = self._hidden_data.get(tag, self.get(tag))
|
| if offset is not None:
|
| ifd = self._get_ifd_dict(offset, tag)
|
| if ifd is not None:
|
| self._ifds[tag] = ifd
|
| elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.MakerNote]:
|
| if ExifTags.IFD.Exif not in self._ifds:
|
| self.get_ifd(ExifTags.IFD.Exif)
|
| tag_data = self._ifds[ExifTags.IFD.Exif][tag]
|
| if tag == ExifTags.IFD.MakerNote:
|
| from .TiffImagePlugin import ImageFileDirectory_v2
|
|
|
| if tag_data.startswith(b"FUJIFILM"):
|
| ifd_offset = i32le(tag_data, 8)
|
| ifd_data = tag_data[ifd_offset:]
|
|
|
| makernote = {}
|
| for i in range(struct.unpack("<H", ifd_data[:2])[0]):
|
| ifd_tag, typ, count, data = struct.unpack(
|
| "<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
| )
|
| try:
|
| (
|
| unit_size,
|
| handler,
|
| ) = ImageFileDirectory_v2._load_dispatch[typ]
|
| except KeyError:
|
| continue
|
| size = count * unit_size
|
| if size > 4:
|
| (offset,) = struct.unpack("<L", data)
|
| data = ifd_data[offset - 12 : offset + size - 12]
|
| else:
|
| data = data[:size]
|
|
|
| if len(data) != size:
|
| warnings.warn(
|
| "Possibly corrupt EXIF MakerNote data. "
|
| f"Expecting to read {size} bytes but only got "
|
| f"{len(data)}. Skipping tag {ifd_tag}"
|
| )
|
| continue
|
|
|
| if not data:
|
| continue
|
|
|
| makernote[ifd_tag] = handler(
|
| ImageFileDirectory_v2(), data, False
|
| )
|
| self._ifds[tag] = dict(self._fixup_dict(makernote))
|
| elif self.get(0x010F) == "Nintendo":
|
| makernote = {}
|
| for i in range(struct.unpack(">H", tag_data[:2])[0]):
|
| ifd_tag, typ, count, data = struct.unpack(
|
| ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
| )
|
| if ifd_tag == 0x1101:
|
|
|
| (offset,) = struct.unpack(">L", data)
|
| self.fp.seek(offset)
|
|
|
| camerainfo: dict[str, int | bytes] = {
|
| "ModelID": self.fp.read(4)
|
| }
|
|
|
| self.fp.read(4)
|
|
|
| camerainfo["TimeStamp"] = i32le(self.fp.read(12))
|
|
|
| self.fp.read(4)
|
| camerainfo["InternalSerialNumber"] = self.fp.read(4)
|
|
|
| self.fp.read(12)
|
| parallax = self.fp.read(4)
|
| handler = ImageFileDirectory_v2._load_dispatch[
|
| TiffTags.FLOAT
|
| ][1]
|
| camerainfo["Parallax"] = handler(
|
| ImageFileDirectory_v2(), parallax, False
|
| )[0]
|
|
|
| self.fp.read(4)
|
| camerainfo["Category"] = self.fp.read(2)
|
|
|
| makernote = {0x1101: camerainfo}
|
| self._ifds[tag] = makernote
|
| else:
|
|
|
| ifd = self._get_ifd_dict(tag_data, tag)
|
| if ifd is not None:
|
| self._ifds[tag] = ifd
|
| ifd = self._ifds.setdefault(tag, {})
|
| if tag == ExifTags.IFD.Exif and self._hidden_data:
|
| ifd = {
|
| k: v
|
| for (k, v) in ifd.items()
|
| if k not in (ExifTags.IFD.Interop, ExifTags.IFD.MakerNote)
|
| }
|
| return ifd
|
|
|
| def hide_offsets(self) -> None:
|
| for tag in (ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo):
|
| if tag in self:
|
| self._hidden_data[tag] = self[tag]
|
| del self[tag]
|
|
|
| def __str__(self) -> str:
|
| if self._info is not None:
|
|
|
| for tag in self._info:
|
| self[tag]
|
|
|
| return str(self._data)
|
|
|
| def __len__(self) -> int:
|
| keys = set(self._data)
|
| if self._info is not None:
|
| keys.update(self._info)
|
| return len(keys)
|
|
|
| def __getitem__(self, tag: int) -> Any:
|
| if self._info is not None and tag not in self._data and tag in self._info:
|
| self._data[tag] = self._fixup(self._info[tag])
|
| del self._info[tag]
|
| return self._data[tag]
|
|
|
| def __contains__(self, tag: object) -> bool:
|
| return tag in self._data or (self._info is not None and tag in self._info)
|
|
|
| def __setitem__(self, tag: int, value: Any) -> None:
|
| if self._info is not None and tag in self._info:
|
| del self._info[tag]
|
| self._data[tag] = value
|
|
|
| def __delitem__(self, tag: int) -> None:
|
| if self._info is not None and tag in self._info:
|
| del self._info[tag]
|
| else:
|
| del self._data[tag]
|
| if tag in self._ifds:
|
| del self._ifds[tag]
|
|
|
| def __iter__(self) -> Iterator[int]:
|
| keys = set(self._data)
|
| if self._info is not None:
|
| keys.update(self._info)
|
| return iter(keys)
|
|
|