PngImagePlugin.py 45 KB

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