PngImagePlugin.py 44 KB

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