PngImagePlugin.py 43 KB

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