Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/imageio-2.35.1-py3.8.egg/imageio/plugins/pillow_legacy.py: 24%

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

350 statements  

1# -*- coding: utf-8 -*- 

2# imageio is distributed under the terms of the (new) BSD License. 

3 

4""" Read/Write images using pillow/PIL (legacy). 

5 

6Backend Library: `Pillow <https://pillow.readthedocs.io/en/stable/>`_ 

7 

8Pillow is a friendly fork of PIL (Python Image Library) and supports 

9reading and writing of common formats (jpg, png, gif, tiff, ...). While 

10these docs provide an overview of some of its features, pillow is 

11constantly improving. Hence, the complete list of features can be found 

12in pillows official docs (see the Backend Library link). 

13 

14Parameters for Reading 

15---------------------- 

16pilmode : str 

17 (Available for all formats except GIF-PIL) 

18 From the Pillow documentation: 

19 

20 * 'L' (8-bit pixels, grayscale) 

21 * 'P' (8-bit pixels, mapped to any other mode using a color palette) 

22 * 'RGB' (3x8-bit pixels, true color) 

23 * 'RGBA' (4x8-bit pixels, true color with transparency mask) 

24 * 'CMYK' (4x8-bit pixels, color separation) 

25 * 'YCbCr' (3x8-bit pixels, color video format) 

26 * 'I' (32-bit signed integer pixels) 

27 * 'F' (32-bit floating point pixels) 

28 

29 PIL also provides limited support for a few special modes, including 

30 'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa' 

31 (true color with premultiplied alpha). 

32 

33 When translating a color image to grayscale (mode 'L', 'I' or 'F'), 

34 the library uses the ITU-R 601-2 luma transform:: 

35 

36 L = R * 299/1000 + G * 587/1000 + B * 114/1000 

37as_gray : bool 

38 (Available for all formats except GIF-PIL) 

39 If True, the image is converted using mode 'F'. When `mode` is 

40 not None and `as_gray` is True, the image is first converted 

41 according to `mode`, and the result is then "flattened" using 

42 mode 'F'. 

43ignoregamma : bool 

44 (Only available in PNG-PIL) 

45 Avoid gamma correction. Default True. 

46exifrotate : bool 

47 (Only available in JPEG-PIL) 

48 Automatically rotate the image according to exif flag. Default True. 

49 

50 

51Parameters for saving 

52--------------------- 

53optimize : bool 

54 (Only available in PNG-PIL) 

55 If present and true, instructs the PNG writer to make the output file 

56 as small as possible. This includes extra processing in order to find 

57 optimal encoder settings. 

58transparency: 

59 (Only available in PNG-PIL) 

60 This option controls what color image to mark as transparent. 

61dpi: tuple of two scalars 

62 (Only available in PNG-PIL) 

63 The desired dpi in each direction. 

64pnginfo: PIL.PngImagePlugin.PngInfo 

65 (Only available in PNG-PIL) 

66 Object containing text tags. 

67compress_level: int 

68 (Only available in PNG-PIL) 

69 ZLIB compression level, a number between 0 and 9: 1 gives best speed, 

70 9 gives best compression, 0 gives no compression at all. Default is 9. 

71 When ``optimize`` option is True ``compress_level`` has no effect 

72 (it is set to 9 regardless of a value passed). 

73compression: int 

74 (Only available in PNG-PIL) 

75 Compatibility with the freeimage PNG format. If given, it overrides 

76 compress_level. 

77icc_profile: 

78 (Only available in PNG-PIL) 

79 The ICC Profile to include in the saved file. 

80bits (experimental): int 

81 (Only available in PNG-PIL) 

82 This option controls how many bits to store. If omitted, 

83 the PNG writer uses 8 bits (256 colors). 

84quantize: 

85 (Only available in PNG-PIL) 

86 Compatibility with the freeimage PNG format. If given, it overrides 

87 bits. In this case, given as a number between 1-256. 

88dictionary (experimental): dict 

89 (Only available in PNG-PIL) 

90 Set the ZLIB encoder dictionary. 

91prefer_uint8: bool 

92 (Only available in PNG-PIL) 

93 Let the PNG writer truncate uint16 image arrays to uint8 if their values fall 

94 within the range [0, 255]. Defaults to true for legacy compatibility, however 

95 it is recommended to set this to false to avoid unexpected behavior when 

96 saving e.g. weakly saturated images. 

97 

98quality : scalar 

99 (Only available in JPEG-PIL) 

100 The compression factor of the saved image (1..100), higher 

101 numbers result in higher quality but larger file size. Default 75. 

102progressive : bool 

103 (Only available in JPEG-PIL) 

104 Save as a progressive JPEG file (e.g. for images on the web). 

105 Default False. 

106optimize : bool 

107 (Only available in JPEG-PIL) 

108 On saving, compute optimal Huffman coding tables (can reduce a few 

109 percent of file size). Default False. 

110dpi : tuple of int 

111 (Only available in JPEG-PIL) 

112 The pixel density, ``(x,y)``. 

113icc_profile : object 

114 (Only available in JPEG-PIL) 

115 If present and true, the image is stored with the provided ICC profile. 

116 If this parameter is not provided, the image will be saved with no 

117 profile attached. 

118exif : dict 

119 (Only available in JPEG-PIL) 

120 If present, the image will be stored with the provided raw EXIF data. 

121subsampling : str 

122 (Only available in JPEG-PIL) 

123 Sets the subsampling for the encoder. See Pillow docs for details. 

124qtables : object 

125 (Only available in JPEG-PIL) 

126 Set the qtables for the encoder. See Pillow docs for details. 

127quality_mode : str 

128 (Only available in JPEG2000-PIL) 

129 Either `"rates"` or `"dB"` depending on the units you want to use to 

130 specify image quality. 

131quality : float 

132 (Only available in JPEG2000-PIL) 

133 Approximate size reduction (if quality mode is `rates`) or a signal to noise ratio 

134 in decibels (if quality mode is `dB`). 

135loop : int 

136 (Only available in GIF-PIL) 

137 The number of iterations. Default 0 (meaning loop indefinitely). 

138duration : {float, list} 

139 (Only available in GIF-PIL) 

140 The duration (in milliseconds) of each frame. Either specify one value 

141 that is used for all frames, or one value for each frame. 

142fps : float 

143 (Only available in GIF-PIL) 

144 The number of frames per second. If duration is not given, the 

145 duration for each frame is set to 1/fps. Default 10. 

146palettesize : int 

147 (Only available in GIF-PIL) 

148 The number of colors to quantize the image to. Is rounded to 

149 the nearest power of two. Default 256. 

150subrectangles : bool 

151 (Only available in GIF-PIL) 

152 If True, will try and optimize the GIF by storing only the 

153 rectangular parts of each frame that change with respect to the 

154 previous. Default False. 

155 

156Notes 

157----- 

158To enable JPEG 2000 support, you need to build and install the OpenJPEG library, 

159version 2.0.0 or higher, before building the Python Imaging Library. Windows 

160users can install the OpenJPEG binaries available on the OpenJPEG website, but 

161must add them to their PATH in order to use PIL (if you fail to do this, you 

162will get errors about not being able to load the ``_imaging`` DLL). 

163 

164GIF images read with this plugin are always RGBA. The alpha channel is ignored 

165when saving RGB images. 

166""" 

