Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/ImageOps.py: 6%

251 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# 

2# The Python Imaging Library. 

3# $Id$ 

4# 

5# standard image operations 

6# 

7# History: 

8# 2001-10-20 fl Created 

9# 2001-10-23 fl Added autocontrast operator 

10# 2001-12-18 fl Added Kevin's fit operator 

11# 2004-03-14 fl Fixed potential division by zero in equalize 

12# 2005-05-05 fl Fixed equalize for low number of values 

13# 

14# Copyright (c) 2001-2004 by Secret Labs AB 

15# Copyright (c) 2001-2004 by Fredrik Lundh 

16# 

17# See the README file for information on usage and redistribution. 

18# 

19 

20import functools 

21import operator 

22import re 

23 

24from . import Image 

25 

26# 

27# helpers 

28 

29 

30def _border(border): 

31 if isinstance(border, tuple): 

32 if len(border) == 2: 

33 left, top = right, bottom = border 

34 elif len(border) == 4: 

35 left, top, right, bottom = border 

36 else: 

37 left = top = right = bottom = border 

38 return left, top, right, bottom 

39 

40 

41def _color(color, mode): 

42 if isinstance(color, str): 

43 from . import ImageColor 

44 

45 color = ImageColor.getcolor(color, mode) 

46 return color 

47 

48 

49def _lut(image, lut): 

50 if image.mode == "P": 

51 # FIXME: apply to lookup table, not image data 

52 raise NotImplementedError("mode P support coming soon") 

53 elif image.mode in ("L", "RGB"): 

54 if image.mode == "RGB" and len(lut) == 256: 

55 lut = lut + lut + lut 

56 return image.point(lut) 

57 else: 

58 raise OSError("not supported for this image mode") 

59 

60 

61# 

62# actions 

63 

64 

65def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): 

66 """ 

67 Maximize (normalize) image contrast. This function calculates a 

68 histogram of the input image (or mask region), removes ``cutoff`` percent of the 

69 lightest and darkest pixels from the histogram, and remaps the image 

70 so that the darkest pixel becomes black (0), and the lightest 

71 becomes white (255). 

72 

73 :param image: The image to process. 

74 :param cutoff: The percent to cut off from the histogram on the low and 

75 high ends. Either a tuple of (low, high), or a single 

76 number for both. 

77 :param ignore: The background pixel value (use None for no background). 

78 :param mask: Histogram used in contrast operation is computed using pixels 

79 within the mask. If no mask is given the entire image is used 

80 for histogram computation. 

81 :param preserve_tone: Preserve image tone in Photoshop-like style autocontrast. 

82 

83 .. versionadded:: 8.2.0 

84 

85 :return: An image. 

86 """ 

87 if preserve_tone: 

88 histogram = image.convert("L").histogram(mask) 

89 else: 

90 histogram = image.histogram(mask) 

91 

92 lut = [] 

93 for layer in range(0, len(histogram), 256): 

94 h = histogram[layer : layer + 256] 

95 if ignore is not None: 

96 # get rid of outliers 

97 try: 

98 h[ignore] = 0 

99 except TypeError: 

100 # assume sequence 

101 for ix in ignore: 

102 h[ix] = 0 

103 if cutoff: 

104 # cut off pixels from both ends of the histogram 

105 if not isinstance(cutoff, tuple): 

106 cutoff = (cutoff, cutoff) 

107 # get number of pixels 

108 n = 0 

109 for ix in range(256): 

110 n = n + h[ix] 

111 # remove cutoff% pixels from the low end 

112 cut = n * cutoff[0] // 100 

113 for lo in range(256): 

114 if cut > h[lo]: 

115 cut = cut - h[lo] 

116 h[lo] = 0 

117 else: 

118 h[lo] -= cut 

119 cut = 0 

120 if cut <= 0: 

121 break 

122 # remove cutoff% samples from the high end 

123 cut = n * cutoff[1] // 100 

124 for hi in range(255, -1, -1): 

125 if cut > h[hi]: 

126 cut = cut - h[hi] 

127 h[hi] = 0 

128 else: 

129 h[hi] -= cut 

130 cut = 0 

131 if cut <= 0: 

132 break 

133 # find lowest/highest samples after preprocessing 

