mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 18:06:18 +03:00
Merge pull request #1320 from radarhere/gifmaker
Merged gifmaker into GifImagePlugin
This commit is contained in:
commit
be1df0f33c
|
@ -24,7 +24,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# 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"
|
__version__ = "0.9"
|
||||||
|
|
||||||
|
@ -299,8 +299,10 @@ RAWMODE = {
|
||||||
"P": "P",
|
"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:
|
if _imaging_gif:
|
||||||
# call external driver
|
# call external driver
|
||||||
|
@ -330,7 +332,31 @@ def _save(im, fp, filename):
|
||||||
palette = None
|
palette = None
|
||||||
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
|
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:
|
for s in header:
|
||||||
fp.write(s)
|
fp.write(s)
|
||||||
|
|
||||||
|
@ -340,7 +366,7 @@ def _save(im, fp, filename):
|
||||||
flags = flags | 64
|
flags = flags | 64
|
||||||
|
|
||||||
# local image header
|
# 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))
|
im_out.encoderconfig = (8, get_interlace(im))
|
||||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||||
|
@ -369,7 +395,7 @@ def get_interlace(im):
|
||||||
return interlace
|
return interlace
|
||||||
|
|
||||||
|
|
||||||
def get_local_header(fp, im, offset, flags):
|
def _get_local_header(fp, im, offset, flags):
|
||||||
transparent_color_exists = False
|
transparent_color_exists = False
|
||||||
try:
|
try:
|
||||||
transparency = im.encoderinfo["transparency"]
|
transparency = im.encoderinfo["transparency"]
|
||||||
|
@ -592,7 +618,7 @@ def getdata(im, offset=(0, 0), **params):
|
||||||
im.encoderinfo = params
|
im.encoderinfo = params
|
||||||
|
|
||||||
# local image header
|
# 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])])
|
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||||
|
|
||||||
|
@ -609,6 +635,7 @@ def getdata(im, offset=(0, 0), **params):
|
||||||
|
|
||||||
Image.register_open(GifImageFile.format, GifImageFile, _accept)
|
Image.register_open(GifImageFile.format, GifImageFile, _accept)
|
||||||
Image.register_save(GifImageFile.format, _save)
|
Image.register_save(GifImageFile.format, _save)
|
||||||
|
Image.register_save_all(GifImageFile.format, _save_all)
|
||||||
Image.register_extension(GifImageFile.format, ".gif")
|
Image.register_extension(GifImageFile.format, ".gif")
|
||||||
Image.register_mime(GifImageFile.format, "image/gif")
|
Image.register_mime(GifImageFile.format, "image/gif")
|
||||||
|
|
||||||
|
|
26
PIL/Image.py
26
PIL/Image.py
|
@ -204,6 +204,7 @@ ID = []
|
||||||
OPEN = {}
|
OPEN = {}
|
||||||
MIME = {}
|
MIME = {}
|
||||||
SAVE = {}
|
SAVE = {}
|
||||||
|
SAVE_ALL = {}
|
||||||
EXTENSION = {}
|
EXTENSION = {}
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -1669,6 +1670,10 @@ class Image(object):
|
||||||
# may mutate self!
|
# may mutate self!
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
|
save_all = False
|
||||||
|
if 'save_all' in params:
|
||||||
|
save_all = params['save_all']
|
||||||
|
del params['save_all']
|
||||||
self.encoderinfo = params
|
self.encoderinfo = params
|
||||||
self.encoderconfig = ()
|
self.encoderconfig = ()
|
||||||
|
|
||||||
|
@ -1686,11 +1691,12 @@ class Image(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError(ext) # unknown extension
|
raise KeyError(ext) # unknown extension
|
||||||
|
|
||||||
try:
|
if format.upper() not in SAVE:
|
||||||
save_handler = SAVE[format.upper()]
|
|
||||||
except KeyError:
|
|
||||||
init()
|
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):
|
if isPath(fp):
|
||||||
fp = builtins.open(fp, "wb")
|
fp = builtins.open(fp, "wb")
|
||||||
|
@ -2469,6 +2475,18 @@ def register_save(id, driver):
|
||||||
SAVE[id.upper()] = 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):
|
def register_extension(id, extension):
|
||||||
"""
|
"""
|
||||||
Registers an image extension. This function should not be
|
Registers an image extension. This function should not be
|
||||||
|
|
|
@ -14,104 +14,9 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# 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 __future__ import print_function
|
||||||
|
|
||||||
from PIL import Image, ImageChops, ImageSequence
|
from PIL import Image
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
@ -122,4 +27,5 @@ if __name__ == "__main__":
|
||||||
print("Usage: gifmaker infile outfile")
|
print("Usage: gifmaker infile outfile")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
compress(sys.argv[1], sys.argv[2])
|
im = Image.open(sys.argv[1])
|
||||||
|
im.save(sys.argv[2], save_all=True)
|
||||||
|
|
|
@ -71,6 +71,14 @@ class TestFileGif(PillowTestCase):
|
||||||
|
|
||||||
self.assert_image_similar(reread.convert('RGB'), hopper(), 50)
|
self.assert_image_similar(reread.convert('RGB'), hopper(), 50)
|
||||||
|
|
||||||
|
def test_roundtrip_save_all(self):
|
||||||
|
out = self.tempfile('temp.gif')
|
||||||
|
im = hopper()
|
||||||
|
im.save(out, save_all=True)
|
||||||
|
reread = Image.open(out)
|
||||||
|
|
||||||
|
self.assert_image_similar(reread.convert('RGB'), im, 50)
|
||||||
|
|
||||||
def test_palette_handling(self):
|
def test_palette_handling(self):
|
||||||
# see https://github.com/python-pillow/Pillow/issues/513
|
# see https://github.com/python-pillow/Pillow/issues/513
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user