From e36e7dd7a2061f287ac093421b9029b34c68a30c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Apr 2015 23:22:13 +1100 Subject: [PATCH 1/5] Added duration set to GifImagePlugin --- PIL/GifImagePlugin.py | 80 ++++++++++++++++++++++++------------------- Scripts/gifmaker.py | 4 +-- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 8db42e8e8..b414a128b 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -292,8 +292,23 @@ def _save(im, fp, filename): for s in header: fp.write(s) - flags = 0 + # local image header + get_local_header(fp, im) + im_out.encoderconfig = (8, get_interlace(im)) + ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0, + RAWMODE[im_out.mode])]) + + fp.write(b"\0") # end of image data + + fp.write(b";") # end of file + + try: + fp.flush() + except: + pass + +def get_interlace(im): try: interlace = im.encoderinfo["interlace"] except KeyError: @@ -302,10 +317,11 @@ def _save(im, fp, filename): # workaround for @PIL153 if min(im.size) < 16: interlace = 0 + + return interlace - if interlace: - flags = flags | 64 - +def get_local_header(fp, im, offset=(0, 0)): + transparent_color_exists = False try: transparency = im.encoderinfo["transparency"] except KeyError: @@ -324,38 +340,36 @@ def _save(im, fp, filename): else: transparent_color_exists = False - # transparency extension block - if transparent_color_exists: - fp.write(b"!" + - o8(249) + # extension intro - o8(4) + # length - o8(1) + # transparency info present - o16(0) + # duration - o8(transparency) + # transparency index - o8(0)) + if 'duration' in im.encoderinfo: + duration = im.encoderinfo["duration"] / 10 + else: + duration = 0 + if transparent_color_exists or duration != 0: + transparency_flag = 1 if transparent_color_exists else 0 + if not transparent_color_exists: + transparency = 0 + + fp.write(b"!" + + o8(249) + # extension intro + o8(4) + # length + o8(transparency_flag) + # transparency info present + o16(duration) + # duration + o8(transparency) + # transparency index + o8(0)) - # local image header + flags = 0 + + if get_interlace(im): + flags = flags | 64 + fp.write(b"," + - o16(0) + o16(0) + # bounding box + o16(offset[0]) + # offset + o16(offset[1]) + o16(im.size[0]) + # size o16(im.size[1]) + o8(flags) + # flags o8(8)) # bits - im_out.encoderconfig = (8, interlace) - ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0, - RAWMODE[im_out.mode])]) - - fp.write(b"\0") # end of image data - - fp.write(b";") # end of file - - try: - fp.flush() - except: - pass - - def _save_netpbm(im, fp, filename): # @@ -510,13 +524,7 @@ def getdata(im, offset=(0, 0), **params): im.encoderinfo = params # local image header - fp.write(b"," + - o16(offset[0]) + # offset - o16(offset[1]) + - o16(im.size[0]) + # size - o16(im.size[1]) + - o8(0) + # flags - o8(8)) # bits + get_local_header(fp, im, offset) ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])]) diff --git a/Scripts/gifmaker.py b/Scripts/gifmaker.py index bd4de99c1..420140303 100644 --- a/Scripts/gifmaker.py +++ b/Scripts/gifmaker.py @@ -72,8 +72,8 @@ def makedelta(fp, sequence): for im in sequence: - # - # FIXME: write graphics control block before each frame + # To specify duration, add the time in milliseconds to getdata(), + # e.g. getdata(im, duration=1000) if not previous: From ecebedba7f2d7f8d7b6921886da58fb3239246f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Apr 2015 10:32:17 +1100 Subject: [PATCH 2/5] Added loop set to GifImagePlugin --- PIL/GifImagePlugin.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index b414a128b..6c1d53a1c 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -340,7 +340,7 @@ def get_local_header(fp, im, offset=(0, 0)): else: transparent_color_exists = False - if 'duration' in im.encoderinfo: + if "duration" in im.encoderinfo: duration = im.encoderinfo["duration"] / 10 else: duration = 0 @@ -357,6 +357,16 @@ def get_local_header(fp, im, offset=(0, 0)): o8(transparency) + # transparency index o8(0)) + if "loop" in im.encoderinfo: + number_of_loops = im.encoderinfo["loop"] + fp.write(b"!" + + o8(255) + o8(11) + # extension intro + b"NETSCAPE2.0" + + o8(3) + + o8(1) + + o16(number_of_loops) + # number of loops + o8(0)) + flags = 0 if get_interlace(im): From a5917b3fa36fb2128a5451fe355b287056042f7f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Apr 2015 11:45:30 +1100 Subject: [PATCH 3/5] Added GifImagePlugin tests --- PIL/GifImagePlugin.py | 5 +++-- Tests/test_file_gif.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 6c1d53a1c..69df28b30 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -341,7 +341,7 @@ def get_local_header(fp, im, offset=(0, 0)): transparent_color_exists = False if "duration" in im.encoderinfo: - duration = im.encoderinfo["duration"] / 10 + duration = int(im.encoderinfo["duration"] / 10) else: duration = 0 if transparent_color_exists or duration != 0: @@ -360,7 +360,8 @@ def get_local_header(fp, im, offset=(0, 0)): if "loop" in im.encoderinfo: number_of_loops = im.encoderinfo["loop"] fp.write(b"!" + - o8(255) + o8(11) + # extension intro + o8(255) + # extension intro + o8(11) + b"NETSCAPE2.0" + o8(3) + o8(1) + diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 2ce728801..1378fb3f6 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -169,6 +169,33 @@ class TestFileGif(PillowTestCase): # first frame self.assertEqual(img.histogram()[img.info['transparency']], 0) + def test_duration(self): + duration = 1000 + + out = self.tempfile('temp.gif') + fp = open(out, "wb") + im = Image.new('L',(100,100),'#000') + for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, duration=duration): + fp.write(s) + fp.write(b";") + fp.close() + reread = Image.open(out) + + self.assertEqual(reread.info['duration'], duration) + + def test_number_of_loops(self): + number_of_loops = 2 + + out = self.tempfile('temp.gif') + fp = open(out, "wb") + im = Image.new('L',(100,100),'#000') + for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, loop=number_of_loops): + fp.write(s) + fp.write(b";") + fp.close() + reread = Image.open(out) + + self.assertEqual(reread.info['loop'], number_of_loops) if __name__ == '__main__': unittest.main() From aa1368f55115b6c33b9a741b55ee805bb567f209 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 23 Apr 2015 23:40:42 +1000 Subject: [PATCH 4/5] Flake8 fixes --- PIL/GifImagePlugin.py | 31 +++++++++++++++++-------------- Tests/test_file_gif.py | 4 ++-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 69df28b30..cc41da949 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -308,6 +308,7 @@ def _save(im, fp, filename): except: pass + def get_interlace(im): try: interlace = im.encoderinfo["interlace"] @@ -317,9 +318,10 @@ def get_interlace(im): # workaround for @PIL153 if min(im.size) < 16: interlace = 0 - + return interlace + def get_local_header(fp, im, offset=(0, 0)): transparent_color_exists = False try: @@ -348,38 +350,39 @@ def get_local_header(fp, im, offset=(0, 0)): transparency_flag = 1 if transparent_color_exists else 0 if not transparent_color_exists: transparency = 0 - + fp.write(b"!" + - o8(249) + # extension intro - o8(4) + # length - o8(transparency_flag) + # transparency info present - o16(duration) + # duration - o8(transparency) + # transparency index + o8(249) + # extension intro + o8(4) + # length + o8(transparency_flag) + # transparency info present + o16(duration) + # duration + o8(transparency) + # transparency index o8(0)) if "loop" in im.encoderinfo: number_of_loops = im.encoderinfo["loop"] fp.write(b"!" + - o8(255) + # extension intro + o8(255) + # extension intro o8(11) + b"NETSCAPE2.0" + o8(3) + o8(1) + - o16(number_of_loops) + # number of loops + o16(number_of_loops) + # number of loops o8(0)) flags = 0 if get_interlace(im): flags = flags | 64 - + fp.write(b"," + - o16(offset[0]) + # offset + o16(offset[0]) + # offset o16(offset[1]) + - o16(im.size[0]) + # size + o16(im.size[0]) + # size o16(im.size[1]) + - o8(flags) + # flags - o8(8)) # bits + o8(flags) + # flags + o8(8)) # bits + def _save_netpbm(im, fp, filename): diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 1378fb3f6..3b682f86b 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -174,7 +174,7 @@ class TestFileGif(PillowTestCase): out = self.tempfile('temp.gif') fp = open(out, "wb") - im = Image.new('L',(100,100),'#000') + im = Image.new('L', (100, 100), '#000') for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, duration=duration): fp.write(s) fp.write(b";") @@ -188,7 +188,7 @@ class TestFileGif(PillowTestCase): out = self.tempfile('temp.gif') fp = open(out, "wb") - im = Image.new('L',(100,100),'#000') + im = Image.new('L', (100, 100), '#000') for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, loop=number_of_loops): fp.write(s) fp.write(b";") From f028928b5acf10eed759827a6900a52542b254e2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 Apr 2015 00:44:27 +1000 Subject: [PATCH 5/5] Rearranged used_palette_colors to fix get_local_header --- PIL/GifImagePlugin.py | 52 ++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index cc41da949..4c59b612b 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -332,15 +332,19 @@ def get_local_header(fp, im, offset=(0, 0)): transparency = int(transparency) # optimize the block away if transparent color is not used transparent_color_exists = True - # adjust the transparency index after optimize - if used_palette_colors is not None and len(used_palette_colors) < 256: - for i in range(len(used_palette_colors)): - if used_palette_colors[i] == transparency: - transparency = i - transparent_color_exists = True - break - else: - transparent_color_exists = False + + if _get_optimize(im, im.encoderinfo): + used_palette_colors = _get_used_palette_colors(im) + + # adjust the transparency index after optimize + if len(used_palette_colors) < 256: + for i in range(len(used_palette_colors)): + if used_palette_colors[i] == transparency: + transparency = i + transparent_color_exists = True + break + else: + transparent_color_exists = False if "duration" in im.encoderinfo: duration = int(im.encoderinfo["duration"] / 10) @@ -433,11 +437,26 @@ def _save_netpbm(im, fp, filename): # -------------------------------------------------------------------- # GIF utilities +def _get_optimize(im, info): + return im.mode in ("P", "L") and info and info.get("optimize", 0) + + +def _get_used_palette_colors(im): + used_palette_colors = [] + + # check which colors are used + i = 0 + for count in im.histogram(): + if count: + used_palette_colors.append(i) + i += 1 + + return used_palette_colors + + def getheader(im, palette=None, info=None): """Return a list of strings representing a GIF header""" - optimize = info and info.get("optimize", 0) - # Header Block # http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp header = [ @@ -459,15 +478,8 @@ def getheader(im, palette=None, info=None): used_palette_colors = palette_bytes = None - if im.mode in ("P", "L") and optimize: - used_palette_colors = [] - - # check which colors are used - i = 0 - for count in im.histogram(): - if count: - used_palette_colors.append(i) - i += 1 + if _get_optimize(im, info): + used_palette_colors = _get_used_palette_colors(im) # create the new palette if not every color is used if len(used_palette_colors) < 256: