You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

825 lines
26 KiB

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