134 for lo in range(256): 

135 if h[lo]: 

136 break 

137 for hi in range(255, -1, -1): 

138 if h[hi]: 

139 break 

140 if hi <= lo: 

141 # don't bother 

142 lut.extend(list(range(256))) 

143 else: 

144 scale = 255.0 / (hi - lo) 

145 offset = -lo * scale 

146 for ix in range(256): 

147 ix = int(ix * scale + offset) 

148 if ix < 0: 

149 ix = 0 

150 elif ix > 255: 

151 ix = 255 

152 lut.append(ix) 

153 return _lut(image, lut) 

154 

155 

156def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoint=127): 

157 """ 

158 Colorize grayscale image. 

159 This function calculates a color wedge which maps all black pixels in 

160 the source image to the first color and all white pixels to the 

161 second color. If ``mid`` is specified, it uses three-color mapping. 

162 The ``black`` and ``white`` arguments should be RGB tuples or color names; 

163 optionally you can use three-color mapping by also specifying ``mid``. 

164 Mapping positions for any of the colors can be specified 

165 (e.g. ``blackpoint``), where these parameters are the integer 

166 value corresponding to where the corresponding color should be mapped. 

167 These parameters must have logical order, such that 

168 ``blackpoint <= midpoint <= whitepoint`` (if ``mid`` is specified). 

169 

170 :param image: The image to colorize. 

171 :param black: The color to use for black input pixels. 

172 :param white: The color to use for white input pixels. 

173 :param mid: The color to use for midtone input pixels. 

174 :param blackpoint: an int value [0, 255] for the black mapping. 

175 :param whitepoint: an int value [0, 255] for the white mapping. 

176 :param midpoint: an int value [0, 255] for the midtone mapping. 

177 :return: An image. 

178 """ 

179 

180 # Initial asserts 

181 assert image.mode == "L" 

182 if mid is None: 

183 assert 0 <= blackpoint <= whitepoint <= 255 

184 else: 

185 assert 0 <= blackpoint <= midpoint <= whitepoint <= 255 

186 

187 # Define colors from arguments 

188 black = _color(black, "RGB") 

189 white = _color(white, "RGB") 

190 if mid is not None: 

191 mid = _color(mid, "RGB") 

192 

193 # Empty lists for the mapping 

194 red = [] 

195 green = [] 

196 blue = [] 

197 

198 # Create the low-end values 

199 for i in range(0, blackpoint): 

200 red.append(black[0]) 

201 green.append(black[1]) 

202 blue.append(black[2]) 

203 

204 # Create the mapping (2-color) 

205 if mid is None: 

206 

207 range_map = range(0, whitepoint - blackpoint) 

208 

209 for i in range_map: 

