Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pillow-10.4.0-py3.8-linux-x86_64.egg/PIL/ImageOps.py: 10%

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

277 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 typing import Protocol, Sequence, cast 

25 

26from . import ExifTags, Image, ImagePalette 

27 

28# 

29# helpers 

30 

31 

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

33 if isinstance(border, tuple): 

34 if len(border) == 2: 

35 left, top = right, bottom = border 

36 elif len(border) == 4: 

37 left, top, right, bottom = border 

38 else: 

39 left = top = right = bottom = border 

40 return left, top, right, bottom 

41 

42 

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

44 if isinstance(color, str): 

45 from . import ImageColor 

46 

47 color = ImageColor.getcolor(color, mode) 

48 return color 

49 

50 

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

52 if image.mode == "P": 

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

54 msg = "mode P support coming soon" 

55 raise NotImplementedError(msg) 

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

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

58 lut = lut + lut + lut 

59 return image.point(lut) 

60 else: 

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

62 raise OSError(msg) 

63 

64 

65# 

66# actions 

67 

68 

69def autocontrast( 

70 image: Image.Image, 

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

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

73 mask: Image.Image | None = None, 

74 preserve_tone: bool = False, 

75) -> Image.Image: 

76 """ 

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

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

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

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

81 becomes white (255). 

82 

83 :param image: The image to process. 

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

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

86 number for both. 

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

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

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

90 for histogram computation. 

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

92 

93 .. versionadded:: 8.2.0 

94 

95 :return: An image. 

96 """ 

97 if preserve_tone: 

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

99 else: 

100 histogram = image.histogram(mask) 

101 

102 lut = [] 

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

104 h = histogram[layer : layer + 256] 

105 if ignore is not None: 

106 # get rid of outliers 

107 if isinstance(ignore, int): 

108 h[ignore] = 0 

109 else: 

110 for ix in ignore: 

111 h[ix] = 0 

112 if cutoff: 

113 # cut off pixels from both ends of the histogram 

114 if not isinstance(cutoff, tuple): 

115 cutoff = (cutoff, cutoff) 

116 # get number of pixels 

117 n = 0 

118 for ix in range(256): 

119 n = n + h[ix] 

120 # remove cutoff% pixels from the low end 

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

122 for lo in range(256): 

123 if cut > h[lo]: 

124 cut = cut - h[lo] 

125 h[lo] = 0 

126 else: 

127 h[lo] -= cut 

128 cut = 0 

129 if cut <= 0: 

130 break 

131 # remove cutoff% samples from the high end 

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

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

134 if cut > h[hi]: 

135 cut = cut - h[hi] 

136 h[hi] = 0 

137 else: 

138 h[hi] -= cut 

139 cut = 0 

140 if cut <= 0: 

141 break 

142 # find lowest/highest samples after preprocessing 

143 for lo in range(256): 

144 if h[lo]: 

145 break 

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

147 if h[hi]: 

148 break 

149 if hi <= lo: 

150 # don't bother 

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

152 else: 

153 scale = 255.0 / (hi - lo) 

154 offset = -lo * scale 

155 for ix in range(256): 

156 ix = int(ix * scale + offset) 

157 if ix < 0: 

158 ix = 0 

159 elif ix > 255: 

160 ix = 255 

161 lut.append(ix) 

162 return _lut(image, lut) 

163 

164 

165def colorize( 

166 image: Image.Image, 

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

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

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

170 blackpoint: int = 0, 

171 whitepoint: int = 255, 

172 midpoint: int = 127, 

173) -> Image.Image: 

174 """ 

175 Colorize grayscale image. 

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

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

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

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

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

181 Mapping positions for any of the colors can be specified 

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

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

184 These parameters must have logical order, such that 

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

186 

187 :param image: The image to colorize. 

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

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

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

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

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

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

194 :return: An image. 

195 """ 

196 

197 # Initial asserts 

198 assert image.mode == "L" 

199 if mid is None: 

200 assert 0 <= blackpoint <= whitepoint <= 255 

201 else: 

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

203 

204 # Define colors from arguments 

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

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

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

208 

209 # Empty lists for the mapping 

210 red = [] 

211 green = [] 

212 blue = [] 

213 

214 # Create the low-end values 

215 for i in range(0, blackpoint): 

216 red.append(rgb_black[0]) 

217 green.append(rgb_black[1]) 

218 blue.append(rgb_black[2]) 

219 

220 # Create the mapping (2-color) 

221 if rgb_mid is None: 

222 range_map = range(0, whitepoint - blackpoint) 

223 

224 for i in range_map: 

225 red.append( 

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

227 ) 

228 green.append( 

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

230 ) 

231 blue.append( 

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

233 ) 

234 

235 # Create the mapping (3-color) 

236 else: 

237 range_map1 = range(0, midpoint - blackpoint) 

238 range_map2 = range(0, whitepoint - midpoint) 

239 