167 

168import logging 

169import threading 

170 

171import numpy as np 

172 

173from ..core import Format, image_as_uint 

174from ..core.request import URI_FILE, URI_BYTES 

175 

176 

177logger = logging.getLogger(__name__) 

178 

179 

180# todo: Pillow ImageGrab module supports grabbing the screen on Win and OSX. 

181 

182 

183GENERIC_DOCS = """ 

184 Parameters for reading 

185 ---------------------- 

186 

187 pilmode : str 

188 From the Pillow documentation: 

189 

190 * 'L' (8-bit pixels, grayscale) 

191 * 'P' (8-bit pixels, mapped to any other mode using a color palette) 

192 * 'RGB' (3x8-bit pixels, true color) 

193 * 'RGBA' (4x8-bit pixels, true color with transparency mask) 

194 * 'CMYK' (4x8-bit pixels, color separation) 

195 * 'YCbCr' (3x8-bit pixels, color video format) 

196 * 'I' (32-bit signed integer pixels) 

197 * 'F' (32-bit floating point pixels) 

198 

199 PIL also provides limited support for a few special modes, including 

200 'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa' 

201 (true color with premultiplied alpha). 

202 

203 When translating a color image to grayscale (mode 'L', 'I' or 'F'), 

204 the library uses the ITU-R 601-2 luma transform:: 

205 

206 L = R * 299/1000 + G * 587/1000 + B * 114/1000 

207 as_gray : bool 

208 If True, the image is converted using mode 'F'. When `mode` is 

209 not None and `as_gray` is True, the image is first converted 

210 according to `mode`, and the result is then "flattened" using 

211 mode 'F'. 

212""" 

