Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/PngImagePlugin.py: 23%

824 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# 

2# The Python Imaging Library. 

3# $Id$ 

4# 

5# PNG support code 

6# 

7# See "PNG (Portable Network Graphics) Specification, version 1.0; 

8# W3C Recommendation", 1996-10-01, Thomas Boutell (ed.). 

9# 

10# history: 

11# 1996-05-06 fl Created (couldn't resist it) 

12# 1996-12-14 fl Upgraded, added read and verify support (0.2) 

13# 1996-12-15 fl Separate PNG stream parser 

14# 1996-12-29 fl Added write support, added getchunks 

15# 1996-12-30 fl Eliminated circular references in decoder (0.3) 

16# 1998-07-12 fl Read/write 16-bit images as mode I (0.4) 

17# 2001-02-08 fl Added transparency support (from Zircon) (0.5) 

18# 2001-04-16 fl Don't close data source in "open" method (0.6) 

19# 2004-02-24 fl Don't even pretend to support interlaced files (0.7) 

20# 2004-08-31 fl Do basic sanity check on chunk identifiers (0.8) 

21# 2004-09-20 fl Added PngInfo chunk container 

22# 2004-12-18 fl Added DPI read support (based on code by Niki Spahiev) 

23# 2008-08-13 fl Added tRNS support for RGB images 

24# 2009-03-06 fl Support for preserving ICC profiles (by Florian Hoech) 

25# 2009-03-08 fl Added zTXT support (from Lowell Alleman) 

26# 2009-03-29 fl Read interlaced PNG files (from Conrado Porto Lopes Gouvua) 

27# 

28# Copyright (c) 1997-2009 by Secret Labs AB 

29# Copyright (c) 1996 by Fredrik Lundh 

30# 

31# See the README file for information on usage and redistribution. 

32# 

33 

34import itertools 

35import logging 

36import re 

37import struct 

38import warnings 

39import zlib 

40from enum import IntEnum 

41 

42from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence 

43from ._binary import i16be as i16 

44from ._binary import i32be as i32 

45from ._binary import o8 

46from ._binary import o16be as o16 

47from ._binary import o32be as o32 

48from ._deprecate import deprecate 

49 

50logger = logging.getLogger(__name__) 

51 

52is_cid = re.compile(rb"\w\w\w\w").match 

53 

54 

55_MAGIC = b"\211PNG\r\n\032\n" 

56 

57 

58_MODES = { 

59 # supported bits/color combinations, and corresponding modes/rawmodes 

60 # Greyscale 

61 (1, 0): ("1", "1"), 

62 (2, 0): ("L", "L;2"), 

63 (4, 0): ("L", "L;4"), 

64 (8, 0): ("L", "L"), 

65 (16, 0): ("I", "I;16B"), 

66 # Truecolour 

67 (8, 2): ("RGB", "RGB"), 

68 (16, 2): ("RGB", "RGB;16B"), 

69 # Indexed-colour 

70 (1, 3): ("P", "P;1"), 

71 (2, 3): ("P", "P;2"), 

72 (4, 3): ("P", "P;4"), 

73 (8, 3): ("P", "P"), 

74 # Greyscale with alpha 

75 (8, 4): ("LA", "LA"), 

76 (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available 

77 # Truecolour with alpha 

78 (8, 6): ("RGBA", "RGBA"), 

79 (16, 6): ("RGBA", "RGBA;16B"), 

80} 

81 

82 

83_simple_palette = re.compile(b"^\xff*\x00\xff*$") 

84 

85MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK 

86""" 

87Maximum decompressed size for a iTXt or zTXt chunk. 

88Eliminates decompression bombs where compressed chunks can expand 1000x. 

89See :ref:`Text in PNG File Format<png-text>`. 

90""" 

91MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK 

92""" 

93Set the maximum total text chunk size. 

94See :ref:`Text in PNG File Format<png-text>`. 

95""" 

96 

97 

98# APNG frame disposal modes 

99class Disposal(IntEnum): 

100 OP_NONE = 0 

101 """ 

102 No disposal is done on this frame before rendering the next frame. 

103 See :ref:`Saving APNG sequences<apng-saving>`. 

104 """ 

105 OP_BACKGROUND = 1 

106 """ 

107 This frame’s modified region is cleared to fully transparent black before rendering 

108 the next frame. 

109 See :ref:`Saving APNG sequences<apng-saving>`. 

110 """ 

111 OP_PREVIOUS = 2 

112 """ 

113 This frame’s modified region is reverted to the previous frame’s contents before 

114 rendering the next frame. 

115 See :ref:`Saving APNG sequences<apng-saving>`. 

116 """ 

117 

118 

119# APNG frame blend modes 

120class Blend(IntEnum): 

121 OP_SOURCE = 0 

122 """ 

123 All color components of this frame, including alpha, overwrite the previous output 

124 image contents. 

125 See :ref:`Saving APNG sequences<apng-saving>`. 

126 """ 

127 OP_OVER = 1 

128 """ 

129 This frame should be alpha composited with the previous output image contents. 

130 See :ref:`Saving APNG sequences<apng-saving>`. 

131 """ 

132 

133 

134def __getattr__(name): 

135 for enum, prefix in {Disposal: "APNG_DISPOSE_", Blend: "APNG_BLEND_"}.items(): 

136 if name.startswith(prefix): 

137 name = name[len(prefix) :] 

138 if name in enum.__members__: 

139 deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}") 

140 return enum[name] 

141 raise AttributeError(f"module '{__name__}' has no attribute '{name}'") 

142 

143 

144def _safe_zlib_decompress(s): 

145 dobj = zlib.decompressobj() 

146 plaintext = dobj.decompress(s, MAX_TEXT_CHUNK) 

147 if dobj.unconsumed_tail: 

148 raise ValueError("Decompressed Data Too Large") 

149 return plaintext 

150 

151 

152def _crc32(data, seed=0): 

153 return zlib.crc32(data, seed) & 0xFFFFFFFF 

154 

155 

156# -------------------------------------------------------------------- 

157# Support classes. Suitable for PNG and related formats like MNG etc. 

158 

159 

160class ChunkStream: 

161 def __init__(self, fp): 

162 

163 self.fp = fp 

164 self.queue = [] 

165 

166 def read(self): 

167 """Fetch a new chunk. Returns header information.""" 

168 cid = None 

169 

170 if self.queue: 170 ↛ 171line 170 didn't jump to line 171, because the condition on line 170 was never true

171 cid, pos, length = self.queue.pop() 

172 self.fp.seek(pos) 

173 else: 

174 s = self.fp.read(8) 

175 cid = s[4:] 

