class TestShellInjection(PillowTestCase): def assert_save_filename_check(self, src_img, save_func): for filename in test_filenames: dest_file = self.tempfile(filename) save_func(src_img, 0, dest_file) # If file can't be opened, shell injection probably occurred Image.open(dest_file).load() @unittest.skipUnless(djpeg_available(), "djpeg not available") def test_load_djpeg_filename(self): for filename in test_filenames: src_file = self.tempfile(filename) shutil.copy(TEST_JPG, src_file) im = Image.open(src_file) im.load_djpeg() @unittest.skipUnless(cjpeg_available(), "cjpeg not available") def test_save_cjpeg_filename(self): im = Image.open(TEST_JPG) self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_filename_bmp_mode(self): im = Image.open(TEST_GIF).convert("RGB") self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_filename_l_mode(self): im = Image.open(TEST_GIF).convert("L") self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
class TestFileGif(PillowTestCase): def setUp(self): if "gif_encoder" not in codecs or "gif_decoder" not in codecs: self.skipTest("gif support not available") # can this happen? def test_sanity(self): im = Image.open(TEST_GIF) im.load() self.assertEqual(im.mode, "P") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "GIF") self.assertEqual(im.info["version"], b"GIF89a") def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" self.assertRaises(SyntaxError, GifImagePlugin.GifImageFile, invalid_file) def test_optimize(self): def test_grayscale(optimize): im = Image.new("L", (1, 1), 0) filename = BytesIO() im.save(filename, "GIF", optimize=optimize) return len(filename.getvalue()) def test_bilevel(optimize): im = Image.new("1", (1, 1), 0) test_file = BytesIO() im.save(test_file, "GIF", optimize=optimize) return len(test_file.getvalue()) self.assertEqual(test_grayscale(0), 800) self.assertEqual(test_grayscale(1), 38) self.assertEqual(test_bilevel(0), 800) self.assertEqual(test_bilevel(1), 800) def test_optimize_correctness(self): # 256 color Palette image, posterize to > 128 and < 128 levels # Size bigger and smaller than 512x512 # Check the palette for number of colors allocated. # Check for correctness after conversion back to RGB def check(colors, size, expected_palette_length): # make an image with empty colors in the start of the palette range im = Image.frombytes( 'P', (colors, colors), bytes(bytearray(range(256 - colors, 256)) * colors)) im = im.resize((size, size)) outfile = BytesIO() im.save(outfile, 'GIF') outfile.seek(0) reloaded = Image.open(outfile) # check palette length palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v) self.assertEqual(expected_palette_length, palette_length) self.assert_image_equal(im.convert('RGB'), reloaded.convert('RGB')) # These do optimize the palette check(128, 511, 128) check(64, 511, 64) check(4, 511, 4) # These don't optimize the palette check(128, 513, 256) check(64, 513, 256) check(4, 513, 256) # other limits that don't optimize the palette check(129, 511, 256) check(255, 511, 256) check(256, 511, 256) def test_optimize_full_l(self): im = Image.frombytes("L", (16, 16), bytes(bytearray(range(256)))) test_file = BytesIO() im.save(test_file, "GIF", optimize=True) self.assertEqual(im.mode, "L") def test_roundtrip(self): out = self.tempfile('temp.gif') im = hopper() im.save(out) reread = Image.open(out) self.assert_image_similar(reread.convert('RGB'), im, 50) def test_roundtrip2(self): # see https://github.com/python-pillow/Pillow/issues/403 out = self.tempfile('temp.gif') im = Image.open(TEST_GIF) im2 = im.copy() im2.save(out) reread = Image.open(out) self.assert_image_similar(reread.convert('RGB'), hopper(), 50) def test_roundtrip_save_all(self): # Single frame image 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) # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") out = self.tempfile('temp.gif') im.save(out, save_all=True) reread = Image.open(out) self.assertEqual(reread.n_frames, 5) def test_headers_saving_for_animated_gifs(self): important_headers = ['background', 'version', 'duration', 'loop'] # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") out = self.tempfile('temp.gif') im.save(out, save_all=True) reread = Image.open(out) for header in important_headers: self.assertEqual(im.info[header], reread.info[header]) def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513 im = Image.open(TEST_GIF) im = im.convert('RGB') im = im.resize((100, 100), Image.LANCZOS) im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) f = self.tempfile('temp.gif') im2.save(f, optimize=True) reloaded = Image.open(f) self.assert_image_similar(im, reloaded.convert('RGB'), 10) def test_palette_434(self): # see https://github.com/python-pillow/Pillow/issues/434 def roundtrip(im, *args, **kwargs): out = self.tempfile('temp.gif') im.copy().save(out, *args, **kwargs) reloaded = Image.open(out) return reloaded orig = "Tests/images/test.colors.gif" im = Image.open(orig) self.assert_image_similar(im, roundtrip(im), 1) self.assert_image_similar(im, roundtrip(im, optimize=True), 1) im = im.convert("RGB") # check automatic P conversion reloaded = roundtrip(im).convert('RGB') self.assert_image_equal(im, reloaded) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_bmp_mode(self): img = Image.open(TEST_GIF).convert("RGB") tempfile = self.tempfile("temp.gif") GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("RGB"), 0) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_l_mode(self): img = Image.open(TEST_GIF).convert("L") tempfile = self.tempfile("temp.gif") GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("L"), 0) def test_seek(self): img = Image.open("Tests/images/dispose_none.gif") framecount = 0 try: while True: framecount += 1 img.seek(img.tell() + 1) except EOFError: self.assertEqual(framecount, 5) def test_n_frames(self): im = Image.open(TEST_GIF) self.assertEqual(im.n_frames, 1) self.assertFalse(im.is_animated) im = Image.open("Tests/images/iss634.gif") self.assertEqual(im.n_frames, 42) self.assertTrue(im.is_animated) def test_eoferror(self): im = Image.open(TEST_GIF) n_frames = im.n_frames while True: n_frames -= 1 try: im.seek(n_frames) break except EOFError: self.assertLess(im.tell(), n_frames) def test_dispose_none(self): img = Image.open("Tests/images/dispose_none.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 1) except EOFError: pass def test_dispose_background(self): img = Image.open("Tests/images/dispose_bgnd.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 2) except EOFError: pass def test_dispose_previous(self): img = Image.open("Tests/images/dispose_prev.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 3) except EOFError: pass def test_iss634(self): img = Image.open("Tests/images/iss634.gif") # seek to the second frame img.seek(img.tell() + 1) # all transparent pixels should be replaced with the color from the # first frame self.assertEqual(img.histogram()[img.info['transparency']], 0) def test_duration(self): duration = 1000 out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') im.save(out, duration=duration) reread = Image.open(out) self.assertEqual(reread.info['duration'], duration) def test_multiple_duration(self): duration_list = [1000, 2000, 3000] out = self.tempfile('temp.gif') im_list = [ Image.new('L', (100, 100), '#000'), Image.new('L', (100, 100), '#111'), Image.new('L', (100, 100), '#222') ] # duration as list im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration_list) reread = Image.open(out) for duration in duration_list: self.assertEqual(reread.info['duration'], duration) try: reread.seek(reread.tell() + 1) except EOFError: pass # duration as tuple im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list)) reread = Image.open(out) for duration in duration_list: self.assertEqual(reread.info['duration'], duration) try: reread.seek(reread.tell() + 1) except EOFError: pass def test_identical_frames(self): duration_list = [1000, 1500, 2000, 4000] out = self.tempfile('temp.gif') im_list = [ Image.new('L', (100, 100), '#000'), Image.new('L', (100, 100), '#000'), Image.new('L', (100, 100), '#000'), Image.new('L', (100, 100), '#111') ] # duration as list im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration_list) reread = Image.open(out) # Assert that the first three frames were combined self.assertEqual(reread.n_frames, 2) # Assert that the new duration is the total of the identical frames self.assertEqual(reread.info['duration'], 4500) def test_number_of_loops(self): number_of_loops = 2 out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') im.save(out, loop=number_of_loops) reread = Image.open(out) self.assertEqual(reread.info['loop'], number_of_loops) def test_background(self): out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') im.info['background'] = 1 im.save(out) reread = Image.open(out) self.assertEqual(reread.info['background'], im.info['background']) def test_comment(self): im = Image.open(TEST_GIF) self.assertEqual(im.info['comment'], b"File written by Adobe Photoshop\xa8 4.0") out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') im.info['comment'] = b"Test comment text" im.save(out) reread = Image.open(out) self.assertEqual(reread.info['comment'], im.info['comment']) def test_version(self): out = self.tempfile('temp.gif') def assertVersionAfterSave(im, version): im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], version) # Test that GIF87a is used by default im = Image.new('L', (100, 100), '#000') assertVersionAfterSave(im, b"GIF87a") # Test setting the version to 89a im = Image.new('L', (100, 100), '#000') im.info["version"] = b"89a" assertVersionAfterSave(im, b"GIF89a") # Test that adding a GIF89a feature changes the version im.info["transparency"] = 1 assertVersionAfterSave(im, b"GIF89a") # Test that a GIF87a image is also saved in that format im = Image.open("Tests/images/test.colors.gif") assertVersionAfterSave(im, b"GIF87a") # Test that a GIF89a image is also saved in that format im.info["version"] = b"GIF89a" assertVersionAfterSave(im, b"GIF87a") def test_append_images(self): out = self.tempfile('temp.gif') # Test appending single frame images im = Image.new('RGB', (100, 100), '#f00') ims = [ Image.new('RGB', (100, 100), color) for color in ['#0f0', '#00f'] ] im.save(out, save_all=True, append_images=ims) reread = Image.open(out) self.assertEqual(reread.n_frames, 3) # Tests appending single and multiple frame images im = Image.open("Tests/images/dispose_none.gif") ims = [Image.open("Tests/images/dispose_prev.gif")] im.save(out, save_all=True, append_images=ims) reread = Image.open(out) self.assertEqual(reread.n_frames, 10) def test_transparent_optimize(self): # from issue #2195, if the transparent color is incorrectly # optimized out, gif loses transparency # Need a palette that isn't using the 0 color, and one # that's > 128 items where the transparent color is actually # the top palette entry to trigger the bug. from PIL import ImagePalette data = bytes(bytearray(range(1, 254))) palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) im = Image.new('L', (253, 1)) im.frombytes(data) im.putpalette(palette) out = self.tempfile('temp.gif') im.save(out, transparency=253) reloaded = Image.open(out) self.assertEqual(reloaded.info['transparency'], 253) def test_bbox(self): out = self.tempfile('temp.gif') im = Image.new('RGB', (100, 100), '#fff') ims = [Image.new("RGB", (100, 100), '#000')] im.save(out, save_all=True, append_images=ims) reread = Image.open(out) self.assertEqual(reread.n_frames, 2) def test_palette_save_L(self): # generate an L mode image with a separate palette im = hopper('P') im_l = Image.frombytes('L', im.size, im.tobytes()) palette = bytes(bytearray(im.getpalette())) out = self.tempfile('temp.gif') im_l.save(out, palette=palette) reloaded = Image.open(out) self.assert_image_equal(reloaded.convert('RGB'), im.convert('RGB')) def test_palette_save_P(self): # pass in a different palette, then construct what the image # would look like. # Forcing a non-straight grayscale palette. im = hopper('P') palette = bytes(bytearray([255 - i // 3 for i in range(768)])) out = self.tempfile('temp.gif') im.save(out, palette=palette) reloaded = Image.open(out) im.putpalette(palette) self.assert_image_equal(reloaded, im) def test_palette_save_ImagePalette(self): # pass in a different palette, as an ImagePalette.ImagePalette # effectively the same as test_palette_save_P im = hopper('P') palette = ImagePalette.ImagePalette('RGB', list(range(256))[::-1] * 3) out = self.tempfile('temp.gif') im.save(out, palette=palette) reloaded = Image.open(out) im.putpalette(palette) self.assert_image_equal(reloaded, im) def test_save_I(self): # Test saving something that would trigger the auto-convert to 'L' im = hopper('I') out = self.tempfile('temp.gif') im.save(out) reloaded = Image.open(out) self.assert_image_equal(reloaded.convert('L'), im.convert('L')) def test_getdata(self): # test getheader/getdata against legacy values # Create a 'P' image with holes in the palette im = Image._wedge().resize((16, 16)) im.putpalette(ImagePalette.ImagePalette('RGB')) im.info = {'background': 0} passed_palette = bytes(bytearray([255 - i // 3 for i in range(768)])) GifImagePlugin._FORCE_OPTIMIZE = True try: h = GifImagePlugin.getheader(im, passed_palette) d = GifImagePlugin.getdata(im) import pickle # Enable to get target values on pre-refactor version # with open('Tests/images/gif_header_data.pkl', 'wb') as f: # pickle.dump((h, d), f, 1) with open('Tests/images/gif_header_data.pkl', 'rb') as f: (h_target, d_target) = pickle.load(f) self.assertEqual(h, h_target) self.assertEqual(d, d_target) finally: GifImagePlugin._FORCE_OPTIMIZE = False
class TestFileGif(PillowTestCase): def setUp(self): if "gif_encoder" not in codecs or "gif_decoder" not in codecs: self.skipTest("gif support not available") # can this happen? def test_sanity(self): im = Image.open(TEST_GIF) im.load() self.assertEqual(im.mode, "P") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "GIF") self.assertEqual(im.info["version"], b"GIF89a") def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" self.assertRaises(SyntaxError, lambda: GifImagePlugin.GifImageFile(invalid_file)) def test_optimize(self): from io import BytesIO def test_grayscale(optimize): im = Image.new("L", (1, 1), 0) filename = BytesIO() im.save(filename, "GIF", optimize=optimize) return len(filename.getvalue()) def test_bilevel(optimize): im = Image.new("1", (1, 1), 0) test_file = BytesIO() im.save(test_file, "GIF", optimize=optimize) return len(test_file.getvalue()) self.assertEqual(test_grayscale(0), 800) self.assertEqual(test_grayscale(1), 38) self.assertEqual(test_bilevel(0), 800) self.assertEqual(test_bilevel(1), 800) def test_optimize_full_l(self): from io import BytesIO im = Image.frombytes("L", (16, 16), bytes(bytearray(range(256)))) test_file = BytesIO() im.save(test_file, "GIF", optimize=True) self.assertEqual(im.mode, "L") def test_roundtrip(self): out = self.tempfile('temp.gif') im = hopper() im.save(out) reread = Image.open(out) self.assert_image_similar(reread.convert('RGB'), im, 50) def test_roundtrip2(self): # see https://github.com/python-pillow/Pillow/issues/403 out = self.tempfile('temp.gif') im = Image.open(TEST_GIF) im2 = im.copy() im2.save(out) reread = Image.open(out) self.assert_image_similar(reread.convert('RGB'), hopper(), 50) def test_roundtrip_save_all(self): # Single frame image 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) # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") out = self.tempfile('temp.gif') im.save(out, save_all=True) reread = Image.open(out) self.assertEqual(reread.n_frames, 5) def test_headers_saving_for_animated_gifs(self): important_headers = ['background', 'version', 'duration', 'loop'] # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") out = self.tempfile('temp.gif') im.save(out, save_all=True) reread = Image.open(out) for header in important_headers: self.assertEqual(im.info[header], reread.info[header]) def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513 im = Image.open(TEST_GIF) im = im.convert('RGB') im = im.resize((100, 100), Image.LANCZOS) im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) f = self.tempfile('temp.gif') im2.save(f, optimize=True) reloaded = Image.open(f) self.assert_image_similar(im, reloaded.convert('RGB'), 10) def test_palette_434(self): # see https://github.com/python-pillow/Pillow/issues/434 def roundtrip(im, *args, **kwargs): out = self.tempfile('temp.gif') im.copy().save(out, *args, **kwargs) reloaded = Image.open(out) return reloaded orig = "Tests/images/test.colors.gif" im = Image.open(orig) self.assert_image_similar(im, roundtrip(im), 1) self.assert_image_similar(im, roundtrip(im, optimize=True), 1) im = im.convert("RGB") # check automatic P conversion reloaded = roundtrip(im).convert('RGB') self.assert_image_equal(im, reloaded) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_bmp_mode(self): img = Image.open(TEST_GIF).convert("RGB") tempfile = self.tempfile("temp.gif") GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("RGB"), 0) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_l_mode(self): img = Image.open(TEST_GIF).convert("L") tempfile = self.tempfile("temp.gif") GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("L"), 0) def test_seek(self): img = Image.open("Tests/images/dispose_none.gif") framecount = 0 try: while True: framecount += 1 img.seek(img.tell() + 1) except EOFError: self.assertEqual(framecount, 5) def test_n_frames(self): im = Image.open(TEST_GIF) self.assertEqual(im.n_frames, 1) self.assertFalse(im.is_animated) im = Image.open("Tests/images/iss634.gif") self.assertEqual(im.n_frames, 42) self.assertTrue(im.is_animated) def test_eoferror(self): im = Image.open(TEST_GIF) n_frames = im.n_frames while True: n_frames -= 1 try: im.seek(n_frames) break except EOFError: self.assertTrue(im.tell() < n_frames) def test_dispose_none(self): img = Image.open("Tests/images/dispose_none.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 1) except EOFError: pass def test_dispose_background(self): img = Image.open("Tests/images/dispose_bgnd.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 2) except EOFError: pass def test_dispose_previous(self): img = Image.open("Tests/images/dispose_prev.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 3) except EOFError: pass def test_iss634(self): img = Image.open("Tests/images/iss634.gif") # seek to the second frame img.seek(img.tell() + 1) # all transparent pixels should be replaced with the color from the # 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) def test_background(self): out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') im.info['background'] = 1 im.save(out) reread = Image.open(out) self.assertEqual(reread.info['background'], im.info['background']) def test_comment(self): im = Image.open(TEST_GIF) self.assertEqual(im.info['comment'], b"File written by Adobe Photoshop\xa8 4.0") out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') im.info['comment'] = b"Test comment text" im.save(out) reread = Image.open(out) self.assertEqual(reread.info['comment'], im.info['comment']) def test_version(self): out = self.tempfile('temp.gif') # Test that GIF87a is used by default im = Image.new('L', (100, 100), '#000') im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF87a") # Test that adding a GIF89a feature changes the version im.info["transparency"] = 1 im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF89a") # Test that a GIF87a image is also saved in that format im = Image.open("Tests/images/test.colors.gif") im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF87a") # Test that a GIF89a image is also saved in that format im.info["version"] = "GIF89a" im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF87a")
class TestFileGif(PillowTestCase): def setUp(self): if "gif_encoder" not in codecs or "gif_decoder" not in codecs: self.skipTest("gif support not available") # can this happen? def test_sanity(self): im = Image.open(TEST_GIF) im.load() self.assertEqual(im.mode, "P") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "GIF") self.assertEqual(im.info["version"], b"GIF89a") def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" self.assertRaises(SyntaxError, lambda: GifImagePlugin.GifImageFile(invalid_file)) def test_optimize(self): def test_grayscale(optimize): im = Image.new("L", (1, 1), 0) filename = BytesIO() im.save(filename, "GIF", optimize=optimize) return len(filename.getvalue()) def test_bilevel(optimize): im = Image.new("1", (1, 1), 0) test_file = BytesIO() im.save(test_file, "GIF", optimize=optimize) return len(test_file.getvalue()) self.assertEqual(test_grayscale(0), 800) self.assertEqual(test_grayscale(1), 38) self.assertEqual(test_bilevel(0), 800) self.assertEqual(test_bilevel(1), 800) def test_optimize_correctness(self): # 256 color Palette image, posterize to > 128 and < 128 levels # Size bigger and smaller than 512x512 # Check the palette for number of colors allocated. # Check for correctness after conversion back to RGB def check(colors, size, expected_palette_length): # make an image with empty colors in the start of the palette range im = Image.frombytes('P', (colors,colors), bytes(bytearray(range(256-colors,256))*colors)) im = im.resize((size,size)) outfile = BytesIO() im.save(outfile, 'GIF') outfile.seek(0) reloaded = Image.open(outfile) # check palette length palette_length = max(i+1 for i,v in enumerate(reloaded.histogram()) if v) self.assertEqual(expected_palette_length, palette_length) self.assert_image_equal(im.convert('RGB'), reloaded.convert('RGB')) # These do optimize the palette check(128, 511, 128) check(64, 511, 64) check(4, 511, 4) # These don't optimize the palette check(128, 513, 256) check(64, 513, 256) check(4, 513, 256) # other limits that don't optimize the palette check(129, 511, 256) check(255, 511, 256) check(256, 511, 256) def test_optimize_full_l(self): from io import BytesIO im = Image.frombytes("L", (16, 16), bytes(bytearray(range(256)))) test_file = BytesIO() im.save(test_file, "GIF", optimize=True) self.assertEqual(im.mode, "L") def test_roundtrip(self): out = self.tempfile('temp.gif') im = hopper() im.save(out) reread = Image.open(out) self.assert_image_similar(reread.convert('RGB'), im, 50) def test_roundtrip2(self): # see https://github.com/python-pillow/Pillow/issues/403 out = self.tempfile('temp.gif') im = Image.open(TEST_GIF) im2 = im.copy() im2.save(out) reread = Image.open(out) self.assert_image_similar(reread.convert('RGB'), hopper(), 50) def test_roundtrip_save_all(self): # Single frame image 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) # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") out = self.tempfile('temp.gif') im.save(out, save_all=True) reread = Image.open(out) self.assertEqual(reread.n_frames, 5) def test_headers_saving_for_animated_gifs(self): important_headers = ['background', 'version', 'duration', 'loop'] # Multiframe image im = Image.open("Tests/images/dispose_bgnd.gif") out = self.tempfile('temp.gif') im.save(out, save_all=True) reread = Image.open(out) for header in important_headers: self.assertEqual( im.info[header], reread.info[header] ) def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513 im = Image.open(TEST_GIF) im = im.convert('RGB') im = im.resize((100, 100), Image.LANCZOS) im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) f = self.tempfile('temp.gif') im2.save(f, optimize=True) reloaded = Image.open(f) self.assert_image_similar(im, reloaded.convert('RGB'), 10) def test_palette_434(self): # see https://github.com/python-pillow/Pillow/issues/434 def roundtrip(im, *args, **kwargs): out = self.tempfile('temp.gif') im.copy().save(out, *args, **kwargs) reloaded = Image.open(out) return reloaded orig = "Tests/images/test.colors.gif" im = Image.open(orig) self.assert_image_similar(im, roundtrip(im), 1) self.assert_image_similar(im, roundtrip(im, optimize=True), 1) im = im.convert("RGB") # check automatic P conversion reloaded = roundtrip(im).convert('RGB') self.assert_image_equal(im, reloaded) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_bmp_mode(self): img = Image.open(TEST_GIF).convert("RGB") tempfile = self.tempfile("temp.gif") GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("RGB"), 0) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_l_mode(self): img = Image.open(TEST_GIF).convert("L") tempfile = self.tempfile("temp.gif") GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("L"), 0) def test_seek(self): img = Image.open("Tests/images/dispose_none.gif") framecount = 0 try: while True: framecount += 1 img.seek(img.tell() + 1) except EOFError: self.assertEqual(framecount, 5) def test_n_frames(self): im = Image.open(TEST_GIF) self.assertEqual(im.n_frames, 1) self.assertFalse(im.is_animated) im = Image.open("Tests/images/iss634.gif") self.assertEqual(im.n_frames, 42) self.assertTrue(im.is_animated) def test_eoferror(self): im = Image.open(TEST_GIF) n_frames = im.n_frames while True: n_frames -= 1 try: im.seek(n_frames) break except EOFError: self.assertTrue(im.tell() < n_frames) def test_dispose_none(self): img = Image.open("Tests/images/dispose_none.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 1) except EOFError: pass def test_dispose_background(self): img = Image.open("Tests/images/dispose_bgnd.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 2) except EOFError: pass def test_dispose_previous(self): img = Image.open("Tests/images/dispose_prev.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 3) except EOFError: pass def test_iss634(self): img = Image.open("Tests/images/iss634.gif") # seek to the second frame img.seek(img.tell() + 1) # all transparent pixels should be replaced with the color from the # first frame self.assertEqual(img.histogram()[img.info['transparency']], 0) def test_duration(self): duration = 1000 out = self.tempfile('temp.gif') with open(out, "wb") as fp: 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";") reread = Image.open(out) self.assertEqual(reread.info['duration'], duration) def test_multiple_duration(self): duration_list = [1000, 2000, 3000] out = self.tempfile('temp.gif') im_list = [ Image.new('L', (100, 100), '#000'), Image.new('L', (100, 100), '#111'), Image.new('L', (100, 100), '#222'), ] #duration as list im_list[0].save( out, save_all=True, append_images=im_list[1:], duration=duration_list ) reread = Image.open(out) for duration in duration_list: self.assertEqual(reread.info['duration'], duration) try: reread.seek(reread.tell() + 1) except EOFError: pass # duration as tuple im_list[0].save( out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list) ) reread = Image.open(out) for duration in duration_list: self.assertEqual(reread.info['duration'], duration) try: reread.seek(reread.tell() + 1) except EOFError: pass def test_number_of_loops(self): number_of_loops = 2 out = self.tempfile('temp.gif') with open(out, "wb") as fp: 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";") reread = Image.open(out) self.assertEqual(reread.info['loop'], number_of_loops) def test_background(self): out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') im.info['background'] = 1 im.save(out) reread = Image.open(out) self.assertEqual(reread.info['background'], im.info['background']) def test_comment(self): im = Image.open(TEST_GIF) self.assertEqual(im.info['comment'], b"File written by Adobe Photoshop\xa8 4.0") out = self.tempfile('temp.gif') im = Image.new('L', (100, 100), '#000') im.info['comment'] = b"Test comment text" im.save(out) reread = Image.open(out) self.assertEqual(reread.info['comment'], im.info['comment']) def test_version(self): out = self.tempfile('temp.gif') # Test that GIF87a is used by default im = Image.new('L', (100, 100), '#000') im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF87a") # Test that adding a GIF89a feature changes the version im.info["transparency"] = 1 im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF89a") # Test that a GIF87a image is also saved in that format im = Image.open("Tests/images/test.colors.gif") im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF87a") # Test that a GIF89a image is also saved in that format im.info["version"] = b"GIF89a" im.save(out) reread = Image.open(out) self.assertEqual(reread.info["version"], b"GIF87a") def test_append_images(self): out = self.tempfile('temp.gif') # Test appending single frame images im = Image.new('RGB', (100, 100), '#f00') ims = [Image.new('RGB', (100, 100), color) for color in ['#0f0', '#00f']] im.save(out, save_all=True, append_images=ims) reread = Image.open(out) self.assertEqual(reread.n_frames, 3) # Tests appending single and multiple frame images im = Image.open("Tests/images/dispose_none.gif") ims = [Image.open("Tests/images/dispose_prev.gif")] im.save(out, save_all=True, append_images=ims) reread = Image.open(out) self.assertEqual(reread.n_frames, 10) def test_transparent_optimize(self): # from issue #2195, if the transparent color is incorrectly # optimized out, gif loses transparency Need a palette that # isn't using the 0 color, and one that's > 128 items where # the transparent color is actually the top palette entry to # trigger the bug. from PIL import ImagePalette data = bytes(bytearray(range(1,254))) palette = ImagePalette.ImagePalette("RGB", list(range(256))*3) im = Image.new('L', (253,1)) im.frombytes(data) im.putpalette(palette) out = self.tempfile('temp.gif') im.save(out, transparency=253) reloaded = Image.open(out) self.assertEqual(reloaded.info['transparency'], 253)
class TestFileGif(PillowTestCase): def setUp(self): if "gif_encoder" not in codecs or "gif_decoder" not in codecs: self.skipTest("gif support not available") # can this happen? def test_sanity(self): im = Image.open(file) im.load() self.assertEqual(im.mode, "P") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "GIF") def test_optimize(self): from io import BytesIO def test(optimize): im = Image.new("L", (1, 1), 0) file = BytesIO() im.save(file, "GIF", optimize=optimize) return len(file.getvalue()) self.assertEqual(test(0), 800) self.assertEqual(test(1), 38) def test_roundtrip(self): out = self.tempfile('temp.gif') im = lena() im.save(out) reread = Image.open(out) self.assert_image_similar(reread.convert('RGB'), im, 50) def test_roundtrip2(self): # see https://github.com/python-pillow/Pillow/issues/403 out = self.tempfile('temp.gif') im = Image.open('Tests/images/lena.gif') im2 = im.copy() im2.save(out) reread = Image.open(out) self.assert_image_similar(reread.convert('RGB'), lena(), 50) def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513 im = Image.open('Tests/images/lena.gif') im = im.convert('RGB') im = im.resize((100, 100), Image.ANTIALIAS) im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) f = self.tempfile('temp.gif') im2.save(f, optimize=True) reloaded = Image.open(f) self.assert_image_similar(im, reloaded.convert('RGB'), 10) def test_palette_434(self): # see https://github.com/python-pillow/Pillow/issues/434 def roundtrip(im, *args, **kwargs): out = self.tempfile('temp.gif') im.save(out, *args, **kwargs) reloaded = Image.open(out) return [im, reloaded] orig = "Tests/images/test.colors.gif" im = Image.open(orig) self.assert_image_equal(*roundtrip(im)) self.assert_image_equal(*roundtrip(im, optimize=True)) im = im.convert("RGB") # check automatic P conversion reloaded = roundtrip(im)[1].convert('RGB') self.assert_image_equal(im, reloaded) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_bmp_mode(self): img = Image.open(file).convert("RGB") tempfile = self.tempfile("temp.gif") GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("RGB"), 0) @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_l_mode(self): img = Image.open(file).convert("L") tempfile = self.tempfile("temp.gif") GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("L"), 0) def test_seek(self): img = Image.open("Tests/images/dispose_none.gif") framecount = 0 try: while True: framecount += 1 img.seek(img.tell() + 1) except EOFError: self.assertEqual(framecount, 5) def test_dispose_none(self): img = Image.open("Tests/images/dispose_none.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 1) except EOFError: pass def test_dispose_background(self): img = Image.open("Tests/images/dispose_bgnd.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 2) except EOFError: pass def test_dispose_previous(self): img = Image.open("Tests/images/dispose_prev.gif") try: while True: img.seek(img.tell() + 1) self.assertEqual(img.disposal_method, 3) except EOFError: pass def test_iss634(self): img = Image.open("Tests/images/iss634.gif") # seek to the second frame img.seek(img.tell() + 1) # all transparent pixels should be replaced with the color from the first frame self.assertEqual(img.histogram()[img.info['transparency']], 0)