213 

214 

215class PillowFormat(Format): 

216 """ 

217 Base format class for Pillow formats. 

218 """ 

219 

220 _pillow_imported = False 

221 _Image = None 

222 _modes = "i" 

223 _description = "" 

224 

225 def __init__(self, *args, plugin_id: str = None, **kwargs): 

226 super(PillowFormat, self).__init__(*args, **kwargs) 

227 # Used to synchronize _init_pillow(), see #244 

228 self._lock = threading.RLock() 

229 

230 self._plugin_id = plugin_id 

231 

232 @property 

233 def plugin_id(self): 

234 """The PIL plugin id.""" 

235 return self._plugin_id # Set when format is created 

236 

237 def _init_pillow(self): 

238 with self._lock: 

239 if not self._pillow_imported: 

240 self._pillow_imported = True # more like tried to import 

241 import PIL 

242 

243 if not hasattr(PIL, "__version__"): # pragma: no cover 

244 raise ImportError( 

245 "Imageio Pillow plugin requires " "Pillow, not PIL!" 

246 ) 

247 from PIL import Image 

248 

249 self._Image = Image 

250 elif self._Image is None: # pragma: no cover 

251 raise RuntimeError("Imageio Pillow plugin requires " "Pillow lib.") 

252 Image = self._Image 

253 

254 if self.plugin_id in ("PNG", "JPEG", "BMP", "GIF", "PPM"): 

255 Image.preinit() 

256 else: 

257 Image.init() 

258 return Image 

259 

260 def _can_read(self, request): 

261 Image = self._init_pillow() 

262 if self.plugin_id in Image.OPEN: 

263 factory, accept = Image.OPEN[self.plugin_id] 

264 if accept: 

265 if request.firstbytes and accept(request.firstbytes): 

266 return True 

267 

268 def _can_write(self, request): 

269 Image = self._init_pillow() 

270 if request.extension in self.extensions or request._uri_type in [ 

271 URI_FILE, 

272 URI_BYTES, 

273 ]: 

274 if self.plugin_id in Image.SAVE: 

275 return True 

276 

277 class Reader(Format.Reader): 

278 def _open(self, pilmode=None, as_gray=False): 

279 Image = self.format._init_pillow() 

280 try: 

281 factory, accept = Image.OPEN[self.format.plugin_id] 

282 except KeyError: 

283 raise RuntimeError("Format %s cannot read images." % self.format.name) 

284 self._fp = self._get_file() 

285 self._im = factory(self._fp, "") 

286 if hasattr(Image, "_decompression_bomb_check"): 

287 Image._decompression_bomb_check(self._im.size) 

288 # Save the raw mode used by the palette for a BMP because it may not be the number of channels 

289 # When the data is read, imageio hands the palette to PIL to handle and clears the rawmode argument 

290 # However, there is a bug in PIL with handling animated GIFs with a different color palette on each frame. 

291 # This issue is resolved by using the raw palette data but the rawmode information is now lost. So we 

292 # store the raw mode for later use 

293 if self._im.palette and self._im.palette.dirty: 

294 self._im.palette.rawmode_saved = self._im.palette.rawmode 

295 pil_try_read(self._im) 

296 # Store args 

297 self._kwargs = dict( 

298 as_gray=as_gray, is_gray=_palette_is_grayscale(self._im) 

299 ) 

300 # setting mode=None is not the same as just not providing it 

301 if pilmode is not None: 

302 self._kwargs["mode"] = pilmode 

303 # Set length 

304 self._length = 1 

305 if hasattr(self._im, "n_frames"): 

306 self._length = self._im.n_frames 

307 

308 def _get_file(self): 

309 self._we_own_fp = False 

310 return self.request.get_file() 

311 

312 def _close(self): 

313 save_pillow_close(self._im) 

314 if self._we_own_fp: 

315 self._fp.close() 

316 # else: request object handles closing the _fp 

317 

318 def _get_length(self): 

319 return self._length 

320 

321 def _seek(self, index): 

322 try: 

323 self._im.seek(index) 

324 except EOFError: 

325 raise IndexError("Could not seek to index %i" % index) 

326 

327 def _get_data(self, index): 

328 if index >= self._length: 

329 raise IndexError("Image index %i > %i" % (index, self._length)) 

330 i = self._im.tell() 

331 if i > index: 

332 self._seek(index) # just try 

333 else: 

334 while i < index: # some formats need to be read in sequence 

335 i += 1 

336 self._seek(i) 

337 if self._im.palette and self._im.palette.dirty: 

338 self._im.palette.rawmode_saved = self._im.palette.rawmode 

339 self._im.getdata()[0] 

340 im = pil_get_frame(self._im, **self._kwargs) 

341 return im, self._im.info 

342 

343 def _get_meta_data(self, index): 

344 if not (index is None or index == 0): 

345 raise IndexError() 

346 return self._im.info 

347 

348 class Writer(Format.Writer): 

349 def _open(self): 

350 Image = self.format._init_pillow() 

351 try: 

352 self._save_func = Image.SAVE[self.format.plugin_id] 

353 except KeyError: 

354 raise RuntimeError("Format %s cannot write images." % self.format.name) 

355 self._fp = self.request.get_file() 

356 self._meta = {} 

357 self._written = False 

358 

359 def _close(self): 

360 pass # request object handled closing _fp 

361 

362 def _append_data(self, im, meta): 

363 if self._written: 

364 raise RuntimeError( 

365 "Format %s only supports single images." % self.format.name 

366 ) 

367 # Pop unit dimension for grayscale images 

368 if im.ndim == 3 and im.shape[-1] == 1: 

369 im = im[:, :, 0] 

370 self._written = True 

371 self._meta.update(meta) 

372 img = ndarray_to_pil( 

373 im, self.format.plugin_id, self._meta.pop("prefer_uint8", True) 

374 ) 

375 if "bits" in self._meta: 

376 img = img.quantize() # Make it a P image, so bits arg is used 

377 img.save(self._fp, format=self.format.plugin_id, **self._meta) 

378 save_pillow_close(img) 

379 

380 def set_meta_data(self, meta): 

381 self._meta.update(meta) 

382 

383 

384class PNGFormat(PillowFormat): 

385 """See :mod:`imageio.plugins.pillow_legacy`""" 

386 

387 class Reader(PillowFormat.Reader): 

388 def _open(self, pilmode=None, as_gray=False, ignoregamma=True): 

389 return PillowFormat.Reader._open(self, pilmode=pilmode, as_gray=as_gray) 

390 

391 def _get_data(self, index): 

392 im, info = PillowFormat.Reader._get_data(self, index) 

393 if not self.request.kwargs.get("ignoregamma", True): 

394 # The gamma value in the file represents the gamma factor for the 

395 # hardware on the system where the file was created, and is meant 

396 # to be able to match the colors with the system on which the 

397 # image is shown. See also issue #366 

398 try: 

399 gamma = float(info["gamma"]) 

400 except (KeyError, ValueError): 

401 pass 

402 else: 

403 scale = float(65536 if im.dtype == np.uint16 else 255) 

404 gain = 1.0 

405 im[:] = ((im / scale) ** gamma) * scale * gain + 0.4999 

406 return im, info 

407 

408 # -- 

409 

410 class Writer(PillowFormat.Writer): 

411 def _open(self, compression=None, quantize=None, interlaced=False, **kwargs): 

412 # Better default for compression 

413 kwargs["compress_level"] = kwargs.get("compress_level", 9) 

414 

415 if compression is not None: 

416 if compression < 0 or compression > 9: 