176 pos = self.fp.tell() 

177 length = i32(s) 

178 

179 if not is_cid(cid): 179 ↛ 180line 179 didn't jump to line 180, because the condition on line 179 was never true

180 if not ImageFile.LOAD_TRUNCATED_IMAGES: 

181 raise SyntaxError(f"broken PNG file (chunk {repr(cid)})") 

182 

183 return cid, pos, length 

184 

185 def __enter__(self): 

186 return self 

187 

188 def __exit__(self, *args): 

189 self.close() 

190 

191 def close(self): 

192 self.queue = self.crc = self.fp = None 

193 

194 def push(self, cid, pos, length): 

195 

196 self.queue.append((cid, pos, length)) 

197 

198 def call(self, cid, pos, length): 

199 """Call the appropriate chunk handler""" 

200 

201 logger.debug("STREAM %r %s %s", cid, pos, length) 

202 return getattr(self, "chunk_" + cid.decode("ascii"))(pos, length) 

203 

204 def crc(self, cid, data): 

205 """Read and verify checksum""" 

206 

207 # Skip CRC checks for ancillary chunks if allowed to load truncated 

208 # images 

209 # 5th byte of first char is 1 [specs, section 5.4] 

210 if ImageFile.LOAD_TRUNCATED_IMAGES and (cid[0] >> 5 & 1): 210 ↛ 211line 210 didn't jump to line 211, because the condition on line 210 was never true

211 self.crc_skip(cid, data) 

212 return 

213 

214 try: 

215 crc1 = _crc32(data, _crc32(cid)) 

216 crc2 = i32(self.fp.read(4)) 

217 if crc1 != crc2: 217 ↛ 218line 217 didn't jump to line 218, because the condition on line 217 was never true

218 raise SyntaxError( 

219 f"broken PNG file (bad header checksum in {repr(cid)})" 

220 ) 

221 except struct.error as e: 

222 raise SyntaxError( 

223 f"broken PNG file (incomplete checksum in {repr(cid)})" 

224 ) from e 

225 

226 def crc_skip(self, cid, data): 

227 """Read checksum. Used if the C module is not present""" 

228 

229 self.fp.read(4) 

230 

231 def verify(self, endchunk=b"IEND"): 

232 

233 # Simple approach; just calculate checksum for all remaining 

234 # blocks. Must be called directly after open. 

235 

236 cids = [] 

237 

238 while True: 

239 try: 

240 cid, pos, length = self.read() 

241 except struct.error as e: 

242 raise OSError("truncated PNG file") from e 

243 

244 if cid == endchunk: 

245 break 

246 self.crc(cid, ImageFile._safe_read(self.fp, length)) 

247 cids.append(cid) 

248 

249 return cids 

250 

251 

252class iTXt(str): 

253 """ 

254 Subclass of string to allow iTXt chunks to look like strings while 

255 keeping their extra information 

256 

257 """ 

258 

259 @staticmethod 

260 def __new__(cls, text, lang=None, tkey=None): 

261 """ 

262 :param cls: the class to use when creating the instance 

263 :param text: value for this key 

264 :param lang: language code 

265 :param tkey: UTF-8 version of the key name 

266 """ 

267 

268 self = str.__new__(cls, text) 

269 self.lang = lang 

270 self.tkey = tkey 

271 return self 

272 

273 

274class PngInfo: 

275 """ 

276 PNG chunk container (for use with save(pnginfo=)) 

277 

278 """ 

279 

280 def __init__(self): 

281 self.chunks = [] 

282 

283 def add(self, cid, data, after_idat=False): 

284 """Appends an arbitrary chunk. Use with caution. 

285 

286 :param cid: a byte string, 4 bytes long. 

287 :param data: a byte string of the encoded data 

288 :param after_idat: for use with private chunks. Whether the chunk 

289 should be written after IDAT 

290 

291 """ 

292 

293 chunk = [cid, data] 

294 if after_idat: 

295 chunk.append(True) 

296 self.chunks.append(tuple(chunk)) 

297 

298 def add_itxt(self, key, value, lang="", tkey="", zip=False): 

299 """Appends an iTXt chunk. 

300 

301 :param key: latin-1 encodable text key name 

302 :param value: value for this key 

303 :param lang: language code 

304 :param tkey: UTF-8 version of the key name 

305 :param zip: compression flag 

306 

307 """ 

308 

309 if not isinstance(key, bytes): 

310 key = key.encode("latin-1", "strict") 

311 if not isinstance(value, bytes): 

312 value = value.encode("utf-8", "strict") 

313 if not isinstance(lang, bytes): 

314 lang = lang.encode("utf-8", "strict") 

315 if not isinstance(tkey, bytes): 

316 tkey = tkey.encode("utf-8", "strict") 

317 

318 if zip: 

319 self.add( 

320 b"iTXt", 

321 key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value), 

322 ) 

323 else: 

324 self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value) 

325 

326 def add_text(self, key, value, zip=False): 

327 """Appends a text chunk. 

328 

329 :param key: latin-1 encodable text key name 

330 :param value: value for this key, text or an 

331 :py:class:`PIL.PngImagePlugin.iTXt` instance 

332 :param zip: compression flag 

333 

334 """ 

335 if isinstance(value, iTXt): 

336 return self.add_itxt(key, value, value.lang, value.tkey, zip=zip) 

337 

338 # The tEXt chunk stores latin-1 text 

339 if not isinstance(value, bytes): 

340 try: 

341 value = value.encode("latin-1", "strict") 

342 except UnicodeError: 

343 return self.add_itxt(key, value, zip=zip) 

344 

345 if not isinstance(key, bytes): 

346 key = key.encode("latin-1", "strict") 

347 

348 if zip: 

349 self.add(b"zTXt", key + b"\0\0" + zlib.compress(value)) 

350 else: 

351 self.add(b"tEXt", key + b"\0" + value) 

352 

353 

354# -------------------------------------------------------------------- 

355# PNG image stream (IHDR/IEND) 

356 

357 

358class PngStream(ChunkStream): 

359 def __init__(self, fp): 

360 super().__init__(fp) 

361 

362 # local copies of Image attributes 

363 self.im_info = {} 

364 self.im_text = {} 

365 self.im_size = (0, 0) 

366 self.im_mode = None 

367 self.im_tile = None 

368 self.im_palette = None 

369 self.im_custom_mimetype = None 

370 self.im_n_frames = None 

371 self._seq_num = None 

372 self.rewind_state = None 

373 

374 self.text_memory = 0 

375 

376 def check_text_memory(self, chunklen): 

377 self.text_memory += chunklen 

378 if self.text_memory > MAX_TEXT_MEMORY: 

