mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 01:16:16 +03:00
Deprecate eval(), replacing it with lambda_eval() and unsafe_eval()
This commit is contained in:
parent
22376775b0
commit
27b10c4bd8
|
@ -115,7 +115,9 @@ def assert_image_similar(
|
||||||
|
|
||||||
diff = 0
|
diff = 0
|
||||||
for ach, bch in zip(a.split(), b.split()):
|
for ach, bch in zip(a.split(), b.split()):
|
||||||
chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert("L")
|
chdiff = ImageMath.lambda_eval(
|
||||||
|
lambda args: abs(args["a"] - args["b"]), a=ach, b=bch
|
||||||
|
).convert("L")
|
||||||
diff += sum(i * num for i, num in enumerate(chdiff.histogram()))
|
diff += sum(i * num for i, num in enumerate(chdiff.histogram()))
|
||||||
|
|
||||||
ave_diff = diff / (a.size[0] * a.size[1])
|
ave_diff = diff / (a.size[0] * a.size[1])
|
||||||
|
|
|
@ -186,7 +186,9 @@ def assert_compare_images(
|
||||||
|
|
||||||
bands = ImageMode.getmode(a.mode).bands
|
bands = ImageMode.getmode(a.mode).bands
|
||||||
for band, ach, bch in zip(bands, a.split(), b.split()):
|
for band, ach, bch in zip(bands, a.split(), b.split()):
|
||||||
ch_diff = ImageMath.eval("convert(abs(a - b), 'L')", a=ach, b=bch)
|
ch_diff = ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](abs(args["a"] - args["b"]), "L"), a=ach, b=bch
|
||||||
|
)
|
||||||
ch_hist = ch_diff.histogram()
|
ch_hist = ch_diff.histogram()
|
||||||
|
|
||||||
average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (
|
average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (
|
||||||
|
|
|
@ -1,214 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PIL import Image, ImageMath
|
|
||||||
|
|
||||||
|
|
||||||
def pixel(im: Image.Image | int) -> str | int:
|
|
||||||
if isinstance(im, int):
|
|
||||||
return int(im) # hack to deal with booleans
|
|
||||||
|
|
||||||
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
|
||||||
|
|
||||||
|
|
||||||
A = Image.new("L", (1, 1), 1)
|
|
||||||
B = Image.new("L", (1, 1), 2)
|
|
||||||
Z = Image.new("L", (1, 1), 0) # Z for zero
|
|
||||||
F = Image.new("F", (1, 1), 3)
|
|
||||||
I = Image.new("I", (1, 1), 4) # noqa: E741
|
|
||||||
|
|
||||||
A2 = A.resize((2, 2))
|
|
||||||
B2 = B.resize((2, 2))
|
|
||||||
|
|
||||||
images = {"A": A, "B": B, "F": F, "I": I}
|
|
||||||
|
|
||||||
|
|
||||||
def test_sanity() -> None:
|
|
||||||
assert ImageMath.eval("1") == 1
|
|
||||||
assert ImageMath.eval("1+A", A=2) == 3
|
|
||||||
assert pixel(ImageMath.eval("A+B", A=A, B=B)) == "I 3"
|
|
||||||
assert pixel(ImageMath.eval("A+B", images)) == "I 3"
|
|
||||||
assert pixel(ImageMath.eval("float(A)+B", images)) == "F 3.0"
|
|
||||||
assert pixel(ImageMath.eval("int(float(A)+B)", images)) == "I 3"
|
|
||||||
|
|
||||||
|
|
||||||
def test_ops() -> None:
|
|
||||||
assert pixel(ImageMath.eval("-A", images)) == "I -1"
|
|
||||||
assert pixel(ImageMath.eval("+B", images)) == "L 2"
|
|
||||||
|
|
||||||
assert pixel(ImageMath.eval("A+B", images)) == "I 3"
|
|
||||||
assert pixel(ImageMath.eval("A-B", images)) == "I -1"
|
|
||||||
assert pixel(ImageMath.eval("A*B", images)) == "I 2"
|
|
||||||
assert pixel(ImageMath.eval("A/B", images)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B**2", images)) == "I 4"
|
|
||||||
assert pixel(ImageMath.eval("B**33", images)) == "I 2147483647"
|
|
||||||
|
|
||||||
assert pixel(ImageMath.eval("float(A)+B", images)) == "F 3.0"
|
|
||||||
assert pixel(ImageMath.eval("float(A)-B", images)) == "F -1.0"
|
|
||||||
assert pixel(ImageMath.eval("float(A)*B", images)) == "F 2.0"
|
|
||||||
assert pixel(ImageMath.eval("float(A)/B", images)) == "F 0.5"
|
|
||||||
assert pixel(ImageMath.eval("float(B)**2", images)) == "F 4.0"
|
|
||||||
assert pixel(ImageMath.eval("float(B)**33", images)) == "F 8589934592.0"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"expression",
|
|
||||||
(
|
|
||||||
"exec('pass')",
|
|
||||||
"(lambda: exec('pass'))()",
|
|
||||||
"(lambda: (lambda: exec('pass'))())()",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_prevent_exec(expression: str) -> None:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ImageMath.eval(expression)
|
|
||||||
|
|
||||||
|
|
||||||
def test_prevent_double_underscores() -> None:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ImageMath.eval("1", {"__": None})
|
|
||||||
|
|
||||||
|
|
||||||
def test_prevent_builtins() -> None:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ImageMath.eval("(lambda: exec('exit()'))()", {"exec": None})
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical() -> None:
|
|
||||||
assert pixel(ImageMath.eval("not A", images)) == 0
|
|
||||||
assert pixel(ImageMath.eval("A and B", images)) == "L 2"
|
|
||||||
assert pixel(ImageMath.eval("A or B", images)) == "L 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_convert() -> None:
|
|
||||||
assert pixel(ImageMath.eval("convert(A+B, 'L')", images)) == "L 3"
|
|
||||||
assert pixel(ImageMath.eval("convert(A+B, '1')", images)) == "1 0"
|
|
||||||
assert pixel(ImageMath.eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)"
|
|
||||||
|
|
||||||
|
|
||||||
def test_compare() -> None:
|
|
||||||
assert pixel(ImageMath.eval("min(A, B)", images)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("max(A, B)", images)) == "I 2"
|
|
||||||
assert pixel(ImageMath.eval("A == 1", images)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A == 2", images)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_one_image_larger() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A+B", A=A2, B=B)) == "I 3"
|
|
||||||
assert pixel(ImageMath.eval("A+B", A=A, B=B2)) == "I 3"
|
|
||||||
|
|
||||||
|
|
||||||
def test_abs() -> None:
|
|
||||||
assert pixel(ImageMath.eval("abs(A)", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("abs(B)", B=B)) == "I 2"
|
|
||||||
|
|
||||||
|
|
||||||
def test_binary_mod() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A%A", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B%B", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A%B", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B%A", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z%A", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z%B", B=B, Z=Z)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_invert() -> None:
|
|
||||||
assert pixel(ImageMath.eval("~Z", Z=Z)) == "I -1"
|
|
||||||
assert pixel(ImageMath.eval("~A", A=A)) == "I -2"
|
|
||||||
assert pixel(ImageMath.eval("~B", B=B)) == "I -3"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_and() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z&Z", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z&A", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A&Z", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A&A", A=A, Z=Z)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_or() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z|Z", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z|A", A=A, Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A|Z", A=A, Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A|A", A=A, Z=Z)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_xor() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z^Z", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z^A", A=A, Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A^Z", A=A, Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A^A", A=A, Z=Z)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_leftshift() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z<<0", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z<<1", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A<<0", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A<<1", A=A)) == "I 2"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_rightshift() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z>>0", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z>>1", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A>>0", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A>>1", A=A)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_eq() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A==A", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B==B", B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A==B", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B==A", A=A, B=B)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_ne() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A!=A", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B!=B", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A!=B", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B!=A", A=A, B=B)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_lt() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A<A", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B<B", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A<B", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B<A", A=A, B=B)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_le() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A<=A", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B<=B", B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A<=B", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B<=A", A=A, B=B)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_gt() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A>A", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B>B", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A>B", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B>A", A=A, B=B)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_ge() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A>=A", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B>=B", B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A>=B", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B>=A", A=A, B=B)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_equal() -> None:
|
|
||||||
assert pixel(ImageMath.eval("equal(A, A)", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("equal(B, B)", B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("equal(Z, Z)", Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("equal(A, B)", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("equal(B, A)", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("equal(A, Z)", A=A, Z=Z)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_not_equal() -> None:
|
|
||||||
assert pixel(ImageMath.eval("notequal(A, A)", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("notequal(B, B)", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("notequal(Z, Z)", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("notequal(A, B)", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("notequal(B, A)", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("notequal(A, Z)", A=A, Z=Z)) == "I 1"
|
|
498
Tests/test_imagemath_lambda_eval.py
Normal file
498
Tests/test_imagemath_lambda_eval.py
Normal file
|
@ -0,0 +1,498 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import Image, ImageMath
|
||||||
|
|
||||||
|
|
||||||
|
def pixel(im: Image.Image | int) -> str | int:
|
||||||
|
if isinstance(im, int):
|
||||||
|
return int(im) # hack to deal with booleans
|
||||||
|
|
||||||
|
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
||||||
|
|
||||||
|
|
||||||
|
A = Image.new("L", (1, 1), 1)
|
||||||
|
B = Image.new("L", (1, 1), 2)
|
||||||
|
Z = Image.new("L", (1, 1), 0) # Z for zero
|
||||||
|
F = Image.new("F", (1, 1), 3)
|
||||||
|
I = Image.new("I", (1, 1), 4) # noqa: E741
|
||||||
|
|
||||||
|
A2 = A.resize((2, 2))
|
||||||
|
B2 = B.resize((2, 2))
|
||||||
|
|
||||||
|
images = {"A": A, "B": B, "F": F, "I": I}
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity() -> None:
|
||||||
|
assert ImageMath.lambda_eval(lambda args: 1) == 1
|
||||||
|
assert ImageMath.lambda_eval(lambda args: 1 + args["A"], A=2) == 3
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], A=A, B=B))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) + args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F 3.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["int"](args["float"](args["A"]) + args["B"]), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ops() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, images)) == "I -1"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], images))
|
||||||
|
== "I -1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], images))
|
||||||
|
== "I 2"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], images))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, images)) == "I 4"
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, images))
|
||||||
|
== "I 2147483647"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) + args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F 3.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) - args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F -1.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) * args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F 2.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) / args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F 0.5"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, images))
|
||||||
|
== "F 4.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, images)
|
||||||
|
)
|
||||||
|
== "F 8589934592.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], images)) == 0
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], images))
|
||||||
|
== "L 2"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], images))
|
||||||
|
== "L 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["A"] + args["B"], "L"), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "L 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["A"] + args["B"], "1"), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "1 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["A"] + args["B"], "RGB"), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "RGB (3, 3, 3)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_compare() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["min"](args["A"], args["B"]), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["max"](args["A"], args["B"]), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 2"
|
||||||
|
)
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, images)) == "I 1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, images)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_image_larger() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], A=A2, B=B))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], A=A, B=B2))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_abs() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: abs(args["A"]), A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: abs(args["B"]), B=B)) == "I 2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary_mod() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] % args["A"], A=A)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] % args["B"], B=B)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] % args["B"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] % args["A"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] % args["A"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] % args["B"], B=B, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_invert() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: ~args["Z"], Z=Z)) == "I -1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: ~args["A"], A=A)) == "I -2"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: ~args["B"], B=B)) == "I -3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_and() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] & args["Z"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] & args["A"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] & args["Z"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] & args["A"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_or() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] | args["Z"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] | args["A"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] | args["Z"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] | args["A"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_xor() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] ^ args["Z"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] ^ args["A"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] ^ args["Z"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] ^ args["A"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_leftshift() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["Z"] << 0, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["Z"] << 1, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] << 0, A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] << 1, A=A)) == "I 2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_rightshift() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["Z"] >> 0, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["Z"] >> 1, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] >> 0, A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] >> 1, A=A)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_eq() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] == args["A"], A=A)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] == args["B"], B=B)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] == args["B"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] == args["A"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_ne() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] != args["A"], A=A)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] != args["B"], B=B)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] != args["B"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] != args["A"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_lt() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] < args["A"], A=A)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] < args["B"], B=B)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] < args["B"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] < args["A"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_le() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] <= args["A"], A=A)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] <= args["B"], B=B)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] <= args["B"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] <= args["A"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_gt() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] > args["A"], A=A)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] > args["B"], B=B)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] > args["B"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] > args["A"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_ge() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] >= args["A"], A=A)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] >= args["B"], B=B)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] >= args["B"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] >= args["A"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_equal() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["equal"](args["A"], args["A"]), A=A)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["equal"](args["B"], args["B"]), B=B)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["equal"](args["Z"], args["Z"]), Z=Z)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["equal"](args["A"], args["B"]), A=A, B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["equal"](args["B"], args["A"]), A=A, B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["equal"](args["A"], args["Z"]), A=A, Z=Z
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_not_equal() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["A"], args["A"]), A=A
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["B"], args["B"]), B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["Z"], args["Z"]), Z=Z
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["A"], args["B"]), A=A, B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["B"], args["A"]), A=A, B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["A"], args["Z"]), A=A, Z=Z
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
221
Tests/test_imagemath_unsafe_eval.py
Normal file
221
Tests/test_imagemath_unsafe_eval.py
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import Image, ImageMath
|
||||||
|
|
||||||
|
|
||||||
|
def pixel(im: Image.Image | int) -> str | int:
|
||||||
|
if isinstance(im, int):
|
||||||
|
return int(im) # hack to deal with booleans
|
||||||
|
|
||||||
|
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
||||||
|
|
||||||
|
|
||||||
|
A = Image.new("L", (1, 1), 1)
|
||||||
|
B = Image.new("L", (1, 1), 2)
|
||||||
|
Z = Image.new("L", (1, 1), 0) # Z for zero
|
||||||
|
F = Image.new("F", (1, 1), 3)
|
||||||
|
I = Image.new("I", (1, 1), 4) # noqa: E741
|
||||||
|
|
||||||
|
A2 = A.resize((2, 2))
|
||||||
|
B2 = B.resize((2, 2))
|
||||||
|
|
||||||
|
images = {"A": A, "B": B, "F": F, "I": I}
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity() -> None:
|
||||||
|
assert ImageMath.unsafe_eval("1") == 1
|
||||||
|
assert ImageMath.unsafe_eval("1+A", A=2) == 3
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", images)) == "I 3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_eval_deprecated() -> None:
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert ImageMath.eval("1") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_ops() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("-A", images)) == "I -1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("+B", images)) == "L 2"
|
||||||
|
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A-B", images)) == "I -1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A*B", images)) == "I 2"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A/B", images)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B**2", images)) == "I 4"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B**33", images)) == "I 2147483647"
|
||||||
|
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)-B", images)) == "F -1.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)*B", images)) == "F 2.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)/B", images)) == "F 0.5"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(B)**2", images)) == "F 4.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(B)**33", images)) == "F 8589934592.0"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"expression",
|
||||||
|
(
|
||||||
|
"exec('pass')",
|
||||||
|
"(lambda: exec('pass'))()",
|
||||||
|
"(lambda: (lambda: exec('pass'))())()",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_prevent_exec(expression: str) -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageMath.unsafe_eval(expression)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prevent_double_underscores() -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageMath.unsafe_eval("1", {"__": None})
|
||||||
|
|
||||||
|
|
||||||
|
def test_prevent_builtins() -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", {"exec": None})
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("not A", images)) == 0
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A and B", images)) == "L 2"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A or B", images)) == "L 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", images)) == "L 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", images)) == "1 0"
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_compare() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("min(A, B)", images)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("max(A, B)", images)) == "I 2"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A == 1", images)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A == 2", images)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_image_larger() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", A=A2, B=B)) == "I 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B2)) == "I 3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_abs() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("abs(A)", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("abs(B)", B=B)) == "I 2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary_mod() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A%A", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B%B", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A%B", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B%A", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z%A", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z%B", B=B, Z=Z)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_invert() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("~Z", Z=Z)) == "I -1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("~A", A=A)) == "I -2"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("~B", B=B)) == "I -3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_and() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z&Z", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z&A", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A&Z", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A&A", A=A, Z=Z)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_or() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z|Z", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z|A", A=A, Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A|Z", A=A, Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A|A", A=A, Z=Z)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_xor() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z^Z", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z^A", A=A, Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A^Z", A=A, Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A^A", A=A, Z=Z)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_leftshift() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z<<0", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z<<1", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<<0", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<<1", A=A)) == "I 2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_rightshift() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z>>0", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z>>1", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>>0", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>>1", A=A)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_eq() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A==A", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B==B", B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A==B", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B==A", A=A, B=B)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_ne() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A!=A", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B!=B", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A!=B", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B!=A", A=A, B=B)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_lt() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<A", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B<B", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<B", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B<A", A=A, B=B)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_le() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<=A", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B<=B", B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<=B", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B<=A", A=A, B=B)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_gt() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>A", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B>B", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>B", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B>A", A=A, B=B)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_ge() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>=A", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B>=B", B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>=B", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B>=A", A=A, B=B)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_equal() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(A, A)", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(B, B)", B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(Z, Z)", Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(A, B)", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(B, A)", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(A, Z)", A=A, Z=Z)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_not_equal() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(A, A)", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(B, B)", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(Z, Z)", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(A, B)", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(B, A)", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(A, Z)", A=A, Z=Z)) == "I 1"
|
|
@ -4,9 +4,12 @@
|
||||||
:py:mod:`~PIL.ImageMath` Module
|
:py:mod:`~PIL.ImageMath` Module
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”. The
|
The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”, that
|
||||||
module provides a single :py:meth:`~PIL.ImageMath.eval` function, which takes
|
can take a number of images and generate a result.
|
||||||
an expression string and one or more images.
|
|
||||||
|
In the current version, :py:mod:`~PIL.ImageMath` only supports single-layer images. To
|
||||||
|
process multi-band images, use the :py:meth:`~PIL.Image.Image.split` method or
|
||||||
|
:py:func:`~PIL.Image.merge` function.
|
||||||
|
|
||||||
Example: Using the :py:mod:`~PIL.ImageMath` module
|
Example: Using the :py:mod:`~PIL.ImageMath` module
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
@ -17,13 +20,36 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
|
||||||
|
|
||||||
with Image.open("image1.jpg") as im1:
|
with Image.open("image1.jpg") as im1:
|
||||||
with Image.open("image2.jpg") as im2:
|
with Image.open("image2.jpg") as im2:
|
||||||
|
out = ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["min"](args["a"], args["b"]), 'L'),
|
||||||
|
a=im1,
|
||||||
|
b=im2
|
||||||
|
)
|
||||||
|
out = ImageMath.unsafe_eval(
|
||||||
|
"convert(min(a, b), 'L')",
|
||||||
|
a=im1,
|
||||||
|
b=im2
|
||||||
|
)
|
||||||
|
|
||||||
out = ImageMath.eval("convert(min(a, b), 'L')", a=im1, b=im2)
|
.. py:function:: lambda_eval(expression, environment)
|
||||||
out.save("result.png")
|
|
||||||
|
|
||||||
.. py:function:: eval(expression, environment)
|
Returns the result of an image function.
|
||||||
|
|
||||||
Evaluate expression in the given environment.
|
:param expression: A function that receives a dictionary.
|
||||||
|
:param options: Values to add to the function's dictionary, mapping image
|
||||||
|
names to Image instances. You can use one or more keyword
|
||||||
|
arguments instead of a dictionary, as shown in the above
|
||||||
|
example. Note that the names must be valid Python
|
||||||
|
identifiers.
|
||||||
|
:return: An image, an integer value, a floating point value,
|
||||||
|
or a pixel tuple, depending on the expression.
|
||||||
|
|
||||||
|
.. py:function:: unsafe_eval(expression, environment)
|
||||||
|
|
||||||
|
Evaluates an image expression. This uses Python's ``eval()`` function to process
|
||||||
|
the expression string, and carries the security risks of doing so. It is not
|
||||||
|
recommended to process expressions without considering this.
|
||||||
|
:py:meth:`~lambda_eval` is a more secure alternative.
|
||||||
|
|
||||||
In the current version, :py:mod:`~PIL.ImageMath` only supports
|
In the current version, :py:mod:`~PIL.ImageMath` only supports
|
||||||
single-layer images. To process multi-band images, use the
|
single-layer images. To process multi-band images, use the
|
||||||
|
@ -33,19 +59,25 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
|
||||||
:param expression: A string which uses the standard Python expression
|
:param expression: A string which uses the standard Python expression
|
||||||
syntax. In addition to the standard operators, you can
|
syntax. In addition to the standard operators, you can
|
||||||
also use the functions described below.
|
also use the functions described below.
|
||||||
:param environment: A dictionary that maps image names to Image instances.
|
:param options: Values to add to the function's dictionary, mapping image
|
||||||
You can use one or more keyword arguments instead of a
|
names to Image instances. You can use one or more keyword
|
||||||
dictionary, as shown in the above example. Note that
|
arguments instead of a dictionary, as shown in the above
|
||||||
the names must be valid Python identifiers.
|
example. Note that the names must be valid Python
|
||||||
|
identifiers.
|
||||||
:return: An image, an integer value, a floating point value,
|
:return: An image, an integer value, a floating point value,
|
||||||
or a pixel tuple, depending on the expression.
|
or a pixel tuple, depending on the expression.
|
||||||
|
|
||||||
Expression syntax
|
Expression syntax
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Expressions are standard Python expressions, but they’re evaluated in a
|
:py:meth:`~lambda_eval` expressions are functions that receive a dictionary containing
|
||||||
non-standard environment. You can use PIL methods as usual, plus the following
|
images and operators.
|
||||||
set of operators and functions:
|
|
||||||
|
:py:meth:`~unsafe_eval` expressions are standard Python expressions, but they’re
|
||||||
|
evaluated in a non-standard environment.
|
||||||
|
|
||||||
|
In both cases, you can use Pillow methods as usual, plus the following set of operators
|
||||||
|
and functions.
|
||||||
|
|
||||||
Standard Operators
|
Standard Operators
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -29,7 +29,7 @@ they do not extend beyond the bitmap image.
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
If an attacker has control over the keys passed to the
|
If an attacker has control over the keys passed to the
|
||||||
``environment`` argument of :py:meth:`PIL.ImageMath.eval`, they may be able to execute
|
``environment`` argument of :py:meth:`!PIL.ImageMath.eval`, they may be able to execute
|
||||||
arbitrary code. To prevent this, keys matching the names of builtins and keys
|
arbitrary code. To prevent this, keys matching the names of builtins and keys
|
||||||
containing double underscores will now raise a :py:exc:`ValueError`.
|
containing double underscores will now raise a :py:exc:`ValueError`.
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ Google's `OSS-Fuzz`_ project for finding this issue.
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
To limit :py:class:`PIL.ImageMath` to working with images, Pillow
|
To limit :py:class:`PIL.ImageMath` to working with images, Pillow
|
||||||
will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will
|
will now restrict the builtins available to :py:meth:`!PIL.ImageMath.eval`. This will
|
||||||
help prevent problems arising if users evaluate arbitrary expressions, such as
|
help prevent problems arising if users evaluate arbitrary expressions, such as
|
||||||
``ImageMath.eval("exec(exit())")``.
|
``ImageMath.eval("exec(exit())")``.
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ has been present since PIL.
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
While Pillow 9.0 restricted top-level builtins available to
|
While Pillow 9.0 restricted top-level builtins available to
|
||||||
:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins
|
:py:meth:`!PIL.ImageMath.eval`, it did not prevent builtins
|
||||||
available to lambda expressions. These are now also restricted.
|
available to lambda expressions. These are now also restricted.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
|
|
|
@ -652,8 +652,17 @@ def _write_multiple_frames(im, fp, palette):
|
||||||
fill = Image.new("P", delta.size, encoderinfo["transparency"])
|
fill = Image.new("P", delta.size, encoderinfo["transparency"])
|
||||||
if delta.mode == "RGBA":
|
if delta.mode == "RGBA":
|
||||||
r, g, b, a = delta.split()
|
r, g, b, a = delta.split()
|
||||||
mask = ImageMath.eval(
|
mask = ImageMath.lambda_eval(
|
||||||
"convert(max(max(max(r, g), b), a) * 255, '1')",
|
lambda args: args["convert"](
|
||||||
|
args["max"](
|
||||||
|
args["max"](
|
||||||
|
args["max"](args["r"], args["g"]), args["b"]
|
||||||
|
),
|
||||||
|
args["a"],
|
||||||
|
)
|
||||||
|
* 255,
|
||||||
|
"1",
|
||||||
|
),
|
||||||
r=r,
|
r=r,
|
||||||
g=g,
|
g=g,
|
||||||
b=b,
|
b=b,
|
||||||
|
@ -665,7 +674,10 @@ def _write_multiple_frames(im, fp, palette):
|
||||||
delta_l = Image.new("L", delta.size)
|
delta_l = Image.new("L", delta.size)
|
||||||
delta_l.putdata(delta.getdata())
|
delta_l.putdata(delta.getdata())
|
||||||
delta = delta_l
|
delta = delta_l
|
||||||
mask = ImageMath.eval("convert(im * 255, '1')", im=delta)
|
mask = ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["im"] * 255, "1"),
|
||||||
|
im=delta,
|
||||||
|
)
|
||||||
diff_frame.paste(fill, mask=ImageOps.invert(mask))
|
diff_frame.paste(fill, mask=ImageOps.invert(mask))
|
||||||
else:
|
else:
|
||||||
bbox = None
|
bbox = None
|
||||||
|
|
|
@ -18,9 +18,10 @@ from __future__ import annotations
|
||||||
|
|
||||||
import builtins
|
import builtins
|
||||||
from types import CodeType
|
from types import CodeType
|
||||||
from typing import Any
|
from typing import Any, Callable
|
||||||
|
|
||||||
from . import Image, _imagingmath
|
from . import Image, _imagingmath
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
|
|
||||||
class _Operand:
|
class _Operand:
|
||||||
|
@ -235,9 +236,55 @@ ops = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def eval(expression: str, _dict: dict[str, Any] = {}, **kw: Any) -> Any:
|
def lambda_eval(expression: Callable[[dict[str, Any]], Any],
|
||||||
|
_dict: dict[str, Any] = {},
|
||||||
|
**kw: Any,) -> Any:
|
||||||
"""
|
"""
|
||||||
Evaluates an image expression.
|
Returns the result of an image function.
|
||||||
|
|
||||||
|
In the current version, :py:mod:`~PIL.ImageMath` only supports
|
||||||
|
single-layer images. To process multi-band images, use the
|
||||||
|
:py:meth:`~PIL.Image.Image.split` method or :py:func:`~PIL.Image.merge`
|
||||||
|
function.
|
||||||
|
|
||||||
|
:param expression: A function that receives a dictionary.
|
||||||
|
:param options: Values to add to the function's dictionary. You
|
||||||
|
can either use a dictionary, or one or more keyword
|
||||||
|
arguments.
|
||||||
|
:return: The expression result. This is usually an image object, but can
|
||||||
|
also be an integer, a floating point value, or a pixel tuple,
|
||||||
|
depending on the expression.
|
||||||
|
"""
|
||||||
|
|
||||||
|
args: dict[str, Any] = ops.copy()
|
||||||
|
args.update(_dict)
|
||||||
|
args.update(kw)
|
||||||
|
for k, v in args.items():
|
||||||
|
if hasattr(v, "im"):
|
||||||
|
args[k] = _Operand(v)
|
||||||
|
|
||||||
|
out = expression(args)
|
||||||
|
try:
|
||||||
|
return out.im
|
||||||
|
except AttributeError:
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def unsafe_eval(
|
||||||
|
expression: str,
|
||||||
|
_dict: dict[str, Any] = {},
|
||||||
|
**kw: Any,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Evaluates an image expression. This uses Python's ``eval()`` function to process
|
||||||
|
the expression string, and carries the security risks of doing so. It is not
|
||||||
|
recommended to process expressions without considering this.
|
||||||
|
:py:meth:`~lambda_eval` is a more secure alternative.
|
||||||
|
|
||||||
|
In the current version, :py:mod:`~PIL.ImageMath` only supports
|
||||||
|
single-layer images. To process multi-band images, use the
|
||||||
|
:py:meth:`~PIL.Image.Image.split` method or :py:func:`~PIL.Image.merge`
|
||||||
|
function.
|
||||||
|
|
||||||
:param expression: A string containing a Python-style expression.
|
:param expression: A string containing a Python-style expression.
|
||||||
:param options: Values to add to the evaluation context. You
|
:param options: Values to add to the evaluation context. You
|
||||||
|
@ -279,3 +326,32 @@ def eval(expression: str, _dict: dict[str, Any] = {}, **kw: Any) -> Any:
|
||||||
return out.im
|
return out.im
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def eval(
|
||||||
|
expression: str,
|
||||||
|
_dict: dict[str, Any] = {},
|
||||||
|
**kw: Any,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Evaluates an image expression.
|
||||||
|
|
||||||
|
Deprecated. Use lambda_eval() or unsafe_eval() instead.
|
||||||
|
|
||||||
|
:param expression: A string containing a Python-style expression.
|
||||||
|
:param options: Values to add to the evaluation context. You
|
||||||
|
can either use a dictionary, or one or more keyword
|
||||||
|
arguments.
|
||||||
|
:return: The evaluated expression. This is usually an image object, but can
|
||||||
|
also be an integer, a floating point value, or a pixel tuple,
|
||||||
|
depending on the expression.
|
||||||
|
|
||||||
|
.. deprecated:: 10.3.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
deprecate(
|
||||||
|
"ImageMath.eval",
|
||||||
|
12,
|
||||||
|
"ImageMath.lambda_eval or ImageMath.unsafe_eval",
|
||||||
|
)
|
||||||
|
return unsafe_eval(expression, _dict, **kw)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user