417 raise ValueError("Invalid PNG compression level: %r" % compression) 

418 kwargs["compress_level"] = compression 

419 if quantize is not None: 

420 for bits in range(1, 9): 

421 if 2**bits == quantize: 

422 break 

423 else: 

424 raise ValueError( 

425 "PNG quantize must be power of two, " "not %r" % quantize 

426 ) 

427 kwargs["bits"] = bits 

428 if interlaced: 

429 logger.warning("PIL PNG writer cannot produce interlaced images.") 

430 

431 ok_keys = ( 

432 "optimize", 

433 "transparency", 

434 "dpi", 

435 "pnginfo", 

436 "bits", 

437 "compress_level", 

438 "icc_profile", 

439 "dictionary", 

440 "prefer_uint8", 

441 ) 

442 for key in kwargs: 

443 if key not in ok_keys: 

444 raise TypeError("Invalid arg for PNG writer: %r" % key) 

445 

446 PillowFormat.Writer._open(self) 

447 self._meta.update(kwargs) 

448 

449 def _append_data(self, im, meta): 

450 if str(im.dtype) == "uint16" and (im.ndim == 2 or im.shape[-1] == 1): 

451 im = image_as_uint(im, bitdepth=16) 

452 else: 

453 im = image_as_uint(im, bitdepth=8) 

454 PillowFormat.Writer._append_data(self, im, meta) 

455 

456 

457class JPEGFormat(PillowFormat): 

458 """See :mod:`imageio.plugins.pillow_legacy`""" 

459 

460 class Reader(PillowFormat.Reader): 

461 def _open(self, pilmode=None, as_gray=False, exifrotate=True): 

462 return PillowFormat.Reader._open(self, pilmode=pilmode, as_gray=as_gray) 

463 

464 def _get_file(self): 

465 # Pillow uses seek for JPG, so we cannot directly stream from web 

466 if self.request.filename.startswith( 

467 ("http://", "https://") 

468 ) or ".zip/" in self.request.filename.replace("\\", "/"): 

469 self._we_own_fp = True 

470 return open(self.request.get_local_filename(), "rb") 

471 else: 

472 self._we_own_fp = False 

473 return self.request.get_file() 

474 

475 def _get_data(self, index): 

476 im, info = PillowFormat.Reader._get_data(self, index) 

477 

478 # Handle exif 

479 if "exif" in info: 

480 from PIL.ExifTags import TAGS 

481 

482 info["EXIF_MAIN"] = {} 

483 for tag, value in self._im._getexif().items(): 

484 decoded = TAGS.get(tag, tag) 

485 info["EXIF_MAIN"][decoded] = value 

486 

487 im = self._rotate(im, info) 

488 return im, info 

489 

490 def _rotate(self, im, meta): 

491 """Use Orientation information from EXIF meta data to 

492 orient the image correctly. Similar code as in FreeImage plugin. 

493 """ 

494 if self.request.kwargs.get("exifrotate", True): 

495 try: 

496 ori = meta["EXIF_MAIN"]["Orientation"] 

497 except KeyError: # pragma: no cover 

498 pass # Orientation not available 

499 else: # pragma: no cover - we cannot touch all cases 

500 # www.impulseadventure.com/photo/exif-orientation.html 

501 if ori in [1, 2]: 

502 pass 

503 if ori in [3, 4]: 

504 im = np.rot90(im, 2) 

505 if ori in [5, 6]: 

506 im = np.rot90(im, 3) 

507 if ori in [7, 8]: 

508 im = np.rot90(im) 

509 if ori in [2, 4, 5, 7]: # Flipped cases (rare) 

510 im = np.fliplr(im) 

511 return im 

512 

513 # -- 

514 

515 class Writer(PillowFormat.Writer): 

516 def _open(self, quality=75, progressive=False, optimize=False, **kwargs): 

517 # The JPEG quality can be between 0 (worst) and 100 (best) 

518 quality = int(quality) 

519 if quality < 0 or quality > 100: 

520 raise ValueError("JPEG quality should be between 0 and 100.") 

521 

522 kwargs["quality"] = quality 

523 kwargs["progressive"] = bool(progressive) 