379 raise ValueError( 

380 "Too much memory used in text chunks: " 

381 f"{self.text_memory}>MAX_TEXT_MEMORY" 

382 ) 

383 

384 def save_rewind(self): 

385 self.rewind_state = { 

386 "info": self.im_info.copy(), 

387 "tile": self.im_tile, 

388 "seq_num": self._seq_num, 

389 } 

390 

391 def rewind(self): 

392 self.im_info = self.rewind_state["info"] 

393 self.im_tile = self.rewind_state["tile"] 

394 self._seq_num = self.rewind_state["seq_num"] 

395 

396 def chunk_iCCP(self, pos, length): 

397 

398 # ICC profile 

399 s = ImageFile._safe_read(self.fp, length) 

400 # according to PNG spec, the iCCP chunk contains: 

401 # Profile name 1-79 bytes (character string) 

402 # Null separator 1 byte (null character) 

403 # Compression method 1 byte (0) 

404 # Compressed profile n bytes (zlib with deflate compression) 

405 i = s.find(b"\0") 

406 logger.debug("iCCP profile name %r", s[:i]) 

407 logger.debug("Compression method %s", s[i]) 

408 comp_method = s[i] 

409 if comp_method != 0: 

410 raise SyntaxError(f"Unknown compression method {comp_method} in iCCP chunk") 

411 try: 

412 icc_profile = _safe_zlib_decompress(s[i + 2 :]) 

413 except ValueError: 

414 if ImageFile.LOAD_TRUNCATED_IMAGES: 

415 icc_profile = None 

416 else: 

417 raise 

418 except zlib.error: 

419 icc_profile = None # FIXME 

420 self.im_info["icc_profile"] = icc_profile 

421 return s 

422 

423 def chunk_IHDR(self, pos, length): 

424 

425 # image header 

426 s = ImageFile._safe_read(self.fp, length) 

427 if length < 13: 427 ↛ 428line 427 didn't jump to line 428, because the condition on line 427 was never true

428 if ImageFile.LOAD_TRUNCATED_IMAGES: 

429 return s 

430 raise ValueError("Truncated IHDR chunk") 

431 self.im_size = i32(s, 0), i32(s, 4) 

432 try: 

433 self.im_mode, self.im_rawmode = _MODES[(s[8], s[9])] 

434 except Exception: 

435 pass 

436 if s[12]: 436 ↛ 437line 436 didn't jump to line 437, because the condition on line 436 was never true

437 self.im_info["interlace"] = 1 

438 if s[11]: 438 ↛ 439line 438 didn't jump to line 439, because the condition on line 438 was never true

439 raise SyntaxError("unknown filter category") 

440 return s 

441 

442 def chunk_IDAT(self, pos, length): 

443 

444 # image data 

445 if "bbox" in self.im_info: 445 ↛ 446line 445 didn't jump to line 446, because the condition on line 445 was never true

446 tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)] 

447 else: 

448 if self.im_n_frames is not None: 448 ↛ 449line 448 didn't jump to line 449, because the condition on line 448 was never true

449 self.im_info["default_image"] = True 

450 tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)] 

451 self.im_tile = tile 

452 self.im_idat = length 

453 raise EOFError 

454 

455 def chunk_IEND(self, pos, length): 

456 

457 # end of PNG image 

458 raise EOFError 

459 

460 def chunk_PLTE(self, pos, length): 

461 

462 # palette 

463 s = ImageFile._safe_read(self.fp, length) 

464 if self.im_mode == "P": 

465 self.im_palette = "RGB", s 

466 return s 

467 

468 def chunk_tRNS(self, pos, length): 

469 

470 # transparency 

471 s = ImageFile._safe_read(self.fp, length) 

472 if self.im_mode == "P": 

473 if _simple_palette.match(s): 

474 # tRNS contains only one full-transparent entry, 

475 # other entries are full opaque 

476 i = s.find(b"\0") 

477 if i >= 0: 

478 self.im_info["transparency"] = i 

479 else: 

480 # otherwise, we have a byte string with one alpha value 

481 # for each palette entry 

482 self.im_info["transparency"] = s 

483 elif self.im_mode in ("1", "L", "I"): 

484 self.im_info["transparency"] = i16(s) 

485 elif self.im_mode == "RGB": 

486 self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4) 

487 return s 

488 

489 def chunk_gAMA(self, pos, length): 

490 # gamma setting 

491 s = ImageFile._safe_read(self.fp, length) 

492 self.im_info["gamma"] = i32(s) / 100000.0 

493 return s 

494 

495 def chunk_cHRM(self, pos, length): 

496 # chromaticity, 8 unsigned ints, actual value is scaled by 100,000 

497 # WP x,y, Red x,y, Green x,y Blue x,y 

498 

499 s = ImageFile._safe_read(self.fp, length) 

