Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/ImageOps.py: 18%

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

292 statements  

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# 

19from __future__ import annotations 

20 

21import functools 

22import operator 

23import re 

24from collections.abc import Sequence 

25from typing import Literal, Protocol, cast, overload 

26 

27from . import ExifTags, Image, ImagePalette 

28 

29# 

30# helpers 

31 

32 

33def _border(border: int | tuple[int, ...]) -> tuple[int, int, int, int]: 

34 if isinstance(border, tuple): 

35 if len(border) == 2: 

36 left, top = right, bottom = border 

37 elif len(border) == 4: 

38 left, top, right, bottom = border 

39 else: 

40 msg = "border must be an integer, or a tuple of two or four elements" 

41 raise ValueError(msg) 

42 else: 

43 left = top = right = bottom = border 

44 return left, top, right, bottom 

45 

46 

47def _color(color: str | int | tuple[int, ...], mode: str) -> int | tuple[int, ...]: 

48 if isinstance(color, str): 

49 from . import ImageColor 

50 

51 color = ImageColor.getcolor(color, mode) 

52 return color 

53 

54 

55def _lut(image: Image.Image, lut: list[int]) -> Image.Image: 

56 if image.mode == "P": 

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

58 msg = "mode P support coming soon" 

59 raise NotImplementedError(msg) 

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

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

62 lut = lut + lut + lut 

63 return image.point(lut) 

64 else: 

65 msg = f"not supported for mode {image.mode}" 

66 raise OSError(msg) 

67 

68 

69# 

70# actions 

71 

72 

73def autocontrast( 

74 image: Image.Image, 

75 cutoff: float | tuple[float, float] = 0, 

76 ignore: int | Sequence[int] | None = None, 

77 mask: Image.Image | None = None, 

78 preserve_tone: bool = False, 

79) -> Image.Image: 

80 """ 

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

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

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

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

85 becomes white (255). 

86 

87 :param image: The image to process. 

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

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

90 number for both. 

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

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

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

94 for histogram computation. 

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

96 

97 .. versionadded:: 8.2.0 

98 

99 :return: An image. 

100 """ 

101 if preserve_tone: 

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

103 else: 

104 histogram = image.histogram(mask) 

105 

106 lut = [] 

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

108 h = histogram[layer : layer + 256] 

109 if ignore is not None: 

110 # get rid of outliers 

111 if isinstance(ignore, int): 

112 h[ignore] = 0 

113 else: 

114 for ix in ignore: 

115 h[ix] = 0 

116 if cutoff: 

117 # cut off pixels from both ends of the histogram 

118 if not isinstance(cutoff, tuple): 

119 cutoff = (cutoff, cutoff) 

120 # get number of pixels 

121 n = 0 

122 for ix in range(256): 

123 n = n + h[ix] 

124 # remove cutoff% pixels from the low end 

