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

290 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 mode = image.palette.mode 

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

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

505 color = palette.getcolor(color) 

506 else: 

507 palette = None 

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

509 if palette: 

510 out.putpalette(palette.palette, mode) 

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

512 return out 

513 

514 

515def fit( 

516 image: Image.Image, 

517 size: tuple[int, int], 

518 method: int = Image.Resampling.BICUBIC, 

519 bleed: float = 0.0, 

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

521) -> Image.Image: 

522 """ 

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

524 requested aspect ratio and size. 

525 

526 This function was contributed by Kevin Cazabon. 

527 

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

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

530 (width, height) tuple. 

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

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

533 See :ref:`concept-filters`. 

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

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

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

537 Cannot be greater than or equal to 0.5. 

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

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

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

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

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

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

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

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

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

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

548 :return: An image. 

549 """ 

550 

551 # by Kevin Cazabon, Feb 17/2000 

552 # kevin@cazabon.com 

553 # https://www.cazabon.com 

554 

555 centering_x, centering_y = centering 

556 

557 if not 0.0 <= centering_x <= 1.0: 

558 centering_x = 0.5 

559 if not 0.0 <= centering_y <= 1.0: 

560 centering_y = 0.5 

561 

562 if not 0.0 <= bleed < 0.5: 

563 bleed = 0.0 

564 

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

566 # the 'bleed' around the edges 

567 

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

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

570 

571 live_size = ( 

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

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

574 ) 

575 

576 # calculate the aspect ratio of the live_size 

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

578 

579 # calculate the aspect ratio of the output image 

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

581 

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

583 if live_size_ratio == output_ratio: 

584 # live_size is already the needed ratio 

585 crop_width = live_size[0] 

586 crop_height = live_size[1] 

587 elif live_size_ratio >= output_ratio: 

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

589 crop_width = output_ratio * live_size[1] 

590 crop_height = live_size[1] 

591 else: 

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

593 crop_width = live_size[0] 

594 crop_height = live_size[0] / output_ratio 

595 

596 # make the crop 

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

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

599 

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

601 

602 # resize the image and return it 

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

604 

605 

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

607 """ 

608 Flip the image vertically (top to bottom). 

609 

610 :param image: The image to flip. 

611 :return: An image. 

612 """ 

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

614 

615 

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

617 """ 

618 Convert the image to grayscale. 

619 

620 :param image: The image to convert. 

621 :return: An image. 

622 """ 

623 return image.convert("L") 

624 

625 

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

627 """ 

628 Invert (negate) the image. 

629 

630 :param image: The image to invert. 

631 :return: An image. 

632 """ 

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

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

635 

636 

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

638 """ 

639 Flip image horizontally (left to right). 

640 

641 :param image: The image to mirror. 

642 :return: An image. 

643 """ 

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

645 

646 

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

648 """ 

649 Reduce the number of bits for each color channel. 

650 

651 :param image: The image to posterize. 

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

653 :return: An image. 

654 """ 

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

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

657 return _lut(image, lut) 

658 

659 

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

661 """ 

662 Invert all pixel values above a threshold. 

663 

664 :param image: The image to solarize. 

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

666 :return: An image. 

667 """ 

668 lut = [] 

669 for i in range(256): 

670 if i < threshold: 

671 lut.append(i) 

672 else: 

673 lut.append(255 - i) 

674 return _lut(image, lut) 

675 

676 

677@overload 

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

679 

680 

681@overload 

682def exif_transpose( 

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

684) -> Image.Image: ... 

685 

686 

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

688 """ 

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

690 accordingly, and remove the orientation data. 

691 

692 :param image: The image to transpose. 

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

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

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

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

697 image will be returned. 

698 """ 

699 image.load() 

700 image_exif = image.getexif() 

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

702 method = { 

703 2: Image.Transpose.FLIP_LEFT_RIGHT, 

704 3: Image.Transpose.ROTATE_180, 

705 4: Image.Transpose.FLIP_TOP_BOTTOM, 

706 5: Image.Transpose.TRANSPOSE, 

707 6: Image.Transpose.ROTATE_270, 

708 7: Image.Transpose.TRANSVERSE, 

709 8: Image.Transpose.ROTATE_90, 

710 }.get(orientation) 

711 if method is not None: 

712 if in_place: 

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

714 image._size = image.im.size 

715 else: 

716 transposed_image = image.transpose(method) 

717 exif_image = image if in_place else transposed_image 

718 

719 exif = exif_image.getexif() 

720 if ExifTags.Base.Orientation in exif: 

721 del exif[ExifTags.Base.Orientation] 

722 if "exif" in exif_image.info: 

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

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

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

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

727 if key in exif_image.info: 

728 for pattern in ( 

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

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

731 ): 

732 value = exif_image.info[key] 

733 if isinstance(value, str): 

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

735 elif isinstance(value, tuple): 

736 value = tuple( 

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

738 ) 

739 else: 

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

741 exif_image.info[key] = value 

742 if not in_place: 

743 return transposed_image 

744 elif not in_place: 

745 return image.copy() 

746 return None