GifImagePlugin.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # GIF file handling
  6. #
  7. # History:
  8. # 1995-09-01 fl Created
  9. # 1996-12-14 fl Added interlace support
  10. # 1996-12-30 fl Added animation support
  11. # 1997-01-05 fl Added write support, fixed local colour map bug
  12. # 1997-02-23 fl Make sure to load raster data in getdata()
  13. # 1997-07-05 fl Support external decoder (0.4)
  14. # 1998-07-09 fl Handle all modes when saving (0.5)
  15. # 1998-07-15 fl Renamed offset attribute to avoid name clash
  16. # 2001-04-16 fl Added rewind support (seek to frame 0) (0.6)
  17. # 2001-04-17 fl Added palette optimization (0.7)
  18. # 2002-06-06 fl Added transparency support for save (0.8)
  19. # 2004-02-24 fl Disable interlacing for small images
  20. #
  21. # Copyright (c) 1997-2004 by Secret Labs AB
  22. # Copyright (c) 1995-2004 by Fredrik Lundh
  23. #
  24. # See the README file for information on usage and redistribution.
  25. #
  26. import itertools
  27. import math
  28. import os
  29. import subprocess
  30. from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
  31. from ._binary import i16le as i16
  32. from ._binary import o8
  33. from ._binary import o16le as o16
  34. # --------------------------------------------------------------------
  35. # Identify/read GIF files
  36. def _accept(prefix):
  37. return prefix[:6] in [b"GIF87a", b"GIF89a"]
  38. ##
  39. # Image plugin for GIF images. This plugin supports both GIF87 and
  40. # GIF89 images.
  41. class GifImageFile(ImageFile.ImageFile):
  42. format = "GIF"
  43. format_description = "Compuserve GIF"
  44. _close_exclusive_fp_after_loading = False
  45. global_palette = None
  46. def data(self):
  47. s = self.fp.read(1)
  48. if s and s[0]:
  49. return self.fp.read(s[0])
  50. return None
  51. def _open(self):
  52. # Screen
  53. s = self.fp.read(13)
  54. if not _accept(s):
  55. raise SyntaxError("not a GIF file")
  56. self.info["version"] = s[:6]
  57. self._size = i16(s, 6), i16(s, 8)
  58. self.tile = []
  59. flags = s[10]
  60. bits = (flags & 7) + 1
  61. if flags & 128:
  62. # get global palette
  63. self.info["background"] = s[11]
  64. # check if palette contains colour indices
  65. p = self.fp.read(3 << bits)
  66. for i in range(0, len(p), 3):
  67. if not (i // 3 == p[i] == p[i + 1] == p[i + 2]):
  68. p = ImagePalette.raw("RGB", p)
  69. self.global_palette = self.palette = p
  70. break
  71. self.__fp = self.fp # FIXME: hack
  72. self.__rewind = self.fp.tell()
  73. self._n_frames = None
  74. self._is_animated = None
  75. self._seek(0) # get ready to read first frame
  76. @property
  77. def n_frames(self):
  78. if self._n_frames is None:
  79. current = self.tell()
  80. try:
  81. while True:
  82. self.seek(self.tell() + 1)
  83. except EOFError:
  84. self._n_frames = self.tell() + 1
  85. self.seek(current)
  86. return self._n_frames
  87. @property
  88. def is_animated(self):
  89. if self._is_animated is None:
  90. if self._n_frames is not None:
  91. self._is_animated = self._n_frames != 1
  92. else:
  93. current = self.tell()
  94. try:
  95. self.seek(1)
  96. self._is_animated = True
  97. except EOFError:
  98. self._is_animated = False
  99. self.seek(current)
  100. return self._is_animated
  101. def seek(self, frame):
  102. if not self._seek_check(frame):
  103. return
  104. if frame < self.__frame:
  105. if frame != 0:
  106. self.im = None
  107. self._seek(0)
  108. last_frame = self.__frame
  109. for f in range(self.__frame + 1, frame + 1):
  110. try:
  111. self._seek(f)
  112. except EOFError as e:
  113. self.seek(last_frame)
  114. raise EOFError("no more images in GIF file") from e
  115. def _seek(self, frame):
  116. if frame == 0:
  117. # rewind
  118. self.__offset = 0
  119. self.dispose = None
  120. self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1
  121. self.__frame = -1
  122. self.__fp.seek(self.__rewind)
  123. self.disposal_method = 0
  124. else:
  125. # ensure that the previous frame was loaded
  126. if self.tile:
  127. self.load()
  128. if frame != self.__frame + 1:
  129. raise ValueError(f"cannot seek to frame {frame}")
  130. self.__frame = frame
  131. self.tile = []
  132. self.fp = self.__fp
  133. if self.__offset:
  134. # backup to last frame
  135. self.fp.seek(self.__offset)
  136. while self.data():
  137. pass
  138. self.__offset = 0
  139. if self.dispose:
  140. self.im.paste(self.dispose, self.dispose_extent)
  141. from copy import copy
  142. self.palette = copy(self.global_palette)
  143. info = {}
  144. frame_transparency = None
  145. interlace = None
  146. while True:
  147. s = self.fp.read(1)
  148. if not s or s == b";":
  149. break
  150. elif s == b"!":
  151. #
  152. # extensions
  153. #
  154. s = self.fp.read(1)
  155. block = self.data()
  156. if s[0] == 249:
  157. #
  158. # graphic control extension
  159. #
  160. flags = block[0]
  161. if flags & 1:
  162. frame_transparency = block[3]
  163. info["duration"] = i16(block, 1) * 10
  164. # disposal method - find the value of bits 4 - 6
  165. dispose_bits = 0b00011100 & flags
  166. dispose_bits = dispose_bits >> 2
  167. if dispose_bits:
  168. # only set the dispose if it is not
  169. # unspecified. I'm not sure if this is
  170. # correct, but it seems to prevent the last
  171. # frame from looking odd for some animations
  172. self.disposal_method = dispose_bits
  173. elif s[0] == 254:
  174. #
  175. # comment extension
  176. #
  177. while block:
  178. if "comment" in info:
  179. info["comment"] += block
  180. else:
  181. info["comment"] = block
  182. block = self.data()
  183. continue
  184. elif s[0] == 255:
  185. #
  186. # application extension
  187. #
  188. info["extension"] = block, self.fp.tell()
  189. if block[:11] == b"NETSCAPE2.0":
  190. block = self.data()
  191. if len(block) >= 3 and block[0] == 1:
  192. info["loop"] = i16(block, 1)
  193. while self.data():
  194. pass
  195. elif s == b",":
  196. #
  197. # local image
  198. #
  199. s = self.fp.read(9)
  200. # extent
  201. x0, y0 = i16(s, 0), i16(s, 2)
  202. x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
  203. if x1 > self.size[0] or y1 > self.size[1]:
  204. self._size = max(x1, self.size[0]), max(y1, self.size[1])
  205. self.dispose_extent = x0, y0, x1, y1
  206. flags = s[8]
  207. interlace = (flags & 64) != 0
  208. if flags & 128:
  209. bits = (flags & 7) + 1
  210. self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits))
  211. # image data
  212. bits = self.fp.read(1)[0]
  213. self.__offset = self.fp.tell()
  214. break
  215. else:
  216. pass
  217. # raise OSError, "illegal GIF tag `%x`" % s[0]
  218. try:
  219. if self.disposal_method < 2:
  220. # do not dispose or none specified
  221. self.dispose = None
  222. elif self.disposal_method == 2:
  223. # replace with background colour
  224. # only dispose the extent in this frame
  225. x0, y0, x1, y1 = self.dispose_extent
  226. dispose_size = (x1 - x0, y1 - y0)
  227. Image._decompression_bomb_check(dispose_size)
  228. # by convention, attempt to use transparency first
  229. color = (
  230. frame_transparency
  231. if frame_transparency is not None
  232. else self.info.get("background", 0)
  233. )
  234. self.dispose = Image.core.fill("P", dispose_size, color)
  235. else:
  236. # replace with previous contents
  237. if self.im:
  238. # only dispose the extent in this frame
  239. self.dispose = self._crop(self.im, self.dispose_extent)
  240. elif frame_transparency is not None:
  241. x0, y0, x1, y1 = self.dispose_extent
  242. dispose_size = (x1 - x0, y1 - y0)
  243. Image._decompression_bomb_check(dispose_size)
  244. self.dispose = Image.core.fill(
  245. "P", dispose_size, frame_transparency
  246. )
  247. except AttributeError:
  248. pass
  249. if interlace is not None:
  250. transparency = -1
  251. if frame_transparency is not None:
  252. if frame == 0:
  253. self.info["transparency"] = frame_transparency
  254. else:
  255. transparency = frame_transparency
  256. self.tile = [
  257. (
  258. "gif",
  259. (x0, y0, x1, y1),
  260. self.__offset,
  261. (bits, interlace, transparency),
  262. )
  263. ]
  264. else:
  265. # self.__fp = None
  266. raise EOFError
  267. for k in ["duration", "comment", "extension", "loop"]:
  268. if k in info:
  269. self.info[k] = info[k]
  270. elif k in self.info:
  271. del self.info[k]
  272. self.mode = "L"
  273. if self.palette:
  274. self.mode = "P"
  275. def load_prepare(self):
  276. if not self.im and "transparency" in self.info:
  277. self.im = Image.core.fill(self.mode, self.size, self.info["transparency"])
  278. super(GifImageFile, self).load_prepare()
  279. def tell(self):
  280. return self.__frame
  281. def _close__fp(self):
  282. try:
  283. if self.__fp != self.fp:
  284. self.__fp.close()
  285. except AttributeError:
  286. pass
  287. finally:
  288. self.__fp = None
  289. # --------------------------------------------------------------------
  290. # Write GIF files
  291. RAWMODE = {"1": "L", "L": "L", "P": "P"}
  292. def _normalize_mode(im, initial_call=False):
  293. """
  294. Takes an image (or frame), returns an image in a mode that is appropriate
  295. for saving in a Gif.
  296. It may return the original image, or it may return an image converted to
  297. palette or 'L' mode.
  298. UNDONE: What is the point of mucking with the initial call palette, for
  299. an image that shouldn't have a palette, or it would be a mode 'P' and
  300. get returned in the RAWMODE clause.
  301. :param im: Image object
  302. :param initial_call: Default false, set to true for a single frame.
  303. :returns: Image object
  304. """
  305. if im.mode in RAWMODE:
  306. im.load()
  307. return im
  308. if Image.getmodebase(im.mode) == "RGB":
  309. if initial_call:
  310. palette_size = 256
  311. if im.palette:
  312. palette_size = len(im.palette.getdata()[1]) // 3
  313. return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size)
  314. else:
  315. return im.convert("P")
  316. return im.convert("L")
  317. def _normalize_palette(im, palette, info):
  318. """
  319. Normalizes the palette for image.
  320. - Sets the palette to the incoming palette, if provided.
  321. - Ensures that there's a palette for L mode images
  322. - Optimizes the palette if necessary/desired.
  323. :param im: Image object
  324. :param palette: bytes object containing the source palette, or ....
  325. :param info: encoderinfo
  326. :returns: Image object
  327. """
  328. source_palette = None
  329. if palette:
  330. # a bytes palette
  331. if isinstance(palette, (bytes, bytearray, list)):
  332. source_palette = bytearray(palette[:768])
  333. if isinstance(palette, ImagePalette.ImagePalette):
  334. source_palette = bytearray(palette.palette)
  335. if im.mode == "P":
  336. if not source_palette:
  337. source_palette = im.im.getpalette("RGB")[:768]
  338. else: # L-mode
  339. if not source_palette:
  340. source_palette = bytearray(i // 3 for i in range(768))
  341. im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
  342. used_palette_colors = _get_optimize(im, info)
  343. if used_palette_colors is not None:
  344. return im.remap_palette(used_palette_colors, source_palette)
  345. im.palette.palette = source_palette
  346. return im
  347. def _write_single_frame(im, fp, palette):
  348. im_out = _normalize_mode(im, True)
  349. for k, v in im_out.info.items():
  350. im.encoderinfo.setdefault(k, v)
  351. im_out = _normalize_palette(im_out, palette, im.encoderinfo)
  352. for s in _get_global_header(im_out, im.encoderinfo):
  353. fp.write(s)
  354. # local image header
  355. flags = 0
  356. if get_interlace(im):
  357. flags = flags | 64
  358. _write_local_header(fp, im, (0, 0), flags)
  359. im_out.encoderconfig = (8, get_interlace(im))
  360. ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])])
  361. fp.write(b"\0") # end of image data
  362. def _write_multiple_frames(im, fp, palette):
  363. duration = im.encoderinfo.get("duration", im.info.get("duration"))
  364. disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
  365. im_frames = []
  366. frame_count = 0
  367. background_im = None
  368. for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
  369. for im_frame in ImageSequence.Iterator(imSequence):
  370. # a copy is required here since seek can still mutate the image
  371. im_frame = _normalize_mode(im_frame.copy())
  372. if frame_count == 0:
  373. for k, v in im_frame.info.items():
  374. im.encoderinfo.setdefault(k, v)
  375. im_frame = _normalize_palette(im_frame, palette, im.encoderinfo)
  376. encoderinfo = im.encoderinfo.copy()
  377. if isinstance(duration, (list, tuple)):
  378. encoderinfo["duration"] = duration[frame_count]
  379. if isinstance(disposal, (list, tuple)):
  380. encoderinfo["disposal"] = disposal[frame_count]
  381. frame_count += 1
  382. if im_frames:
  383. # delta frame
  384. previous = im_frames[-1]
  385. if encoderinfo.get("disposal") == 2:
  386. if background_im is None:
  387. color = im.encoderinfo.get(
  388. "transparency", im.info.get("transparency", (0, 0, 0))
  389. )
  390. background = _get_background(im_frame, color)
  391. background_im = Image.new("P", im_frame.size, background)
  392. background_im.putpalette(im_frames[0]["im"].palette)
  393. base_im = background_im
  394. else:
  395. base_im = previous["im"]
  396. if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im):
  397. delta = ImageChops.subtract_modulo(im_frame, base_im)
  398. else:
  399. delta = ImageChops.subtract_modulo(
  400. im_frame.convert("RGB"), base_im.convert("RGB")
  401. )
  402. bbox = delta.getbbox()
  403. if not bbox:
  404. # This frame is identical to the previous frame
  405. if duration:
  406. previous["encoderinfo"]["duration"] += encoderinfo["duration"]
  407. continue
  408. else:
  409. bbox = None
  410. im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
  411. if len(im_frames) > 1:
  412. for frame_data in im_frames:
  413. im_frame = frame_data["im"]
  414. if not frame_data["bbox"]:
  415. # global header
  416. for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
  417. fp.write(s)
  418. offset = (0, 0)
  419. else:
  420. # compress difference
  421. frame_data["encoderinfo"]["include_color_table"] = True
  422. im_frame = im_frame.crop(frame_data["bbox"])
  423. offset = frame_data["bbox"][:2]
  424. _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
  425. return True
  426. elif "duration" in im.encoderinfo and isinstance(
  427. im.encoderinfo["duration"], (list, tuple)
  428. ):
  429. # Since multiple frames will not be written, add together the frame durations
  430. im.encoderinfo["duration"] = sum(im.encoderinfo["duration"])
  431. def _save_all(im, fp, filename):
  432. _save(im, fp, filename, save_all=True)
  433. def _save(im, fp, filename, save_all=False):
  434. # header
  435. if "palette" in im.encoderinfo or "palette" in im.info:
  436. palette = im.encoderinfo.get("palette", im.info.get("palette"))
  437. else:
  438. palette = None
  439. im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
  440. if not save_all or not _write_multiple_frames(im, fp, palette):
  441. _write_single_frame(im, fp, palette)
  442. fp.write(b";") # end of file
  443. if hasattr(fp, "flush"):
  444. fp.flush()
  445. def get_interlace(im):
  446. interlace = im.encoderinfo.get("interlace", 1)
  447. # workaround for @PIL153
  448. if min(im.size) < 16:
  449. interlace = 0
  450. return interlace
  451. def _write_local_header(fp, im, offset, flags):
  452. transparent_color_exists = False
  453. try:
  454. transparency = im.encoderinfo["transparency"]
  455. except KeyError:
  456. pass
  457. else:
  458. transparency = int(transparency)
  459. # optimize the block away if transparent color is not used
  460. transparent_color_exists = True
  461. used_palette_colors = _get_optimize(im, im.encoderinfo)
  462. if used_palette_colors is not None:
  463. # adjust the transparency index after optimize
  464. try:
  465. transparency = used_palette_colors.index(transparency)
  466. except ValueError:
  467. transparent_color_exists = False
  468. if "duration" in im.encoderinfo:
  469. duration = int(im.encoderinfo["duration"] / 10)
  470. else:
  471. duration = 0
  472. disposal = int(im.encoderinfo.get("disposal", 0))
  473. if transparent_color_exists or duration != 0 or disposal:
  474. packed_flag = 1 if transparent_color_exists else 0
  475. packed_flag |= disposal << 2
  476. if not transparent_color_exists:
  477. transparency = 0
  478. fp.write(
  479. b"!"
  480. + o8(249) # extension intro
  481. + o8(4) # length
  482. + o8(packed_flag) # packed fields
  483. + o16(duration) # duration
  484. + o8(transparency) # transparency index
  485. + o8(0)
  486. )
  487. if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]):
  488. fp.write(b"!" + o8(254)) # extension intro
  489. comment = im.encoderinfo["comment"]
  490. if isinstance(comment, str):
  491. comment = comment.encode()
  492. for i in range(0, len(comment), 255):
  493. subblock = comment[i : i + 255]
  494. fp.write(o8(len(subblock)) + subblock)
  495. fp.write(o8(0))
  496. if "loop" in im.encoderinfo:
  497. number_of_loops = im.encoderinfo["loop"]
  498. fp.write(
  499. b"!"
  500. + o8(255) # extension intro
  501. + o8(11)
  502. + b"NETSCAPE2.0"
  503. + o8(3)
  504. + o8(1)
  505. + o16(number_of_loops) # number of loops
  506. + o8(0)
  507. )
  508. include_color_table = im.encoderinfo.get("include_color_table")
  509. if include_color_table:
  510. palette_bytes = _get_palette_bytes(im)
  511. color_table_size = _get_color_table_size(palette_bytes)
  512. if color_table_size:
  513. flags = flags | 128 # local color table flag
  514. flags = flags | color_table_size
  515. fp.write(
  516. b","
  517. + o16(offset[0]) # offset
  518. + o16(offset[1])
  519. + o16(im.size[0]) # size
  520. + o16(im.size[1])
  521. + o8(flags) # flags
  522. )
  523. if include_color_table and color_table_size:
  524. fp.write(_get_header_palette(palette_bytes))
  525. fp.write(o8(8)) # bits
  526. def _save_netpbm(im, fp, filename):
  527. # Unused by default.
  528. # To use, uncomment the register_save call at the end of the file.
  529. #
  530. # If you need real GIF compression and/or RGB quantization, you
  531. # can use the external NETPBM/PBMPLUS utilities. See comments
  532. # below for information on how to enable this.
  533. tempfile = im._dump()
  534. try:
  535. with open(filename, "wb") as f:
  536. if im.mode != "RGB":
  537. subprocess.check_call(
  538. ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL
  539. )
  540. else:
  541. # Pipe ppmquant output into ppmtogif
  542. # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
  543. quant_cmd = ["ppmquant", "256", tempfile]
  544. togif_cmd = ["ppmtogif"]
  545. quant_proc = subprocess.Popen(
  546. quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
  547. )
  548. togif_proc = subprocess.Popen(
  549. togif_cmd,
  550. stdin=quant_proc.stdout,
  551. stdout=f,
  552. stderr=subprocess.DEVNULL,
  553. )
  554. # Allow ppmquant to receive SIGPIPE if ppmtogif exits
  555. quant_proc.stdout.close()
  556. retcode = quant_proc.wait()
  557. if retcode:
  558. raise subprocess.CalledProcessError(retcode, quant_cmd)
  559. retcode = togif_proc.wait()
  560. if retcode:
  561. raise subprocess.CalledProcessError(retcode, togif_cmd)
  562. finally:
  563. try:
  564. os.unlink(tempfile)
  565. except OSError:
  566. pass
  567. # Force optimization so that we can test performance against
  568. # cases where it took lots of memory and time previously.
  569. _FORCE_OPTIMIZE = False
  570. def _get_optimize(im, info):
  571. """
  572. Palette optimization is a potentially expensive operation.
  573. This function determines if the palette should be optimized using
  574. some heuristics, then returns the list of palette entries in use.
  575. :param im: Image object
  576. :param info: encoderinfo
  577. :returns: list of indexes of palette entries in use, or None
  578. """
  579. if im.mode in ("P", "L") and info and info.get("optimize", 0):
  580. # Potentially expensive operation.
  581. # The palette saves 3 bytes per color not used, but palette
  582. # lengths are restricted to 3*(2**N) bytes. Max saving would
  583. # be 768 -> 6 bytes if we went all the way down to 2 colors.
  584. # * If we're over 128 colors, we can't save any space.
  585. # * If there aren't any holes, it's not worth collapsing.
  586. # * If we have a 'large' image, the palette is in the noise.
  587. # create the new palette if not every color is used
  588. optimise = _FORCE_OPTIMIZE or im.mode == "L"
  589. if optimise or im.width * im.height < 512 * 512:
  590. # check which colors are used
  591. used_palette_colors = []
  592. for i, count in enumerate(im.histogram()):
  593. if count:
  594. used_palette_colors.append(i)
  595. if optimise or (
  596. len(used_palette_colors) <= 128
  597. and max(used_palette_colors) > len(used_palette_colors)
  598. ):
  599. return used_palette_colors
  600. def _get_color_table_size(palette_bytes):
  601. # calculate the palette size for the header
  602. if not palette_bytes:
  603. return 0
  604. elif len(palette_bytes) < 9:
  605. return 1
  606. else:
  607. return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1
  608. def _get_header_palette(palette_bytes):
  609. """
  610. Returns the palette, null padded to the next power of 2 (*3) bytes
  611. suitable for direct inclusion in the GIF header
  612. :param palette_bytes: Unpadded palette bytes, in RGBRGB form
  613. :returns: Null padded palette
  614. """
  615. color_table_size = _get_color_table_size(palette_bytes)
  616. # add the missing amount of bytes
  617. # the palette has to be 2<<n in size
  618. actual_target_size_diff = (2 << color_table_size) - len(palette_bytes) // 3
  619. if actual_target_size_diff > 0:
  620. palette_bytes += o8(0) * 3 * actual_target_size_diff
  621. return palette_bytes
  622. def _get_palette_bytes(im):
  623. """
  624. Gets the palette for inclusion in the gif header
  625. :param im: Image object
  626. :returns: Bytes, len<=768 suitable for inclusion in gif header
  627. """
  628. return im.palette.palette
  629. def _get_background(im, infoBackground):
  630. background = 0
  631. if infoBackground:
  632. background = infoBackground
  633. if isinstance(background, tuple):
  634. # WebPImagePlugin stores an RGBA value in info["background"]
  635. # So it must be converted to the same format as GifImagePlugin's
  636. # info["background"] - a global color table index
  637. try:
  638. background = im.palette.getcolor(background, im)
  639. except ValueError as e:
  640. if str(e) == "cannot allocate more than 256 colors":
  641. # If all 256 colors are in use,
  642. # then there is no need for the background color
  643. return 0
  644. else:
  645. raise
  646. return background
  647. def _get_global_header(im, info):
  648. """Return a list of strings representing a GIF header"""
  649. # Header Block
  650. # http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
  651. version = b"87a"
  652. for extensionKey in ["transparency", "duration", "loop", "comment"]:
  653. if info and extensionKey in info:
  654. if (extensionKey == "duration" and info[extensionKey] == 0) or (
  655. extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255)
  656. ):
  657. continue
  658. version = b"89a"
  659. break
  660. else:
  661. if im.info.get("version") == b"89a":
  662. version = b"89a"
  663. background = _get_background(im, info.get("background"))
  664. palette_bytes = _get_palette_bytes(im)
  665. color_table_size = _get_color_table_size(palette_bytes)
  666. return [
  667. b"GIF" # signature
  668. + version # version
  669. + o16(im.size[0]) # canvas width
  670. + o16(im.size[1]), # canvas height
  671. # Logical Screen Descriptor
  672. # size of global color table + global color table flag
  673. o8(color_table_size + 128), # packed fields
  674. # background + reserved/aspect
  675. o8(background) + o8(0),
  676. # Global Color Table
  677. _get_header_palette(palette_bytes),
  678. ]
  679. def _write_frame_data(fp, im_frame, offset, params):
  680. try:
  681. im_frame.encoderinfo = params
  682. # local image header
  683. _write_local_header(fp, im_frame, offset, 0)
  684. ImageFile._save(
  685. im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])]
  686. )
  687. fp.write(b"\0") # end of image data
  688. finally:
  689. del im_frame.encoderinfo
  690. # --------------------------------------------------------------------
  691. # Legacy GIF utilities
  692. def getheader(im, palette=None, info=None):
  693. """
  694. Legacy Method to get Gif data from image.
  695. Warning:: May modify image data.
  696. :param im: Image object
  697. :param palette: bytes object containing the source palette, or ....
  698. :param info: encoderinfo
  699. :returns: tuple of(list of header items, optimized palette)
  700. """
  701. used_palette_colors = _get_optimize(im, info)
  702. if info is None:
  703. info = {}
  704. if "background" not in info and "background" in im.info:
  705. info["background"] = im.info["background"]
  706. im_mod = _normalize_palette(im, palette, info)
  707. im.palette = im_mod.palette
  708. im.im = im_mod.im
  709. header = _get_global_header(im, info)
  710. return header, used_palette_colors
  711. # To specify duration, add the time in milliseconds to getdata(),
  712. # e.g. getdata(im_frame, duration=1000)
  713. def getdata(im, offset=(0, 0), **params):
  714. """
  715. Legacy Method
  716. Return a list of strings representing this image.
  717. The first string is a local image header, the rest contains
  718. encoded image data.
  719. :param im: Image object
  720. :param offset: Tuple of (x, y) pixels. Defaults to (0,0)
  721. :param \\**params: E.g. duration or other encoder info parameters
  722. :returns: List of Bytes containing gif encoded frame data
  723. """
  724. class Collector:
  725. data = []
  726. def write(self, data):
  727. self.data.append(data)
  728. im.load() # make sure raster data is available
  729. fp = Collector()
  730. _write_frame_data(fp, im, offset, params)
  731. return fp.data
  732. # --------------------------------------------------------------------
  733. # Registry
  734. Image.register_open(GifImageFile.format, GifImageFile, _accept)
  735. Image.register_save(GifImageFile.format, _save)
  736. Image.register_save_all(GifImageFile.format, _save_all)
  737. Image.register_extension(GifImageFile.format, ".gif")
  738. Image.register_mime(GifImageFile.format, "image/gif")
  739. #
  740. # Uncomment the following line if you wish to use NETPBM/PBMPLUS
  741. # instead of the built-in "uncompressed" GIF encoder
  742. # Image.register_save(GifImageFile.format, _save_netpbm)