Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support more affine expression forms in im.point(), deprecate Image.coerce_e #6254

Merged
merged 5 commits into from May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 22 additions & 2 deletions Tests/test_image_point.py
@@ -1,5 +1,7 @@
import pytest

from PIL import Image

from .helper import assert_image_equal, hopper


Expand All @@ -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():
Expand All @@ -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
8 changes: 8 additions & 0 deletions docs/deprecations.rst
Expand Up @@ -170,6 +170,14 @@ in Pillow 10 (2023-07-01). Upgrade to
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
`PySide6 <https://doc.qt.io/qtforpython/>`_ 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
----------------

Expand Down
8 changes: 8 additions & 0 deletions docs/releasenotes/9.2.0.rst
Expand Up @@ -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
===========

Expand Down
61 changes: 33 additions & 28 deletions src/PIL/Image.py
Expand Up @@ -29,7 +29,6 @@
import io
import logging
import math
import numbers
import os
import re
import struct
Expand Down Expand Up @@ -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)


# --------------------------------------------------------------------
Expand Down