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

289 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 left = top = right = bottom = border 

41 return left, top, right, bottom 

42 

43 

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

45 if isinstance(color, str): 

46 from . import ImageColor 

47 

48 color = ImageColor.getcolor(color, mode) 

49 return color 

50 

51 

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

53 if image.mode == "P": 

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

55 msg = "mode P support coming soon" 

56 raise NotImplementedError(msg) 

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

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

59 lut = lut + lut + lut 

60 return image.point(lut) 

61 else: 

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

63 raise OSError(msg) 

64 

65 

66# 

67# actions 

68 

69 

70def autocontrast( 

71 image: Image.Image, 

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

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

74 mask: Image.Image | None = None, 

75 preserve_tone: bool = False, 

76) -> Image.Image: 

77 """ 

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

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

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

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

82 becomes white (255). 

83 

84 :param image: The image to process. 

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

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

87 number for both. 

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

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

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

91 for histogram computation. 

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

93 

94 .. versionadded:: 8.2.0 

95 

96 :return: An image. 

97 """ 

98 if preserve_tone: 

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

100 else: 

101 histogram = image.histogram(mask) 

102 

103 lut = [] 

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

105 h = histogram[layer : layer + 256] 

106 if ignore is not None: 

107 # get rid of outliers 

108 if isinstance(ignore, int): 

109 h[ignore] = 0 

110 else: 

111 for ix in ignore: 

112 h[ix] = 0 

113 if cutoff: 

114 # cut off pixels from both ends of the histogram 

115 if not isinstance(cutoff, tuple): 

116 cutoff = (cutoff, cutoff) 

117 # get number of pixels 

118 n = 0 

119 for ix in range(256): 

120 n = n + h[ix] 

121 # remove cutoff% pixels from the low end 

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

123 for lo in range(256): 

124 if cut > h[lo]: 

125 cut = cut - h[lo] 

126 h[lo] = 0 

127 else: 

128 h[lo] -= cut 

129 cut = 0 

130 if cut <= 0: 

131 break 

132 # remove cutoff% samples from the high end 

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

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

135 if cut > h[hi]: 

136 cut = cut - h[hi] 

137 h[hi] = 0 

138 else: 

139 h[hi] -= cut 

140 cut = 0 

141 if cut <= 0: 

142 break 

143 # find lowest/highest samples after preprocessing 

144 for lo in range(256): 

145 if h[lo]: 

146 break 

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

148 if h[hi]: 

149 break 

150 if hi <= lo: 

151 # don't bother 

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

153 else: 

154 scale = 255.0 / (hi - lo) 

155 offset = -lo * scale 

156 for ix in range(256): 

157 ix = int(ix * scale + offset) 

158 if ix < 0: 

159 ix = 0 

160 elif ix > 255: 

161 ix = 255 

162 lut.append(ix) 

163 return _lut(image, lut) 

164 

165 

166def colorize( 

167 image: Image.Image, 

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

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

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

171 blackpoint: int = 0, 

172 whitepoint: int = 255, 

173 midpoint: int = 127, 

174) -> Image.Image: 

175 """ 

176 Colorize grayscale image. 

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

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

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

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

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

182 Mapping positions for any of the colors can be specified 

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

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

185 These parameters must have logical order, such that 

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

187 

188 :param image: The image to colorize. 

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

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

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

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

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

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

195 :return: An image. 

196 """ 

197 

198 # Initial asserts 

199 assert image.mode == "L" 

200 if mid is None: 

201 assert 0 <= blackpoint <= whitepoint <= 255 

202 else: 

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

204 

205 # Define colors from arguments 

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

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

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

209 

210 # Empty lists for the mapping 

211 red = [] 

212 green = [] 

213 blue = [] 

214 

215 # Create the low-end values 

216 for i in range(blackpoint): 

217 red.append(rgb_black[0]) 

218 green.append(rgb_black[1]) 

219 blue.append(rgb_black[2]) 

220 

221 # Create the mapping (2-color) 

222 if rgb_mid is None: 

223 range_map = range(whitepoint - blackpoint) 

224 

225 for i in range_map: 

226 red.append( 

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

228 ) 

229 green.append( 

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

231 ) 

232 blue.append( 

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

234 ) 

235 

236 # Create the mapping (3-color) 

237 else: 

238 range_map1 = range(midpoint - blackpoint) 

239 range_map2 = range(whitepoint - midpoint) 

240 

241 for i in range_map1: 

242 red.append( 

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

244 ) 

245 green.append( 

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

247 ) 

248 blue.append( 

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

250 ) 

251 for i in range_map2: 

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

253 green.append( 

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

255 ) 

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

257 

258 # Create the high-end values 

259 for i in range(256 - whitepoint): 

260 red.append(rgb_white[0]) 

261 green.append(rgb_white[1]) 