500 raw_vals = struct.unpack(">%dI" % (len(s) // 4), s) 

501 self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) 

502 return s 

503 

504 def chunk_sRGB(self, pos, length): 

505 # srgb rendering intent, 1 byte 

506 # 0 perceptual 

507 # 1 relative colorimetric 

508 # 2 saturation 

509 # 3 absolute colorimetric 

510 

511 s = ImageFile._safe_read(self.fp, length) 

512 self.im_info["srgb"] = s[0] 

513 return s 

514 

515 def chunk_pHYs(self, pos, length): 

516 

517 # pixels per unit 

518 s = ImageFile._safe_read(self.fp, length) 

519 if length < 9: 

520 if ImageFile.LOAD_TRUNCATED_IMAGES: 

521 return s 

522 raise ValueError("Truncated pHYs chunk") 

523 px, py = i32(s, 0), i32(s, 4) 

524 unit = s[8] 

525 if unit == 1: # meter 

526 dpi = px * 0.0254, py * 0.0254 

527 self.im_info["dpi"] = dpi 

528 elif unit == 0: 

529 self.im_info["aspect"] = px, py 

530 return s 

531 

532 def chunk_tEXt(self, pos, length): 

533 

534 # text 

535 s = ImageFile._safe_read(self.fp, length) 

536 try: 

537 k, v = s.split(b"\0", 1) 

538 except ValueError: 

539 # fallback for broken tEXt tags 

540 k = s 

541 v = b"" 

542 if k: 

543 k = k.decode("latin-1", "strict") 

544 v_str = v.decode("latin-1", "replace") 

545 

546 self.im_info[k] = v if k == "exif" else v_str 

547 self.im_text[k] = v_str 

548 self.check_text_memory(len(v_str)) 

549 

550 return s 

551 

552 def chunk_zTXt(self, pos, length): 

553 

554 # compressed text 

555 s = ImageFile._safe_read(self.fp, length) 

556 try: 

557 k, v = s.split(b"\0", 1) 

558 except ValueError: 

559 k = s 

560 v = b"" 

561 if v: 

562 comp_method = v[0] 

563 else: 

564 comp_method = 0 

565 if comp_method != 0: 

566 raise SyntaxError(f"Unknown compression method {comp_method} in zTXt chunk") 

567 try: 

568 v = _safe_zlib_decompress(v[1:]) 

569 except ValueError: 

570 if ImageFile.LOAD_TRUNCATED_IMAGES: 

571 v = b"" 

572 else: 

573 raise 

574 except zlib.error: 

575 v = b"" 

576 

577 if k: 

578 k = k.decode("latin-1", "strict") 

579 v = v.decode("latin-1", "replace") 

580 

581 self.im_info[k] = self.im_text[k] = v 

582 self.check_text_memory(len(v)) 

583 

584 return s 

585 

586 def chunk_iTXt(self, pos, length): 

587 

588 # international text 

589 r = s = ImageFile._safe_read(self.fp, length) 

590 try: 

591 k, r = r.split(b"\0", 1) 

592 except ValueError: 

593 return s 

594 if len(r) < 2: 

595 return s 

596 cf, cm, r = r[0], r[1], r[2:] 

597 try: 

598 lang, tk, v = r.split(b"\0", 2) 

599 except ValueError: 

600 return s 

601 if cf != 0: 

602 if cm == 0: 

603 try: 

604 v = _safe_zlib_decompress(v) 

605 except ValueError: 

606 if ImageFile.LOAD_TRUNCATED_IMAGES: 

607 return s 

608 else: 

609 raise 

610 except zlib.error: 

611 return s 

612 else: 

613 return s 

614 try: 

615 k = k.decode("latin-1", "strict") 

616 lang = lang.decode("utf-8", "strict") 

617 tk = tk.decode("utf-8", "strict") 

618 v = v.decode("utf-8", "strict") 

619 except UnicodeError: 

620 return s 

621 

622 self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) 

623 self.check_text_memory(len(v)) 

624 

625 return s 

626 

627 def chunk_eXIf(self, pos, length): 

628 s = ImageFile._safe_read(self.fp, length) 

629 self.im_info["exif"] = b"Exif\x00\x00" + s 

630 return s 

631 

632 # APNG chunks 

633 def chunk_acTL(self, pos, length): 

634 s = ImageFile._safe_read(self.fp, length) 

635 if length < 8: 

636 if ImageFile.LOAD_TRUNCATED_IMAGES: 

637 return s 

638 raise ValueError("APNG contains truncated acTL chunk") 

639 if self.im_n_frames is not None: 

640 self.im_n_frames = None 

641 warnings.warn("Invalid APNG, will use default PNG image if possible") 

642 return s 

643 n_frames = i32(s) 

644 if n_frames == 0 or n_frames > 0x80000000: 

645 warnings.warn("Invalid APNG, will use default PNG image if possible") 

646 return s 

647 self.im_n_frames = n_frames 

648 self.im_info["loop"] = i32(s, 4) 

649 self.im_custom_mimetype = "image/apng" 

650 return s 

651 

652 def chunk_fcTL(self, pos, length): 

653 s = ImageFile._safe_read(self.fp, length) 

654 if length < 26: 

655 if ImageFile.LOAD_TRUNCATED_IMAGES: 

656 return s 

657 raise ValueError("APNG contains truncated fcTL chunk") 

658 seq = i32(s) 

659 if (self._seq_num is None and seq != 0) or ( 

660 self._seq_num is not None and self._seq_num != seq - 1 

661 ): 

662 raise SyntaxError("APNG contains frame sequence errors") 

663 self._seq_num = seq 

664 width, height = i32(s, 4), i32(s, 8) 

665 px, py = i32(s, 12), i32(s, 16) 

666 im_w, im_h = self.im_size 

667 if px + width > im_w or py + height > im_h: 

668 raise SyntaxError("APNG contains invalid frames") 

669 self.im_info["bbox"] = (px, py, px + width, py + height) 

670 delay_num, delay_den = i16(s, 20), i16(s, 22) 

671 if delay_den == 0: 

672 delay_den = 100 

673 self.im_info["duration"] = float(delay_num) / float(delay_den) * 1000 

674 self.im_info["disposal"] = s[24] 

675 self.im_info["blend"] = s[25] 

676 return s 

677 

678 def chunk_fdAT(self, pos, length): 

679 if length < 4: 

680 if ImageFile.LOAD_TRUNCATED_IMAGES: 

681 s = ImageFile._safe_read(self.fp, length) 

682 return s 

683 raise ValueError("APNG contains truncated fDAT chunk") 

684 s = ImageFile._safe_read(self.fp, 4) 

685 seq = i32(s) 

686 if self._seq_num != seq - 1: 

687 raise SyntaxError("APNG contains frame sequence errors") 

688 self._seq_num = seq 

689 return self.chunk_IDAT(pos + 4, length - 4) 

690 

691 

692# -------------------------------------------------------------------- 

693# PNG reader 

694 

695 

696def _accept(prefix): 

697 return prefix[:8] == _MAGIC 

698 

699 

700## 

701# Image plugin for PNG images. 

702 

703 

704class PngImageFile(ImageFile.ImageFile): 

705 

706 format = "PNG" 

707 format_description = "Portable network graphics" 

708 

709 def _open(self): 

710 

711 if not _accept(self.fp.read(8)): 711 ↛ 712line 711 didn't jump to line 712, because the condition on line 711 was never true

712 raise SyntaxError("not a PNG file") 

713 self._fp = self.fp 

714 self.__frame = 0 

715 

716 # 

717 # Parse headers up to the first IDAT or fDAT chunk 

718 

719 self.private_chunks = [] 

720 self.png = PngStream(self.fp) 

721 

722 while True: 

723 

724 # 

725 # get next chunk 

726 

727 cid, pos, length = self.png.read() 

728 

729 try: 

730 s = self.png.call(cid, pos, length) 

731 except EOFError: 731 ↛ 733line 731 didn't jump to line 733

732 break 

733 except AttributeError: 

734 logger.debug("%r %s %s (unknown)", cid, pos, length) 

735 s = ImageFile._safe_read(self.fp, length) 

736 if cid[1:2].islower(): 

737 self.private_chunks.append((cid, s)) 

738 

739 self.png.crc(cid, s) 

740 

741 # 

742 # Copy relevant attributes from the PngStream. An alternative 

743 # would be to let the PngStream class modify these attributes 

744 # directly, but that introduces circular references which are 

745 # difficult to break if things go wrong in the decoder... 

746 # (believe me, I've tried ;-) 

747 

748 self.mode = self.png.im_mode 

749 self._size = self.png.im_size 

750 self.info = self.png.im_info 

751 self._text = None 

752 self.tile = self.png.im_tile 

753 self.custom_mimetype = self.png.im_custom_mimetype 

754 self.n_frames = self.png.im_n_frames or 1 

755 self.default_image = self.info.get("default_image", False) 

756 

757 if self.png.im_palette: 757 ↛ 758line 757 didn't jump to line 758, because the condition on line 757 was never true

758 rawmode, data = self.png.im_palette 

759 self.palette = ImagePalette.raw(rawmode, data) 

760 

761 if cid == b"fdAT": 761 ↛ 762line 761 didn't jump to line 762, because the condition on line 761 was never true

762 self.__prepare_idat = length - 4 

763 else: 

764 self.__prepare_idat = length # used by load_prepare() 

765 

766 if self.png.im_n_frames is not None: 766 ↛ 767line 766 didn't jump to line 767, because the condition on line 766 was never true

767 self._close_exclusive_fp_after_loading = False 

768 self.png.save_rewind() 

769 self.__rewind_idat = self.__prepare_idat 

770 self.__rewind = self._fp.tell() 

771 if self.default_image: 

772 # IDAT chunk contains default image and not first animation frame 

773 self.n_frames += 1 

774 self._seek(0) 

775 self.is_animated = self.n_frames > 1 

776 

777 @property 

778 def text(self): 

779 # experimental 

780 if self._text is None: 

781 # iTxt, tEXt and zTXt chunks may appear at the end of the file 

782 # So load the file to ensure that they are read 

783 if self.is_animated: 

784 frame = self.__frame 

785 # for APNG, seek to the final frame before loading 

786 self.seek(self.n_frames - 1) 

787 self.load() 

788 if self.is_animated: 

789 self.seek(frame) 

790 return self._text 

791 

792 def verify(self): 

793 """Verify PNG file""" 

794 

795 if self.fp is None: 795 ↛ 796line 795 didn't jump to line 796, because the condition on line 795 was never true

796 raise RuntimeError("verify must be called directly after open") 

797 

798 # back up to beginning of IDAT block 

799 self.fp.seek(self.tile[0][2] - 8) 

800 

801 self.png.verify() 

802 self.png.close() 

803 

804 if self._exclusive_fp: 804 ↛ 805line 804 didn't jump to line 805, because the condition on line 804 was never true

805 self.fp.close() 

806 self.fp = None 

807 

808 def seek(self, frame): 

809 if not self._seek_check(frame): 

810 return 

811 if frame < self.__frame: 

812 self._seek(0, True) 

813 

814 last_frame = self.__frame 

815 for f in range(self.__frame + 1, frame + 1): 

816 try: 

817 self._seek(f) 

818 except EOFError as e: 

819 self.seek(last_frame) 

820 raise EOFError("no more images in APNG file") from e 

821 

822 def _seek(self, frame, rewind=False): 

823 if frame == 0: 

824 if rewind: 

825 self._fp.seek(self.__rewind) 

826 self.png.rewind() 

827 self.__prepare_idat = self.__rewind_idat 

828 self.im = None 

829 if self.pyaccess: 

830 self.pyaccess = None 

831 self.info = self.png.im_info 

832 self.tile = self.png.im_tile 

833 self.fp = self._fp 

834 self._prev_im = None 

835 self.dispose = None 

836 self.default_image = self.info.get("default_image", False) 

837 self.dispose_op = self.info.get("disposal") 

838 self.blend_op = self.info.get("blend") 

839 self.dispose_extent = self.info.get("bbox") 

840 self.__frame = 0 

841 else: 

842 if frame != self.__frame + 1: 

843 raise ValueError(f"cannot seek to frame {frame}") 

844 

845 # ensure previous frame was loaded 

846 self.load() 

847 

848 if self.dispose: 

849 self.im.paste(self.dispose, self.dispose_extent) 

850 self._prev_im = self.im.copy() 

851 

852 self.fp = self._fp 

853 

854 # advance to the next frame 

855 if self.__prepare_idat: 

856 ImageFile._safe_read(self.fp, self.__prepare_idat) 

857 self.__prepare_idat = 0 

858 frame_start = False 

859 while True: 

860 self.fp.read(4) # CRC 

861 

862 try: 

863 cid, pos, length = self.png.read() 

864 except (struct.error, SyntaxError): 

865 break 

866 

867 if cid == b"IEND": 

868 raise EOFError("No more images in APNG file") 

869 if cid == b"fcTL": 

870 if frame_start: 

871 # there must be at least one fdAT chunk between fcTL chunks 

872 raise SyntaxError("APNG missing frame data") 

873 frame_start = True 

874 

875 try: 

876 self.png.call(cid, pos, length) 

877 except UnicodeDecodeError: 

878 break 

879 except EOFError: 

880 if cid == b"fdAT": 

881 length -= 4 

882 if frame_start: 

883 self.__prepare_idat = length 

884 break 

885 ImageFile._safe_read(self.fp, length) 

886 except AttributeError: 

887 logger.debug("%r %s %s (unknown)", cid, pos, length) 

888 ImageFile._safe_read(self.fp, length) 

889 

890 self.__frame = frame 

891 self.tile = self.png.im_tile 

892 self.dispose_op = self.info.get("disposal") 

893 self.blend_op = self.info.get("blend") 

894 self.dispose_extent = self.info.get("bbox") 

895 

896 if not self.tile: 

897 raise EOFError 

898 

899 # setup frame disposal (actual disposal done when needed in the next _seek()) 

900 if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS: 

901 self.dispose_op = Disposal.OP_BACKGROUND 

902 

903 if self.dispose_op == Disposal.OP_PREVIOUS: 

904 self.dispose = self._prev_im.copy() 

905 self.dispose = self._crop(self.dispose, self.dispose_extent) 

906 elif self.dispose_op == Disposal.OP_BACKGROUND: 

907 self.dispose = Image.core.fill(self.mode, self.size) 

908 self.dispose = self._crop(self.dispose, self.dispose_extent) 

909 else: 

910 self.dispose = None 

911 

912 def tell(self): 

913 return self.__frame 

914 

915 def load_prepare(self): 

916 """internal: prepare to read PNG file""" 

917 

918 if self.info.get("interlace"): 

919 self.decoderconfig = self.decoderconfig + (1,) 

920 

921 self.__idat = self.__prepare_idat # used by load_read() 

922 ImageFile.ImageFile.load_prepare(self) 

