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

173 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 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) 

95 

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 

100 

101 def __abs__(self) -> _Operand: 

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

103 

104 def __pos__(self) -> _Operand: 

105 return self 

106 

107 def __neg__(self) -> _Operand: 

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

109 

110 # binary operators 

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

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

113 

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

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

116 

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

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

119 

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

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

122 

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

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

125 

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

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

128 

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

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

131 

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

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

134 

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

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

137 

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

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

140 

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

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

143 

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

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

146 

147 # bitwise 

148 def __invert__(self) -> _Operand: 

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

150 

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

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

153 

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

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

156 

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

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

159 

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

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

162 

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

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

165 

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

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

168 

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

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

171 

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

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

174 

175 # logical 

176 def __eq__(self, other: _Operand | float) -> _Operand: # type: ignore[override] 

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

178 

179 def __ne__(self, other: _Operand | float) -> _Operand: # type: ignore[override] 

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

181 

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

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

184 

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

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

187 

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

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

190 

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

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

193 

194 

195# conversions 

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

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

198 

199 

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

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

202 

203 

204# logical 

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

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

207 

208 

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

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

211 

212 

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

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

215 

216 

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

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

219 

220 

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

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

223 

224 

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} 

234 

235 

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. 

243 

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. 

247 

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 """ 

256 

257 if options: 

258 deprecate( 

259 "ImageMath.lambda_eval options", 

260 12, 

261 "ImageMath.lambda_eval keyword arguments", 

262 ) 

263 

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) 

270 

271 out = expression(args) 

272 try: 

273 return out.im 

274 except AttributeError: 

275 return out 

276 

277 

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. 

288 

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. 

292 

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 """ 

301 

302 if options: 

303 deprecate( 

304 "ImageMath.unsafe_eval options", 

305 12, 

306 "ImageMath.unsafe_eval keyword arguments", 

307 ) 

308 

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) 

315 

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) 

321 

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

323 

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

325 for const in code.co_consts: 

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

327 scan(const) 

328 

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) 

333 

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 

340 

341 

342def eval( 

343 expression: str, 

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

345 **kw: Any, 

346) -> Any: 

347 """ 

348 Evaluates an image expression. 

349 

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

351 

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. 

359 

360 .. deprecated:: 10.3.0 

361 """ 

362 

363 deprecate( 

364 "ImageMath.eval", 

365 12, 

366 "ImageMath.lambda_eval or ImageMath.unsafe_eval", 

367 ) 

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