524 kwargs["optimize"] = bool(progressive) 

525 

526 PillowFormat.Writer._open(self) 

527 self._meta.update(kwargs) 

528 

529 def _append_data(self, im, meta): 

530 if im.ndim == 3 and im.shape[-1] == 4: 

531 raise IOError("JPEG does not support alpha channel.") 

532 im = image_as_uint(im, bitdepth=8) 

533 PillowFormat.Writer._append_data(self, im, meta) 

534 return 

535 

536 

537class JPEG2000Format(PillowFormat): 

538 """See :mod:`imageio.plugins.pillow_legacy`""" 

539 

540 class Reader(PillowFormat.Reader): 

541 def _open(self, pilmode=None, as_gray=False): 

542 return PillowFormat.Reader._open(self, pilmode=pilmode, as_gray=as_gray) 

543 

544 def _get_file(self): 

545 # Pillow uses seek for JPG, so we cannot directly stream from web 

546 if self.request.filename.startswith( 

547 ("http://", "https://") 

548 ) or ".zip/" in self.request.filename.replace("\\", "/"): 

549 self._we_own_fp = True 

550 return open(self.request.get_local_filename(), "rb") 

551 else: 

552 self._we_own_fp = False 

553 return self.request.get_file() 

554 

555 def _get_data(self, index): 

556 im, info = PillowFormat.Reader._get_data(self, index) 

557 

558 # Handle exif 

559 if "exif" in info: 

560 from PIL.ExifTags import TAGS 

561 

562 info["EXIF_MAIN"] = {} 

563 for tag, value in self._im._getexif().items(): 

564 decoded = TAGS.get(tag, tag) 

565 info["EXIF_MAIN"][decoded] = value 

566 

567 im = self._rotate(im, info) 

568 return im, info 

569 

570 def _rotate(self, im, meta): 

571 """Use Orientation information from EXIF meta data to 

572 orient the image correctly. Similar code as in FreeImage plugin. 

573 """ 

574 if self.request.kwargs.get("exifrotate", True): 

575 try: 

576 ori = meta["EXIF_MAIN"]["Orientation"] 

577 except KeyError: # pragma: no cover 

578 pass # Orientation not available 

579 else: # pragma: no cover - we cannot touch all cases 

580 # www.impulseadventure.com/photo/exif-orientation.html 

581 if ori in [1, 2]: 

582 pass 

583 if ori in [3, 4]: 

584 im = np.rot90(im, 2) 

585 if ori in [5, 6]: 

586 im = np.rot90(im, 3) 

587 if ori in [7, 8]: 

588 im = np.rot90(im) 

589 if ori in [2, 4, 5, 7]: # Flipped cases (rare) 

590 im = np.fliplr(im) 

591 return im 

592 

593 # -- 

594 

595 class Writer(PillowFormat.Writer): 

596 def _open(self, quality_mode="rates", quality=5, **kwargs): 

597 # Check quality - in Pillow it should be no higher than 95 

598 if quality_mode not in {"rates", "dB"}: 

599 raise ValueError("Quality mode should be either 'rates' or 'dB'") 

600 

601 quality = float(quality) 

602 

603 if quality_mode == "rates" and (quality < 1 or quality > 1000): 

604 raise ValueError( 

605 "The quality value {} seems to be an invalid rate!".format(quality) 

606 ) 

607 elif quality_mode == "dB" and (quality < 15 or quality > 100): 

608 raise ValueError( 

609 "The quality value {} seems to be an invalid PSNR!".format(quality) 

610 ) 

611 

612 kwargs["quality_mode"] = quality_mode 

613 kwargs["quality_layers"] = [quality] 

614 

615 PillowFormat.Writer._open(self) 

616 self._meta.update(kwargs) 

617 

618 def _append_data(self, im, meta): 

619 if im.ndim == 3 and im.shape[-1] == 4: 

620 raise IOError( 

621 "The current implementation of JPEG 2000 does not support alpha channel." 

622 ) 

623 im = image_as_uint(im, bitdepth=8) 

624 PillowFormat.Writer._append_data(self, im, meta) 

625 return 

626 

627 

628def save_pillow_close(im): 

629 # see issue #216 and #300 

