diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py
index 428ad116b3d..157ecb120f0 100644
--- a/Tests/test_image_point.py
+++ b/Tests/test_image_point.py
@@ -1,5 +1,7 @@
import pytest
+from PIL import Image
+
from .helper import assert_image_equal, hopper
@@ -17,11 +19,24 @@ def test_sanity():
im.point(list(range(256)))
im.point(lambda x: x * 1)
im.point(lambda x: x + 1)
+ im.point(lambda x: x - 1)
im.point(lambda x: x * 1 + 1)
+ im.point(lambda x: 0.1 + 0.2 * x)
+ im.point(lambda x: -x)
+ im.point(lambda x: x - 0.5)
+ im.point(lambda x: 1 - x / 2)
+ im.point(lambda x: (2 + x) / 3)
+ im.point(lambda x: 0.5)
+ im.point(lambda x: x / 1)
+ im.point(lambda x: x + x)
+ with pytest.raises(TypeError):
+ im.point(lambda x: x * x)
+ with pytest.raises(TypeError):
+ im.point(lambda x: x / x)
with pytest.raises(TypeError):
- im.point(lambda x: x - 1)
+ im.point(lambda x: 1 / x)
with pytest.raises(TypeError):
- im.point(lambda x: x / 1)
+ im.point(lambda x: x // 2)
def test_16bit_lut():
@@ -47,3 +62,8 @@ def test_f_mode():
im = hopper("F")
with pytest.raises(ValueError):
im.point(None)
+
+
+def test_coerce_e_deprecation():
+ with pytest.warns(DeprecationWarning):
+ assert Image.coerce_e(2).data == 2
diff --git a/docs/deprecations.rst b/docs/deprecations.rst
index ad030acd071..8c5b8a748d7 100644
--- a/docs/deprecations.rst
+++ b/docs/deprecations.rst
@@ -170,6 +170,14 @@ in Pillow 10 (2023-07-01). Upgrade to
`PyQt6 `_ or
`PySide6 `_ instead.
+Image.coerce_e
+~~~~~~~~~~~~~~
+
+.. deprecated:: 9.2.0
+
+This undocumented method has been deprecated and will be removed in Pillow 10
+(2023-07-01).
+
Removed features
----------------
diff --git a/docs/releasenotes/9.2.0.rst b/docs/releasenotes/9.2.0.rst
index c38944b10e9..db051d1881f 100644
--- a/docs/releasenotes/9.2.0.rst
+++ b/docs/releasenotes/9.2.0.rst
@@ -31,6 +31,14 @@ FreeTypeFont.getmask2 fill parameter
The undocumented ``fill`` parameter of :py:meth:`.FreeTypeFont.getmask2`
has been deprecated and will be removed in Pillow 10 (2023-07-01).
+Image.coerce_e
+~~~~~~~~~~~~~~
+
+.. deprecated:: 9.2.0
+
+This undocumented method has been deprecated and will be removed in Pillow 10
+(2023-07-01).
+
API Changes
===========
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index fead48b29d9..4ac4fda26b5 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -29,7 +29,6 @@
import io
import logging
import math
-import numbers
import os
import re
import struct
@@ -432,44 +431,50 @@ def _getencoder(mode, encoder_name, args, extra=()):
def coerce_e(value):
- return value if isinstance(value, _E) else _E(value)
+ deprecate("coerce_e", 10)
+ return value if isinstance(value, _E) else _E(1, value)
+# _E(scale, offset) represents the affine transformation scale * x + offset.
+# The "data" field is named for compatibility with the old implementation,
+# and should be renamed once coerce_e is removed.
class _E:
- def __init__(self, data):
+ def __init__(self, scale, data):
+ self.scale = scale
self.data = data
+ def __neg__(self):
+ return _E(-self.scale, -self.data)
+
def __add__(self, other):
- return _E((self.data, "__add__", coerce_e(other).data))
+ if isinstance(other, _E):
+ return _E(self.scale + other.scale, self.data + other.data)
+ return _E(self.scale, self.data + other)
+
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ return self + -other
+
+ def __rsub__(self, other):
+ return other + -self
def __mul__(self, other):
- return _E((self.data, "__mul__", coerce_e(other).data))
+ if isinstance(other, _E):
+ return NotImplemented
+ return _E(self.scale * other, self.data * other)
+
+ __rmul__ = __mul__
+
+ def __truediv__(self, other):
+ if isinstance(other, _E):
+ return NotImplemented
+ return _E(self.scale / other, self.data / other)
def _getscaleoffset(expr):
- stub = ["stub"]
- data = expr(_E(stub)).data
- try:
- (a, b, c) = data # simplified syntax
- if a is stub and b == "__mul__" and isinstance(c, numbers.Number):
- return c, 0.0
- if a is stub and b == "__add__" and isinstance(c, numbers.Number):
- return 1.0, c
- except TypeError:
- pass
- try:
- ((a, b, c), d, e) = data # full syntax
- if (
- a is stub
- and b == "__mul__"
- and isinstance(c, numbers.Number)
- and d == "__add__"
- and isinstance(e, numbers.Number)
- ):
- return c, e
- except TypeError:
- pass
- raise ValueError("illegal expression")
+ a = expr(_E(1, 0))
+ return (a.scale, a.data) if isinstance(a, _E) else (0, a)
# --------------------------------------------------------------------