Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pillow-10.4.0-py3.8-linux-x86_64.egg/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

171 statements  

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 

18 

19import builtins 

20from types import CodeType 

21from typing import Any, Callable 

22 

23from . import Image, _imagingmath 

24from ._deprecate import deprecate 

25 

26 

27class _Operand: 

28 """Wraps an image operand, providing standard operators""" 

29 

30 def __init__(self, im: Image.Image): 

31 self.im = im 

32 

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) 

50 

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 im_1.load() 

63 try: 

64 op = getattr(_imagingmath, f"{op}_{im_1.mode}") 

65 except AttributeError as e: 

66 msg = f"bad operand type for '{op}'" 

67 raise TypeError(msg) from e 

68 _imagingmath.unop(op, out.im.id, im_1.im.id) 

69 else: 

70 # binary operation 

71 im_2 = self.__fixup(im2) 

72 if im_1.mode != im_2.mode: 

73 # convert both arguments to floating point 

74 if im_1.mode != "F": 

75 im_1 = im_1.convert("F") 

76 if im_2.mode != "F": 

77 im_2 = im_2.convert("F") 

78 if im_1.size != im_2.size: 

79 # crop both arguments to a common size 

80 size = ( 

81 min(im_1.size[0], im_2.size[0]), 

82 min(im_1.size[1], im_2.size[1]), 

83 ) 

84 if im_1.size != size: 

85 im_1 = im_1.crop((0, 0) + size) 

86 if im_2.size != size: 

87 im_2 = im_2.crop((0, 0) + size) 

88 out = Image.new(mode or im_1.mode, im_1.size, None) 

89 im_1.load() 

90 im_2.load() 

91 try: 

92 op = getattr(_imagingmath, f"{op}_{im_1.mode}") 

93 except AttributeError as e: 

94 msg = f"bad operand type for '{op}'" 

95 raise TypeError(msg) from e 

96 _imagingmath.binop(op, out.im.id, im_1.im.id, im_2.im.id) 

97 return _Operand(out) 

98 

99 # unary operators 

100 def __bool__(self) -> bool: 

101 # an image is "true" if it contains at least one non-zero pixel 

102 return self.im.getbbox() is not None 

103 

104 def __abs__(self) -> _Operand: 

105 return self.apply("abs", self) 

106 

107 def __pos__(self) -> _Operand: 

108 return self 

109 

110 def __neg__(self) -> _Operand: 

111 return self.apply("neg", self) 

112 

113 # binary operators 

114 def __add__(self, other: _Operand | float) -> _Operand: 

115 return self.apply("add", self, other) 

116 

117 def __radd__(self, other: _Operand | float) -> _Operand: 

118 return self.apply("add", other, self) 

119 

120 def __sub__(self, other: _Operand | float) -> _Operand: 

121 return self.apply("sub", self, other) 

122 

123 def __rsub__(self, other: _Operand | float) -> _Operand: 

124 return self.apply("sub", other, self) 

125 

126 def __mul__(self, other: _Operand | float) -> _Operand: 

127 return self.apply("mul", self, other) 

128 

129 def __rmul__(self, other: _Operand | float) -> _Operand: 

130 return self.apply("mul", other, self) 

131 

132 def __truediv__(self, other: _Operand | float) -> _Operand: 

133 return self.apply("div", self, other) 

134 

135 def __rtruediv__(self, other: _Operand | float) -> _Operand: 

136 return self.apply("div", other, self) 

137 

138 def __mod__(self, other: _Operand | float) -> _Operand: 

139 return self.apply("mod", self, other) 

140 

141 def __rmod__(self, other: _Operand | float) -> _Operand: 

142 return self.apply("mod", other, self) 

143 

144 def __pow__(self, other: _Operand | float) -> _Operand: 

145 return self.apply("pow", self, other) 

146 

147 def __rpow__(self, other: _Operand | float) -> _Operand: 

148 return self.apply("pow", other, self) 

149 

150 # bitwise 

151 def __invert__(self) -> _Operand: 

152 return self.apply("invert", self) 

153 

154 def __and__(self, other: _Operand | float) -> _Operand: 

155 return self.apply("and", self, other) 

156 

157 def __rand__(self, other: _Operand | float) -> _Operand: 

158 return self.apply("and", other, self) 

159 

160 def __or__(self, other: _Operand | float) -> _Operand: 

161 return self.apply("or", self, other) 

162 

163 def __ror__(self, other: _Operand | float) -> _Operand: 

164 return self.apply("or", other, self) 

165 

166 def __xor__(self, other: _Operand | float) -> _Operand: 

167 return self.apply("xor", self, other) 

168 

169 def __rxor__(self, other: _Operand | float) -> _Operand: 

170 return self.apply("xor", other, self) 

171 

172 def __lshift__(self, other: _Operand | float) -> _Operand: 

173 return self.apply("lshift", self, other) 

174 

175 def __rshift__(self, other: _Operand | float) -> _Operand: 

176 return self.apply("rshift", self, other) 

177 

178 # logical 

179 def __eq__(self, other): 

180 return self.apply("eq", self, other) 

181 

182 def __ne__(self, other): 

183 return self.apply("ne", self, other) 

184 

185 def __lt__(self, other: _Operand | float) -> _Operand: 

186 return self.apply("lt", self, other) 

187 

188 def __le__(self, other: _Operand | float) -> _Operand: 

189 return self.apply("le", self, other) 

190 

191 def __gt__(self, other: _Operand | float) -> _Operand: 