630 if hasattr(im, "close"): 

631 if hasattr(getattr(im, "fp", None), "close"): 

632 im.close() 

633 

634 

635# Func from skimage 

636 

637# This cells contains code from scikit-image, in particular from 

638# http://github.com/scikit-image/scikit-image/blob/master/ 

639# skimage/io/_plugins/pil_plugin.py 

640# The scikit-image license applies. 

641 

642 

643def pil_try_read(im): 

644 try: 

645 # this will raise an IOError if the file is not readable 

646 im.getdata()[0] 

647 except IOError as e: 

648 site = "http://pillow.readthedocs.io/en/latest/installation.html" 

649 site += "#external-libraries" 

650 pillow_error_message = str(e) 

651 error_message = ( 

652 'Could not load "%s" \n' 

653 'Reason: "%s"\n' 

654 "Please see documentation at: %s" 

655 % (im.filename, pillow_error_message, site) 

656 ) 

657 raise ValueError(error_message) 

658 

659 

660def _palette_is_grayscale(pil_image): 

661 if pil_image.mode != "P": 

662 return False 

663 elif pil_image.info.get("transparency", None): # see issue #475 

664 return False 

665 # get palette as an array with R, G, B columns 

666 # Note: starting in pillow 9.1 palettes may have less than 256 entries 

667 palette = np.asarray(pil_image.getpalette()).reshape((-1, 3)) 

668 # Not all palette colors are used; unused colors have junk values. 

669 start, stop = pil_image.getextrema() 

670 valid_palette = palette[start : stop + 1] 

671 # Image is grayscale if channel differences (R - G and G - B) 

672 # are all zero. 

673 return np.allclose(np.diff(valid_palette), 0) 

674 

675 

676def pil_get_frame(im, is_gray=None, as_gray=None, mode=None, dtype=None): 

677 """ 

678 is_gray: Whether the image *is* gray (by inspecting its palette). 

679 as_gray: Whether the resulting image must be converted to gaey. 

680 mode: The mode to convert to. 

681 """ 

682 

683 if is_gray is None: 

684 is_gray = _palette_is_grayscale(im) 

685 

686 frame = im 

687 

688 # Convert ... 

689 if mode is not None: 

690 # Mode is explicitly given ... 

691 if mode != im.mode: 

692 frame = im.convert(mode) 

693 elif as_gray: 

694 pass # don't do any auto-conversions (but do the explicit one above) 

695 elif im.mode == "P" and is_gray: 

696 # Paletted images that are already gray by their palette 

697 # are converted so that the resulting numpy array is 2D. 

698 frame = im.convert("L") 

699 elif im.mode == "P": 

700 # Paletted images are converted to RGB/RGBA. We jump some loops to make 

701 # this work well. 

702 if im.info.get("transparency", None) is not None: 

703 # Let Pillow apply the transparency, see issue #210 and #246 

704 frame = im.convert("RGBA") 

705 elif im.palette.mode in ("RGB", "RGBA"): 

706 # We can do this ourselves. Pillow seems to sometimes screw 

707 # this up if a multi-gif has a palette for each frame ... 

708 # Create palette array 

709 p = np.frombuffer(im.palette.getdata()[1], np.uint8) 

710 # Restore the raw mode that was saved to be used to parse the palette 

711 if hasattr(im.palette, "rawmode_saved"): 

712 im.palette.rawmode = im.palette.rawmode_saved 

713 mode = im.palette.rawmode if im.palette.rawmode else im.palette.mode 

714 nchannels = len(mode) 

715 # Shape it. 

716 p.shape = -1, nchannels 

717 if p.shape[1] == 3 or (p.shape[1] == 4 and mode[-1] == "X"): 

718 p = np.column_stack((p[:, :3], 255 * np.ones(p.shape[0], p.dtype))) 

719 # Swap the axes if the mode is in BGR and not RGB 

720 if mode.startswith("BGR"): 

721 p = p[:, [2, 1, 0]] if p.shape[1] == 3 else p[:, [2, 1, 0, 3]] 

722 # Apply palette 

723 frame_paletted = np.array(im, np.uint8) 

724 try: 

725 frame = p[frame_paletted] 

