Skip to content

Commit

Permalink
Merge pull request #6254 from benrg/affine-transform
Browse files Browse the repository at this point in the history
Support more affine expression forms in im.point()
  • Loading branch information
radarhere committed May 19, 2022
2 parents 6bb408c + c7f5b4c commit 80782bb
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 30 deletions.
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

0 comments on commit 80782bb

Please sign in to comment.