262 blue.append(rgb_white[2]) 

263 

264 # Return converted image 

265 image = image.convert("RGB") 

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

267 

268 

269def contain( 

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

271) -> Image.Image: 

272 """ 

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

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

275 

276 :param image: The image to resize. 

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

278 (width, height) tuple. 

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

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

281 See :ref:`concept-filters`. 

282 :return: An image. 

283 """ 

284 

285 im_ratio = image.width / image.height 

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

287 

288 if im_ratio != dest_ratio: 

289 if im_ratio > dest_ratio: 

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

291 if new_height != size[1]: 

292 size = (size[0], new_height) 

293 else: 

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

295 if new_width != size[0]: 

296 size = (new_width, size[1]) 

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

298 

299 

300def cover( 

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

302) -> Image.Image: 

303 """ 

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

305 covered, while maintaining the original aspect ratio. 

306 

307 :param image: The image to resize. 

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

309 (width, height) tuple. 

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

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

312 See :ref:`concept-filters`. 

313 :return: An image. 

314 """ 

315 

316 im_ratio = image.width / image.height 

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

318 

319 if im_ratio != dest_ratio: 

320 if im_ratio < dest_ratio: 

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

322 if new_height != size[1]: 

323 size = (size[0], new_height) 

324 else: 

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

326 if new_width != size[0]: 

327 size = (new_width, size[1]) 

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

329 

330 

331def pad( 

332 image: Image.Image, 

333 size: tuple[int, int], 

334 method: int = Image.Resampling.BICUBIC, 

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

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

337) -> Image.Image: 

338 """ 

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

340 requested aspect ratio and size. 

341 

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

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

344 (width, height) tuple. 

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

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

347 See :ref:`concept-filters`. 

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

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

350 padded version. 

351 

352 (0.5, 0.5) will keep the image centered 

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

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

355 right 

356 :return: An image. 

357 """ 

358 

359 resized = contain(image, size, method) 

360 if resized.size == size: 

361 out = resized 

362 else: 

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

364 if resized.palette: 

365 palette = resized.getpalette() 

366 if palette is not None: 

367 out.putpalette(palette) 

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

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

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

371 else: 

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

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

374 return out 

375 

376 

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

378 """ 

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

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

381 

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

383 

384 :param image: The image to crop. 

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

386 :return: An image. 

387 """ 

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

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

390 

391 

392def scale( 

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

394) -> Image.Image: 

395 """ 

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

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

398 image. 

399 

400 :param image: The image to rescale. 

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

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

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

404 See :ref:`concept-filters`. 

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

406 """ 

407 if factor == 1: 

408 return image.copy() 

409 elif factor <= 0: 

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

411 raise ValueError(msg) 

412 else: 

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

414 return image.resize(size, resample) 

415 

416 

417class SupportsGetMesh(Protocol): 

418 """ 

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

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

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

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

423 right. 

424 """ 

425 

426 def getmesh( 

427 self, image: Image.Image 

428 ) -> list[ 

429 tuple[tuple[int, int, int, int], tuple[int, int, int, int, int, int, int, int]] 

430 ]: ... 

431 

432 

433def deform( 

434 image: Image.Image, 

435 deformer: SupportsGetMesh, 

436 resample: int = Image.Resampling.BILINEAR, 

437) -> Image.Image: 

438 """ 

439 Deform the image. 

440 

441 :param image: The image to deform. 

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

443 ``getmesh`` method can be used. 

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

445 in the PIL.Image.transform function. 

446 :return: An image. 

447 """ 

448 return image.transform( 

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

450 ) 

451 

452 

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

454 """ 

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

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

457 distribution of grayscale values in the output image. 

458 

459 :param image: The image to equalize. 

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

461 the mask are included in the analysis. 

462 :return: An image. 

463 """ 

464 if image.mode == "P": 

465 image = image.convert("RGB") 

466 h = image.histogram(mask) 

467 lut = [] 

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

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

470 if len(histo) <= 1: 

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

472 else: 

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

474 if not step: 

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

476 else: 

477 n = step // 2 

478 for i in range(256): 