240 for i in range_map1: 

241 red.append( 

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

243 ) 

244 green.append( 

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

246 ) 

247 blue.append( 

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

249 ) 

250 for i in range_map2: 

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

252 green.append( 

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

254 ) 

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

256 

257 # Create the high-end values 

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

259 red.append(rgb_white[0]) 

260 green.append(rgb_white[1]) 

261 blue.append(rgb_white[2]) 

262 

263 # Return converted image 

264 image = image.convert("RGB") 

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

266 

267 

268def contain( 

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

270) -> Image.Image: 

271 """ 

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

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

274 

275 :param image: The image to resize. 

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

277 (width, height) tuple. 

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

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

280 See :ref:`concept-filters`. 

281 :return: An image. 

282 """ 

283 

284 im_ratio = image.width / image.height 

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

286 

287 if im_ratio != dest_ratio: 

288 if im_ratio > dest_ratio: 

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

290 if new_height != size[1]: 

291 size = (size[0], new_height) 

292 else: 

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

294 if new_width != size[0]: 

295 size = (new_width, size[1]) 

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

297 

298 

299def cover( 

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

301) -> Image.Image: 

302 """ 

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

304 covered, while maintaining the original aspect ratio. 

305 

306 :param image: The image to resize. 

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

308 (width, height) tuple. 

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

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

311 See :ref:`concept-filters`. 

312 :return: An image. 

313 """ 

314 

315 im_ratio = image.width / image.height 

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

317 

318 if im_ratio != dest_ratio: 

319 if im_ratio < dest_ratio: 

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

321 if new_height != size[1]: 

322 size = (size[0], new_height) 

323 else: 

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

325 if new_width != size[0]: 

326 size = (new_width, size[1]) 

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

328 

329 

330def pad( 

331 image: Image.Image, 

332 size: tuple[int, int], 

333 method: int = Image.Resampling.BICUBIC, 

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

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

336) -> Image.Image: 

337 """ 

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

339 requested aspect ratio and size. 

340 

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

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

343 (width, height) tuple. 

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

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

346 See :ref:`concept-filters`. 

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

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

349 padded version. 

350 

351 (0.5, 0.5) will keep the image centered 

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

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

354 right 

355 :return: An image. 

356 """ 

357 

358 resized = contain(image, size, method) 

359 if resized.size == size: 

360 out = resized 

361 else: 

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

363 if resized.palette: 

364 out.putpalette(resized.getpalette()) 

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

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

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

368 else: 

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

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

371 return out 

372 

373 

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

375 """ 

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

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

378 

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

380 

381 :param image: The image to crop. 

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

383 :return: An image. 

384 """ 

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

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

387 

388 

389def scale( 

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

391) -> Image.Image: 

392 """ 

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

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

395 image. 

396 

397 :param image: The image to rescale. 

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

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

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

401 See :ref:`concept-filters`. 

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

403 """ 

404 if factor == 1: 

405 return image.copy() 

406 elif factor <= 0: 

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

408 raise ValueError(msg) 

409 else: 

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

411 return image.resize(size, resample) 

412 

413 

414class SupportsGetMesh(Protocol): 

415 """ 

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

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

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

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

420 right. 

421 """ 

422 

423 def getmesh( 

424 self, image: Image.Image 

425 ) -> list[ 

426 tuple[tuple[int, int, int, int], tuple[int, int, int, int, int, int, int, int]] 

427 ]: ... 

428 

429 

430def deform( 

431 image: Image.Image, 

432 deformer: SupportsGetMesh, 

433 resample: int = Image.Resampling.BILINEAR, 

434) -> Image.Image: 

435 """ 

436 Deform the image. 

437 

438 :param image: The image to deform. 

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

440 ``getmesh`` method can be used. 

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

442 in the PIL.Image.transform function. 

443 :return: An image. 

444 """ 

445 return image.transform( 

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

447 ) 

448 

449 

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

451 """ 

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

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

454 distribution of grayscale values in the output image. 

455 

456 :param image: The image to equalize. 

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

458 the mask are included in the analysis. 

459 :return: An image. 

460 """ 

461 if image.mode == "P": 

462 image = image.convert("RGB") 

463 h = image.histogram(mask) 

464 lut = [] 

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

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

467 if len(histo) <= 1: 

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

469 else: 

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

471 if not step: 

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

473 else: 

474 n = step // 2 

475 for i in range(256): 

