GifImagePlugin.py 28 KB

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