mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
- flake8 formatting fixes
- webp => WebP doc and comment changes
This commit is contained in:
parent
c9258d6cdb
commit
28bec69e98
|
@ -44,6 +44,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
self.size = width, height
|
self.size = width, height
|
||||||
self.fp = BytesIO(data)
|
self.fp = BytesIO(data)
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
|
self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
|
||||||
|
self._n_frames = 1
|
||||||
return
|
return
|
||||||
|
|
||||||
# Use the newer AnimDecoder API to parse the (possibly) animated file,
|
# Use the newer AnimDecoder API to parse the (possibly) animated file,
|
||||||
|
@ -51,7 +52,8 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
self._decoder = _webp.WebPAnimDecoder(self.fp.read())
|
self._decoder = _webp.WebPAnimDecoder(self.fp.read())
|
||||||
|
|
||||||
# Get info from decoder
|
# Get info from decoder
|
||||||
width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()
|
width, height, loop_count, bgcolor, frame_count, mode = \
|
||||||
|
self._decoder.get_info()
|
||||||
self.size = width, height
|
self.size = width, height
|
||||||
self.info["loop"] = loop_count
|
self.info["loop"] = loop_count
|
||||||
bg_a, bg_r, bg_g, bg_b = \
|
bg_a, bg_r, bg_g, bg_b = \
|
||||||
|
@ -85,14 +87,10 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
if not _webp.HAVE_WEBPANIM:
|
|
||||||
return 1
|
|
||||||
return self._n_frames
|
return self._n_frames
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_animated(self):
|
def is_animated(self):
|
||||||
if not _webp.HAVE_WEBPANIM:
|
|
||||||
return False
|
|
||||||
return self._n_frames > 1
|
return self._n_frames > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
@ -122,7 +120,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# Check if an error occurred
|
# Check if an error occurred
|
||||||
if ret is None:
|
if ret is None:
|
||||||
self._reset() # Reset just to be safe
|
self._reset() # Reset just to be safe
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
raise EOFError("failed to decode next frame in WebP file")
|
raise EOFError("failed to decode next frame in WebP file")
|
||||||
|
|
||||||
|
@ -130,20 +128,18 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
data, timestamp = ret
|
data, timestamp = ret
|
||||||
duration = timestamp - self.__timestamp
|
duration = timestamp - self.__timestamp
|
||||||
self.__timestamp = timestamp
|
self.__timestamp = timestamp
|
||||||
timestamp -= duration # libwebp gives frame end, adjust to start of frame
|
|
||||||
|
# libwebp gives frame end, adjust to start of frame
|
||||||
|
timestamp -= duration
|
||||||
return data, timestamp, duration
|
return data, timestamp, duration
|
||||||
|
|
||||||
def _seek(self, frame):
|
def _seek(self, frame):
|
||||||
if self.__physical_frame == frame:
|
if self.__physical_frame == frame:
|
||||||
return # Nothing to do
|
return # Nothing to do
|
||||||
|
|
||||||
if frame < self.__physical_frame:
|
if frame < self.__physical_frame:
|
||||||
# Rewind to beginning
|
self._reset() # Rewind to beginning
|
||||||
self._reset()
|
|
||||||
|
|
||||||
# Advance to the requested frame
|
|
||||||
while self.__physical_frame < frame:
|
while self.__physical_frame < frame:
|
||||||
self._get_next()
|
self._get_next() # Advance to the requested frame
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if _webp.HAVE_WEBPANIM:
|
if _webp.HAVE_WEBPANIM:
|
||||||
|
@ -168,6 +164,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
return self.__logical_frame
|
return self.__logical_frame
|
||||||
|
|
||||||
|
|
||||||
def _save_all(im, fp, filename):
|
def _save_all(im, fp, filename):
|
||||||
encoderinfo = im.encoderinfo.copy()
|
encoderinfo = im.encoderinfo.copy()
|
||||||
append_images = encoderinfo.get("append_images", [])
|
append_images = encoderinfo.get("append_images", [])
|
||||||
|
@ -207,9 +204,12 @@ def _save_all(im, fp, filename):
|
||||||
# Validate background color
|
# Validate background color
|
||||||
if (not isinstance(background, (list, tuple)) or len(background) != 4 or
|
if (not isinstance(background, (list, tuple)) or len(background) != 4 or
|
||||||
not all(v >= 0 and v < 256 for v in background)):
|
not all(v >= 0 and v < 256 for v in background)):
|
||||||
raise IOError("Background color is not an RGBA tuple clamped to (0-255): %s" % str(background))
|
raise IOError("Background color is not an RGBA tuple clamped "
|
||||||
|
"to (0-255): %s" % str(background))
|
||||||
|
|
||||||
|
# Convert to packed uint
|
||||||
bg_r, bg_g, bg_b, bg_a = background
|
bg_r, bg_g, bg_b, bg_a = background
|
||||||
background = (bg_a << 24) | (bg_r << 16) | (bg_g << 8) | (bg_b << 0) # Convert to packed uint
|
background = (bg_a << 24) | (bg_r << 16) | (bg_g << 8) | (bg_b << 0)
|
||||||
|
|
||||||
# Setup the WebP animation encoder
|
# Setup the WebP animation encoder
|
||||||
enc = _webp.WebPAnimEncoder(
|
enc = _webp.WebPAnimEncoder(
|
||||||
|
@ -240,7 +240,7 @@ def _save_all(im, fp, filename):
|
||||||
|
|
||||||
# Make sure image mode is supported
|
# Make sure image mode is supported
|
||||||
frame = ims
|
frame = ims
|
||||||
if not ims.mode in _VALID_WEBP_MODES:
|
if ims.mode not in _VALID_WEBP_MODES:
|
||||||
alpha = ims.mode == 'P' and 'A' in ims.im.getpalettemode()
|
alpha = ims.mode == 'P' and 'A' in ims.im.getpalettemode()
|
||||||
frame = ims.convert('RGBA' if alpha else 'RGBX')
|
frame = ims.convert('RGBA' if alpha else 'RGBX')
|
||||||
|
|
||||||
|
@ -256,7 +256,10 @@ def _save_all(im, fp, filename):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update timestamp and frame index
|
# Update timestamp and frame index
|
||||||
timestamp += duration[frame_idx] if isinstance(duration, (list, tuple)) else duration
|
if isinstance(duration, (list, tuple)):
|
||||||
|
timestamp += duration[frame_idx]
|
||||||
|
else:
|
||||||
|
timestamp += duration
|
||||||
frame_idx += 1
|
frame_idx += 1
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
@ -272,10 +275,11 @@ def _save_all(im, fp, filename):
|
||||||
# Get the final output from the encoder
|
# Get the final output from the encoder
|
||||||
data = enc.assemble(icc_profile, exif, xmp)
|
data = enc.assemble(icc_profile, exif, xmp)
|
||||||
if data is None:
|
if data is None:
|
||||||
raise IOError("cannot write file as WEBP (encoder returned None)")
|
raise IOError("cannot write file as WebP (encoder returned None)")
|
||||||
|
|
||||||
fp.write(data)
|
fp.write(data)
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
lossless = im.encoderinfo.get("lossless", False)
|
lossless = im.encoderinfo.get("lossless", False)
|
||||||
quality = im.encoderinfo.get("quality", 80)
|
quality = im.encoderinfo.get("quality", 80)
|
||||||
|
@ -299,7 +303,7 @@ def _save(im, fp, filename):
|
||||||
xmp
|
xmp
|
||||||
)
|
)
|
||||||
if data is None:
|
if data is None:
|
||||||
raise IOError("cannot write file as WEBP (encoder returned None)")
|
raise IOError("cannot write file as WebP (encoder returned None)")
|
||||||
|
|
||||||
fp.write(data)
|
fp.write(data)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAVE_WEBP = False
|
HAVE_WEBP = False
|
||||||
|
|
||||||
|
|
||||||
class TestFileWebp(PillowTestCase):
|
class TestFileWebp(PillowTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -24,7 +25,7 @@ class TestFileWebp(PillowTestCase):
|
||||||
|
|
||||||
def test_read_rgb(self):
|
def test_read_rgb(self):
|
||||||
"""
|
"""
|
||||||
Can we read a RGB mode webp file without error.
|
Can we read a RGB mode WebP file without error?
|
||||||
Does it have the bits we expect?
|
Does it have the bits we expect?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -39,7 +40,8 @@ class TestFileWebp(PillowTestCase):
|
||||||
|
|
||||||
# generated with:
|
# generated with:
|
||||||
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
|
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
|
||||||
target = Image.open('Tests/images/hopper_webp_bits.ppm').convert(self.rgb_mode)
|
target = Image.open('Tests/images/hopper_webp_bits.ppm')
|
||||||
|
target = target.convert(self.rgb_mode)
|
||||||
self.assert_image_similar(image, target, 20.0)
|
self.assert_image_similar(image, target, 20.0)
|
||||||
|
|
||||||
def test_write_rgb(self):
|
def test_write_rgb(self):
|
||||||
|
@ -77,7 +79,7 @@ class TestFileWebp(PillowTestCase):
|
||||||
|
|
||||||
def test_write_unsupported_mode_L(self):
|
def test_write_unsupported_mode_L(self):
|
||||||
"""
|
"""
|
||||||
Saving a black-and-white file to webp format should work, and be
|
Saving a black-and-white file to WebP format should work, and be
|
||||||
similar to the original file.
|
similar to the original file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -97,7 +99,7 @@ class TestFileWebp(PillowTestCase):
|
||||||
|
|
||||||
def test_write_unsupported_mode_P(self):
|
def test_write_unsupported_mode_P(self):
|
||||||
"""
|
"""
|
||||||
Saving a palette-based file to webp format should work, and be
|
Saving a palette-based file to WebP format should work, and be
|
||||||
similar to the original file.
|
similar to the original file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ class TestFileWebpAlpha(PillowTestCase):
|
||||||
|
|
||||||
def test_read_rgba(self):
|
def test_read_rgba(self):
|
||||||
"""
|
"""
|
||||||
Can we read a RGBA mode file without error.
|
Can we read an RGBA mode file without error?
|
||||||
Does it have the bits we expect?
|
Does it have the bits we expect?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ class TestFileWebpAlpha(PillowTestCase):
|
||||||
|
|
||||||
def test_write_lossless_rgb(self):
|
def test_write_lossless_rgb(self):
|
||||||
"""
|
"""
|
||||||
Can we write a RGBA mode file with lossless compression without error.
|
Can we write an RGBA mode file with lossless compression without
|
||||||
Does it have the bits we expect?
|
error? Does it have the bits we expect?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
temp_file = self.tempfile("temp.webp")
|
temp_file = self.tempfile("temp.webp")
|
||||||
|
@ -102,7 +102,7 @@ class TestFileWebpAlpha(PillowTestCase):
|
||||||
|
|
||||||
def test_write_unsupported_mode_PA(self):
|
def test_write_unsupported_mode_PA(self):
|
||||||
"""
|
"""
|
||||||
Saving a palette-based file with transparency to webp format
|
Saving a palette-based file with transparency to WebP format
|
||||||
should work, and be similar to the original file.
|
should work, and be similar to the original file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from helper import unittest, PillowTestCase, hopper
|
from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAVE_WEBP = False
|
HAVE_WEBP = False
|
||||||
|
|
||||||
|
|
||||||
class TestFileWebpAnimation(PillowTestCase):
|
class TestFileWebpAnimation(PillowTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -21,7 +22,7 @@ class TestFileWebpAnimation(PillowTestCase):
|
||||||
|
|
||||||
def test_n_frames(self):
|
def test_n_frames(self):
|
||||||
"""
|
"""
|
||||||
Ensure that webp format sets n_frames and is_animated
|
Ensure that WebP format sets n_frames and is_animated
|
||||||
attributes correctly.
|
attributes correctly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -68,16 +69,15 @@ class TestFileWebpAnimation(PillowTestCase):
|
||||||
temp_file2 = self.tempfile("temp.png")
|
temp_file2 = self.tempfile("temp.png")
|
||||||
frame1 = Image.open('Tests/images/anim_frame1.webp')
|
frame1 = Image.open('Tests/images/anim_frame1.webp')
|
||||||
frame2 = Image.open('Tests/images/anim_frame2.webp')
|
frame2 = Image.open('Tests/images/anim_frame2.webp')
|
||||||
frame1.save(temp_file, save_all=True, append_images=[frame2], lossless=True)
|
frame1.save(temp_file,
|
||||||
|
save_all=True, append_images=[frame2], lossless=True)
|
||||||
|
|
||||||
im = Image.open(temp_file)
|
im = Image.open(temp_file)
|
||||||
self.assertEqual(im.n_frames, 2)
|
self.assertEqual(im.n_frames, 2)
|
||||||
print("Test File: " + temp_file)
|
|
||||||
|
|
||||||
# Compare first frame to original
|
# Compare first frame to original
|
||||||
im.load()
|
im.load()
|
||||||
im.save(temp_file2)
|
im.save(temp_file2)
|
||||||
print("Frame 1: " + temp_file2)
|
|
||||||
self.assert_image_equal(im, frame1.convert("RGBA"))
|
self.assert_image_equal(im, frame1.convert("RGBA"))
|
||||||
|
|
||||||
# Compare second frame to original
|
# Compare second frame to original
|
||||||
|
@ -103,7 +103,7 @@ class TestFileWebpAnimation(PillowTestCase):
|
||||||
self.assertEqual(im.n_frames, 5)
|
self.assertEqual(im.n_frames, 5)
|
||||||
self.assertTrue(im.is_animated)
|
self.assertTrue(im.is_animated)
|
||||||
|
|
||||||
# Double-check that timestamps and durations match original values specified
|
# Check that timestamps and durations match original values specified
|
||||||
ts = 0
|
ts = 0
|
||||||
for frame in range(im.n_frames):
|
for frame in range(im.n_frames):
|
||||||
im.seek(frame)
|
im.seek(frame)
|
||||||
|
@ -114,7 +114,7 @@ class TestFileWebpAnimation(PillowTestCase):
|
||||||
|
|
||||||
def test_seeking(self):
|
def test_seeking(self):
|
||||||
"""
|
"""
|
||||||
Create an animated webp file, and then try seeking through
|
Create an animated WebP file, and then try seeking through
|
||||||
frames in reverse-order, verifying the timestamps and durations
|
frames in reverse-order, verifying the timestamps and durations
|
||||||
are correct.
|
are correct.
|
||||||
"""
|
"""
|
||||||
|
@ -131,7 +131,7 @@ class TestFileWebpAnimation(PillowTestCase):
|
||||||
self.assertEqual(im.n_frames, 5)
|
self.assertEqual(im.n_frames, 5)
|
||||||
self.assertTrue(im.is_animated)
|
self.assertTrue(im.is_animated)
|
||||||
|
|
||||||
# Traverse frames in reverse order, double-check timestamps and duration
|
# Traverse frames in reverse, checking timestamps and durations
|
||||||
ts = dur * (im.n_frames-1)
|
ts = dur * (im.n_frames-1)
|
||||||
for frame in reversed(range(im.n_frames)):
|
for frame in reversed(range(im.n_frames)):
|
||||||
im.seek(frame)
|
im.seek(frame)
|
||||||
|
|
|
@ -8,6 +8,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAVE_WEBP = False
|
HAVE_WEBP = False
|
||||||
|
|
||||||
|
|
||||||
class TestFileWebpLossless(PillowTestCase):
|
class TestFileWebpLossless(PillowTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -8,6 +8,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAVE_WEBP = False
|
HAVE_WEBP = False
|
||||||
|
|
||||||
|
|
||||||
class TestFileWebpMetadata(PillowTestCase):
|
class TestFileWebpMetadata(PillowTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
4
_webp.c
4
_webp.c
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check the versions from mux.h and demux.h, to ensure the WebPAnimEncoder and
|
* Check the versions from mux.h and demux.h, to ensure the WebPAnimEncoder and
|
||||||
* WebPAnimDecoder API's are present (initial support was added in 0.5.0). The
|
* WebPAnimDecoder APIs are present (initial support was added in 0.5.0). The
|
||||||
* very early versions added had some significant differences, so we require
|
* very early versions added had some significant differences, so we require
|
||||||
* later versions, before enabling animation support.
|
* later versions, before enabling animation support.
|
||||||
*/
|
*/
|
||||||
|
@ -591,7 +591,7 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
/* I want to truncate the *_size items that get passed into webp
|
/* I want to truncate the *_size items that get passed into WebP
|
||||||
data. Pypy2.1.0 had some issues where the Py_ssize_t items had
|
data. Pypy2.1.0 had some issues where the Py_ssize_t items had
|
||||||
data in the upper byte. (Not sure why, it shouldn't have been there)
|
data in the upper byte. (Not sure why, it shouldn't have been there)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -658,7 +658,7 @@ format are currently undocumented.
|
||||||
The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
|
|
||||||
**lossless**
|
**lossless**
|
||||||
If present and true, instructs the WEBP writer to use lossless compression.
|
If present and true, instructs the WebP writer to use lossless compression.
|
||||||
|
|
||||||
**quality**
|
**quality**
|
||||||
Integer, 1-100, Defaults to 80. For lossy, 0 gives the smallest
|
Integer, 1-100, Defaults to 80. For lossy, 0 gives the smallest
|
||||||
|
@ -671,23 +671,23 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
|
|
||||||
**icc_procfile**
|
**icc_procfile**
|
||||||
The ICC Profile to include in the saved file. Only supported if
|
The ICC Profile to include in the saved file. Only supported if
|
||||||
the system webp library was built with webpmux support.
|
the system WebP library was built with webpmux support.
|
||||||
|
|
||||||
**exif**
|
**exif**
|
||||||
The exif data to include in the saved file. Only supported if
|
The exif data to include in the saved file. Only supported if
|
||||||
the system webp library was built with webpmux support.
|
the system WebP library was built with webpmux support.
|
||||||
|
|
||||||
Saving sequences
|
Saving sequences
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Support for animated WebP files will only be enabled if the system webp
|
Support for animated WebP files will only be enabled if the system WebP
|
||||||
library is v0.5.0 or later. You can check webp animation support at
|
library is v0.5.0 or later. You can check webp animation support at
|
||||||
runtime by inspecting the `_webp.HAVE_WEBPANIM` module flag.
|
runtime by calling `features.check("webp_anim")`.
|
||||||
|
|
||||||
When calling :py:meth:`~PIL.Image.Image.save`, the following options
|
When calling :py:meth:`~PIL.Image.Image.save`, the following options
|
||||||
are available when the save_all argument is present and true.
|
are available when the `save_all` argument is present and true.
|
||||||
|
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of images to append as additional frames. Each of the
|
A list of images to append as additional frames. Each of the
|
||||||
|
|
Loading…
Reference in New Issue
Block a user