125 cut = int(n * cutoff[0] // 100) 

126 for lo in range(256): 

127 if cut > h[lo]: 

128 cut = cut - h[lo] 

129 h[lo] = 0 

130 else: 

131 h[lo] -= cut 

132 cut = 0 

133 if cut <= 0: 

134 break 

135 # remove cutoff% samples from the high end 

136 cut = int(n * cutoff[1] // 100) 

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

138 if cut > h[hi]: 

139 cut = cut - h[hi] 

140 h[hi] = 0 

141 else: 

142 h[hi] -= cut 

143 cut = 0 

144 if cut <= 0: 

145 break 

146 # find lowest/highest samples after preprocessing 

147 for lo in range(256): 

148 if h[lo]: 

149 break 

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

151 if h[hi]: 

152 break 

153 if hi <= lo: 

154 # don't bother 

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

156 else: 

157 scale = 255.0 / (hi - lo) 

158 offset = -lo * scale 

159 for ix in range(256): 

160 ix = int(ix * scale + offset) 

161 if ix < 0: 

162 ix = 0 

163 elif ix > 255: 

164 ix = 255 

165 lut.append(ix) 

166 return _lut(image, lut) 

167 

168 

169def colorize( 

170 image: Image.Image, 

171 black: str | tuple[int, ...], 

172 white: str | tuple[int, ...], 

173 mid: str | int | tuple[int, ...] | None = None, 

174 blackpoint: int = 0, 

175 whitepoint: int = 255, 

176 midpoint: int = 127, 

177) -> Image.Image: 

178 """ 

179 Colorize grayscale image. 

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

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

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

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

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

185 Mapping positions for any of the colors can be specified 

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

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

188 These parameters must have logical order, such that 

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

190 

191 :param image: The image to colorize. 

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

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

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

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

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

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

198 :return: An image. 

199 """ 

200 

201 # Initial asserts 

202 assert image.mode == "L" 

203 if mid is None: 

204 assert 0 <= blackpoint <= whitepoint <= 255 

205 else: 

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

207 

208 # Define colors from arguments 

209 rgb_black = cast(Sequence[int], _color(black, "RGB")) 

210 rgb_white = cast(Sequence[int], _color(white, "RGB")) 

211 rgb_mid = cast(Sequence[int], _color(mid, "RGB")) if mid is not None else None 

212 

213 # Empty lists for the mapping 

214 red = [] 

215 green = [] 

216 blue = [] 

217 

218 # Create the low-end values 

219 for i in range(blackpoint): 

220 red.append(rgb_black[0]) 

221 green.append(rgb_black[1]) 

222 blue.append(rgb_black[2]) 

223 

224 # Create the mapping (2-color) 

225 if rgb_mid is None: 

226 range_map = range(whitepoint - blackpoint) 

227 

228 for i in range_map: 

229 red.append( 

230 rgb_black[0] + i * (rgb_white[0] - rgb_black[0]) // len(range_map) 

231 ) 

232 green.append( 

233 rgb_black[1] + i * (rgb_white[1] - rgb_black[1]) // len(range_map) 

234 ) 

235 blue.append( 

236 rgb_black[2] + i * (rgb_white[2] - rgb_black[2]) // len(range_map) 

237 ) 

238 

239 # Create the mapping (3-color) 

240 else: 

241 range_map1 = range(midpoint - blackpoint) 

242 range_map2 = range(whitepoint - midpoint) 

243 

244 for i in range_map1: 

245 red.append( 

246 rgb_black[0] + i * (rgb_mid[0] - rgb_black[0]) // len(range_map1) 

247 ) 

248 green.append( 

249 rgb_black[1] + i * (rgb_mid[1] - rgb_black[1]) // len(range_map1) 

250 ) 

251 blue.append( 

252 rgb_black[2] + i * (rgb_mid[2] - rgb_black[2]) // len(range_map1) 

253 ) 

254 for i in range_map2: 

255 red.append(rgb_mid[0] + i * (rgb_white[0] - rgb_mid[0]) // len(range_map2)) 

256 green.append( 

257 rgb_mid[1] + i * (rgb_white[1] - rgb_mid[1]) // len(range_map2) 

258 ) 

259 blue.append(rgb_mid[2] + i * (rgb_white[2] - rgb_mid[2]) // len(range_map2)) 

260 

261 # Create the high-end values 

262 for i in range(256 - whitepoint): 

263 red.append(rgb_white[0]) 

264 green.append(rgb_white[1]) 

265 blue.append(rgb_white[2]) 

266 

267 # Return converted image 

268 image = image.convert("RGB") 

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

270 

271 

272def contain( 

273 image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC 

274) -> Image.Image: 

275 """ 

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

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

278 

279 :param image: The image to resize. 

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

281 (width, height) tuple. 

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

283 :py:attr:`~PIL.Image.Resampling.BICUBIC`. 

284 See :ref:`concept-filters`. 

285 :return: An image. 

286 """ 

287 

288 im_ratio = image.width / image.height 

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

290 

291 if im_ratio != dest_ratio: 

292 if im_ratio > dest_ratio: 

293 new_height = round(image.height / image.width * size[0]) 

294 if new_height != size[1]: 

295 size = (size[0], new_height) 

296 else: 

297 new_width = round(image.width / image.height * size[1]) 

298 if new_width != size[0]: 

299 size = (new_width, size[1]) 

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

301 

302 

303def cover( 

304 image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC 

305) -> Image.Image: 

306 """ 

307 Returns a resized version of the image, so that the requested size is 

308 covered, while maintaining the original aspect ratio. 

309 

310 :param image: The image to resize. 

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

312 (width, height) tuple. 

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

314 :py:attr:`~PIL.Image.Resampling.BICUBIC`. 

315 See :ref:`concept-filters`. 

316 :return: An image. 

317 """ 

318 

319 im_ratio = image.width / image.height 

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

321 

322 if im_ratio != dest_ratio: 

323 if im_ratio < dest_ratio: 

324 new_height = round(image.height / image.width * size[0]) 

325 if new_height != size[1]: 

326 size = (size[0], new_height) 

327 else: 

328 new_width = round(image.width / image.height * size[1]) 

329 if new_width != size[0]: 

330 size = (new_width, size[1]) 

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

332 

333 

334def pad( 

335 image: Image.Image, 

336 size: tuple[int, int], 

337 method: int = Image.Resampling.BICUBIC, 

338 color: str | int | tuple[int, ...] | None = None, 

339 centering: tuple[float, float] = (0.5, 0.5), 

340) -> Image.Image: 

341 """ 

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

343 requested aspect ratio and size. 

344 

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

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

347 (width, height) tuple. 

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

349 :py:attr:`~PIL.Image.Resampling.BICUBIC`. 

350 See :ref:`concept-filters`. 

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

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

353 padded version. 

354 

355 (0.5, 0.5) will keep the image centered 

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

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

358 right 

359 :return: An image. 

360 """ 

361 

362 resized = contain(image, size, method) 

363 if resized.size == size: 

364 out = resized 

365 else: 

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

367 if resized.palette: 

368 palette = resized.getpalette() 

369 if palette is not None: 

370 out.putpalette(palette) 

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

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

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

374 else: 

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

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

377 return out 

378 

379 

380def crop(image: Image.Image, border: int = 0) -> Image.Image: 

381 """ 

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

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

384 

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

386 

387 :param image: The image to crop. 

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

389 :return: An image. 

390 """ 

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

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

393 

394 

395def scale( 

396 image: Image.Image, factor: float, resample: int = Image.Resampling.BICUBIC 

397) -> Image.Image: 

398 """ 

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

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

401 image. 

402 

403 :param image: The image to rescale. 

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

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

406 :py:attr:`~PIL.Image.Resampling.BICUBIC`. 

407 See :ref:`concept-filters`. 

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

409 """ 

410 if factor == 1: 

411 return image.copy() 

412 elif factor <= 0: 

413 msg = "the factor must be greater than 0" 

414 raise ValueError(msg) 

415 else: 

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

417 return image.resize(size, resample) 

418 

419 

420class SupportsGetMesh(Protocol): 

421 """ 

422 An object that supports the ``getmesh`` method, taking an image as an 

423 argument, and returning a list of tuples. Each tuple contains two tuples, 

424 the source box as a tuple of 4 integers, and a tuple of 8 integers for the 

425 final quadrilateral, in order of top left, bottom left, bottom right, top 

426 right. 

427 """ 

428 

429 def getmesh( 

430 self, image: Image.Image 

431 ) -> list[ 

432 tuple[tuple[int, int, int, int], tuple[int, int, int, int, int, int, int, int]] 

433 ]: ... 

434 

435 

436def deform( 

437 image: Image.Image, 

438 deformer: SupportsGetMesh, 

439 resample: int = Image.Resampling.BILINEAR, 

440) -> Image.Image: 

441 """ 

442 Deform the image. 

443 

444 :param image: The image to deform. 

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

446 ``getmesh`` method can be used. 

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

448 in the PIL.Image.transform function. 

449 :return: An image. 

450 """ 

451 return image.transform( 

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

453 ) 

454 

455 

456def equalize(image: Image.Image, mask: Image.Image | None = None) -> Image.Image: 

457 """ 

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

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

460 distribution of grayscale values in the output image. 

461 

462 :param image: The image to equalize. 

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

464 the mask are included in the analysis. 

465 :return: An image. 

466 """ 

467 if image.mode == "P": 

468 image = image.convert("RGB") 

469 h = image.histogram(mask) 

470 lut = [] 

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

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

473 if len(histo) <= 1: 

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

475 else: 

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

477 if not step: 

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

479 else: 

480 n = step // 2 

481 for i in range(256): 

482 lut.append(n // step) 

483 n = n + h[i + b] 

484 return _lut(image, lut) 

485 

486 

487def expand( 

488 image: Image.Image, 

489 border: int | tuple[int, ...] = 0, 

490 fill: str | int | tuple[int, ...] = 0, 

491) -> Image.Image: 

492 """ 

493 Add border to the image 

494 

495 :param image: The image to expand. 

496 :param border: Border width, in pixels. 

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

498 :return: An image. 

499 """ 

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

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

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

503 color = _color(fill, image.mode) 

504 if image.palette: 

505 mode = image.palette.mode 

506 palette = ImagePalette.ImagePalette(mode, image.getpalette(mode)) 

507 if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4): 

508 color = palette.getcolor(color) 

509 else: 

510 palette = None 

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

512 if palette: 

513 out.putpalette(palette.palette, mode) 

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

515 return out 

516 

517 

518def fit( 

519 image: Image.Image, 

520 size: tuple[int, int], 

521 method: int = Image.Resampling.BICUBIC, 

522 bleed: float = 0.0, 

523 centering: tuple[float, float] = (0.5, 0.5), 

524) -> Image.Image: 

525 """ 

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

527 requested aspect ratio and size. 

528 

529 This function was contributed by Kevin Cazabon. 

530 

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

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

533 (width, height) tuple. 

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

535 :py:attr:`~PIL.Image.Resampling.BICUBIC`. 

536 See :ref:`concept-filters`. 

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

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

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

540 Cannot be greater than or equal to 0.5. 

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

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

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

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

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

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

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

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

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

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

551 :return: An image. 

552 """ 

553 

554 # by Kevin Cazabon, Feb 17/2000 

555 # kevin@cazabon.com 

556 # https://www.cazabon.com 

557 

558 centering_x, centering_y = centering 

559 

560 if not 0.0 <= centering_x <= 1.0: 

561 centering_x = 0.5 

562 if not 0.0 <= centering_y <= 1.0: 

563 centering_y = 0.5 

564 

565 if not 0.0 <= bleed < 0.5: 

566 bleed = 0.0 

567 

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

569 # the 'bleed' around the edges 

570 

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

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

573 

574 live_size = ( 

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

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

577 ) 

578 

579 # calculate the aspect ratio of the live_size 

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

581 

582 # calculate the aspect ratio of the output image 

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

584 

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

586 if live_size_ratio == output_ratio: 

587 # live_size is already the needed ratio 

588 crop_width = live_size[0] 

589 crop_height = live_size[1] 

590 elif live_size_ratio >= output_ratio: 

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

592 crop_width = output_ratio * live_size[1] 

593 crop_height = live_size[1] 

594 else: 

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

596 crop_width = live_size[0] 

597 crop_height = live_size[0] / output_ratio 

598 

599 # make the crop 

600 crop_left = bleed_pixels[0] + (live_size[0] - crop_width) * centering_x 

601 crop_top = bleed_pixels[1] + (live_size[1] - crop_height) * centering_y 

602 

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

604 

605 # resize the image and return it 

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

607 

608 

609def flip(image: Image.Image) -> Image.Image: 

610 """ 

611 Flip the image vertically (top to bottom). 

612 

613 :param image: The image to flip. 

614 :return: An image. 

615 """ 

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

617 

618 

619def grayscale(image: Image.Image) -> Image.Image: 

620 """ 

621 Convert the image to grayscale. 

622 

623 :param image: The image to convert. 

624 :return: An image. 

625 """ 

626 return image.convert("L") 

627 

628 

629def invert(image: Image.Image) -> Image.Image: 

630 """ 

631 Invert (negate) the image. 

632 

633 :param image: The image to invert. 

634 :return: An image. 

635 """ 

636 lut = list(range(255, -1, -1)) 

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

638 

639 

640def mirror(image: Image.Image) -> Image.Image: 

641 """ 

642 Flip image horizontally (left to right). 

643 

644 :param image: The image to mirror. 

645 :return: An image. 

646 """ 

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

648 

649 

650def posterize(image: Image.Image, bits: int) -> Image.Image: 

651 """ 

652 Reduce the number of bits for each color channel. 

653 

654 :param image: The image to posterize. 

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

656 :return: An image. 

657 """ 

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

659 lut = [i & mask for i in range(256)] 

660 return _lut(image, lut) 

661 

662 

663def solarize(image: Image.Image, threshold: int = 128) -> Image.Image: 

664 """ 

665 Invert all pixel values above a threshold. 

666 

667 :param image: The image to solarize. 

668 :param threshold: All pixels above this grayscale level are inverted. 

669 :return: An image. 

670 """ 

671 lut = [] 

672 for i in range(256): 

673 if i < threshold: 

674 lut.append(i) 

675 else: 

676 lut.append(255 - i) 

677 return _lut(image, lut) 

678 

679 

680@overload 

681def exif_transpose(image: Image.Image, *, in_place: Literal[True]) -> None: ... 

682 

683 

684@overload 

685def exif_transpose( 

686 image: Image.Image, *, in_place: Literal[False] = False 

687) -> Image.Image: ... 

688 

689 

690def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image | None: 

691 """ 

692 If an image has an EXIF Orientation tag, other than 1, transpose the image 

693 accordingly, and remove the orientation data. 

694 

695 :param image: The image to transpose. 

696 :param in_place: Boolean. Keyword-only argument. 

697 If ``True``, the original image is modified in-place, and ``None`` is returned. 

698 If ``False`` (default), a new :py:class:`~PIL.Image.Image` object is returned 

699 with the transposition applied. If there is no transposition, a copy of the 

700 image will be returned. 

701 """ 

702 image.load() 

703 image_exif = image.getexif() 

704 orientation = image_exif.get(ExifTags.Base.Orientation, 1) 

705 method = { 

706 2: Image.Transpose.FLIP_LEFT_RIGHT, 

707 3: Image.Transpose.ROTATE_180, 

708 4: Image.Transpose.FLIP_TOP_BOTTOM, 

709 5: Image.Transpose.TRANSPOSE, 

710 6: Image.Transpose.ROTATE_270, 

711 7: Image.Transpose.TRANSVERSE, 

712 8: Image.Transpose.ROTATE_90, 

713 }.get(orientation) 

714 if method is not None: 

715 if in_place: 

716 image.im = image.im.transpose(method) 

717 image._size = image.im.size 

718 else: 

719 transposed_image = image.transpose(method) 

720 exif_image = image if in_place else transposed_image 

721 

722 exif = exif_image.getexif() 

723 if ExifTags.Base.Orientation in exif: 

724 del exif[ExifTags.Base.Orientation] 

725 if "exif" in exif_image.info: 

726 exif_image.info["exif"] = exif.tobytes() 

727 elif "Raw profile type exif" in exif_image.info: 

728 exif_image.info["Raw profile type exif"] = exif.tobytes().hex() 

729 for key in ("XML:com.adobe.xmp", "xmp"): 

730 if key in exif_image.info: 

731 for pattern in ( 

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

733 r"<tiff:Orientation>([0-9])</tiff:Orientation>", 

734 ): 

735 value = exif_image.info[key] 

736 if isinstance(value, str): 

737 value = re.sub(pattern, "", value) 

738 elif isinstance(value, tuple): 

739 value = tuple( 

740 re.sub(pattern.encode(), b"", v) for v in value 

741 ) 

742 else: 

743 value = re.sub(pattern.encode(), b"", value) 

744 exif_image.info[key] = value 

745 if not in_place: 

746 return transposed_image 

747 elif not in_place: 

748 return image.copy() 

749 return None