PngImagePlugin.py 45 KB

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