923 

924 def load_read(self, read_bytes): 

925 """internal: read more image data""" 

926 

927 while self.__idat == 0: 

928 # end of chunk, skip forward to next one 

929 

930 self.fp.read(4) # CRC 

931 

932 cid, pos, length = self.png.read() 

933 

934 if cid not in [b"IDAT", b"DDAT", b"fdAT"]: 

935 self.png.push(cid, pos, length) 

936 return b"" 

937 

938 if cid == b"fdAT": 

939 try: 

940 self.png.call(cid, pos, length) 

941 except EOFError: 

942 pass 

943 self.__idat = length - 4 # sequence_num has already been read 

944 else: 

945 self.__idat = length # empty chunks are allowed 

946 

947 # read more data from this chunk 

948 if read_bytes <= 0: 

949 read_bytes = self.__idat 

950 else: 

951 read_bytes = min(read_bytes, self.__idat) 

952 

953 self.__idat = self.__idat - read_bytes 

954 

955 return self.fp.read(read_bytes) 

956 

957 def load_end(self): 

958 """internal: finished reading image data""" 

959 if self.__idat != 0: 

960 self.fp.read(self.__idat) 

961 while True: 

962 self.fp.read(4) # CRC 

963 

964 try: 

965 cid, pos, length = self.png.read() 

966 except (struct.error, SyntaxError): 

967 break 

968 

969 if cid == b"IEND": 

970 break 

971 elif cid == b"fcTL" and self.is_animated: 

972 # start of the next frame, stop reading 

973 self.__prepare_idat = 0 

974 self.png.push(cid, pos, length) 

975 break 

976 

977 try: 

978 self.png.call(cid, pos, length) 

979 except UnicodeDecodeError: 

980 break 

981 except EOFError: 

982 if cid == b"fdAT": 

983 length -= 4 

984 ImageFile._safe_read(self.fp, length) 

985 except AttributeError: 

986 logger.debug("%r %s %s (unknown)", cid, pos, length) 

987 s = ImageFile._safe_read(self.fp, length) 

988 if cid[1:2].islower(): 

989 self.private_chunks.append((cid, s, True)) 

990 self._text = self.png.im_text 

991 if not self.is_animated: 

992 self.png.close() 

993 self.png = None 

994 else: 

995 if self._prev_im and self.blend_op == Blend.OP_OVER: 

996 updated = self._crop(self.im, self.dispose_extent) 

997 self._prev_im.paste( 

998 updated, self.dispose_extent, updated.convert("RGBA") 

999 ) 

1000 self.im = self._prev_im 

1001 if self.pyaccess: 

1002 self.pyaccess = None 

1003 

1004 def _getexif(self): 

1005 if "exif" not in self.info: 

1006 self.load() 

1007 if "exif" not in self.info and "Raw profile type exif" not in self.info: 

1008 return None 

1009 return self.getexif()._get_merged_dict() 

1010 

1011 def getexif(self): 

1012 if "exif" not in self.info: 

1013 self.load() 

1014 

1015 return super().getexif() 

1016 

1017 def getxmp(self): 

1018 """ 

1019 Returns a dictionary containing the XMP tags. 

1020 Requires defusedxml to be installed. 

1021 

1022 :returns: XMP tags in a dictionary. 

1023 """ 

1024 return ( 

1025 self._getxmp(self.info["XML:com.adobe.xmp"]) 

1026 if "XML:com.adobe.xmp" in self.info 

1027 else {} 

1028 ) 

1029 

1030 

1031# -------------------------------------------------------------------- 

1032# PNG writer 

1033 

1034_OUTMODES = { 

1035 # supported PIL modes, and corresponding rawmodes/bits/color combinations 

1036 "1": ("1", b"\x01\x00"), 

1037 "L;1": ("L;1", b"\x01\x00"), 

1038 "L;2": ("L;2", b"\x02\x00"), 

1039 "L;4": ("L;4", b"\x04\x00"), 

1040 "L": ("L", b"\x08\x00"), 

1041 "LA": ("LA", b"\x08\x04"), 

1042 "I": ("I;16B", b"\x10\x00"), 

1043 "I;16": ("I;16B", b"\x10\x00"), 

1044 "P;1": ("P;1", b"\x01\x03"), 

1045 "P;2": ("P;2", b"\x02\x03"), 

1046 "P;4": ("P;4", b"\x04\x03"), 

1047 "P": ("P", b"\x08\x03"), 

1048 "RGB": ("RGB", b"\x08\x02"), 

1049 "RGBA": ("RGBA", b"\x08\x06"), 

1050} 

1051 

1052 

1053def putchunk(fp, cid, *data): 

1054 """Write a PNG chunk (including CRC field)""" 

1055 

1056 data = b"".join(data) 

1057 

1058 fp.write(o32(len(data)) + cid) 

1059 fp.write(data) 

1060 crc = _crc32(data, _crc32(cid)) 

1061 fp.write(o32(crc)) 

1062 

1063 

1064class _idat: 

1065 # wrap output from the encoder in IDAT chunks 

1066 

1067 def __init__(self, fp, chunk): 

1068 self.fp = fp 

1069 self.chunk = chunk 

1070 

1071 def write(self, data): 

1072 self.chunk(self.fp, b"IDAT", data) 

1073 

1074 

1075class _fdat: 

1076 # wrap encoder output in fdAT chunks 

1077 

1078 def __init__(self, fp, chunk, seq_num): 

1079 self.fp = fp 

1080 self.chunk = chunk 

1081 self.seq_num = seq_num 

1082 

1083 def write(self, data): 

1084 self.chunk(self.fp, b"fdAT", o32(self.seq_num), data) 

1085 self.seq_num += 1 

1086 

1087 

1088def _write_multiple_frames(im, fp, chunk, rawmode): 

1089 default_image = im.encoderinfo.get("default_image", im.info.get("default_image")) 

1090 duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) 

1091 loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) 

1092 disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) 

1093 blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE)) 

1094 

1095 if default_image: 

1096 chain = itertools.chain(im.encoderinfo.get("append_images", [])) 

1097 else: 

1098 chain = itertools.chain([im], im.encoderinfo.get("append_images", [])) 

1099 

1100 im_frames = [] 

1101 frame_count = 0 

1102 for im_seq in chain: 

1103 for im_frame in ImageSequence.Iterator(im_seq): 

1104 im_frame = im_frame.copy() 

1105 if im_frame.mode != im.mode: 

1106 if im.mode == "P": 

1107 im_frame = im_frame.convert(im.mode, palette=im.palette) 

1108 else: 

1109 im_frame = im_frame.convert(im.mode) 

