mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 07:57:27 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			218 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import annotations
 | |
| 
 | |
| from collections.abc import Generator
 | |
| from pathlib import Path
 | |
| 
 | |
| import pytest
 | |
| from packaging.version import parse as parse_version
 | |
| 
 | |
| from PIL import GifImagePlugin, Image, WebPImagePlugin, features
 | |
| 
 | |
| from .helper import (
 | |
|     assert_image_equal,
 | |
|     assert_image_similar,
 | |
|     is_big_endian,
 | |
|     skip_unless_feature,
 | |
| )
 | |
| 
 | |
| pytestmark = skip_unless_feature("webp")
 | |
| 
 | |
| 
 | |
| def test_n_frames() -> None:
 | |
|     """Ensure that WebP format sets n_frames and is_animated attributes correctly."""
 | |
| 
 | |
|     with Image.open("Tests/images/hopper.webp") as im:
 | |
|         assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | |
|         assert im.n_frames == 1
 | |
|         assert not im.is_animated
 | |
| 
 | |
|     with Image.open("Tests/images/iss634.webp") as im:
 | |
|         assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | |
|         assert im.n_frames == 42
 | |
|         assert im.is_animated
 | |
| 
 | |
| 
 | |
| def test_write_animation_L(tmp_path: Path) -> None:
 | |
|     """
 | |
|     Convert an animated GIF to animated WebP, then compare the frame count, and first
 | |
|     and last frames to ensure they're visually similar.
 | |
|     """
 | |
| 
 | |
|     with Image.open("Tests/images/iss634.gif") as orig:
 | |
|         assert isinstance(orig, GifImagePlugin.GifImageFile)
 | |
|         assert orig.n_frames > 1
 | |
| 
 | |
|         temp_file = tmp_path / "temp.webp"
 | |
|         orig.save(temp_file, save_all=True)
 | |
|         with Image.open(temp_file) as im:
 | |
|             assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | |
|             assert im.n_frames == orig.n_frames
 | |
| 
 | |
|             # Compare first and last frames to the original animated GIF
 | |
|             orig.load()
 | |
|             im.load()
 | |
|             assert_image_similar(im, orig.convert("RGBA"), 32.9)
 | |
| 
 | |
|             if is_big_endian():
 | |
|                 version = features.version_module("webp")
 | |
|                 assert version is not None
 | |
|                 if parse_version(version) < parse_version("1.2.2"):
 | |
|                     pytest.skip("Fails with libwebp earlier than 1.2.2")
 | |
|             orig.seek(orig.n_frames - 1)
 | |
|             im.seek(im.n_frames - 1)
 | |
|             orig.load()
 | |
|             im.load()
 | |
|             assert_image_similar(im, orig.convert("RGBA"), 32.9)
 | |
| 
 | |
| 
 | |
| def test_write_animation_RGB(tmp_path: Path) -> None:
 | |
|     """
 | |
|     Write an animated WebP from RGB frames, and ensure the frames
 | |
|     are visually similar to the originals.
 | |
|     """
 | |
| 
 | |
|     def check(temp_file: Path) -> None:
 | |
|         with Image.open(temp_file) as im:
 | |
|             assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | |
|             assert im.n_frames == 2
 | |
| 
 | |
|             # Compare first frame to original
 | |
|             im.load()
 | |
|             assert_image_equal(im, frame1.convert("RGBA"))
 | |
| 
 | |
|             # Compare second frame to original
 | |
|             if is_big_endian():
 | |
|                 version = features.version_module("webp")
 | |
|                 assert version is not None
 | |
|                 if parse_version(version) < parse_version("1.2.2"):
 | |
|                     pytest.skip("Fails with libwebp earlier than 1.2.2")
 | |
|             im.seek(1)
 | |
|             im.load()
 | |
|             assert_image_equal(im, frame2.convert("RGBA"))
 | |
| 
 | |
|     with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | |
|         with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | |
|             temp_file1 = tmp_path / "temp.webp"
 | |
|             frame1.copy().save(
 | |
|                 temp_file1, save_all=True, append_images=[frame2], lossless=True
 | |
|             )
 | |
|             check(temp_file1)
 | |
| 
 | |
|             # Tests appending using a generator
 | |
|             def im_generator(
 | |
|                 ims: list[Image.Image],
 | |
|             ) -> Generator[Image.Image, None, None]:
 | |
|                 yield from ims
 | |
| 
 | |
|             temp_file2 = tmp_path / "temp_generator.webp"
 | |