210 red.append(black[0] + i * (white[0] - black[0]) // len(range_map)) 

211 green.append(black[1] + i * (white[1] - black[1]) // len(range_map)) 

212 blue.append(black[2] + i * (white[2] - black[2]) // len(range_map)) 

213 

214 # Create the mapping (3-color) 

215 else: 

216 

217 range_map1 = range(0, midpoint - blackpoint) 

218 range_map2 = range(0, whitepoint - midpoint) 

219 

220 for i in range_map1: 

221 red.append(black[0] + i * (mid[0] - black[0]) // len(range_map1)) 

222 green.append(black[1] + i * (mid[1] - black[1]) // len(range_map1)) 

223 blue.append(black[2] + i * (mid[2] - black[2]) // len(range_map1)) 

224 for i in range_map2: 

225 red.append(mid[0] + i * (white[0] - mid[0]) // len(range_map2)) 

226 green.append(mid[1] + i * (white[1] - mid[1]) // len(range_map2)) 

227 blue.append(mid[2] + i * (white[2] - mid[2]) // len(range_map2)) 

228 

229 # Create the high-end values 

230 for i in range(0, 256 - whitepoint): 

231 red.append(white[0]) 

232 green.append(white[1]) 

233 blue.append(white[2]) 

234 

235 # Return converted image 

236 image = image.convert("RGB") 

237 return _lut(image, red + green + blue) 

238 

239 

240def contain(image, size, method=Image.Resampling.BICUBIC): 

241 """ 

242 Returns a resized version of the image, set to the maximum width and height 

243 within the requested size, while maintaining the original aspect ratio. 

244 

245 :param image: The image to resize and crop. 

246 :param size: The requested output size in pixels, given as a 

247 (width, height) tuple. 

248 :param method: Resampling method to use. Default is 

249 :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. 

250 :return: An image. 

251 """ 

252 

253 im_ratio = image.width / image.height 

254 dest_ratio = size[0] / size[1] 

255 

256 if im_ratio != dest_ratio: 

257 if im_ratio > dest_ratio: 

258 new_height = int(image.height / image.width * size[0]) 

259 if new_height != size[1]: 

260 size = (size[0], new_height) 

261 else: 

262 new_width = int(image.width / image.height * size[1]) 

263 if new_width != size[0]: 

264 size = (new_width, size[1]) 

265 return image.resize(size, resample=method) 

266 

267 

268def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5, 0.5)): 

269 """ 

270 Returns a resized and padded version of the image, expanded to fill the 

271 requested aspect ratio and size. 

272 

273 :param image: The image to resize and crop. 

274 :param size: The requested output size in pixels, given as a 

275 (width, height) tuple. 

276 :param method: Resampling method to use. Default is 

277 :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. 

278 :param color: The background color of the padded image. 

279 :param centering: Control the position of the original image within the 

280 padded version. 

281 

282 (0.5, 0.5) will keep the image centered 

283 (0, 0) will keep the image aligned to the top left 

284 (1, 1) will keep the image aligned to the bottom 

285 right 

286 :return: An image. 

287 """ 

288 

289 resized = contain(image, size, method) 

290 if resized.size == size: 

291 out = resized 

292 else: 

293 out = Image.new(image.mode, size, color) 

294 if resized.width != size[0]: 

295 x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) 

296 out.paste(resized, (x, 0)) 

297 else: 

298 y = int((size[1] - resized.height) * max(0, min(centering[1], 1))) 

299 out.paste(resized, (0, y)) 

300 return out 

301 

302 

303def crop(image, border=0): 

304 """ 

305 Remove border from image. The same amount of pixels are removed 

306 from all four sides. This function works on all image modes. 

307 

308 .. seealso:: :py:meth:`~PIL.Image.Image.crop` 

309 

310 :param image: The image to crop. 

311 :param border: The number of pixels to remove. 

312 :return: An image. 

313 """ 

314 left, top, right, bottom = _border(border) 

315 return image.crop((left, top, image.size[0] - right, image.size[1] - bottom)) 

316 

317 

318def scale(image, factor, resample=Image.Resampling.BICUBIC): 

319 """ 

320 Returns a rescaled image by a specific factor given in parameter. 

321 A factor greater than 1 expands the image, between 0 and 1 contracts the 

322 image. 

323 

324 :param image: The image to rescale. 

325 :param factor: The expansion factor, as a float. 

326 :param resample: Resampling method to use. Default is 

327 :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. 

328 :returns: An :py:class:`~PIL.Image.Image` object. 

329 """ 

330 if factor == 1: 

331 return image.copy() 

332 elif factor <= 0: 

333 raise ValueError("the factor must be greater than 0") 

334 else: 

335 size = (round(factor * image.width), round(factor * image.height)) 

336 return image.resize(size, resample) 

337 

338 

339def deform(image, deformer, resample=Image.Resampling.BILINEAR): 

340 """ 

341 Deform the image. 

342 

343 :param image: The image to deform. 

344 :param deformer: A deformer object. Any object that implements a 

345 ``getmesh`` method can be used. 

346 :param resample: An optional resampling filter. Same values possible as 

347 in the PIL.Image.transform function. 

348 :return: An image. 

349 """ 

350 return image.transform( 

351 image.size, Image.Transform.MESH, deformer.getmesh(image), resample 

352 ) 

353 

354 

355def equalize(image, mask=None): 

356 """ 

357 Equalize the image histogram. This function applies a non-linear 

358 mapping to the input image, in order to create a uniform 

359 distribution of grayscale values in the output image. 

360 

361 :param image: The image to equalize. 

362 :param mask: An optional mask. If given, only the pixels selected by 

363 the mask are included in the analysis. 

364 :return: An image. 

365 """ 

366 if image.mode == "P": 

367 image = image.convert("RGB") 

368 h = image.histogram(mask) 

369 lut = [] 

370 for b in range(0, len(h), 256): 

371 histo = [_f for _f in h[b : b + 256] if _f] 

372 if len(histo) <= 1: 

373 lut.extend(list(range(256))) 

374 else: 

375 step = (functools.reduce(operator.add, histo) - histo[-1]) // 255 

376 if not step: 

377 lut.extend(list(range(256))) 

378 else: 

379 n = step // 2 

380 for i in range(256): 

381 lut.append(n // step) 

382 n = n + h[i + b] 

383 return _lut(image, lut) 

384 

385 

386def expand(image, border=0, fill=0): 

387 """ 

388 Add border to the image 

389 

390 :param image: The image to expand. 

391 :param border: Border width, in pixels. 

392 :param fill: Pixel fill value (a color value). Default is 0 (black). 

393 :return: An image. 

394 """ 

395 left, top, right, bottom = _border(border) 

396 width = left + image.size[0] + right 

397 height = top + image.size[1] + bottom 

398 color = _color(fill, image.mode) 

399 if image.mode == "P" and image.palette: 

400 image.load() 

401 palette = image.palette.copy() 

402 if isinstance(color, tuple): 

403 color = palette.getcolor(color) 

404 else: 

405 palette = None 

406 out = Image.new(image.mode, (width, height), color) 

407 if palette: 

408 out.putpalette(palette.palette) 

409 out.paste(image, (left, top)) 

410 return out 

411 

412 

413def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): 

414 """ 

415 Returns a resized and cropped version of the image, cropped to the 

416 requested aspect ratio and size. 

417 

418 This function was contributed by Kevin Cazabon. 

419 

420 :param image: The image to resize and crop. 

421 :param size: The requested output size in pixels, given as a 

422 (width, height) tuple. 

423 :param method: Resampling method to use. Default is 

424 :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. 

425 :param bleed: Remove a border around the outside of the image from all 

426 four edges. The value is a decimal percentage (use 0.01 for 

427 one percent). The default value is 0 (no border). 

428 Cannot be greater than or equal to 0.5. 

429 :param centering: Control the cropping position. Use (0.5, 0.5) for 

430 center cropping (e.g. if cropping the width, take 50% off 

431 of the left side, and therefore 50% off the right side). 

432 (0.0, 0.0) will crop from the top left corner (i.e. if 

433 cropping the width, take all of the crop off of the right 

434 side, and if cropping the height, take all of it off the 

435 bottom). (1.0, 0.0) will crop from the bottom left 

436 corner, etc. (i.e. if cropping the width, take all of the 

437 crop off the left side, and if cropping the height take 

438 none from the top, and therefore all off the bottom). 

439 :return: An image. 

440 """ 

441 

442 # by Kevin Cazabon, Feb 17/2000 

443 # kevin@cazabon.com 

444 # https://www.cazabon.com 

445 

446 # ensure centering is mutable 

447 centering = list(centering) 

448 

449 if not 0.0 <= centering[0] <= 1.0: 

450 centering[0] = 0.5 

451 if not 0.0 <= centering[1] <= 1.0: 

452 centering[1] = 0.5 

453 

454 if not 0.0 <= bleed < 0.5: 

455 bleed = 0.0 

456 

457 # calculate the area to use for resizing and cropping, subtracting 

458 # the 'bleed' around the edges 

459 

460 # number of pixels to trim off on Top and Bottom, Left and Right 

461 bleed_pixels = (bleed * image.size[0], bleed * image.size[1]) 

462 

463 live_size = ( 

464 image.size[0] - bleed_pixels[0] * 2, 

465 image.size[1] - bleed_pixels[1] * 2, 

466 ) 

467 

468 # calculate the aspect ratio of the live_size 

469 live_size_ratio = live_size[0] / live_size[1] 

470 

471 # calculate the aspect ratio of the output image 

472 output_ratio = size[0] / size[1] 

473 

474 # figure out if the sides or top/bottom will be cropped off 

475 if live_size_ratio == output_ratio: 

476 # live_size is already the needed ratio 

477 crop_width = live_size[0] 

478 crop_height = live_size[1] 

479 elif live_size_ratio >= output_ratio: 

480 # live_size is wider than what's needed, crop the sides 

481 crop_width = output_ratio * live_size[1] 

482 crop_height = live_size[1] 

483 else: 

484 # live_size is taller than what's needed, crop the top and bottom 

485 crop_width = live_size[0] 

486 crop_height = live_size[0] / output_ratio 

487 

488 # make the crop 

489 crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering[0] 

490 crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering[1] 

491 

492 crop = (crop_left, crop_top, crop_left + crop_width, crop_top + crop_height) 

493 

494 # resize the image and return it 

495 return image.resize(size, method, box=crop) 

496 

497 

498def flip(image): 

499 """ 

500 Flip the image vertically (top to bottom). 

501 

502 :param image: The image to flip. 

503 :return: An image. 

504 """ 

505 return image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) 

506 

507 

508def grayscale(image): 

509 """ 

510 Convert the image to grayscale. 

511 

512 :param image: The image to convert. 

513 :return: An image. 

514 """ 

515 return image.convert("L") 

516 

517 

518def invert(image): 

519 """ 

520 Invert (negate) the image. 

521 

522 :param image: The image to invert. 

523 :return: An image. 

524 """ 

525 lut = [] 

526 for i in range(256): 

527 lut.append(255 - i) 

528 return image.point(lut) if image.mode == "1" else _lut(image, lut) 

529 

530 

531def mirror(image): 

532 """ 

533 Flip image horizontally (left to right). 

534 

535 :param image: The image to mirror. 

536 :return: An image. 

537 """ 

538 return image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) 

539 

540 

541def posterize(image, bits): 

542 """ 

543 Reduce the number of bits for each color channel. 

544 

545 :param image: The image to posterize. 

546 :param bits: The number of bits to keep for each channel (1-8). 

547 :return: An image. 

548 """ 

549 lut = [] 

550 mask = ~(2 ** (8 - bits) - 1) 

551 for i in range(256): 

552 lut.append(i & mask) 

553 return _lut(image, lut) 

554 

555 

556def solarize(image, threshold=128): 

557 """ 

558 Invert all pixel values above a threshold. 

559 

560 :param image: The image to solarize. 

561 :param threshold: All pixels above this greyscale level are inverted. 

562 :return: An image. 

563 """ 

564 lut = [] 

565 for i in range(256): 

566 if i < threshold: 

567 lut.append(i) 

568 else: 

569 lut.append(255 - i) 

570 return _lut(image, lut) 

571 

572 

573def exif_transpose(image): 

574 """ 

575 If an image has an EXIF Orientation tag, return a new image that is 

576 transposed accordingly. Otherwise, return a copy of the image. 

577 

578 :param image: The image to transpose. 

579 :return: An image. 

580 """ 

581 exif = image.getexif() 

582 orientation = exif.get(0x0112) 

583 method = { 

584 2: Image.Transpose.FLIP_LEFT_RIGHT, 

585 3: Image.Transpose.ROTATE_180, 

586 4: Image.Transpose.FLIP_TOP_BOTTOM, 

587 5: Image.Transpose.TRANSPOSE, 

588 6: Image.Transpose.ROTATE_270, 

589 7: Image.Transpose.TRANSVERSE, 

590 8: Image.Transpose.ROTATE_90, 

591 }.get(orientation) 

592 if method is not None: 

593 transposed_image = image.transpose(method) 

594 transposed_exif = transposed_image.getexif() 

595 if 0x0112 in transposed_exif: 

596 del transposed_exif[0x0112] 

597 if "exif" in transposed_image.info: 

598 transposed_image.info["exif"] = transposed_exif.tobytes() 

599 elif "Raw profile type exif" in transposed_image.info: 

600 transposed_image.info[ 

601 "Raw profile type exif" 

602 ] = transposed_exif.tobytes().hex() 

603 elif "XML:com.adobe.xmp" in transposed_image.info: 

604 transposed_image.info["XML:com.adobe.xmp"] = re.sub( 

605 r'tiff:Orientation="([0-9])"', 

606 "", 

607 transposed_image.info["XML:com.adobe.xmp"], 

608 ) 

609 return transposed_image 

610 return image.copy()