Merged gifmaker into GifImagePlugin

This commit is contained in:
Andrew Murray 2015-06-30 18:02:48 +10:00
parent 75be4af068
commit 2c4fe7281f
3 changed files with 68 additions and 117 deletions

View File

@ -24,7 +24,7 @@
# See the README file for information on usage and redistribution.
#
from PIL import Image, ImageFile, ImagePalette, _binary
from PIL import Image, ImageFile, ImagePalette, ImageChops, ImageSequence, _binary
__version__ = "0.9"
@ -284,8 +284,10 @@ RAWMODE = {
"P": "P",
}
def _save_all(im, fp, filename):
_save(im, fp, filename, save_all=True)
def _save(im, fp, filename):
def _save(im, fp, filename, save_all=False):
if _imaging_gif:
# call external driver
@ -315,7 +317,31 @@ def _save(im, fp, filename):
palette = None
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
if save_all:
previous = None
for im_frame in ImageSequence.Iterator(im_out):
# To specify duration, add the time in milliseconds to getdata(),
# e.g. getdata(im_frame, duration=1000)
if not previous:
# global header
for s in getheader(im_frame, palette, im.encoderinfo)[0] + getdata(im_frame):
fp.write(s)
else:
# delta frame
delta = ImageChops.subtract_modulo(im_frame, previous)
bbox = delta.getbbox()
if bbox:
# compress difference
for s in getdata(im_frame.crop(bbox), offset=bbox[:2]):
fp.write(s)
else:
# FIXME: what should we do in this case?
pass
previous = im_frame.copy()
else:
header = getheader(im_out, palette, im.encoderinfo)[0]
for s in header:
fp.write(s)
@ -325,7 +351,7 @@ def _save(im, fp, filename):
flags = flags | 64
# local image header
get_local_header(fp, im, (0, 0), flags)
_get_local_header(fp, im, (0, 0), flags)
im_out.encoderconfig = (8, get_interlace(im))
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
@ -354,7 +380,7 @@ def get_interlace(im):
return interlace
def get_local_header(fp, im, offset, flags):
def _get_local_header(fp, im, offset, flags):
transparent_color_exists = False
try:
transparency = im.encoderinfo["transparency"]
@ -577,7 +603,7 @@ def getdata(im, offset=(0, 0), **params):
im.encoderinfo = params
# local image header
get_local_header(fp, im, offset, 0)
_get_local_header(fp, im, offset, 0)
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
@ -594,6 +620,7 @@ def getdata(im, offset=(0, 0), **params):
Image.register_open(GifImageFile.format, GifImageFile, _accept)
Image.register_save(GifImageFile.format, _save)
Image.register_save_all(GifImageFile.format, _save_all)
Image.register_extension(GifImageFile.format, ".gif")
Image.register_mime(GifImageFile.format, "image/gif")

View File

@ -204,6 +204,7 @@ ID = []
OPEN = {}
MIME = {}
SAVE = {}
SAVE_ALL = {}
EXTENSION = {}
# --------------------------------------------------------------------
@ -1669,6 +1670,10 @@ class Image(object):
# may mutate self!
self.load()
save_all = False
if 'save_all' in params:
save_all = params['save_all']
del params['save_all']
self.encoderinfo = params
self.encoderconfig = ()
@ -1686,11 +1691,12 @@ class Image(object):
except KeyError:
raise KeyError(ext) # unknown extension
try:
save_handler = SAVE[format.upper()]
except KeyError:
if format.upper() not in SAVE:
init()
save_handler = SAVE[format.upper()] # unknown format
if save_all:
save_handler = SAVE_ALL[format.upper()]
else:
save_handler = SAVE[format.upper()]
if isPath(fp):
fp = builtins.open(fp, "wb")
@ -2469,6 +2475,18 @@ def register_save(id, driver):
SAVE[id.upper()] = driver
def register_save_all(id, driver):
"""
Registers an image function to save all the frames
of a multiframe format. This function should not be
used in application code.
:param id: An image format identifier.
:param driver: A function to save images in this format.
"""
SAVE_ALL[id.upper()] = driver
def register_extension(id, extension):
"""
Registers an image extension. This function should not be

View File

@ -14,104 +14,9 @@
# See the README file for information on usage and redistribution.
#
#
# For special purposes, you can import this module and call
# the makedelta or compress functions yourself. For example,
# if you have an application that generates a sequence of
# images, you can convert it to a GIF animation using some-
# thing like the following code:
#
# import Image
# import gifmaker
#
# sequence = []
#
# # generate sequence
# for i in range(100):
# im = <generate image i>
# sequence.append(im)
#
# # write GIF animation
# fp = open("out.gif", "wb")
# gifmaker.makedelta(fp, sequence)
# fp.close()
#
# Alternatively, use an iterator to generate the sequence, and
# write data directly to a socket. Or something...
#
from __future__ import print_function
from PIL import Image, ImageChops, ImageSequence
from PIL.GifImagePlugin import getheader, getdata
# --------------------------------------------------------------------
# straightforward delta encoding
def makedelta(fp, sequence):
"""Convert list of image frames to a GIF animation file"""
frames = 0
previous = None
for im in sequence:
# To specify duration, add the time in milliseconds to getdata(),
# e.g. getdata(im, duration=1000)
if not previous:
# global header
for s in getheader(im)[0] + getdata(im):
fp.write(s)
else:
# delta frame
delta = ImageChops.subtract_modulo(im, previous)
bbox = delta.getbbox()
if bbox:
# compress difference
for s in getdata(im.crop(bbox), offset=bbox[:2]):
fp.write(s)
else:
# FIXME: what should we do in this case?
pass
previous = im.copy()
frames += 1
fp.write(b";")
return frames
# --------------------------------------------------------------------
# main hack
def compress(infile, outfile):
# open input image, and force loading of first frame
im = Image.open(infile)
im.load()
# open output file
fp = open(outfile, "wb")
seq = ImageSequence.Iterator(im)
makedelta(fp, seq)
fp.close()
from PIL import Image
if __name__ == "__main__":
@ -122,4 +27,5 @@ if __name__ == "__main__":
print("Usage: gifmaker infile outfile")
sys.exit(1)
compress(sys.argv[1], sys.argv[2])
im = Image.open(sys.argv[1])
im.save(sys.argv[2], save_all=True)