mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 09:57:43 +03:00 
			
		
		
		
	Move apng tests into test_file_apng.py
This commit is contained in:
		
							parent
							
								
									0b536fb599
								
							
						
					
					
						commit
						0f84fa7707
					
				
							
								
								
									
										539
									
								
								Tests/test_file_apng.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										539
									
								
								Tests/test_file_apng.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,539 @@
 | 
			
		|||
from PIL import Image, ImageSequence, PngImagePlugin
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from .helper import PillowTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestFilePng(PillowTestCase):
 | 
			
		||||
 | 
			
		||||
    # APNG browser support tests and fixtures via:
 | 
			
		||||
    # https://philip.html5.org/tests/apng/tests.html
 | 
			
		||||
    # (referenced from https://wiki.mozilla.org/APNG_Specification)
 | 
			
		||||
    def test_apng_basic(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/single_frame.png")
 | 
			
		||||
        self.assertTrue(im.is_animated)
 | 
			
		||||
        self.assertEqual(im.get_format_mimetype(), "image/apng")
 | 
			
		||||
        self.assertIsNone(im.info.get("default_image"))
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/single_frame_default.png")
 | 
			
		||||
        self.assertTrue(im.is_animated)
 | 
			
		||||
        self.assertEqual(im.get_format_mimetype(), "image/apng")
 | 
			
		||||
        self.assertTrue(im.info.get("default_image"))
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (255, 0, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (255, 0, 0, 255))
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        # test out of bounds seek
 | 
			
		||||
        with self.assertRaises(EOFError):
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
 | 
			
		||||
        # test rewind support
 | 
			
		||||
        im.seek(0)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (255, 0, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (255, 0, 0, 255))
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_fdat(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/split_fdat.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/split_fdat_zero_chunk.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_dispose(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_none.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_background.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_background_final.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_previous.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_previous_final.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_previous_first.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
    def test_apng_dispose_region(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_none_region.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_background_before_region.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_background_region.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 255, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_previous_region.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_blend(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_source_solid.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_source_transparent.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_source_near_transparent.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 2))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 2))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_over.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_over_near_transparent.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 97))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_chunk_order(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/fctl_actl.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_delay(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
        im.seek(3)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(4)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay_round.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay_short_max.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay_zero_denom.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay_zero_numer.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 0.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 0.0)
 | 
			
		||||
        im.seek(3)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(4)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
    def test_apng_num_plays(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/num_plays.png")
 | 
			
		||||
        self.assertEqual(im.info.get("loop"), 0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/num_plays_1.png")
 | 
			
		||||
        self.assertEqual(im.info.get("loop"), 1)
 | 
			
		||||
 | 
			
		||||
    def test_apng_mode(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_16bit.png")
 | 
			
		||||
        self.assertEqual(im.mode, "RGBA")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 128, 191))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 128, 191))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_greyscale.png")
 | 
			
		||||
        self.assertEqual(im.mode, "L")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), 128)
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), 255)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_greyscale_alpha.png")
 | 
			
		||||
        self.assertEqual(im.mode, "LA")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (128, 191))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (128, 191))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_palette.png")
 | 
			
		||||
        self.assertEqual(im.mode, "P")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        im = im.convert("RGB")
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_palette_alpha.png")
 | 
			
		||||
        self.assertEqual(im.mode, "P")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        im = im.convert("RGBA")
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_palette_1bit_alpha.png")
 | 
			
		||||
        self.assertEqual(im.mode, "P")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        im = im.convert("RGBA")
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 255, 128))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 255, 128))
 | 
			
		||||
 | 
			
		||||
    def test_apng_chunk_errors(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_no_actl.png")
 | 
			
		||||
        self.assertFalse(im.is_animated)
 | 
			
		||||
 | 
			
		||||
        def open():
 | 
			
		||||
            im = Image.open("Tests/images/apng/chunk_multi_actl.png")
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(UserWarning, open)
 | 
			
		||||
        self.assertFalse(im.is_animated)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_actl_after_idat.png")
 | 
			
		||||
        self.assertFalse(im.is_animated)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_no_fctl.png")
 | 
			
		||||
        with self.assertRaises(SyntaxError):
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_repeat_fctl.png")
 | 
			
		||||
        with self.assertRaises(SyntaxError):
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_no_fdat.png")
 | 
			
		||||
        with self.assertRaises(SyntaxError):
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
 | 
			
		||||
    def test_apng_syntax_errors(self):
 | 
			
		||||
        def open():
 | 
			
		||||
            im = Image.open("Tests/images/apng/syntax_num_frames_zero.png")
 | 
			
		||||
            self.assertFalse(im.is_animated)
 | 
			
		||||
            with self.assertRaises(OSError):
 | 
			
		||||
                im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
        def open():
 | 
			
		||||
            im = Image.open("Tests/images/apng/syntax_num_frames_zero_default.png")
 | 
			
		||||
            self.assertFalse(im.is_animated)
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
        # we can handle this case gracefully
 | 
			
		||||
        exception = None
 | 
			
		||||
        im = Image.open("Tests/images/apng/syntax_num_frames_low.png")
 | 
			
		||||
        try:
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            exception = e
 | 
			
		||||
        self.assertIsNone(exception)
 | 
			
		||||
 | 
			
		||||
        with self.assertRaises(SyntaxError):
 | 
			
		||||
            im = Image.open("Tests/images/apng/syntax_num_frames_high.png")
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        def open():
 | 
			
		||||
            im = Image.open("Tests/images/apng/syntax_num_frames_invalid.png")
 | 
			
		||||
            self.assertFalse(im.is_animated)
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
    def test_apng_sequence_errors(self):
 | 
			
		||||
        test_files = [
 | 
			
		||||
            "sequence_start.png",
 | 
			
		||||
            "sequence_gap.png",
 | 
			
		||||
            "sequence_repeat.png",
 | 
			
		||||
            "sequence_repeat_chunk.png",
 | 
			
		||||
            "sequence_reorder.png",
 | 
			
		||||
            "sequence_reorder_chunk.png",
 | 
			
		||||
            "sequence_fdat_fctl.png",
 | 
			
		||||
        ]
 | 
			
		||||
        for f in test_files:
 | 
			
		||||
            with self.assertRaises(SyntaxError):
 | 
			
		||||
                im = Image.open("Tests/images/apng/{0}".format(f))
 | 
			
		||||
                im.seek(im.n_frames - 1)
 | 
			
		||||
                im.load()
 | 
			
		||||
 | 
			
		||||
    def test_apng_save(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/single_frame.png")
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        im.save(test_file, save_all=True)
 | 
			
		||||
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
            self.assertTrue(im.is_animated)
 | 
			
		||||
            self.assertEqual(im.get_format_mimetype(), "image/apng")
 | 
			
		||||
            self.assertIsNone(im.info.get("default_image"))
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/single_frame_default.png")
 | 
			
		||||
        frames = []
 | 
			
		||||
        for im in ImageSequence.Iterator(im):
 | 
			
		||||
            frames.append(im.copy())
 | 
			
		||||
        frames[0].save(
 | 
			
		||||
            test_file, save_all=True, default_image=True, append_images=frames[1:]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
            self.assertTrue(im.is_animated)
 | 
			
		||||
            self.assertEqual(im.get_format_mimetype(), "image/apng")
 | 
			
		||||
            self.assertTrue(im.info.get("default_image"))
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_save_split_fdat(self):
 | 
			
		||||
        # test to make sure we do not generate sequence errors when writing
 | 
			
		||||
        # frames with image data spanning multiple fdAT chunks (in this case
 | 
			
		||||
        # both the default image and first animation frame will span multiple
 | 
			
		||||
        # data chunks)
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        with Image.open("Tests/images/old-style-jpeg-compression.png") as im:
 | 
			
		||||
            frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))]
 | 
			
		||||
            im.save(
 | 
			
		||||
                test_file, save_all=True, default_image=True, append_images=frames,
 | 
			
		||||
            )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            exception = None
 | 
			
		||||
            try:
 | 
			
		||||
                im.seek(im.n_frames - 1)
 | 
			
		||||
                im.load()
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                exception = e
 | 
			
		||||
            self.assertIsNone(exception)
 | 
			
		||||
 | 
			
		||||
    def test_apng_save_duration_loop(self):
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay.png")
 | 
			
		||||
        frames = []
 | 
			
		||||
        durations = []
 | 
			
		||||
        loop = im.info.get("loop")
 | 
			
		||||
        default_image = im.info.get("default_image")
 | 
			
		||||
        for i, im in enumerate(ImageSequence.Iterator(im)):
 | 
			
		||||
            frames.append(im.copy())
 | 
			
		||||
            if i != 0 or not default_image:
 | 
			
		||||
                durations.append(im.info.get("duration", 0))
 | 
			
		||||
        frames[0].save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            default_image=default_image,
 | 
			
		||||
            append_images=frames[1:],
 | 
			
		||||
            duration=durations,
 | 
			
		||||
            loop=loop,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
            self.assertEqual(im.info.get("loop"), loop)
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
            im.seek(3)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
            im.seek(4)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        # test removal of duplicated frames
 | 
			
		||||
        frame = Image.new("RGBA", (128, 64), (255, 0, 0, 255))
 | 
			
		||||
        frame.save(test_file, save_all=True, append_images=[frame], duration=[500, 250])
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
            self.assertEqual(im.n_frames, 1)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 750)
 | 
			
		||||
 | 
			
		||||
    def test_apng_save_disposal(self):
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        size = (128, 64)
 | 
			
		||||
        red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
			
		||||
        green = Image.new("RGBA", size, (0, 255, 0, 255))
 | 
			
		||||
        transparent = Image.new("RGBA", size, (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        # test APNG_DISPOSE_OP_NONE
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green, transparent],
 | 
			
		||||
            disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        # test APNG_DISPOSE_OP_BACKGROUND
 | 
			
		||||
        disposal = [
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[red, transparent],
 | 
			
		||||
            disposal=disposal,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        disposal = [
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green],
 | 
			
		||||
            disposal=disposal,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        # test APNG_DISPOSE_OP_PREVIOUS
 | 
			
		||||
        disposal = [
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green, red, transparent],
 | 
			
		||||
            default_image=True,
 | 
			
		||||
            disposal=disposal,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(3)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        disposal = [
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green],
 | 
			
		||||
            disposal=disposal,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_save_blend(self):
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        size = (128, 64)
 | 
			
		||||
        red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
			
		||||
        green = Image.new("RGBA", size, (0, 255, 0, 255))
 | 
			
		||||
        transparent = Image.new("RGBA", size, (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        # test APNG_BLEND_OP_SOURCE on solid color
 | 
			
		||||
        blend = [
 | 
			
		||||
            PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
            PngImagePlugin.APNG_BLEND_OP_SOURCE,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[red, green],
 | 
			
		||||
            default_image=True,
 | 
			
		||||
            disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            blend=blend,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        # test APNG_BLEND_OP_SOURCE on transparent color
 | 
			
		||||
        blend = [
 | 
			
		||||
            PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
            PngImagePlugin.APNG_BLEND_OP_SOURCE,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[red, transparent],
 | 
			
		||||
            default_image=True,
 | 
			
		||||
            disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            blend=blend,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        # test APNG_BLEND_OP_OVER
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green, transparent],
 | 
			
		||||
            default_image=True,
 | 
			
		||||
            disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import zlib
 | 
			
		|||
from io import BytesIO
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from PIL import Image, ImageFile, ImageSequence, PngImagePlugin
 | 
			
		||||
from PIL import Image, ImageFile, PngImagePlugin
 | 
			
		||||
 | 
			
		||||
from .helper import (
 | 
			
		||||
    PillowLeakTestCase,
 | 
			
		||||
| 
						 | 
				
			
			@ -625,541 +625,6 @@ class TestFilePng(PillowTestCase):
 | 
			
		|||
        with Image.open(test_file) as reloaded:
 | 
			
		||||
            assert reloaded.info["exif"] == b"Exif\x00\x00exifstring"
 | 
			
		||||
 | 
			
		||||
    # APNG browser support tests and fixtures via:
 | 
			
		||||
    # https://philip.html5.org/tests/apng/tests.html
 | 
			
		||||
    # (referenced from https://wiki.mozilla.org/APNG_Specification)
 | 
			
		||||
    def test_apng_basic(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/single_frame.png")
 | 
			
		||||
        self.assertTrue(im.is_animated)
 | 
			
		||||
        self.assertEqual(im.get_format_mimetype(), "image/apng")
 | 
			
		||||
        self.assertIsNone(im.info.get("default_image"))
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/single_frame_default.png")
 | 
			
		||||
        self.assertTrue(im.is_animated)
 | 
			
		||||
        self.assertEqual(im.get_format_mimetype(), "image/apng")
 | 
			
		||||
        self.assertTrue(im.info.get("default_image"))
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (255, 0, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (255, 0, 0, 255))
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        # test out of bounds seek
 | 
			
		||||
        with self.assertRaises(EOFError):
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
 | 
			
		||||
        # test rewind support
 | 
			
		||||
        im.seek(0)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (255, 0, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (255, 0, 0, 255))
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_fdat(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/split_fdat.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/split_fdat_zero_chunk.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_dispose(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_none.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_background.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_background_final.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_previous.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_previous_final.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_previous_first.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
    def test_apng_dispose_region(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_none_region.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_background_before_region.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_background_region.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 255, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/dispose_op_previous_region.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_blend(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_source_solid.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_source_transparent.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_source_near_transparent.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 2))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 2))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_over.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/blend_op_over_near_transparent.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 97))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_chunk_order(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/fctl_actl.png")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_delay(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
        im.seek(3)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(4)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay_round.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay_short_max.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay_zero_denom.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay_zero_numer.png")
 | 
			
		||||
        im.seek(1)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 0.0)
 | 
			
		||||
        im.seek(2)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 0.0)
 | 
			
		||||
        im.seek(3)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
        im.seek(4)
 | 
			
		||||
        self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
    def test_apng_num_plays(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/num_plays.png")
 | 
			
		||||
        self.assertEqual(im.info.get("loop"), 0)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/num_plays_1.png")
 | 
			
		||||
        self.assertEqual(im.info.get("loop"), 1)
 | 
			
		||||
 | 
			
		||||
    def test_apng_mode(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_16bit.png")
 | 
			
		||||
        self.assertEqual(im.mode, "RGBA")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 128, 191))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 128, 191))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_greyscale.png")
 | 
			
		||||
        self.assertEqual(im.mode, "L")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), 128)
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), 255)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_greyscale_alpha.png")
 | 
			
		||||
        self.assertEqual(im.mode, "LA")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (128, 191))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (128, 191))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_palette.png")
 | 
			
		||||
        self.assertEqual(im.mode, "P")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        im = im.convert("RGB")
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_palette_alpha.png")
 | 
			
		||||
        self.assertEqual(im.mode, "P")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        im = im.convert("RGBA")
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/mode_palette_1bit_alpha.png")
 | 
			
		||||
        self.assertEqual(im.mode, "P")
 | 
			
		||||
        im.seek(im.n_frames - 1)
 | 
			
		||||
        im = im.convert("RGBA")
 | 
			
		||||
        self.assertEqual(im.getpixel((0, 0)), (0, 0, 255, 128))
 | 
			
		||||
        self.assertEqual(im.getpixel((64, 32)), (0, 0, 255, 128))
 | 
			
		||||
 | 
			
		||||
    def test_apng_chunk_errors(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_no_actl.png")
 | 
			
		||||
        self.assertFalse(im.is_animated)
 | 
			
		||||
 | 
			
		||||
        def open():
 | 
			
		||||
            im = Image.open("Tests/images/apng/chunk_multi_actl.png")
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(UserWarning, open)
 | 
			
		||||
        self.assertFalse(im.is_animated)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_actl_after_idat.png")
 | 
			
		||||
        self.assertFalse(im.is_animated)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_no_fctl.png")
 | 
			
		||||
        with self.assertRaises(SyntaxError):
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_repeat_fctl.png")
 | 
			
		||||
        with self.assertRaises(SyntaxError):
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/chunk_no_fdat.png")
 | 
			
		||||
        with self.assertRaises(SyntaxError):
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
 | 
			
		||||
    def test_apng_syntax_errors(self):
 | 
			
		||||
        def open():
 | 
			
		||||
            im = Image.open("Tests/images/apng/syntax_num_frames_zero.png")
 | 
			
		||||
            self.assertFalse(im.is_animated)
 | 
			
		||||
            with self.assertRaises(OSError):
 | 
			
		||||
                im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
        def open():
 | 
			
		||||
            im = Image.open("Tests/images/apng/syntax_num_frames_zero_default.png")
 | 
			
		||||
            self.assertFalse(im.is_animated)
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
        # we can handle this case gracefully
 | 
			
		||||
        exception = None
 | 
			
		||||
        im = Image.open("Tests/images/apng/syntax_num_frames_low.png")
 | 
			
		||||
        try:
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            exception = e
 | 
			
		||||
        self.assertIsNone(exception)
 | 
			
		||||
 | 
			
		||||
        with self.assertRaises(SyntaxError):
 | 
			
		||||
            im = Image.open("Tests/images/apng/syntax_num_frames_high.png")
 | 
			
		||||
            im.seek(im.n_frames - 1)
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        def open():
 | 
			
		||||
            im = Image.open("Tests/images/apng/syntax_num_frames_invalid.png")
 | 
			
		||||
            self.assertFalse(im.is_animated)
 | 
			
		||||
            im.load()
 | 
			
		||||
 | 
			
		||||
        pytest.warns(UserWarning, open)
 | 
			
		||||
 | 
			
		||||
    def test_apng_sequence_errors(self):
 | 
			
		||||
        test_files = [
 | 
			
		||||
            "sequence_start.png",
 | 
			
		||||
            "sequence_gap.png",
 | 
			
		||||
            "sequence_repeat.png",
 | 
			
		||||
            "sequence_repeat_chunk.png",
 | 
			
		||||
            "sequence_reorder.png",
 | 
			
		||||
            "sequence_reorder_chunk.png",
 | 
			
		||||
            "sequence_fdat_fctl.png",
 | 
			
		||||
        ]
 | 
			
		||||
        for f in test_files:
 | 
			
		||||
            with self.assertRaises(SyntaxError):
 | 
			
		||||
                im = Image.open("Tests/images/apng/{0}".format(f))
 | 
			
		||||
                im.seek(im.n_frames - 1)
 | 
			
		||||
                im.load()
 | 
			
		||||
 | 
			
		||||
    def test_apng_save(self):
 | 
			
		||||
        im = Image.open("Tests/images/apng/single_frame.png")
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        im.save(test_file, save_all=True)
 | 
			
		||||
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
            self.assertTrue(im.is_animated)
 | 
			
		||||
            self.assertEqual(im.get_format_mimetype(), "image/apng")
 | 
			
		||||
            self.assertIsNone(im.info.get("default_image"))
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        im = Image.open("Tests/images/apng/single_frame_default.png")
 | 
			
		||||
        frames = []
 | 
			
		||||
        for im in ImageSequence.Iterator(im):
 | 
			
		||||
            frames.append(im.copy())
 | 
			
		||||
        frames[0].save(
 | 
			
		||||
            test_file, save_all=True, default_image=True, append_images=frames[1:]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
            self.assertTrue(im.is_animated)
 | 
			
		||||
            self.assertEqual(im.get_format_mimetype(), "image/apng")
 | 
			
		||||
            self.assertTrue(im.info.get("default_image"))
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_save_split_fdat(self):
 | 
			
		||||
        # test to make sure we do not generate sequence errors when writing
 | 
			
		||||
        # frames with image data spanning multiple fdAT chunks (in this case
 | 
			
		||||
        # both the default image and first animation frame will span multiple
 | 
			
		||||
        # data chunks)
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        with Image.open("Tests/images/old-style-jpeg-compression.png") as im:
 | 
			
		||||
            frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))]
 | 
			
		||||
            im.save(
 | 
			
		||||
                test_file, save_all=True, default_image=True, append_images=frames,
 | 
			
		||||
            )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            exception = None
 | 
			
		||||
            try:
 | 
			
		||||
                im.seek(im.n_frames - 1)
 | 
			
		||||
                im.load()
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                exception = e
 | 
			
		||||
            self.assertIsNone(exception)
 | 
			
		||||
 | 
			
		||||
    def test_apng_save_duration_loop(self):
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        im = Image.open("Tests/images/apng/delay.png")
 | 
			
		||||
        frames = []
 | 
			
		||||
        durations = []
 | 
			
		||||
        loop = im.info.get("loop")
 | 
			
		||||
        default_image = im.info.get("default_image")
 | 
			
		||||
        for i, im in enumerate(ImageSequence.Iterator(im)):
 | 
			
		||||
            frames.append(im.copy())
 | 
			
		||||
            if i != 0 or not default_image:
 | 
			
		||||
                durations.append(im.info.get("duration", 0))
 | 
			
		||||
        frames[0].save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            default_image=default_image,
 | 
			
		||||
            append_images=frames[1:],
 | 
			
		||||
            duration=durations,
 | 
			
		||||
            loop=loop,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
            self.assertEqual(im.info.get("loop"), loop)
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
            im.seek(3)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 500.0)
 | 
			
		||||
            im.seek(4)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 1000.0)
 | 
			
		||||
 | 
			
		||||
        # test removal of duplicated frames
 | 
			
		||||
        frame = Image.new("RGBA", (128, 64), (255, 0, 0, 255))
 | 
			
		||||
        frame.save(test_file, save_all=True, append_images=[frame], duration=[500, 250])
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.load()
 | 
			
		||||
            self.assertEqual(im.n_frames, 1)
 | 
			
		||||
            self.assertEqual(im.info.get("duration"), 750)
 | 
			
		||||
 | 
			
		||||
            # This also tests reading unknown PNG chunks (fcTL and fdAT) in load_end
 | 
			
		||||
            with Image.open("Tests/images/iss634.webp") as expected:
 | 
			
		||||
                assert_image_similar(im, expected, 0.23)
 | 
			
		||||
 | 
			
		||||
    def test_apng_save_disposal(self):
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        size = (128, 64)
 | 
			
		||||
        red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
			
		||||
        green = Image.new("RGBA", size, (0, 255, 0, 255))
 | 
			
		||||
        transparent = Image.new("RGBA", size, (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        # test APNG_DISPOSE_OP_NONE
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green, transparent],
 | 
			
		||||
            disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        # test APNG_DISPOSE_OP_BACKGROUND
 | 
			
		||||
        disposal = [
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[red, transparent],
 | 
			
		||||
            disposal=disposal,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        disposal = [
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green],
 | 
			
		||||
            disposal=disposal,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        # test APNG_DISPOSE_OP_PREVIOUS
 | 
			
		||||
        disposal = [
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green, red, transparent],
 | 
			
		||||
            default_image=True,
 | 
			
		||||
            disposal=disposal,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(3)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        disposal = [
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green],
 | 
			
		||||
            disposal=disposal,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
    def test_apng_save_blend(self):
 | 
			
		||||
        test_file = self.tempfile("temp.png")
 | 
			
		||||
        size = (128, 64)
 | 
			
		||||
        red = Image.new("RGBA", size, (255, 0, 0, 255))
 | 
			
		||||
        green = Image.new("RGBA", size, (0, 255, 0, 255))
 | 
			
		||||
        transparent = Image.new("RGBA", size, (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        # test APNG_BLEND_OP_SOURCE on solid color
 | 
			
		||||
        blend = [
 | 
			
		||||
            PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
            PngImagePlugin.APNG_BLEND_OP_SOURCE,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[red, green],
 | 
			
		||||
            default_image=True,
 | 
			
		||||
            disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            blend=blend,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
        # test APNG_BLEND_OP_SOURCE on transparent color
 | 
			
		||||
        blend = [
 | 
			
		||||
            PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
            PngImagePlugin.APNG_BLEND_OP_SOURCE,
 | 
			
		||||
        ]
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[red, transparent],
 | 
			
		||||
            default_image=True,
 | 
			
		||||
            disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            blend=blend,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 0, 0, 0))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 0, 0, 0))
 | 
			
		||||
 | 
			
		||||
        # test APNG_BLEND_OP_OVER
 | 
			
		||||
        red.save(
 | 
			
		||||
            test_file,
 | 
			
		||||
            save_all=True,
 | 
			
		||||
            append_images=[green, transparent],
 | 
			
		||||
            default_image=True,
 | 
			
		||||
            disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
 | 
			
		||||
            blend=PngImagePlugin.APNG_BLEND_OP_OVER,
 | 
			
		||||
        )
 | 
			
		||||
        with Image.open(test_file) as im:
 | 
			
		||||
            im.seek(1)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
            im.seek(2)
 | 
			
		||||
            self.assertEqual(im.getpixel((0, 0)), (0, 255, 0, 255))
 | 
			
		||||
            self.assertEqual(im.getpixel((64, 32)), (0, 255, 0, 255))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@unittest.skipIf(is_win32(), "requires Unix or macOS")
 | 
			
		||||
@skip_unless_feature("zlib")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user