726 except Exception: 

727 # Ok, let PIL do it. The introduction of the branch that 

728 # tests `im.info['transparency']` should make this happen 

729 # much less often, but let's keep it, to be safe. 

730 frame = im.convert("RGBA") 

731 else: 

732 # Let Pillow do it. Unlinke skimage, we always convert 

733 # to RGBA; palettes can be RGBA. 

734 if True: # im.format == 'PNG' and 'transparency' in im.info: 

735 frame = im.convert("RGBA") 

736 else: 

737 frame = im.convert("RGB") 

738 elif "A" in im.mode: 

739 frame = im.convert("RGBA") 

740 elif im.mode == "CMYK": 

741 frame = im.convert("RGB") 

742 elif im.format == "GIF" and im.mode == "RGB": 

743 # pillow9 returns RGBA images for subsequent frames so that it can deal 

744 # with multi-frame GIF that use frame-level palettes and don't dispose 

745 # all areas. 

746 

747 # For backwards compatibility, we promote everything to RGBA. 

748 frame = im.convert("RGBA") 

749 

750 # Apply a post-convert if necessary 

751 if as_gray: 

752 frame = frame.convert("F") # Scipy compat 

753 elif not isinstance(frame, np.ndarray) and frame.mode == "1": 

754 # Workaround for crash in PIL. When im is 1-bit, the call array(im) 

755 # can cause a segfault, or generate garbage. See 

756 # https://github.com/scipy/scipy/issues/2138 and 

757 # https://github.com/python-pillow/Pillow/issues/350. 

758 # 

759 # This converts im from a 1-bit image to an 8-bit image. 

760 frame = frame.convert("L") 

761 

762 # Convert to numpy array 

763 if im.mode.startswith("I;16"): 

764 # e.g. in16 PNG's 

765 shape = im.size 

766 dtype = ">u2" if im.mode.endswith("B") else "<u2" 

767 if "S" in im.mode: 

768 dtype = dtype.replace("u", "i") 

769 frame = np.frombuffer(frame.tobytes(), dtype).copy() 

770 frame.shape = shape[::-1] 

771 else: 

772 # Use uint16 for PNG's in mode I 

773 if im.format == "PNG" and im.mode == "I" and dtype is None: 

774 dtype = "uint16" 

775 frame = np.array(frame, dtype=dtype) 

776 

777 return frame 

778 

779 

780def ndarray_to_pil(arr, format_str=None, prefer_uint8=True): 

781 from PIL import Image 

782 

783 if arr.ndim == 3: 

784 arr = image_as_uint(arr, bitdepth=8) 

785 mode = {3: "RGB", 4: "RGBA"}[arr.shape[2]] 

786 

787 elif format_str in ["png", "PNG"]: 

788 mode = "I;16" 

789 mode_base = "I" 

790 

791 if arr.dtype.kind == "f": 

792 arr = image_as_uint(arr) 

793 

794 elif prefer_uint8 and arr.max() < 256 and arr.min() >= 0: 

795 arr = arr.astype(np.uint8) 

796 mode = mode_base = "L" 

797 

798 else: 

799 arr = image_as_uint(arr, bitdepth=16) 

800 

801 else: 

802 arr = image_as_uint(arr, bitdepth=8) 

803 mode = "L" 

804 mode_base = "L" 

805 

806 if mode == "I;16" and int(getattr(Image, "__version__", "0").split(".")[0]) < 6: 

807 # Pillow < v6.0.0 has limited support for the "I;16" mode, 

808 # requiring us to fall back to this expensive workaround. 

809 # tobytes actually creates a copy of the image, which is costly. 

810 array_buffer = arr.tobytes() 

811 if arr.ndim == 2: 

812 im = Image.new(mode_base, arr.T.shape) 

813 im.frombytes(array_buffer, "raw", mode) 

814 else: 

815 image_shape = (arr.shape[1], arr.shape[0]) 

816 im = Image.frombytes(mode, image_shape, array_buffer) 

817 return im 

818 else: 

819 return Image.fromarray(arr, mode) 

820 

821 

822# imported for backwards compatibility 

823from .pillowmulti import GIFFormat, TIFFFormat # noqa: E402, F401