Add APNG test cases

Includes tests for reading and writing APNG files.

The tests for reading files are based on the APNG browser compatibility
tests from https://philip.html5.org/tests/apng/tests.html
(which is linked in the Tests section of https://wiki.mozilla.org/APNG_Specification)
This commit is contained in:
Peter Rowlands 2019-12-01 12:50:37 +09:00 committed by Andrew Murray
parent 67e3ccffeb
commit 7c0df1034f
52 changed files with 512 additions and 6 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

BIN
Tests/images/apng/delay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

View File

@ -3,7 +3,7 @@ import zlib
from io import BytesIO
import pytest
from PIL import Image, ImageFile, PngImagePlugin
from PIL import Image, ImageFile, ImageSequence, PngImagePlugin
from .helper import (
PillowLeakTestCase,
@ -18,6 +18,7 @@ from .helper import (
skip_unless_feature,
)
# sample png stream
TEST_PNG_FILE = "Tests/images/hopper.png"
@ -624,16 +625,521 @@ class TestFilePng(PillowTestCase):
with Image.open(test_file) as reloaded:
assert reloaded.info["exif"] == b"Exif\x00\x00exifstring"
@skip_unless_feature("webp")
@skip_unless_feature("webp_anim")
def test_apng(self):
with Image.open("Tests/images/iss634.apng") as im:
assert im.get_format_mimetype() == "image/apng"
# 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_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")