Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/ImageMath.py: 30%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# The Python Imaging Library
3# $Id$
4#
5# a simple math add-on for the Python Imaging Library
6#
7# History:
8# 1999-02-15 fl Original PIL Plus release
9# 2005-05-05 fl Simplified and cleaned up for PIL 1.1.6
10# 2005-09-12 fl Fixed int() and float() for Python 2.4.1
11#
12# Copyright (c) 1999-2005 by Secret Labs AB
13# Copyright (c) 2005 by Fredrik Lundh
14#
15# See the README file for information on usage and redistribution.
16#
17from __future__ import annotations
19import builtins
20from types import CodeType
21from typing import Any, Callable
23from . import Image, _imagingmath
24from ._deprecate import deprecate
27class _Operand:
28 """Wraps an image operand, providing standard operators"""
30 def __init__(self, im: Image.Image):
31 self.im = im
33 def __fixup(self, im1: _Operand | float) -> Image.Image:
34 # convert image to suitable mode
35 if isinstance(im1, _Operand):
36 # argument was an image.
37 if im1.im.mode in ("1", "L"):
38 return im1.im.convert("I")
39 elif im1.im.mode in ("I", "F"):
40 return im1.im
41 else:
42 msg = f"unsupported mode: {im1.im.mode}"
43 raise ValueError(msg)
44 else:
45 # argument was a constant
46 if isinstance(im1, (int, float)) and self.im.mode in ("1", "L", "I"):
47 return Image.new("I", self.im.size, im1)
48 else:
49 return Image.new("F", self.im.size, im1)
51 def apply(
52 self,
53 op: str,
54 im1: _Operand | float,
55 im2: _Operand | float | None = None,
56 mode: str | None = None,
57 ) -> _Operand:
58 im_1 = self.__fixup(im1)
59 if im2 is None:
60 # unary operation
61 out = Image.new(mode or im_1.mode, im_1.size, None)
62 try:
63 op = getattr(_imagingmath, f"{op}_{im_1.mode}")
64 except AttributeError as e:
65 msg = f"bad operand type for '{op}'"
66 raise TypeError(msg) from e
67 _imagingmath.unop(op, out.getim(), im_1.getim())
68 else:
69 # binary operation
70 im_2 = self.__fixup(im2)
71 if im_1.mode != im_2.mode:
72 # convert both arguments to floating point
73 if im_1.mode != "F":
74 im_1 = im_1.convert("F")
75 if im_2.mode != "F":
76 im_2 = im_2.convert("F")
77 if im_1.size != im_2.size:
78 # crop both arguments to a common size
79 size = (
80 min(im_1.size[0], im_2.size[0]),
81 min(im_1.size[1], im_2.size[1]),
82 )
83 if im_1.size != size:
84 im_1 = im_1.crop((0, 0) + size)
85 if im_2.size != size:
86 im_2 = im_2.crop((0, 0) + size)
87 out = Image.new(mode or im_1.mode, im_1.size, None)
88 try:
89 op = getattr(_imagingmath, f"{op}_{im_1.mode}")
90 except AttributeError as e:
91 msg = f"bad operand type for '{op}'"
92 raise TypeError(msg) from e
93 _imagingmath.binop(op, out.getim(), im_1.getim(), im_2.getim())
94 return _Operand(out)
96 # unary operators
97 def __bool__(self) -> bool:
98 # an image is "true" if it contains at least one non-zero pixel
99 return self.im.getbbox() is not None
101 def __abs__(self) -> _Operand:
102 return self.apply("abs", self)
104 def __pos__(self) -> _Operand:
105 return self
107 def __neg__(self) -> _Operand:
108 return self.apply("neg", self)
110 # binary operators
111 def __add__(self, other: _Operand | float) -> _Operand:
112 return self.apply("add", self, other)
114 def __radd__(self, other: _Operand | float) -> _Operand:
115 return self.apply("add", other, self)
117 def __sub__(self, other: _Operand | float) -> _Operand:
118 return self.apply("sub", self, other)
120 def __rsub__(self, other: _Operand | float) -> _Operand:
121 return self.apply("sub", other, self)
123 def __mul__(self, other: _Operand | float) -> _Operand:
124 return self.apply("mul", self, other)
126 def __rmul__(self, other: _Operand | float) -> _Operand:
127 return self.apply("mul", other, self)
129 def __truediv__(self, other: _Operand | float) -> _Operand:
130 return self.apply("div", self, other)
132 def __rtruediv__(self, other: _Operand | float) -> _Operand:
133 return self.apply("div", other, self)
135 def __mod__(self, other: _Operand | float) -> _Operand:
136 return self.apply("mod", self, other)
138 def __rmod__(self, other: _Operand | float) -> _Operand:
139 return self.apply("mod", other, self)
141 def __pow__(self, other: _Operand | float) -> _Operand:
142 return self.apply("pow", self, other)
144 def __rpow__(self, other: _Operand | float) -> _Operand:
145 return self.apply("pow", other, self)
147 # bitwise
148 def __invert__(self) -> _Operand:
149 return self.apply("invert", self)
151 def __and__(self, other: _Operand | float) -> _Operand:
152 return self.apply("and", self, other)
154 def __rand__(self, other: _Operand | float) -> _Operand:
155 return self.apply("and", other, self)
157 def __or__(self, other: _Operand | float) -> _Operand:
158 return self.apply("or", self, other)
160 def __ror__(self, other: _Operand | float) -> _Operand:
161 return self.apply("or", other, self)
163 def __xor__(self, other: _Operand | float) -> _Operand:
164 return self.apply("xor", self, other)
166 def __rxor__(self, other: _Operand | float) -> _Operand:
167 return self.apply("xor", other, self)
169 def __lshift__(self, other: _Operand | float) -> _Operand:
170 return self.apply("lshift", self, other)
172 def __rshift__(self, other: _Operand | float) -> _Operand:
173 return self.apply("rshift", self, other)
175 # logical
176 def __eq__(self, other: _Operand | float) -> _Operand: # type: ignore[override]
177 return self.apply("eq", self, other)
179 def __ne__(self, other: _Operand | float) -> _Operand: # type: ignore[override]
180 return self.apply("ne", self, other)
182 def __lt__(self, other: _Operand | float) -> _Operand:
183 return self.apply("lt", self, other)
185 def __le__(self, other: _Operand | float) -> _Operand:
186 return self.apply("le", self, other)
188 def __gt__(self, other: _Operand | float) -> _Operand:
189 return self.apply("gt", self, other)
191 def __ge__(self, other: _Operand | float) -> _Operand:
192 return self.apply("ge", self, other)
195# conversions
196def imagemath_int(self: _Operand) -> _Operand:
197 return _Operand(self.im.convert("I"))
200def imagemath_float(self: _Operand) -> _Operand:
201 return _Operand(self.im.convert("F"))
204# logical
205def imagemath_equal(self: _Operand, other: _Operand | float | None) -> _Operand:
206 return self.apply("eq", self, other, mode="I")
209def imagemath_notequal(self: _Operand, other: _Operand | float | None) -> _Operand:
210 return self.apply("ne", self, other, mode="I")
213def imagemath_min(self: _Operand, other: _Operand | float | None) -> _Operand:
214 return self.apply("min", self, other)
217def imagemath_max(self: _Operand, other: _Operand | float | None) -> _Operand:
218 return self.apply("max", self, other)
221def imagemath_convert(self: _Operand, mode: str) -> _Operand:
222 return _Operand(self.im.convert(mode))
225ops = {
226 "int": imagemath_int,
227 "float": imagemath_float,
228 "equal": imagemath_equal,
229 "notequal": imagemath_notequal,
230 "min": imagemath_min,
231 "max": imagemath_max,
232 "convert": imagemath_convert,
233}
236def lambda_eval(
237 expression: Callable[[dict[str, Any]], Any],
238 options: dict[str, Any] = {},
239 **kw: Any,
240) -> Any:
241 """
242 Returns the result of an image function.
244 :py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band
245 images, use the :py:meth:`~PIL.Image.Image.split` method or
246 :py:func:`~PIL.Image.merge` function.
248 :param expression: A function that receives a dictionary.
249 :param options: Values to add to the function's dictionary. Deprecated.
250 You can instead use one or more keyword arguments.
251 :param **kw: Values to add to the function's dictionary.
252 :return: The expression result. This is usually an image object, but can
253 also be an integer, a floating point value, or a pixel tuple,
254 depending on the expression.
255 """
257 if options:
258 deprecate(
259 "ImageMath.lambda_eval options",
260 12,
261 "ImageMath.lambda_eval keyword arguments",
262 )
264 args: dict[str, Any] = ops.copy()
265 args.update(options)
266 args.update(kw)
267 for k, v in args.items():
268 if isinstance(v, Image.Image):
269 args[k] = _Operand(v)
271 out = expression(args)
272 try:
273 return out.im
274 except AttributeError:
275 return out
278def unsafe_eval(
279 expression: str,
280 options: dict[str, Any] = {},
281 **kw: Any,
282) -> Any:
283 """
284 Evaluates an image expression. This uses Python's ``eval()`` function to process
285 the expression string, and carries the security risks of doing so. It is not
286 recommended to process expressions without considering this.
287 :py:meth:`~lambda_eval` is a more secure alternative.
289 :py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band
290 images, use the :py:meth:`~PIL.Image.Image.split` method or
291 :py:func:`~PIL.Image.merge` function.
293 :param expression: A string containing a Python-style expression.
294 :param options: Values to add to the evaluation context. Deprecated.
295 You can instead use one or more keyword arguments.
296 :param **kw: Values to add to the evaluation context.
297 :return: The evaluated expression. This is usually an image object, but can
298 also be an integer, a floating point value, or a pixel tuple,
299 depending on the expression.
300 """
302 if options:
303 deprecate(
304 "ImageMath.unsafe_eval options",
305 12,
306 "ImageMath.unsafe_eval keyword arguments",
307 )
309 # build execution namespace
310 args: dict[str, Any] = ops.copy()
311 for k in list(options.keys()) + list(kw.keys()):
312 if "__" in k or hasattr(builtins, k):
313 msg = f"'{k}' not allowed"
314 raise ValueError(msg)
316 args.update(options)
317 args.update(kw)
318 for k, v in args.items():
319 if isinstance(v, Image.Image):
320 args[k] = _Operand(v)
322 compiled_code = compile(expression, "<string>", "eval")
324 def scan(code: CodeType) -> None:
325 for const in code.co_consts:
326 if type(const) is type(compiled_code):
327 scan(const)
329 for name in code.co_names:
330 if name not in args and name != "abs":
331 msg = f"'{name}' not allowed"
332 raise ValueError(msg)
334 scan(compiled_code)
335 out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args)
336 try:
337 return out.im
338 except AttributeError:
339 return out
342def eval(
343 expression: str,
344 _dict: dict[str, Any] = {},
345 **kw: Any,
346) -> Any:
347 """
348 Evaluates an image expression.
350 Deprecated. Use lambda_eval() or unsafe_eval() instead.
352 :param expression: A string containing a Python-style expression.
353 :param _dict: Values to add to the evaluation context. You
354 can either use a dictionary, or one or more keyword
355 arguments.
356 :return: The evaluated expression. This is usually an image object, but can
357 also be an integer, a floating point value, or a pixel tuple,
358 depending on the expression.
360 .. deprecated:: 10.3.0
361 """
363 deprecate(
364 "ImageMath.eval",
365 12,
366 "ImageMath.lambda_eval or ImageMath.unsafe_eval",
367 )
368 return unsafe_eval(expression, _dict, **kw)