PngImagePlugin.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407
  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. import itertools
  34. import logging
  35. import re
  36. import struct
  37. import warnings
  38. import zlib
  39. from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
  40. from ._binary import i16be as i16
  41. from ._binary import i32be as i32
  42. from ._binary import o8
  43. from ._binary import o16be as o16
  44. from ._binary import o32be as o32
  45. logger = logging.getLogger(__name__)
  46. is_cid = re.compile(br"\w\w\w\w").match
  47. _MAGIC = b"\211PNG\r\n\032\n"
  48. _MODES = {
  49. # supported bits/color combinations, and corresponding modes/rawmodes
  50. # Greyscale
  51. (1, 0): ("1", "1"),
  52. (2, 0): ("L", "L;2"),
  53. (4, 0): ("L", "L;4"),
  54. (8, 0): ("L", "L"),
  55. (16, 0): ("I", "I;16B"),
  56. # Truecolour
  57. (8, 2): ("RGB", "RGB"),
  58. (16, 2): ("RGB", "RGB;16B"),
  59. # Indexed-colour
  60. (1, 3): ("P", "P;1"),
  61. (2, 3): ("P", "P;2"),
  62. (4, 3): ("P", "P;4"),
  63. (8, 3): ("P", "P"),
  64. # Greyscale with alpha
  65. (8, 4): ("LA", "LA"),
  66. (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
  67. # Truecolour with alpha
  68. (8, 6): ("RGBA", "RGBA"),
  69. (16, 6): ("RGBA", "RGBA;16B"),
  70. }
  71. _simple_palette = re.compile(b"^\xff*\x00\xff*$")
  72. MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK
  73. """
  74. Maximum decompressed size for a iTXt or zTXt chunk.
  75. Eliminates decompression bombs where compressed chunks can expand 1000x.
  76. See :ref:`Text in PNG File Format<png-text>`.
  77. """
  78. MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
  79. """
  80. Set the maximum total text chunk size.
  81. See :ref:`Text in PNG File Format<png-text>`.
  82. """
  83. # APNG frame disposal modes
  84. APNG_DISPOSE_OP_NONE = 0
  85. """
  86. No disposal is done on this frame before rendering the next frame.
  87. See :ref:`Saving APNG sequences<apng-saving>`.
  88. """
  89. APNG_DISPOSE_OP_BACKGROUND = 1
  90. """
  91. This frame’s modified region is cleared to fully transparent black before rendering
  92. the next frame.
  93. See :ref:`Saving APNG sequences<apng-saving>`.
  94. """
  95. APNG_DISPOSE_OP_PREVIOUS = 2
  96. """
  97. This frame’s modified region is reverted to the previous frame’s contents before
  98. rendering the next frame.
  99. See :ref:`Saving APNG sequences<apng-saving>`.
  100. """
  101. # APNG frame blend modes
  102. APNG_BLEND_OP_SOURCE = 0
  103. """
  104. All color components of this frame, including alpha, overwrite the previous output
  105. image contents.
  106. See :ref:`Saving APNG sequences<apng-saving>`.
  107. """
  108. APNG_BLEND_OP_OVER = 1
  109. """
  110. This frame should be alpha composited with the previous output image contents.
  111. See :ref:`Saving APNG sequences<apng-saving>`.
  112. """
  113. def _safe_zlib_decompress(s):
  114. dobj = zlib.decompressobj()
  115. plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
  116. if dobj.unconsumed_tail:
  117. raise ValueError("Decompressed Data Too Large")
  118. return plaintext
  119. def _crc32(data, seed=0):
  120. return zlib.crc32(data, seed) & 0xFFFFFFFF
  121. # --------------------------------------------------------------------
  122. # Support classes. Suitable for PNG and related formats like MNG etc.
  123. class ChunkStream:
  124. def __init__(self, fp):
  125. self.fp = fp
  126. self.queue = []
  127. def read(self):
  128. """Fetch a new chunk. Returns header information."""
  129. cid = None
  130. if self.queue:
  131. cid, pos, length = self.queue.pop()
  132. self.fp.seek(pos)
  133. else:
  134. s = self.fp.read(8)
  135. cid = s[4:]
  136. pos = self.fp.tell()
  137. length = i32(s)
  138. if not is_cid(cid):
  139. if not ImageFile.LOAD_TRUNCATED_IMAGES:
  140. raise SyntaxError(f"broken PNG file (chunk {repr(cid)})")
  141. return cid, pos, length
  142. def __enter__(self):
  143. return self
  144. def __exit__(self, *args):
  145. self.close()
  146. def close(self):
  147. self.queue = self.crc = self.fp = None
  148. def push(self, cid, pos, length):
  149. self.queue.append((cid, pos, length))
  150. def call(self, cid, pos, length):
  151. """Call the appropriate chunk handler"""
  152. logger.debug("STREAM %r %s %s", cid, pos, length)
  153. return getattr(self, "chunk_" + cid.decode("ascii"))(pos, length)
  154. def crc(self, cid, data):
  155. """Read and verify checksum"""
  156. # Skip CRC checks for ancillary chunks if allowed to load truncated
  157. # images
  158. # 5th byte of first char is 1 [specs, section 5.4]
  159. if ImageFile.LOAD_TRUNCATED_IMAGES and (cid[0] >> 5 & 1):
  160. self.crc_skip(cid, data)
  161. return
  162. try:
  163. crc1 = _crc32(data, _crc32(cid))
  164. crc2 = i32(self.fp.read(4))
  165. if crc1 != crc2:
  166. raise SyntaxError(
  167. f"broken PNG file (bad header checksum in {repr(cid)})"
  168. )
  169. except struct.error as e:
  170. raise SyntaxError(
  171. f"broken PNG file (incomplete checksum in {repr(cid)})"
  172. ) from e
  173. def crc_skip(self, cid, data):
  174. """Read checksum. Used if the C module is not present"""
  175. self.fp.read(4)
  176. def verify(self, endchunk=b"IEND"):
  177. # Simple approach; just calculate checksum for all remaining
  178. # blocks. Must be called directly after open.
  179. cids = []
  180. while True:
  181. try:
  182. cid, pos, length = self.read()
  183. except struct.error as e:
  184. raise OSError("truncated PNG file") from e
  185. if cid == endchunk:
  186. break
  187. self.crc(cid, ImageFile._safe_read(self.fp, length))
  188. cids.append(cid)
  189. return cids
  190. class iTXt(str):
  191. """
  192. Subclass of string to allow iTXt chunks to look like strings while
  193. keeping their extra information
  194. """
  195. @staticmethod
  196. def __new__(cls, text, lang=None, tkey=None):
  197. """
  198. :param cls: the class to use when creating the instance
  199. :param text: value for this key
  200. :param lang: language code
  201. :param tkey: UTF-8 version of the key name
  202. """
  203. self = str.__new__(cls, text)
  204. self.lang = lang
  205. self.tkey = tkey
  206. return self
  207. class PngInfo:
  208. """
  209. PNG chunk container (for use with save(pnginfo=))
  210. """
  211. def __init__(self):
  212. self.chunks = []
  213. def add(self, cid, data, after_idat=False):
  214. """Appends an arbitrary chunk. Use with caution.
  215. :param cid: a byte string, 4 bytes long.
  216. :param data: a byte string of the encoded data
  217. :param after_idat: for use with private chunks. Whether the chunk
  218. should be written after IDAT
  219. """
  220. chunk = [cid, data]
  221. if after_idat:
  222. chunk.append(True)
  223. self.chunks.append(tuple(chunk))
  224. def add_itxt(self, key, value, lang="", tkey="", zip=False):
  225. """Appends an iTXt chunk.
  226. :param key: latin-1 encodable text key name
  227. :param value: value for this key
  228. :param lang: language code
  229. :param tkey: UTF-8 version of the key name
  230. :param zip: compression flag
  231. """
  232. if not isinstance(key, bytes):
  233. key = key.encode("latin-1", "strict")
  234. if not isinstance(value, bytes):
  235. value = value.encode("utf-8", "strict")
  236. if not isinstance(lang, bytes):
  237. lang = lang.encode("utf-8", "strict")
  238. if not isinstance(tkey, bytes):
  239. tkey = tkey.encode("utf-8", "strict")
  240. if zip:
  241. self.add(
  242. b"iTXt",
  243. key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value),
  244. )
  245. else:
  246. self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
  247. def add_text(self, key, value, zip=False):
  248. """Appends a text chunk.
  249. :param key: latin-1 encodable text key name
  250. :param value: value for this key, text or an
  251. :py:class:`PIL.PngImagePlugin.iTXt` instance
  252. :param zip: compression flag
  253. """
  254. if isinstance(value, iTXt):
  255. return self.add_itxt(key, value, value.lang, value.tkey, zip=zip)
  256. # The tEXt chunk stores latin-1 text
  257. if not isinstance(value, bytes):
  258. try:
  259. value = value.encode("latin-1", "strict")
  260. except UnicodeError:
  261. return self.add_itxt(key, value, zip=zip)
  262. if not isinstance(key, bytes):
  263. key = key.encode("latin-1", "strict")
  264. if zip:
  265. self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
  266. else:
  267. self.add(b"tEXt", key + b"\0" + value)
  268. # --------------------------------------------------------------------
  269. # PNG image stream (IHDR/IEND)
  270. class PngStream(ChunkStream):
  271. def __init__(self, fp):
  272. super().__init__(fp)
  273. # local copies of Image attributes
  274. self.im_info = {}
  275. self.im_text = {}
  276. self.im_size = (0, 0)
  277. self.im_mode = None
  278. self.im_tile = None
  279. self.im_palette = None
  280. self.im_custom_mimetype = None
  281. self.im_n_frames = None
  282. self._seq_num = None
  283. self.rewind_state = None
  284. self.text_memory = 0
  285. def check_text_memory(self, chunklen):
  286. self.text_memory += chunklen
  287. if self.text_memory > MAX_TEXT_MEMORY:
  288. raise ValueError(
  289. "Too much memory used in text chunks: "
  290. f"{self.text_memory}>MAX_TEXT_MEMORY"
  291. )
  292. def save_rewind(self):
  293. self.rewind_state = {
  294. "info": self.im_info.copy(),
  295. "tile": self.im_tile,
  296. "seq_num": self._seq_num,
  297. }
  298. def rewind(self):
  299. self.im_info = self.rewind_state["info"]
  300. self.im_tile = self.rewind_state["tile"]
  301. self._seq_num = self.rewind_state["seq_num"]
  302. def chunk_iCCP(self, pos, length):
  303. # ICC profile
  304. s = ImageFile._safe_read(self.fp, length)
  305. # according to PNG spec, the iCCP chunk contains:
  306. # Profile name 1-79 bytes (character string)
  307. # Null separator 1 byte (null character)
  308. # Compression method 1 byte (0)
  309. # Compressed profile n bytes (zlib with deflate compression)
  310. i = s.find(b"\0")
  311. logger.debug("iCCP profile name %r", s[:i])
  312. logger.debug("Compression method %s", s[i])
  313. comp_method = s[i]
  314. if comp_method != 0:
  315. raise SyntaxError(f"Unknown compression method {comp_method} in iCCP chunk")
  316. try:
  317. icc_profile = _safe_zlib_decompress(s[i + 2 :])
  318. except ValueError:
  319. if ImageFile.LOAD_TRUNCATED_IMAGES:
  320. icc_profile = None
  321. else:
  322. raise
  323. except zlib.error:
  324. icc_profile = None # FIXME
  325. self.im_info["icc_profile"] = icc_profile
  326. return s
  327. def chunk_IHDR(self, pos, length):
  328. # image header
  329. s = ImageFile._safe_read(self.fp, length)
  330. self.im_size = i32(s, 0), i32(s, 4)
  331. try:
  332. self.im_mode, self.im_rawmode = _MODES[(s[8], s[9])]
  333. except Exception:
  334. pass
  335. if s[12]:
  336. self.im_info["interlace"] = 1
  337. if s[11]:
  338. raise SyntaxError("unknown filter category")
  339. return s
  340. def chunk_IDAT(self, pos, length):
  341. # image data
  342. if "bbox" in self.im_info:
  343. tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)]
  344. else:
  345. if self.im_n_frames is not None:
  346. self.im_info["default_image"] = True
  347. tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)]
  348. self.im_tile = tile
  349. self.im_idat = length
  350. raise EOFError
  351. def chunk_IEND(self, pos, length):
  352. # end of PNG image
  353. raise EOFError
  354. def chunk_PLTE(self, pos, length):
  355. # palette
  356. s = ImageFile._safe_read(self.fp, length)
  357. if self.im_mode == "P":
  358. self.im_palette = "RGB", s
  359. return s
  360. def chunk_tRNS(self, pos, length):
  361. # transparency
  362. s = ImageFile._safe_read(self.fp, length)
  363. if self.im_mode == "P":
  364. if _simple_palette.match(s):
  365. # tRNS contains only one full-transparent entry,
  366. # other entries are full opaque
  367. i = s.find(b"\0")
  368. if i >= 0:
  369. self.im_info["transparency"] = i
  370. else:
  371. # otherwise, we have a byte string with one alpha value
  372. # for each palette entry
  373. self.im_info["transparency"] = s
  374. elif self.im_mode in ("1", "L", "I"):
  375. self.im_info["transparency"] = i16(s)
  376. elif self.im_mode == "RGB":
  377. self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4)
  378. return s
  379. def chunk_gAMA(self, pos, length):
  380. # gamma setting
  381. s = ImageFile._safe_read(self.fp, length)
  382. self.im_info["gamma"] = i32(s) / 100000.0
  383. return s
  384. def chunk_cHRM(self, pos, length):
  385. # chromaticity, 8 unsigned ints, actual value is scaled by 100,000
  386. # WP x,y, Red x,y, Green x,y Blue x,y
  387. s = ImageFile._safe_read(self.fp, length)
  388. raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
  389. self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
  390. return s
  391. def chunk_sRGB(self, pos, length):
  392. # srgb rendering intent, 1 byte
  393. # 0 perceptual
  394. # 1 relative colorimetric
  395. # 2 saturation
  396. # 3 absolute colorimetric
  397. s = ImageFile._safe_read(self.fp, length)
  398. self.im_info["srgb"] = s[0]
  399. return s
  400. def chunk_pHYs(self, pos, length):
  401. # pixels per unit
  402. s = ImageFile._safe_read(self.fp, length)
  403. px, py = i32(s, 0), i32(s, 4)
  404. unit = s[8]
  405. if unit == 1: # meter
  406. dpi = px * 0.0254, py * 0.0254
  407. self.im_info["dpi"] = dpi
  408. elif unit == 0:
  409. self.im_info["aspect"] = px, py
  410. return s
  411. def chunk_tEXt(self, pos, length):
  412. # text
  413. s = ImageFile._safe_read(self.fp, length)
  414. try:
  415. k, v = s.split(b"\0", 1)
  416. except ValueError:
  417. # fallback for broken tEXt tags
  418. k = s
  419. v = b""
  420. if k:
  421. k = k.decode("latin-1", "strict")
  422. v_str = v.decode("latin-1", "replace")
  423. self.im_info[k] = v if k == "exif" else v_str
  424. self.im_text[k] = v_str
  425. self.check_text_memory(len(v_str))
  426. return s
  427. def chunk_zTXt(self, pos, length):
  428. # compressed text
  429. s = ImageFile._safe_read(self.fp, length)
  430. try:
  431. k, v = s.split(b"\0", 1)
  432. except ValueError:
  433. k = s
  434. v = b""
  435. if v:
  436. comp_method = v[0]
  437. else:
  438. comp_method = 0
  439. if comp_method != 0:
  440. raise SyntaxError(f"Unknown compression method {comp_method} in zTXt chunk")
  441. try:
  442. v = _safe_zlib_decompress(v[1:])
  443. except ValueError:
  444. if ImageFile.LOAD_TRUNCATED_IMAGES:
  445. v = b""
  446. else:
  447. raise
  448. except zlib.error:
  449. v = b""
  450. if k:
  451. k = k.decode("latin-1", "strict")
  452. v = v.decode("latin-1", "replace")
  453. self.im_info[k] = self.im_text[k] = v
  454. self.check_text_memory(len(v))
  455. return s
  456. def chunk_iTXt(self, pos, length):
  457. # international text
  458. r = s = ImageFile._safe_read(self.fp, length)
  459. try:
  460. k, r = r.split(b"\0", 1)
  461. except ValueError:
  462. return s
  463. if len(r) < 2:
  464. return s
  465. cf, cm, r = r[0], r[1], r[2:]
  466. try:
  467. lang, tk, v = r.split(b"\0", 2)
  468. except ValueError:
  469. return s
  470. if cf != 0:
  471. if cm == 0:
  472. try:
  473. v = _safe_zlib_decompress(v)
  474. except ValueError:
  475. if ImageFile.LOAD_TRUNCATED_IMAGES:
  476. return s
  477. else:
  478. raise
  479. except zlib.error:
  480. return s
  481. else:
  482. return s
  483. try:
  484. k = k.decode("latin-1", "strict")
  485. lang = lang.decode("utf-8", "strict")
  486. tk = tk.decode("utf-8", "strict")
  487. v = v.decode("utf-8", "strict")
  488. except UnicodeError:
  489. return s
  490. self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
  491. self.check_text_memory(len(v))
  492. return s
  493. def chunk_eXIf(self, pos, length):
  494. s = ImageFile._safe_read(self.fp, length)
  495. self.im_info["exif"] = b"Exif\x00\x00" + s
  496. return s
  497. # APNG chunks
  498. def chunk_acTL(self, pos, length):
  499. s = ImageFile._safe_read(self.fp, length)
  500. if self.im_n_frames is not None:
  501. self.im_n_frames = None
  502. warnings.warn("Invalid APNG, will use default PNG image if possible")
  503. return s
  504. n_frames = i32(s)
  505. if n_frames == 0 or n_frames > 0x80000000:
  506. warnings.warn("Invalid APNG, will use default PNG image if possible")
  507. return s
  508. self.im_n_frames = n_frames
  509. self.im_info["loop"] = i32(s, 4)
  510. self.im_custom_mimetype = "image/apng"
  511. return s
  512. def chunk_fcTL(self, pos, length):
  513. s = ImageFile._safe_read(self.fp, length)
  514. seq = i32(s)
  515. if (self._seq_num is None and seq != 0) or (
  516. self._seq_num is not None and self._seq_num != seq - 1
  517. ):
  518. raise SyntaxError("APNG contains frame sequence errors")
  519. self._seq_num = seq
  520. width, height = i32(s, 4), i32(s, 8)
  521. px, py = i32(s, 12), i32(s, 16)
  522. im_w, im_h = self.im_size
  523. if px + width > im_w or py + height > im_h:
  524. raise SyntaxError("APNG contains invalid frames")
  525. self.im_info["bbox"] = (px, py, px + width, py + height)
  526. delay_num, delay_den = i16(s, 20), i16(s, 22)
  527. if delay_den == 0:
  528. delay_den = 100
  529. self.im_info["duration"] = float(delay_num) / float(delay_den) * 1000
  530. self.im_info["disposal"] = s[24]
  531. self.im_info["blend"] = s[25]
  532. return s
  533. def chunk_fdAT(self, pos, length):
  534. s = ImageFile._safe_read(self.fp, 4)
  535. seq = i32(s)
  536. if self._seq_num != seq - 1:
  537. raise SyntaxError("APNG contains frame sequence errors")
  538. self._seq_num = seq
  539. return self.chunk_IDAT(pos + 4, length - 4)
  540. # --------------------------------------------------------------------
  541. # PNG reader
  542. def _accept(prefix):
  543. return prefix[:8] == _MAGIC
  544. ##
  545. # Image plugin for PNG images.
  546. class PngImageFile(ImageFile.ImageFile):
  547. format = "PNG"
  548. format_description = "Portable network graphics"
  549. def _open(self):
  550. if not _accept(self.fp.read(8)):
  551. raise SyntaxError("not a PNG file")
  552. self.__fp = self.fp
  553. self.__frame = 0
  554. #
  555. # Parse headers up to the first IDAT or fDAT chunk
  556. self.private_chunks = []
  557. self.png = PngStream(self.fp)
  558. while True:
  559. #
  560. # get next chunk
  561. cid, pos, length = self.png.read()
  562. try:
  563. s = self.png.call(cid, pos, length)
  564. except EOFError:
  565. break
  566. except AttributeError:
  567. logger.debug("%r %s %s (unknown)", cid, pos, length)
  568. s = ImageFile._safe_read(self.fp, length)
  569. if cid[1:2].islower():
  570. self.private_chunks.append((cid, s))
  571. self.png.crc(cid, s)
  572. #
  573. # Copy relevant attributes from the PngStream. An alternative
  574. # would be to let the PngStream class modify these attributes
  575. # directly, but that introduces circular references which are
  576. # difficult to break if things go wrong in the decoder...
  577. # (believe me, I've tried ;-)
  578. self.mode = self.png.im_mode
  579. self._size = self.png.im_size
  580. self.info = self.png.im_info
  581. self._text = None
  582. self.tile = self.png.im_tile
  583. self.custom_mimetype = self.png.im_custom_mimetype
  584. self.n_frames = self.png.im_n_frames or 1
  585. self.default_image = self.info.get("default_image", False)
  586. if self.png.im_palette:
  587. rawmode, data = self.png.im_palette
  588. self.palette = ImagePalette.raw(rawmode, data)
  589. if cid == b"fdAT":
  590. self.__prepare_idat = length - 4
  591. else:
  592. self.__prepare_idat = length # used by load_prepare()
  593. if self.png.im_n_frames is not None:
  594. self._close_exclusive_fp_after_loading = False
  595. self.png.save_rewind()
  596. self.__rewind_idat = self.__prepare_idat
  597. self.__rewind = self.__fp.tell()
  598. if self.default_image:
  599. # IDAT chunk contains default image and not first animation frame
  600. self.n_frames += 1
  601. self._seek(0)
  602. self.is_animated = self.n_frames > 1
  603. @property
  604. def text(self):
  605. # experimental
  606. if self._text is None:
  607. # iTxt, tEXt and zTXt chunks may appear at the end of the file
  608. # So load the file to ensure that they are read
  609. if self.is_animated:
  610. frame = self.__frame
  611. # for APNG, seek to the final frame before loading
  612. self.seek(self.n_frames - 1)
  613. self.load()
  614. if self.is_animated:
  615. self.seek(frame)
  616. return self._text
  617. def verify(self):
  618. """Verify PNG file"""
  619. if self.fp is None:
  620. raise RuntimeError("verify must be called directly after open")
  621. # back up to beginning of IDAT block
  622. self.fp.seek(self.tile[0][2] - 8)
  623. self.png.verify()
  624. self.png.close()
  625. if self._exclusive_fp:
  626. self.fp.close()
  627. self.fp = None
  628. def seek(self, frame):
  629. if not self._seek_check(frame):
  630. return
  631. if frame < self.__frame:
  632. self._seek(0, True)
  633. last_frame = self.__frame
  634. for f in range(self.__frame + 1, frame + 1):
  635. try:
  636. self._seek(f)
  637. except EOFError as e:
  638. self.seek(last_frame)
  639. raise EOFError("no more images in APNG file") from e
  640. def _seek(self, frame, rewind=False):
  641. if frame == 0:
  642. if rewind:
  643. self.__fp.seek(self.__rewind)
  644. self.png.rewind()
  645. self.__prepare_idat = self.__rewind_idat
  646. self.im = None
  647. if self.pyaccess:
  648. self.pyaccess = None
  649. self.info = self.png.im_info
  650. self.tile = self.png.im_tile
  651. self.fp = self.__fp
  652. self._prev_im = None
  653. self.dispose = None
  654. self.default_image = self.info.get("default_image", False)
  655. self.dispose_op = self.info.get("disposal")
  656. self.blend_op = self.info.get("blend")
  657. self.dispose_extent = self.info.get("bbox")
  658. self.__frame = 0
  659. else:
  660. if frame != self.__frame + 1:
  661. raise ValueError(f"cannot seek to frame {frame}")
  662. # ensure previous frame was loaded
  663. self.load()
  664. if self.dispose:
  665. self.im.paste(self.dispose, self.dispose_extent)
  666. self._prev_im = self.im.copy()
  667. self.fp = self.__fp
  668. # advance to the next frame
  669. if self.__prepare_idat:
  670. ImageFile._safe_read(self.fp, self.__prepare_idat)
  671. self.__prepare_idat = 0
  672. frame_start = False
  673. while True:
  674. self.fp.read(4) # CRC
  675. try:
  676. cid, pos, length = self.png.read()
  677. except (struct.error, SyntaxError):
  678. break
  679. if cid == b"IEND":
  680. raise EOFError("No more images in APNG file")
  681. if cid == b"fcTL":
  682. if frame_start:
  683. # there must be at least one fdAT chunk between fcTL chunks
  684. raise SyntaxError("APNG missing frame data")
  685. frame_start = True
  686. try:
  687. self.png.call(cid, pos, length)
  688. except UnicodeDecodeError:
  689. break
  690. except EOFError:
  691. if cid == b"fdAT":
  692. length -= 4
  693. if frame_start:
  694. self.__prepare_idat = length
  695. break
  696. ImageFile._safe_read(self.fp, length)
  697. except AttributeError:
  698. logger.debug("%r %s %s (unknown)", cid, pos, length)
  699. ImageFile._safe_read(self.fp, length)
  700. self.__frame = frame
  701. self.tile = self.png.im_tile
  702. self.dispose_op = self.info.get("disposal")
  703. self.blend_op = self.info.get("blend")
  704. self.dispose_extent = self.info.get("bbox")
  705. if not self.tile:
  706. raise EOFError
  707. # setup frame disposal (actual disposal done when needed in the next _seek())
  708. if self._prev_im is None and self.dispose_op == APNG_DISPOSE_OP_PREVIOUS:
  709. self.dispose_op = APNG_DISPOSE_OP_BACKGROUND
  710. if self.dispose_op == APNG_DISPOSE_OP_PREVIOUS:
  711. self.dispose = self._prev_im.copy()
  712. self.dispose = self._crop(self.dispose, self.dispose_extent)
  713. elif self.dispose_op == APNG_DISPOSE_OP_BACKGROUND:
  714. self.dispose = Image.core.fill(self.mode, self.size)
  715. self.dispose = self._crop(self.dispose, self.dispose_extent)
  716. else:
  717. self.dispose = None
  718. def tell(self):
  719. return self.__frame
  720. def load_prepare(self):
  721. """internal: prepare to read PNG file"""
  722. if self.info.get("interlace"):
  723. self.decoderconfig = self.decoderconfig + (1,)
  724. self.__idat = self.__prepare_idat # used by load_read()
  725. ImageFile.ImageFile.load_prepare(self)
  726. def load_read(self, read_bytes):
  727. """internal: read more image data"""
  728. while self.__idat == 0:
  729. # end of chunk, skip forward to next one
  730. self.fp.read(4) # CRC
  731. cid, pos, length = self.png.read()
  732. if cid not in [b"IDAT", b"DDAT", b"fdAT"]:
  733. self.png.push(cid, pos, length)
  734. return b""
  735. if cid == b"fdAT":
  736. try:
  737. self.png.call(cid, pos, length)
  738. except EOFError:
  739. pass
  740. self.__idat = length - 4 # sequence_num has already been read
  741. else:
  742. self.__idat = length # empty chunks are allowed
  743. # read more data from this chunk
  744. if read_bytes <= 0:
  745. read_bytes = self.__idat
  746. else:
  747. read_bytes = min(read_bytes, self.__idat)
  748. self.__idat = self.__idat - read_bytes
  749. return self.fp.read(read_bytes)
  750. def load_end(self):
  751. """internal: finished reading image data"""
  752. if self.__idat != 0:
  753. self.fp.read(self.__idat)
  754. while True:
  755. self.fp.read(4) # CRC
  756. try:
  757. cid, pos, length = self.png.read()
  758. except (struct.error, SyntaxError):
  759. break
  760. if cid == b"IEND":
  761. break
  762. elif cid == b"fcTL" and self.is_animated:
  763. # start of the next frame, stop reading
  764. self.__prepare_idat = 0
  765. self.png.push(cid, pos, length)
  766. break
  767. try:
  768. self.png.call(cid, pos, length)
  769. except UnicodeDecodeError:
  770. break
  771. except EOFError:
  772. if cid == b"fdAT":
  773. length -= 4
  774. ImageFile._safe_read(self.fp, length)
  775. except AttributeError:
  776. logger.debug("%r %s %s (unknown)", cid, pos, length)
  777. s = ImageFile._safe_read(self.fp, length)
  778. if cid[1:2].islower():
  779. self.private_chunks.append((cid, s, True))
  780. self._text = self.png.im_text
  781. if not self.is_animated:
  782. self.png.close()
  783. self.png = None
  784. else:
  785. if self._prev_im and self.blend_op == APNG_BLEND_OP_OVER:
  786. updated = self._crop(self.im, self.dispose_extent)
  787. self._prev_im.paste(
  788. updated, self.dispose_extent, updated.convert("RGBA")
  789. )
  790. self.im = self._prev_im
  791. if self.pyaccess:
  792. self.pyaccess = None
  793. def _getexif(self):
  794. if "exif" not in self.info:
  795. self.load()
  796. if "exif" not in self.info and "Raw profile type exif" not in self.info:
  797. return None
  798. return self.getexif()._get_merged_dict()
  799. def getexif(self):
  800. if "exif" not in self.info:
  801. self.load()
  802. return super().getexif()
  803. def getxmp(self):
  804. """
  805. Returns a dictionary containing the XMP tags.
  806. Requires defusedxml to be installed.
  807. :returns: XMP tags in a dictionary.
  808. """
  809. return (
  810. self._getxmp(self.info["XML:com.adobe.xmp"])
  811. if "XML:com.adobe.xmp" in self.info
  812. else {}
  813. )
  814. def _close__fp(self):
  815. try:
  816. if self.__fp != self.fp:
  817. self.__fp.close()
  818. except AttributeError:
  819. pass
  820. finally:
  821. self.__fp = None
  822. # --------------------------------------------------------------------
  823. # PNG writer
  824. _OUTMODES = {
  825. # supported PIL modes, and corresponding rawmodes/bits/color combinations
  826. "1": ("1", b"\x01\x00"),
  827. "L;1": ("L;1", b"\x01\x00"),
  828. "L;2": ("L;2", b"\x02\x00"),
  829. "L;4": ("L;4", b"\x04\x00"),
  830. "L": ("L", b"\x08\x00"),
  831. "LA": ("LA", b"\x08\x04"),
  832. "I": ("I;16B", b"\x10\x00"),
  833. "I;16": ("I;16B", b"\x10\x00"),
  834. "P;1": ("P;1", b"\x01\x03"),
  835. "P;2": ("P;2", b"\x02\x03"),
  836. "P;4": ("P;4", b"\x04\x03"),
  837. "P": ("P", b"\x08\x03"),
  838. "RGB": ("RGB", b"\x08\x02"),
  839. "RGBA": ("RGBA", b"\x08\x06"),
  840. }
  841. def putchunk(fp, cid, *data):
  842. """Write a PNG chunk (including CRC field)"""
  843. data = b"".join(data)
  844. fp.write(o32(len(data)) + cid)
  845. fp.write(data)
  846. crc = _crc32(data, _crc32(cid))
  847. fp.write(o32(crc))
  848. class _idat:
  849. # wrap output from the encoder in IDAT chunks
  850. def __init__(self, fp, chunk):
  851. self.fp = fp
  852. self.chunk = chunk
  853. def write(self, data):
  854. self.chunk(self.fp, b"IDAT", data)
  855. class _fdat:
  856. # wrap encoder output in fdAT chunks
  857. def __init__(self, fp, chunk, seq_num):
  858. self.fp = fp
  859. self.chunk = chunk
  860. self.seq_num = seq_num
  861. def write(self, data):
  862. self.chunk(self.fp, b"fdAT", o32(self.seq_num), data)
  863. self.seq_num += 1
  864. def _write_multiple_frames(im, fp, chunk, rawmode):
  865. default_image = im.encoderinfo.get("default_image", im.info.get("default_image"))
  866. duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
  867. loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
  868. disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
  869. blend = im.encoderinfo.get("blend", im.info.get("blend"))
  870. if default_image:
  871. chain = itertools.chain(im.encoderinfo.get("append_images", []))
  872. else:
  873. chain = itertools.chain([im], im.encoderinfo.get("append_images", []))
  874. im_frames = []
  875. frame_count = 0
  876. for im_seq in chain:
  877. for im_frame in ImageSequence.Iterator(im_seq):
  878. im_frame = im_frame.copy()
  879. if im_frame.mode != im.mode:
  880. if im.mode == "P":
  881. im_frame = im_frame.convert(im.mode, palette=im.palette)
  882. else:
  883. im_frame = im_frame.convert(im.mode)
  884. encoderinfo = im.encoderinfo.copy()
  885. if isinstance(duration, (list, tuple)):
  886. encoderinfo["duration"] = duration[frame_count]
  887. if isinstance(disposal, (list, tuple)):
  888. encoderinfo["disposal"] = disposal[frame_count]
  889. if isinstance(blend, (list, tuple)):
  890. encoderinfo["blend"] = blend[frame_count]
  891. frame_count += 1
  892. if im_frames:
  893. previous = im_frames[-1]
  894. prev_disposal = previous["encoderinfo"].get("disposal")
  895. prev_blend = previous["encoderinfo"].get("blend")
  896. if prev_disposal == APNG_DISPOSE_OP_PREVIOUS and len(im_frames) < 2:
  897. prev_disposal = APNG_DISPOSE_OP_BACKGROUND
  898. if prev_disposal == APNG_DISPOSE_OP_BACKGROUND:
  899. base_im = previous["im"]
  900. dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0))
  901. bbox = previous["bbox"]
  902. if bbox:
  903. dispose = dispose.crop(bbox)
  904. else:
  905. bbox = (0, 0) + im.size
  906. base_im.paste(dispose, bbox)
  907. elif prev_disposal == APNG_DISPOSE_OP_PREVIOUS:
  908. base_im = im_frames[-2]["im"]
  909. else:
  910. base_im = previous["im"]
  911. delta = ImageChops.subtract_modulo(
  912. im_frame.convert("RGB"), base_im.convert("RGB")
  913. )
  914. bbox = delta.getbbox()
  915. if (
  916. not bbox
  917. and prev_disposal == encoderinfo.get("disposal")
  918. and prev_blend == encoderinfo.get("blend")
  919. ):
  920. duration = encoderinfo.get("duration", 0)
  921. if duration:
  922. if "duration" in previous["encoderinfo"]:
  923. previous["encoderinfo"]["duration"] += duration
  924. else:
  925. previous["encoderinfo"]["duration"] = duration
  926. continue
  927. else:
  928. bbox = None
  929. im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
  930. # animation control
  931. chunk(
  932. fp,
  933. b"acTL",
  934. o32(len(im_frames)), # 0: num_frames
  935. o32(loop), # 4: num_plays
  936. )
  937. # default image IDAT (if it exists)
  938. if default_image:
  939. ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
  940. seq_num = 0
  941. for frame, frame_data in enumerate(im_frames):
  942. im_frame = frame_data["im"]
  943. if not frame_data["bbox"]:
  944. bbox = (0, 0) + im_frame.size
  945. else:
  946. bbox = frame_data["bbox"]
  947. im_frame = im_frame.crop(bbox)
  948. size = im_frame.size
  949. duration = int(round(frame_data["encoderinfo"].get("duration", 0)))
  950. disposal = frame_data["encoderinfo"].get("disposal", APNG_DISPOSE_OP_NONE)
  951. blend = frame_data["encoderinfo"].get("blend", APNG_BLEND_OP_SOURCE)
  952. # frame control
  953. chunk(
  954. fp,
  955. b"fcTL",
  956. o32(seq_num), # sequence_number
  957. o32(size[0]), # width
  958. o32(size[1]), # height
  959. o32(bbox[0]), # x_offset
  960. o32(bbox[1]), # y_offset
  961. o16(duration), # delay_numerator
  962. o16(1000), # delay_denominator
  963. o8(disposal), # dispose_op
  964. o8(blend), # blend_op
  965. )
  966. seq_num += 1
  967. # frame data
  968. if frame == 0 and not default_image:
  969. # first frame must be in IDAT chunks for backwards compatibility
  970. ImageFile._save(
  971. im_frame,
  972. _idat(fp, chunk),
  973. [("zip", (0, 0) + im_frame.size, 0, rawmode)],
  974. )
  975. else:
  976. fdat_chunks = _fdat(fp, chunk, seq_num)
  977. ImageFile._save(
  978. im_frame,
  979. fdat_chunks,
  980. [("zip", (0, 0) + im_frame.size, 0, rawmode)],
  981. )
  982. seq_num = fdat_chunks.seq_num
  983. def _save_all(im, fp, filename):
  984. _save(im, fp, filename, save_all=True)
  985. def _save(im, fp, filename, chunk=putchunk, save_all=False):
  986. # save an image to disk (called by the save method)
  987. mode = im.mode
  988. if mode == "P":
  989. #
  990. # attempt to minimize storage requirements for palette images
  991. if "bits" in im.encoderinfo:
  992. # number of bits specified by user
  993. colors = min(1 << im.encoderinfo["bits"], 256)
  994. else:
  995. # check palette contents
  996. if im.palette:
  997. colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1)
  998. else:
  999. colors = 256
  1000. if colors <= 16:
  1001. if colors <= 2:
  1002. bits = 1
  1003. elif colors <= 4:
  1004. bits = 2
  1005. else:
  1006. bits = 4
  1007. mode = f"{mode};{bits}"
  1008. # encoder options
  1009. im.encoderconfig = (
  1010. im.encoderinfo.get("optimize", False),
  1011. im.encoderinfo.get("compress_level", -1),
  1012. im.encoderinfo.get("compress_type", -1),
  1013. im.encoderinfo.get("dictionary", b""),
  1014. )
  1015. # get the corresponding PNG mode
  1016. try:
  1017. rawmode, mode = _OUTMODES[mode]
  1018. except KeyError as e:
  1019. raise OSError(f"cannot write mode {mode} as PNG") from e
  1020. #
  1021. # write minimal PNG file
  1022. fp.write(_MAGIC)
  1023. chunk(
  1024. fp,
  1025. b"IHDR",
  1026. o32(im.size[0]), # 0: size
  1027. o32(im.size[1]),
  1028. mode, # 8: depth/type
  1029. b"\0", # 10: compression
  1030. b"\0", # 11: filter category
  1031. b"\0", # 12: interlace flag
  1032. )
  1033. chunks = [b"cHRM", b"gAMA", b"sBIT", b"sRGB", b"tIME"]
  1034. icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
  1035. if icc:
  1036. # ICC profile
  1037. # according to PNG spec, the iCCP chunk contains:
  1038. # Profile name 1-79 bytes (character string)
  1039. # Null separator 1 byte (null character)
  1040. # Compression method 1 byte (0)
  1041. # Compressed profile n bytes (zlib with deflate compression)
  1042. name = b"ICC Profile"
  1043. data = name + b"\0\0" + zlib.compress(icc)
  1044. chunk(fp, b"iCCP", data)
  1045. # You must either have sRGB or iCCP.
  1046. # Disallow sRGB chunks when an iCCP-chunk has been emitted.
  1047. chunks.remove(b"sRGB")
  1048. info = im.encoderinfo.get("pnginfo")
  1049. if info:
  1050. chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"]
  1051. for info_chunk in info.chunks:
  1052. cid, data = info_chunk[:2]
  1053. if cid in chunks:
  1054. chunks.remove(cid)
  1055. chunk(fp, cid, data)
  1056. elif cid in chunks_multiple_allowed:
  1057. chunk(fp, cid, data)
  1058. elif cid[1:2].islower():
  1059. # Private chunk
  1060. after_idat = info_chunk[2:3]
  1061. if not after_idat:
  1062. chunk(fp, cid, data)
  1063. if im.mode == "P":
  1064. palette_byte_number = colors * 3
  1065. palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
  1066. while len(palette_bytes) < palette_byte_number:
  1067. palette_bytes += b"\0"
  1068. chunk(fp, b"PLTE", palette_bytes)
  1069. transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None))
  1070. if transparency or transparency == 0:
  1071. if im.mode == "P":
  1072. # limit to actual palette size
  1073. alpha_bytes = colors
  1074. if isinstance(transparency, bytes):
  1075. chunk(fp, b"tRNS", transparency[:alpha_bytes])
  1076. else:
  1077. transparency = max(0, min(255, transparency))
  1078. alpha = b"\xFF" * transparency + b"\0"
  1079. chunk(fp, b"tRNS", alpha[:alpha_bytes])
  1080. elif im.mode in ("1", "L", "I"):
  1081. transparency = max(0, min(65535, transparency))
  1082. chunk(fp, b"tRNS", o16(transparency))
  1083. elif im.mode == "RGB":
  1084. red, green, blue = transparency
  1085. chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue))
  1086. else:
  1087. if "transparency" in im.encoderinfo:
  1088. # don't bother with transparency if it's an RGBA
  1089. # and it's in the info dict. It's probably just stale.
  1090. raise OSError("cannot use transparency for this mode")
  1091. else:
  1092. if im.mode == "P" and im.im.getpalettemode() == "RGBA":
  1093. alpha = im.im.getpalette("RGBA", "A")
  1094. alpha_bytes = colors
  1095. chunk(fp, b"tRNS", alpha[:alpha_bytes])
  1096. dpi = im.encoderinfo.get("dpi")
  1097. if dpi:
  1098. chunk(
  1099. fp,
  1100. b"pHYs",
  1101. o32(int(dpi[0] / 0.0254 + 0.5)),
  1102. o32(int(dpi[1] / 0.0254 + 0.5)),
  1103. b"\x01",
  1104. )
  1105. if info:
  1106. chunks = [b"bKGD", b"hIST"]
  1107. for info_chunk in info.chunks:
  1108. cid, data = info_chunk[:2]
  1109. if cid in chunks:
  1110. chunks.remove(cid)
  1111. chunk(fp, cid, data)
  1112. exif = im.encoderinfo.get("exif", im.info.get("exif"))
  1113. if exif:
  1114. if isinstance(exif, Image.Exif):
  1115. exif = exif.tobytes(8)
  1116. if exif.startswith(b"Exif\x00\x00"):
  1117. exif = exif[6:]
  1118. chunk(fp, b"eXIf", exif)
  1119. if save_all:
  1120. _write_multiple_frames(im, fp, chunk, rawmode)
  1121. else:
  1122. ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
  1123. if info:
  1124. for info_chunk in info.chunks:
  1125. cid, data = info_chunk[:2]
  1126. if cid[1:2].islower():
  1127. # Private chunk
  1128. after_idat = info_chunk[2:3]
  1129. if after_idat:
  1130. chunk(fp, cid, data)
  1131. chunk(fp, b"IEND", b"")
  1132. if hasattr(fp, "flush"):
  1133. fp.flush()
  1134. # --------------------------------------------------------------------
  1135. # PNG chunk converter
  1136. def getchunks(im, **params):
  1137. """Return a list of PNG chunks representing this image."""
  1138. class collector:
  1139. data = []
  1140. def write(self, data):
  1141. pass
  1142. def append(self, chunk):
  1143. self.data.append(chunk)
  1144. def append(fp, cid, *data):
  1145. data = b"".join(data)
  1146. crc = o32(_crc32(data, _crc32(cid)))
  1147. fp.append((cid, data, crc))
  1148. fp = collector()
  1149. try:
  1150. im.encoderinfo = params
  1151. _save(im, fp, None, append)
  1152. finally:
  1153. del im.encoderinfo
  1154. return fp.data
  1155. # --------------------------------------------------------------------
  1156. # Registry
  1157. Image.register_open(PngImageFile.format, PngImageFile, _accept)
  1158. Image.register_save(PngImageFile.format, _save)
  1159. Image.register_save_all(PngImageFile.format, _save_all)
  1160. Image.register_extensions(PngImageFile.format, [".png", ".apng"])
  1161. Image.register_mime(PngImageFile.format, "image/png")