1110 encoderinfo = im.encoderinfo.copy() 

1111 if isinstance(duration, (list, tuple)): 

1112 encoderinfo["duration"] = duration[frame_count] 

1113 if isinstance(disposal, (list, tuple)): 

1114 encoderinfo["disposal"] = disposal[frame_count] 

1115 if isinstance(blend, (list, tuple)): 

1116 encoderinfo["blend"] = blend[frame_count] 

1117 frame_count += 1 

1118 

1119 if im_frames: 

1120 previous = im_frames[-1] 

1121 prev_disposal = previous["encoderinfo"].get("disposal") 

1122 prev_blend = previous["encoderinfo"].get("blend") 

1123 if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2: 

1124 prev_disposal = Disposal.OP_BACKGROUND 

1125 

1126 if prev_disposal == Disposal.OP_BACKGROUND: 

1127 base_im = previous["im"] 

1128 dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0)) 

1129 bbox = previous["bbox"] 

1130 if bbox: 

1131 dispose = dispose.crop(bbox) 

1132 else: 

1133 bbox = (0, 0) + im.size 

1134 base_im.paste(dispose, bbox) 

1135 elif prev_disposal == Disposal.OP_PREVIOUS: 

1136 base_im = im_frames[-2]["im"] 

1137 else: 

1138 base_im = previous["im"] 

1139 delta = ImageChops.subtract_modulo( 

1140 im_frame.convert("RGB"), base_im.convert("RGB") 

1141 ) 

1142 bbox = delta.getbbox() 

1143 if ( 

1144 not bbox 

1145 and prev_disposal == encoderinfo.get("disposal") 

1146 and prev_blend == encoderinfo.get("blend") 

1147 ): 

1148 if isinstance(duration, (list, tuple)): 

1149 previous["encoderinfo"]["duration"] += encoderinfo["duration"] 

1150 continue 

1151 else: 

1152 bbox = None 

1153 im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) 

1154 

1155 # animation control 

1156 chunk( 

1157 fp, 

1158 b"acTL", 

1159 o32(len(im_frames)), # 0: num_frames 

1160 o32(loop), # 4: num_plays 

1161 ) 

1162 

1163 # default image IDAT (if it exists) 

1164 if default_image: 

1165 ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) 

1166 

1167 seq_num = 0 

1168 for frame, frame_data in enumerate(im_frames): 

1169 im_frame = frame_data["im"] 

1170 if not frame_data["bbox"]: 

1171 bbox = (0, 0) + im_frame.size 

1172 else: 

1173 bbox = frame_data["bbox"] 

1174 im_frame = im_frame.crop(bbox) 

1175 size = im_frame.size 

1176 encoderinfo = frame_data["encoderinfo"] 

1177 frame_duration = int(round(encoderinfo.get("duration", duration))) 

1178 frame_disposal = encoderinfo.get("disposal", disposal) 

1179 frame_blend = encoderinfo.get("blend", blend) 

1180 # frame control 

1181 chunk( 

1182 fp, 

1183 b"fcTL", 

1184 o32(seq_num), # sequence_number 

1185 o32(size[0]), # width 

1186 o32(size[1]), # height 

1187 o32(bbox[0]), # x_offset 

1188 o32(bbox[1]), # y_offset 

1189 o16(frame_duration), # delay_numerator 

1190 o16(1000), # delay_denominator 

1191 o8(frame_disposal), # dispose_op 

1192 o8(frame_blend), # blend_op 

1193 ) 

1194 seq_num += 1 

1195 # frame data 

1196 if frame == 0 and not default_image: 

1197 # first frame must be in IDAT chunks for backwards compatibility 

1198 ImageFile._save( 

1199 im_frame, 

1200 _idat(fp, chunk), 

1201 [("zip", (0, 0) + im_frame.size, 0, rawmode)], 

1202 ) 

1203 else: 

1204 fdat_chunks = _fdat(fp, chunk, seq_num) 

1205 ImageFile._save( 

1206 im_frame, 

1207 fdat_chunks, 

1208 [("zip", (0, 0) + im_frame.size, 0, rawmode)], 

1209 ) 

1210 seq_num = fdat_chunks.seq_num 

1211 

1212 

1213def _save_all(im, fp, filename): 

1214 _save(im, fp, filename, save_all=True) 

1215 

1216 

1217def _save(im, fp, filename, chunk=putchunk, save_all=False): 

1218 # save an image to disk (called by the save method) 

1219 

1220 mode = im.mode 

1221 

1222 if mode == "P": 1222 ↛ 1226line 1222 didn't jump to line 1226, because the condition on line 1222 was never true

1223 

1224 # 

1225 # attempt to minimize storage requirements for palette images 

1226 if "bits" in im.encoderinfo: 

1227 # number of bits specified by user 

1228 colors = min(1 << im.encoderinfo["bits"], 256) 

1229 else: 

1230 # check palette contents 

1231 if im.palette: 

1232 colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1) 

1233 else: 

1234 colors = 256 

1235 

1236 if colors <= 16: 

1237 if colors <= 2: 

1238 bits = 1 

1239 elif colors <= 4: 

1240 bits = 2 

1241 else: 

1242 bits = 4 

1243 mode = f"{mode};{bits}" 

1244 

1245 # encoder options 

1246 im.encoderconfig = ( 

1247 im.encoderinfo.get("optimize", False), 

1248 im.encoderinfo.get("compress_level", -1), 

1249 im.encoderinfo.get("compress_type", -1), 

1250 im.encoderinfo.get("dictionary", b""), 

1251 ) 

1252 

1253 # get the corresponding PNG mode 

1254 try: 

1255 rawmode, mode = _OUTMODES[mode] 

1256 except KeyError as e: 

1257 raise OSError(f"cannot write mode {mode} as PNG") from e 

1258 

1259 # 

1260 # write minimal PNG file 

1261 

1262 fp.write(_MAGIC) 

1263 

1264 chunk( 

1265 fp, 

1266 b"IHDR", 

1267 o32(im.size[0]), # 0: size 

1268 o32(im.size[1]), 

1269 mode, # 8: depth/type 

1270 b"\0", # 10: compression 

1271 b"\0", # 11: filter category 

1272 b"\0", # 12: interlace flag 

1273 ) 

1274 

1275 chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"] 

1276 

1277 icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile")) 

1278 if icc: 1278 ↛ 1285line 1278 didn't jump to line 1285, because the condition on line 1278 was never true

1279 # ICC profile 

1280 # according to PNG spec, the iCCP chunk contains: 

1281 # Profile name 1-79 bytes (character string) 

1282 # Null separator 1 byte (null character) 

