Merge cba716420a into 877527cefc
This commit is contained in:
commit
920bcc55cc
@ -4,7 +4,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
from PIL import Image, WebPImagePlugin
|
||||
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
||||
@ -26,3 +26,21 @@ def test_write_lossless_rgb(tmp_path: Path) -> None:
|
||||
image.load()
|
||||
|
||||
assert_image_equal(image, hopper(RGB_MODE))
|
||||
|
||||
|
||||
def test_is_lossless(tmp_path: Path) -> None:
|
||||
lossless_file = tmp_path / "lossless.webp"
|
||||
hopper(RGB_MODE).save(lossless_file, lossless=True)
|
||||
with Image.open(lossless_file) as image:
|
||||
assert isinstance(image, WebPImagePlugin.WebPImageFile)
|
||||
assert image.is_lossless is True
|
||||
|
||||
lossy_file = tmp_path / "lossy.webp"
|
||||
hopper(RGB_MODE).save(lossy_file, lossless=False)
|
||||
with Image.open(lossy_file) as image:
|
||||
assert isinstance(image, WebPImagePlugin.WebPImageFile)
|
||||
assert image.is_lossless is False
|
||||
|
||||
with Image.open("Tests/images/hopper.webp") as image:
|
||||
assert isinstance(image, WebPImagePlugin.WebPImageFile)
|
||||
assert image.is_lossless is False
|
||||
|
||||
@ -1375,6 +1375,13 @@ WebP
|
||||
|
||||
Pillow reads and writes WebP files. Requires libwebp v0.5.0 or later.
|
||||
|
||||
The :py:class:`~PIL.WebPImagePlugin.WebPImageFile` class also exposes the
|
||||
following attribute:
|
||||
|
||||
**is_lossless**
|
||||
``True`` if every coded frame in the file uses VP8L (lossless)
|
||||
compression, ``False`` otherwise.
|
||||
|
||||
.. _webp-saving:
|
||||
|
||||
Saving
|
||||
|
||||
@ -22,6 +22,38 @@ _VP8_MODES_BY_IDENTIFIER = {
|
||||
}
|
||||
|
||||
|
||||
def _is_lossless(data: bytes) -> bool:
|
||||
# A WebP file is considered lossless when every coded frame uses the
|
||||
# VP8L bitstream. See https://developers.google.com/speed/webp/docs/riff_container
|
||||
chunk = data[12:16]
|
||||
if chunk == b"VP8L":
|
||||
return True
|
||||
if chunk != b"VP8X":
|
||||
return False
|
||||
|
||||
# Extended file format: walk the sub-chunks looking for any lossy frame.
|
||||
pos = 12
|
||||
found_frame = False
|
||||
end = len(data)
|
||||
while pos + 8 <= end:
|
||||
fourcc = data[pos : pos + 4]
|
||||
size = int.from_bytes(data[pos + 4 : pos + 8], "little")
|
||||
pos += 8
|
||||
if fourcc == b"VP8 ":
|
||||
return False
|
||||
if fourcc == b"VP8L":
|
||||
found_frame = True
|
||||
elif fourcc == b"ANMF" and pos + 20 <= end:
|
||||
# ANMF: 16 bytes of frame info, then one VP8 / VP8L sub-chunk
|
||||
sub = data[pos + 16 : pos + 20]
|
||||
if sub == b"VP8 ":
|
||||
return False
|
||||
if sub == b"VP8L":
|
||||
found_frame = True
|
||||
pos += size + (size & 1) # RIFF chunks are padded to an even length
|
||||
return found_frame
|
||||
|
||||
|
||||
def _accept(prefix: bytes) -> bool | str:
|
||||
is_riff_file_format = prefix.startswith(b"RIFF")
|
||||
is_webp_file = prefix[8:12] == b"WEBP"
|
||||
@ -51,6 +83,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||
|
||||
# Use the newer AnimDecoder API to parse the (possibly) animated file,
|
||||
# and access muxed chunks like ICC/EXIF/XMP.
|
||||
self.is_lossless = _is_lossless(s)
|
||||
self._decoder = _webp.WebPAnimDecoder(s)
|
||||
|
||||
# Get info from decoder
|
||||
|
||||
Loading…
Reference in New Issue
Block a user