Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PngImagePlugin.py 46KB

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