192 return self.apply("gt", self, other) 

193 

194 def __ge__(self, other: _Operand | float) -> _Operand: 

195 return self.apply("ge", self, other) 

196 

197 

198# conversions 

199def imagemath_int(self: _Operand) -> _Operand: 

200 return _Operand(self.im.convert("I")) 

201 

202 

203def imagemath_float(self: _Operand) -> _Operand: 

204 return _Operand(self.im.convert("F")) 

205 

206 

207# logical 

208def imagemath_equal(self: _Operand, other: _Operand | float | None) -> _Operand: 

209 return self.apply("eq", self, other, mode="I") 

210 

211 

212def imagemath_notequal(self: _Operand, other: _Operand | float | None) -> _Operand: 

213 return self.apply("ne", self, other, mode="I") 

214 

215 

216def imagemath_min(self: _Operand, other: _Operand | float | None) -> _Operand: 

217 return self.apply("min", self, other) 

218 

219 

220def imagemath_max(self: _Operand, other: _Operand | float | None) -> _Operand: 

221 return self.apply("max", self, other) 

222 

223 

224def imagemath_convert(self: _Operand, mode: str) -> _Operand: 

225 return _Operand(self.im.convert(mode)) 

226 

227 

228ops = { 

229 "int": imagemath_int, 

230 "float": imagemath_float, 

231 "equal": imagemath_equal, 

232 "notequal": imagemath_notequal, 

233 "min": imagemath_min, 

234 "max": imagemath_max, 

235 "convert": imagemath_convert, 

236} 

237 

238 

239def lambda_eval( 

240 expression: Callable[[dict[str, Any]], Any], 

241 options: dict[str, Any] = {}, 

242 **kw: Any, 

243) -> Any: 

244 """ 

245 Returns the result of an image function. 

246 

247 :py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band 

248 images, use the :py:meth:`~PIL.Image.Image.split` method or 

249 :py:func:`~PIL.Image.merge` function. 

250 

251 :param expression: A function that receives a dictionary. 

252 :param options: Values to add to the function's dictionary. You 

253 can either use a dictionary, or one or more keyword 

254 arguments. 

255 :return: The expression result. This is usually an image object, but can 

256 also be an integer, a floating point value, or a pixel tuple, 

257 depending on the expression. 

258 """ 

259 

260 args: dict[str, Any] = ops.copy() 

261 args.update(options) 

262 args.update(kw) 

263 for k, v in args.items(): 

264 if hasattr(v, "im"): 

265 args[k] = _Operand(v) 

266 

267 out = expression(args) 

268 try: 

269 return out.im 

270 except AttributeError: 

271 return out 

272 

273 

274def unsafe_eval( 

275 expression: str, 

276 options: dict[str, Any] = {}, 

277 **kw: Any, 

278) -> Any: 

279 """ 

280 Evaluates an image expression. This uses Python's ``eval()`` function to process 

281 the expression string, and carries the security risks of doing so. It is not 

282 recommended to process expressions without considering this. 

283 :py:meth:`~lambda_eval` is a more secure alternative. 

284 

285 :py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band 

286 images, use the :py:meth:`~PIL.Image.Image.split` method or 

287 :py:func:`~PIL.Image.merge` function. 

288 

289 :param expression: A string containing a Python-style expression. 

290 :param options: Values to add to the evaluation context. You 

291 can either use a dictionary, or one or more keyword 

292 arguments. 

293 :return: The evaluated expression. This is usually an image object, but can 

294 also be an integer, a floating point value, or a pixel tuple, 

295 depending on the expression. 

296 """ 

297 

298 # build execution namespace 

299 args: dict[str, Any] = ops.copy() 

300 for k in list(options.keys()) + list(kw.keys()): 

301 if "__" in k or hasattr(builtins, k): 

302 msg = f"'{k}' not allowed" 

303 raise ValueError(msg) 

304 

305 args.update(options) 

306 args.update(kw) 

307 for k, v in args.items(): 

308 if hasattr(v, "im"): 

309 args[k] = _Operand(v) 

310 

311 compiled_code = compile(expression, "<string>", "eval") 

312 

313 def scan(code: CodeType) -> None: 

314 for const in code.co_consts: 

315 if type(const) is type(compiled_code): 

316 scan(const) 

317 

318 for name in code.co_names: 

319 if name not in args and name != "abs": 

320 msg = f"'{name}' not allowed" 

321 raise ValueError(msg) 

322 

323 scan(compiled_code) 

324 out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args) 

325 try: 

326 return out.im 

327 except AttributeError: 

328 return out 

329 

330 

331def eval( 

332 expression: str, 

333 _dict: dict[str, Any] = {}, 

334 **kw: Any, 

335) -> Any: 

336 """ 

337 Evaluates an image expression. 

338 

339 Deprecated. Use lambda_eval() or unsafe_eval() instead. 

340 

341 :param expression: A string containing a Python-style expression. 

342 :param _dict: Values to add to the evaluation context. You 

343 can either use a dictionary, or one or more keyword 

344 arguments. 

345 :return: The evaluated expression. This is usually an image object, but can 

346 also be an integer, a floating point value, or a pixel tuple, 

347 depending on the expression. 

348 

349 .. deprecated:: 10.3.0 

350 """ 

351 

352 deprecate( 

353 "ImageMath.eval", 

354 12, 

355 "ImageMath.lambda_eval or ImageMath.unsafe_eval", 

356 ) 

357 return unsafe_eval(expression, _dict, **kw)