1283 # Compression method 1 byte (0) 

1284 # Compressed profile n bytes (zlib with deflate compression) 

1285 name = b"ICC Profile" 

1286 data = name + b"\0\0" + zlib.compress(icc) 

1287 chunk(fp, b"iCCP", data) 

1288 

1289 # You must either have sRGB or iCCP. 

1290 # Disallow sRGB chunks when an iCCP-chunk has been emitted. 

1291 chunks.remove(b"sRGB") 

1292 

1293 info = im.encoderinfo.get("pnginfo") 

1294 if info: 1294 ↛ 1295line 1294 didn't jump to line 1295, because the condition on line 1294 was never true

1295 chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"] 

1296 for info_chunk in info.chunks: 

1297 cid, data = info_chunk[:2] 

1298 if cid in chunks: 

1299 chunks.remove(cid) 

1300 chunk(fp, cid, data) 

1301 elif cid in chunks_multiple_allowed: 

1302 chunk(fp, cid, data) 

1303 elif cid[1:2].islower(): 

1304 # Private chunk 

1305 after_idat = info_chunk[2:3] 

1306 if not after_idat: 

1307 chunk(fp, cid, data) 

1308 

1309 if im.mode == "P": 1309 ↛ 1310line 1309 didn't jump to line 1310, because the condition on line 1309 was never true

1310 palette_byte_number = colors * 3 

1311 palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] 

1312 while len(palette_bytes) < palette_byte_number: 

1313 palette_bytes += b"\0" 

1314 chunk(fp, b"PLTE", palette_bytes) 

1315 

1316 transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None)) 

1317 

1318 if transparency or transparency == 0: 1318 ↛ 1319line 1318 didn't jump to line 1319, because the condition on line 1318 was never true

1319 if im.mode == "P": 

1320 # limit to actual palette size 

1321 alpha_bytes = colors 

1322 if isinstance(transparency, bytes): 

1323 chunk(fp, b"tRNS", transparency[:alpha_bytes]) 

1324 else: 

1325 transparency = max(0, min(255, transparency)) 

1326 alpha = b"\xFF" * transparency + b"\0" 

1327 chunk(fp, b"tRNS", alpha[:alpha_bytes]) 

1328 elif im.mode in ("1", "L", "I"): 

1329 transparency = max(0, min(65535, transparency)) 

1330 chunk(fp, b"tRNS", o16(transparency)) 

1331 elif im.mode == "RGB": 

1332 red, green, blue = transparency 

1333 chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue)) 

1334 else: 

1335 if "transparency" in im.encoderinfo: 

1336 # don't bother with transparency if it's an RGBA 

1337 # and it's in the info dict. It's probably just stale. 

1338 raise OSError("cannot use transparency for this mode") 

1339 else: 

1340 if im.mode == "P" and im.im.getpalettemode() == "RGBA": 1340 ↛ 1341line 1340 didn't jump to line 1341, because the condition on line 1340 was never true

1341 alpha = im.im.getpalette("RGBA", "A") 

1342 alpha_bytes = colors 

1343 chunk(fp, b"tRNS", alpha[:alpha_bytes]) 

1344 

1345 dpi = im.encoderinfo.get("dpi") 

1346 if dpi: 1346 ↛ 1347line 1346 didn't jump to line 1347, because the condition on line 1346 was never true

1347 chunk( 

1348 fp, 

1349 b"pHYs", 

1350 o32(int(dpi[0] / 0.0254 + 0.5)), 

1351 o32(int(dpi[1] / 0.0254 + 0.5)), 

1352 b"\x01", 

1353 ) 

1354 

1355 if info: 1355 ↛ 1356line 1355 didn't jump to line 1356, because the condition on line 1355 was never true

1356 chunks = [b"bKGD", b"hIST"] 

1357 for info_chunk in info.chunks: 

1358 cid, data = info_chunk[:2] 

1359 if cid in chunks: 

1360 chunks.remove(cid) 

1361 chunk(fp, cid, data) 

1362 

1363 exif = im.encoderinfo.get("exif", im.info.get("exif")) 

1364 if exif: 1364 ↛ 1365line 1364 didn't jump to line 1365, because the condition on line 1364 was never true

1365 if isinstance(exif, Image.Exif): 

1366 exif = exif.tobytes(8) 

1367 if exif.startswith(b"Exif\x00\x00"): 

1368 exif = exif[6:] 

1369 chunk(fp, b"eXIf", exif) 

1370 

1371 if save_all: 1371 ↛ 1372line 1371 didn't jump to line 1372, because the condition on line 1371 was never true

1372 _write_multiple_frames(im, fp, chunk, rawmode) 

1373 else: 

1374 ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) 

1375 

1376 if info: 1376 ↛ 1377line 1376 didn't jump to line 1377, because the condition on line 1376 was never true

1377 for info_chunk in info.chunks: 

1378 cid, data = info_chunk[:2] 

1379 if cid[1:2].islower(): 

1380 # Private chunk 

1381 after_idat = info_chunk[2:3] 

1382 if after_idat: 

1383 chunk(fp, cid, data) 

1384 

1385 chunk(fp, b"IEND", b"") 

1386 

1387 if hasattr(fp, "flush"): 1387 ↛ exitline 1387 didn't return from function '_save', because the condition on line 1387 was never false

1388 fp.flush() 

1389 

1390 

1391# -------------------------------------------------------------------- 

1392# PNG chunk converter 

1393 

1394 

1395def getchunks(im, **params): 

1396 """Return a list of PNG chunks representing this image.""" 

1397 

1398 class collector: 

1399 data = [] 

1400 

1401 def write(self, data): 

1402 pass 

1403 

1404 def append(self, chunk): 

1405 self.data.append(chunk) 

1406 

1407 def append(fp, cid, *data): 

1408 data = b"".join(data) 

1409 crc = o32(_crc32(data, _crc32(cid))) 

1410 fp.append((cid, data, crc)) 

1411 

1412 fp = collector() 

1413 

1414 try: 

1415 im.encoderinfo = params 

1416 _save(im, fp, None, append) 

1417 finally: 

1418 del im.encoderinfo 

1419 

1420 return fp.data 

1421 

1422 

1423# -------------------------------------------------------------------- 

1424# Registry 

1425 

1426Image.register_open(PngImageFile.format, PngImageFile, _accept) 

1427Image.register_save(PngImageFile.format, _save) 

1428Image.register_save_all(PngImageFile.format, _save_all) 

1429 

1430Image.register_extensions(PngImageFile.format, [".png", ".apng"]) 

1431 

1432Image.register_mime(PngImageFile.format, "image/png")