476 lut.append(n // step) 

477 n = n + h[i + b] 

478 return _lut(image, lut) 

479 

480 

481def expand( 

482 image: Image.Image, 

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

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

485) -> Image.Image: 

486 """ 

487 Add border to the image 

488 

489 :param image: The image to expand. 

490 :param border: Border width, in pixels. 

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

492 :return: An image. 

493 """ 

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

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

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

497 color = _color(fill, image.mode) 

498 if image.palette: 

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

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

501 color = palette.getcolor(color) 

502 else: 

503 palette = None 

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

505 if palette: 

506 out.putpalette(palette.palette) 

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

508 return out 

509 

510 

511def fit( 

512 image: Image.Image, 

513 size: tuple[int, int], 

514 method: int = Image.Resampling.BICUBIC, 

515 bleed: float = 0.0, 

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

517) -> Image.Image: 

518 """ 

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

520 requested aspect ratio and size. 

521 

522 This function was contributed by Kevin Cazabon. 

523 

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

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

526 (width, height) tuple. 

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

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

529 See :ref:`concept-filters`. 

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

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

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

533 Cannot be greater than or equal to 0.5. 

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

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

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

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

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

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

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

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

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

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

544 :return: An image. 

545 """ 

546 

547 # by Kevin Cazabon, Feb 17/2000 

548 # kevin@cazabon.com 

549 # https://www.cazabon.com 

550 

551 centering_x, centering_y = centering 

552 

553 if not 0.0 <= centering_x <= 1.0: 

554 centering_x = 0.5 

555 if not 0.0 <= centering_y <= 1.0: 

556 centering_y = 0.5 

557 

558 if not 0.0 <= bleed < 0.5: 

559 bleed = 0.0 

560 

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

562 # the 'bleed' around the edges 

563 

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

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

566 

567 live_size = ( 

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

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

570 ) 

571 

572 # calculate the aspect ratio of the live_size 

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

574 

575 # calculate the aspect ratio of the output image 

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

577 

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

579 if live_size_ratio == output_ratio: 

580 # live_size is already the needed ratio 

581 crop_width = live_size[0] 

582 crop_height = live_size[1] 

583 elif live_size_ratio >= output_ratio: 

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

585 crop_width = output_ratio * live_size[1] 

586 crop_height = live_size[1] 

587 else: 

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

589 crop_width = live_size[0] 

590 crop_height = live_size[0] / output_ratio 

591 

592 # make the crop 

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

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

595 

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

597 

598 # resize the image and return it 

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

600 

601 

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

603 """ 

604 Flip the image vertically (top to bottom). 

605 

606 :param image: The image to flip. 

607 :return: An image. 

608 """ 

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

610 

611 

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

613 """ 

614 Convert the image to grayscale. 

615 

616 :param image: The image to convert. 

617 :return: An image. 

618 """ 

619 return image.convert("L") 

620 

621 

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

623 """ 

624 Invert (negate) the image. 

625 

626 :param image: The image to invert. 

627 :return: An image. 

628 """ 

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

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

631 

632 

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

634 """ 

635 Flip image horizontally (left to right). 

636 

637 :param image: The image to mirror. 

638 :return: An image. 

639 """ 

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

641 

642 

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

644 """ 

645 Reduce the number of bits for each color channel. 

646 

647 :param image: The image to posterize. 

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

649 :return: An image. 

650 """ 

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

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

653 return _lut(image, lut) 

654 

655 

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

657 """ 

658 Invert all pixel values above a threshold. 

659 

660 :param image: The image to solarize. 

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

662 :return: An image. 

663 """ 

664 lut = [] 

665 for i in range(256): 

666 if i < threshold: 

667 lut.append(i) 

668 else: 

669 lut.append(255 - i) 

670 return _lut(image, lut) 

671 

672 

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

674 """ 

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

676 accordingly, and remove the orientation data. 

677 

678 :param image: The image to transpose. 

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

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

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

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

683 image will be returned. 

684 """ 

685 image.load() 

686 image_exif = image.getexif() 

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

688 method = { 

689 2: Image.Transpose.FLIP_LEFT_RIGHT, 

690 3: Image.Transpose.ROTATE_180, 

691 4: Image.Transpose.FLIP_TOP_BOTTOM, 

692 5: Image.Transpose.TRANSPOSE, 

693 6: Image.Transpose.ROTATE_270, 

694 7: Image.Transpose.TRANSVERSE, 

695 8: Image.Transpose.ROTATE_90, 

696 }.get(orientation) 

697 if method is not None: 

698 transposed_image = image.transpose(method) 

699 if in_place: 

700 image.im = transposed_image.im 

701 image.pyaccess = None 

702 image._size = transposed_image._size 

703 exif_image = image if in_place else transposed_image 

704 

705 exif = exif_image.getexif() 

706 if ExifTags.Base.Orientation in exif: 

707 del exif[ExifTags.Base.Orientation] 

708 if "exif" in exif_image.info: 

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

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

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

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

713 if key in exif_image.info: 

714 for pattern in ( 

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

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

717 ): 

718 value = exif_image.info[key] 

719 exif_image.info[key] = ( 

720 re.sub(pattern, "", value) 

721 if isinstance(value, str) 

722 else re.sub(pattern.encode(), b"", value) 

723 ) 

724 if not in_place: 

725 return transposed_image 

726 elif not in_place: 

727 return image.copy() 

728 return None