479 lut.append(n // step) 

480 n = n + h[i + b] 

481 return _lut(image, lut) 

482 

483 

484def expand( 

485 image: Image.Image, 

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

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

488) -> Image.Image: 

489 """ 

490 Add border to the image 

491 

492 :param image: The image to expand. 

493 :param border: Border width, in pixels. 

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

495 :return: An image. 

496 """ 

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

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

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

500 color = _color(fill, image.mode) 

501 if image.palette: 

502 palette = ImagePalette.ImagePalette(palette=image.getpalette()) 

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

504 color = palette.getcolor(color) 

505 else: 

506 palette = None 

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

508 if palette: 

509 out.putpalette(palette.palette) 

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

511 return out 

512 

513 

514def fit( 

515 image: Image.Image, 

516 size: tuple[int, int], 

517 method: int = Image.Resampling.BICUBIC, 

518 bleed: float = 0.0, 

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

520) -> Image.Image: 

521 """ 

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

523 requested aspect ratio and size. 

524 

525 This function was contributed by Kevin Cazabon. 

526 

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

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

529 (width, height) tuple. 

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

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

532 See :ref:`concept-filters`. 

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

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

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

536 Cannot be greater than or equal to 0.5. 

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

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

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

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

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

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

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

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

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

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

547 :return: An image. 

548 """ 

549 

550 # by Kevin Cazabon, Feb 17/2000 

551 # kevin@cazabon.com 

552 # https://www.cazabon.com 

553 

554 centering_x, centering_y = centering 

555 

556 if not 0.0 <= centering_x <= 1.0: 

557 centering_x = 0.5 

558 if not 0.0 <= centering_y <= 1.0: 

559 centering_y = 0.5 

560 

561 if not 0.0 <= bleed < 0.5: 

562 bleed = 0.0 

563 

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

565 # the 'bleed' around the edges 

566 

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

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

569 

570 live_size = ( 

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

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

573 ) 

574 

575 # calculate the aspect ratio of the live_size 

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

577 

578 # calculate the aspect ratio of the output image 

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

580 

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

582 if live_size_ratio == output_ratio: 

583 # live_size is already the needed ratio 

584 crop_width = live_size[0] 

585 crop_height = live_size[1] 

586 elif live_size_ratio >= output_ratio: 

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

588 crop_width = output_ratio * live_size[1] 

589 crop_height = live_size[1] 

590 else: 

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

592 crop_width = live_size[0] 

593 crop_height = live_size[0] / output_ratio 

594 

595 # make the crop 

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

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

598 

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

600 

601 # resize the image and return it 

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

603 

604 

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

606 """ 

607 Flip the image vertically (top to bottom). 

608 

609 :param image: The image to flip. 

610 :return: An image. 

611 """ 

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

613 

614 

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

616 """ 

617 Convert the image to grayscale. 

618 

619 :param image: The image to convert. 

620 :return: An image. 

621 """ 

622 return image.convert("L") 

623 

624 

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

626 """ 

627 Invert (negate) the image. 

628 

629 :param image: The image to invert. 

630 :return: An image. 

631 """ 

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

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

634 

635 

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

637 """ 

638 Flip image horizontally (left to right). 

639 

640 :param image: The image to mirror. 

641 :return: An image. 

642 """ 

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

644 

645 

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

647 """ 

648 Reduce the number of bits for each color channel. 

649 

650 :param image: The image to posterize. 

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

652 :return: An image. 

653 """ 

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

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

656 return _lut(image, lut) 

657 

658 

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

660 """ 

661 Invert all pixel values above a threshold. 

662 

663 :param image: The image to solarize. 

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

665 :return: An image. 

666 """ 

667 lut = [] 

668 for i in range(256): 

669 if i < threshold: 

670 lut.append(i) 

671 else: 

672 lut.append(255 - i) 

673 return _lut(image, lut) 

674 

675 

676@overload 

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

678 

679 

680@overload 

681def exif_transpose( 

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

683) -> Image.Image: ... 

684 

685 

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

687 """ 

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

689 accordingly, and remove the orientation data. 

690 

691 :param image: The image to transpose. 

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

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

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

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

696 image will be returned. 

697 """ 

698 image.load() 

699 image_exif = image.getexif() 

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

701 method = { 

702 2: Image.Transpose.FLIP_LEFT_RIGHT, 

703 3: Image.Transpose.ROTATE_180, 

704 4: Image.Transpose.FLIP_TOP_BOTTOM, 

705 5: Image.Transpose.TRANSPOSE, 

706 6: Image.Transpose.ROTATE_270, 

707 7: Image.Transpose.TRANSVERSE, 

708 8: Image.Transpose.ROTATE_90, 

709 }.get(orientation) 

710 if method is not None: 

711 if in_place: 

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

713 image._size = image.im.size 

714 else: 

715 transposed_image = image.transpose(method) 

716 exif_image = image if in_place else transposed_image 

717 

718 exif = exif_image.getexif() 

719 if ExifTags.Base.Orientation in exif: 

720 del exif[ExifTags.Base.Orientation] 

721 if "exif" in exif_image.info: 

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

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

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

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

726 if key in exif_image.info: 

727 for pattern in ( 

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

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

730 ): 

731 value = exif_image.info[key] 

732 if isinstance(value, str): 

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

734 elif isinstance(value, tuple): 

735 value = tuple( 

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

737 ) 

738 else: 

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

740 exif_image.info[key] = value 

741 if not in_place: 

742 return transposed_image 

743 elif not in_place: 

744 return image.copy() 

745 return None