|             frame1.copy().save(
 | |
|                 temp_file2,
 | |
|                 save_all=True,
 | |
|                 append_images=im_generator([frame2]),
 | |
|                 lossless=True,
 | |
|             )
 | |
|             check(temp_file2)
 | |
| 
 | |
| 
 | |
| def test_timestamp_and_duration(tmp_path: Path) -> None:
 | |
|     """
 | |
|     Try passing a list of durations, and make sure the encoded
 | |
|     timestamps and durations are correct.
 | |
|     """
 | |
| 
 | |
|     durations = [0, 10, 20, 30, 40]
 | |
|     temp_file = tmp_path / "temp.webp"
 | |
|     with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | |
|         with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | |
|             frame1.save(
 | |
|                 temp_file,
 | |
|                 save_all=True,
 | |
|                 append_images=[frame2, frame1, frame2, frame1],
 | |
|                 duration=durations,
 | |
|             )
 | |
| 
 | |
|     with Image.open(temp_file) as im:
 | |
|         assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | |
|         assert im.n_frames == 5
 | |
|         assert im.is_animated
 | |
| 
 | |
|         # Check that timestamps and durations match original values specified
 | |
|         ts = 0
 | |
|         for frame in range(im.n_frames):
 | |
|             im.seek(frame)
 | |
|             im.load()
 | |
|             assert im.info["duration"] == durations[frame]
 | |
|             assert im.info["timestamp"] == ts
 | |
|             ts += durations[frame]
 | |
| 
 | |
| 
 | |
| def test_float_duration(tmp_path: Path) -> None:
 | |
|     temp_file = tmp_path / "temp.webp"
 | |
|     with Image.open("Tests/images/iss634.apng") as im:
 | |
|         assert im.info["duration"] == 70.0
 | |
| 
 | |
|         im.save(temp_file, save_all=True)
 | |
| 
 | |
|     with Image.open(temp_file) as reloaded:
 | |
|         reloaded.load()
 | |
|         assert reloaded.info["duration"] == 70
 | |
| 
 | |
| 
 | |
| def test_seeking(tmp_path: Path) -> None:
 | |
|     """
 | |
|     Create an animated WebP file, and then try seeking through frames in reverse-order,
 | |
|     verifying the timestamps and durations are correct.
 | |
|     """
 | |
| 
 | |
|     dur = 33
 | |
|     temp_file = tmp_path / "temp.webp"
 | |
|     with Image.open("Tests/images/anim_frame1.webp") as frame1:
 | |
|         with Image.open("Tests/images/anim_frame2.webp") as frame2:
 | |
|             frame1.save(
 | |
|                 temp_file,
 | |
|                 save_all=True,
 | |
|                 append_images=[frame2, frame1, frame2, frame1],
 | |
|                 duration=dur,
 | |
|             )
 | |
| 
 | |
|     with Image.open(temp_file) as im:
 | |
|         assert isinstance(im, WebPImagePlugin.WebPImageFile)
 | |
|         assert im.n_frames == 5
 | |
|         assert im.is_animated
 | |
| 
 | |
|         # Traverse frames in reverse, checking timestamps and durations
 | |
|         ts = dur * (im.n_frames - 1)
 | |
|         for frame in reversed(range(im.n_frames)):
 | |
|             im.seek(frame)
 | |
|             im.load()
 | |
|             assert im.info["duration"] == dur
 | |
|             assert im.info["timestamp"] == ts
 | |
|             ts -= dur
 | |
| 
 | |
| 
 | |
| def test_seek_errors() -> None:
 | |
|     with Image.open("Tests/images/iss634.webp") as im:
 | |
|         with pytest.raises(EOFError):
 | |
|             im.seek(-1)
 | |
| 
 | |
|         with pytest.raises(EOFError):
 | |
|             im.seek(42)
 | |
| 
 | |
| 
 | |
| def test_alpha_quality(tmp_path: Path) -> None:
 | |
|     with Image.open("Tests/images/transparent.png") as im:
 | |
|         first_frame = Image.new("L", im.size)
 | |
| 
 | |
|         out = tmp_path / "temp.webp"
 | |
|         first_frame.save(out, save_all=True, append_images=[im])
 | |
| 
 | |
|         out_quality = tmp_path / "quality.webp"
 | |
|         first_frame.save(
 | |
|             out_quality, save_all=True, append_images=[im], alpha_quality=50
 | |
|         )
 | |
|         with Image.open(out) as reloaded:
 | |
|             reloaded.seek(1)
 | |
|             with Image.open(out_quality) as reloaded_quality:
 | |
|                 reloaded_quality.seek(1)
 | |
|                 assert reloaded.tobytes() != reloaded_quality.tobytes()
 |