diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index a5d5a15dbab..8929da313be 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -2,6 +2,8 @@ import pytest +from PIL import Image + from .helper import assert_image_equal, hopper @@ -62,3 +64,8 @@ def test_f_mode() -> None: im = hopper("F") with pytest.raises(ValueError): im.point([]) + + +def test_deprecation() -> None: + with pytest.warns(DeprecationWarning): + Image._E(1, 0) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index a9498d5ed5e..cb999e1d109 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -136,6 +136,14 @@ Specific WebP Feature Checks ``True`` if the WebP module is installed, until they are removed in Pillow 12.0.0 (2025-10-15). +Image._E +^^^^^^^^ + +.. deprecated:: 11.0.0 + +``Image._E`` is now deprecated, in favour of a more helpful name, +``Image.ImagePointTransform``. ``Image._E`` will be removed in Pillow 12 (2025-10-15). + Removed features ---------------- diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 02e714f20b8..bc3758218db 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -362,8 +362,8 @@ Classes :undoc-members: :show-inheritance: .. autoclass:: PIL.Image.ImagePointHandler +.. autoclass:: PIL.Image.ImagePointTransform .. autoclass:: PIL.Image.ImageTransformHandler -.. autoclass:: PIL.Image._E Protocols --------- diff --git a/docs/releasenotes/11.0.0.rst b/docs/releasenotes/11.0.0.rst index ac9237acf78..adef1f6e2d7 100644 --- a/docs/releasenotes/11.0.0.rst +++ b/docs/releasenotes/11.0.0.rst @@ -66,6 +66,14 @@ Specific WebP Feature Checks ``True`` if the WebP module is installed, until they are removed in Pillow 12.0.0 (2025-10-15). +Image._E +^^^^^^^^ + +.. deprecated:: 11.0.0 + +``Image._E`` is now deprecated, in favour of a more helpful name, +``Image.ImagePointTransform``. ``Image._E`` will be removed in Pillow 12 (2025-10-15). + API Changes =========== diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 37d8cb33517..00d65f12eb8 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -470,43 +470,60 @@ def _getencoder( # Simple expression analyzer -class _E: +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) -> _E: - return _E(-self.scale, -self.offset) + def __neg__(self) -> ImagePointTransform: + return ImagePointTransform(-self.scale, -self.offset) - def __add__(self, other: _E | float) -> _E: - if isinstance(other, _E): - return _E(self.scale + other.scale, self.offset + other.offset) - return _E(self.scale, self.offset + other) + 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: _E | float) -> _E: + def __sub__(self, other: ImagePointTransform | float) -> ImagePointTransform: return self + -other - def __rsub__(self, other: _E | float) -> _E: + def __rsub__(self, other: ImagePointTransform | float) -> ImagePointTransform: return other + -self - def __mul__(self, other: _E | float) -> _E: - if isinstance(other, _E): + def __mul__(self, other: ImagePointTransform | float) -> ImagePointTransform: + if isinstance(other, ImagePointTransform): return NotImplemented - return _E(self.scale * other, self.offset * other) + return ImagePointTransform(self.scale * other, self.offset * other) __rmul__ = __mul__ - def __truediv__(self, other: _E | float) -> _E: - if isinstance(other, _E): + def __truediv__(self, other: ImagePointTransform | float) -> ImagePointTransform: + if isinstance(other, ImagePointTransform): return NotImplemented - return _E(self.scale / other, self.offset / other) + return ImagePointTransform(self.scale / other, self.offset / other) + + +class _E(ImagePointTransform): + + def __init__(self, scale: float, offset: float) -> None: + deprecate("_E", 12, "ImagePointTransform") + super().__init__(scale, offset) -def _getscaleoffset(expr: Callable[[_E], _E | float]) -> tuple[float, float]: - a = expr(_E(1, 0)) - return (a.scale, a.offset) if isinstance(a, _E) else (0, a) +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) # -------------------------------------------------------------------- @@ -1898,7 +1915,7 @@ def point( Sequence[float] | NumpyArray | Callable[[int], float] - | Callable[[_E], _E | float] + | Callable[[ImagePointTransform], ImagePointTransform | float] | ImagePointHandler ), mode: str | None = None,