mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-10 19:56:47 +03:00
Merge pull request #8213 from uploadcare/webp-require-anim
Remove WebP support without anim, mux/demux, and with buggy alpha
This commit is contained in:
commit
35a70e4b97
|
@ -10,11 +10,6 @@ from PIL import features
|
|||
|
||||
from .helper import skip_unless_feature
|
||||
|
||||
try:
|
||||
from PIL import _webp
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def test_check() -> None:
|
||||
# Check the correctness of the convenience function
|
||||
|
@ -23,7 +18,11 @@ def test_check() -> None:
|
|||
for codec in features.codecs:
|
||||
assert features.check_codec(codec) == features.check(codec)
|
||||
for feature in features.features:
|
||||
assert features.check_feature(feature) == features.check(feature)
|
||||
if "webp" in feature:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert features.check_feature(feature) == features.check(feature)
|
||||
else:
|
||||
assert features.check_feature(feature) == features.check(feature)
|
||||
|
||||
|
||||
def test_version() -> None:
|
||||
|
@ -48,23 +47,26 @@ def test_version() -> None:
|
|||
for codec in features.codecs:
|
||||
test(codec, features.version_codec)
|
||||
for feature in features.features:
|
||||
test(feature, features.version_feature)
|
||||
if "webp" in feature:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
test(feature, features.version_feature)
|
||||
else:
|
||||
test(feature, features.version_feature)
|
||||
|
||||
|
||||
@skip_unless_feature("webp")
|
||||
def test_webp_transparency() -> None:
|
||||
assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha()
|
||||
assert features.check("transp_webp") == _webp.HAVE_TRANSPARENCY
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert features.check("transp_webp") == features.check_module("webp")
|
||||
|
||||
|
||||
@skip_unless_feature("webp")
|
||||
def test_webp_mux() -> None:
|
||||
assert features.check("webp_mux") == _webp.HAVE_WEBPMUX
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert features.check("webp_mux") == features.check_module("webp")
|
||||
|
||||
|
||||
@skip_unless_feature("webp")
|
||||
def test_webp_anim() -> None:
|
||||
assert features.check("webp_anim") == _webp.HAVE_WEBPANIM
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert features.check("webp_anim") == features.check_module("webp")
|
||||
|
||||
|
||||
@skip_unless_feature("libjpeg_turbo")
|
||||
|
|
|
@ -978,7 +978,7 @@ def test_webp_background(tmp_path: Path) -> None:
|
|||
out = str(tmp_path / "temp.gif")
|
||||
|
||||
# Test opaque WebP background
|
||||
if features.check("webp") and features.check("webp_anim"):
|
||||
if features.check("webp"):
|
||||
with Image.open("Tests/images/hopper.webp") as im:
|
||||
assert im.info["background"] == (255, 255, 255, 255)
|
||||
im.save(out)
|
||||
|
|
|
@ -48,8 +48,6 @@ class TestFileWebp:
|
|||
self.rgb_mode = "RGB"
|
||||
|
||||
def test_version(self) -> None:
|
||||
_webp.WebPDecoderVersion()
|
||||
_webp.WebPDecoderBuggyAlpha()
|
||||
version = features.version_module("webp")
|
||||
assert version is not None
|
||||
assert re.search(r"\d+\.\d+\.\d+$", version)
|
||||
|
@ -117,7 +115,6 @@ class TestFileWebp:
|
|||
hopper().save(buffer_method, format="WEBP", method=6)
|
||||
assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
|
||||
|
||||
@skip_unless_feature("webp_anim")
|
||||
def test_save_all(self, tmp_path: Path) -> None:
|
||||
temp_file = str(tmp_path / "temp.webp")
|
||||
im = Image.new("RGB", (1, 1))
|
||||
|
@ -132,10 +129,9 @@ class TestFileWebp:
|
|||
|
||||
def test_icc_profile(self, tmp_path: Path) -> None:
|
||||
self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None})
|
||||
if _webp.HAVE_WEBPANIM:
|
||||
self._roundtrip(
|
||||
tmp_path, self.rgb_mode, 12.5, {"icc_profile": None, "save_all": True}
|
||||
)
|
||||
self._roundtrip(
|
||||
tmp_path, self.rgb_mode, 12.5, {"icc_profile": None, "save_all": True}
|
||||
)
|
||||
|
||||
def test_write_unsupported_mode_L(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
|
@ -165,23 +161,17 @@ class TestFileWebp:
|
|||
"""
|
||||
Calling encoder functions with no arguments should result in an error.
|
||||
"""
|
||||
|
||||
if _webp.HAVE_WEBPANIM:
|
||||
with pytest.raises(TypeError):
|
||||
_webp.WebPAnimEncoder()
|
||||
with pytest.raises(TypeError):
|
||||
_webp.WebPAnimEncoder()
|
||||
with pytest.raises(TypeError):
|
||||
_webp.WebPEncode()
|
||||
|
||||
def test_WebPDecode_with_invalid_args(self) -> None:
|
||||
def test_WebPAnimDecoder_with_invalid_args(self) -> None:
|
||||
"""
|
||||
Calling decoder functions with no arguments should result in an error.
|
||||
"""
|
||||
|
||||
if _webp.HAVE_WEBPANIM:
|
||||
with pytest.raises(TypeError):
|
||||
_webp.WebPAnimDecoder()
|
||||
with pytest.raises(TypeError):
|
||||
_webp.WebPDecode()
|
||||
_webp.WebPAnimDecoder()
|
||||
|
||||
def test_no_resource_warning(self, tmp_path: Path) -> None:
|
||||
file_path = "Tests/images/hopper.webp"
|
||||
|
@ -200,7 +190,6 @@ class TestFileWebp:
|
|||
"background",
|
||||
(0, (0,), (-1, 0, 1, 2), (253, 254, 255, 256)),
|
||||
)
|
||||
@skip_unless_feature("webp_anim")
|
||||
def test_invalid_background(
|
||||
self, background: int | tuple[int, ...], tmp_path: Path
|
||||
) -> None:
|
||||
|
@ -209,7 +198,6 @@ class TestFileWebp:
|
|||
with pytest.raises(OSError):
|
||||
im.save(temp_file, save_all=True, append_images=[im], background=background)
|
||||
|
||||
@skip_unless_feature("webp_anim")
|
||||
def test_background_from_gif(self, tmp_path: Path) -> None:
|
||||
# Save L mode GIF with background
|
||||
with Image.open("Tests/images/no_palette_with_background.gif") as im:
|
||||
|
@ -234,7 +222,6 @@ class TestFileWebp:
|
|||
difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3))
|
||||
assert difference < 5
|
||||
|
||||
@skip_unless_feature("webp_anim")
|
||||
def test_duration(self, tmp_path: Path) -> None:
|
||||
with Image.open("Tests/images/dispose_bgnd.gif") as im:
|
||||
assert im.info["duration"] == 1000
|
||||
|
|
|
@ -13,12 +13,7 @@ from .helper import (
|
|||
hopper,
|
||||
)
|
||||
|
||||
_webp = pytest.importorskip("PIL._webp", reason="WebP support not installed")
|
||||
|
||||
|
||||
def setup_module() -> None:
|
||||
if _webp.WebPDecoderBuggyAlpha():
|
||||
pytest.skip("Buggy early version of WebP installed, not testing transparency")
|
||||
pytest.importorskip("PIL._webp", reason="WebP support not installed")
|
||||
|
||||
|
||||
def test_read_rgba() -> None:
|
||||
|
@ -81,9 +76,6 @@ def test_write_rgba(tmp_path: Path) -> None:
|
|||
pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20))
|
||||
pil_image.save(temp_file)
|
||||
|
||||
if _webp.WebPDecoderBuggyAlpha():
|
||||
return
|
||||
|
||||
with Image.open(temp_file) as image:
|
||||
image.load()
|
||||
|
||||
|
@ -93,12 +85,7 @@ def test_write_rgba(tmp_path: Path) -> None:
|
|||
image.load()
|
||||
image.getdata()
|
||||
|
||||
# Early versions of WebP are known to produce higher deviations:
|
||||
# deal with it
|
||||
if _webp.WebPDecoderVersion() <= 0x201:
|
||||
assert_image_similar(image, pil_image, 3.0)
|
||||
else:
|
||||
assert_image_similar(image, pil_image, 1.0)
|
||||
assert_image_similar(image, pil_image, 1.0)
|
||||
|
||||
|
||||
def test_keep_rgb_values_when_transparent(tmp_path: Path) -> None:
|
||||
|
|
|
@ -15,10 +15,7 @@ from .helper import (
|
|||
skip_unless_feature,
|
||||
)
|
||||
|
||||
pytestmark = [
|
||||
skip_unless_feature("webp"),
|
||||
skip_unless_feature("webp_anim"),
|
||||
]
|
||||
pytestmark = skip_unless_feature("webp")
|
||||
|
||||
|
||||
def test_n_frames() -> None:
|
||||
|
|
|
@ -8,14 +8,11 @@ from PIL import Image
|
|||
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
||||
_webp = pytest.importorskip("PIL._webp", reason="WebP support not installed")
|
||||
pytest.importorskip("PIL._webp", reason="WebP support not installed")
|
||||
RGB_MODE = "RGB"
|
||||
|
||||
|
||||
def test_write_lossless_rgb(tmp_path: Path) -> None:
|
||||
if _webp.WebPDecoderVersion() < 0x0200:
|
||||
pytest.skip("lossless not included")
|
||||
|
||||
temp_file = str(tmp_path / "temp.webp")
|
||||
|
||||
hopper(RGB_MODE).save(temp_file, lossless=True)
|
||||
|
|
|
@ -10,10 +10,7 @@ from PIL import Image
|
|||
|
||||
from .helper import mark_if_feature_version, skip_unless_feature
|
||||
|
||||
pytestmark = [
|
||||
skip_unless_feature("webp"),
|
||||
skip_unless_feature("webp_mux"),
|
||||
]
|
||||
pytestmark = skip_unless_feature("webp")
|
||||
|
||||
ElementTree: ModuleType | None
|
||||
try:
|
||||
|
@ -136,7 +133,6 @@ def test_getxmp() -> None:
|
|||
)
|
||||
|
||||
|
||||
@skip_unless_feature("webp_anim")
|
||||
def test_write_animated_metadata(tmp_path: Path) -> None:
|
||||
iccp_data = b"<iccp_data>"
|
||||
exif_data = b"<exif_data>"
|
||||
|
|
|
@ -817,7 +817,6 @@ class TestImage:
|
|||
assert reloaded_exif[305] == "Pillow test"
|
||||
|
||||
@skip_unless_feature("webp")
|
||||
@skip_unless_feature("webp_anim")
|
||||
def test_exif_webp(self, tmp_path: Path) -> None:
|
||||
with Image.open("Tests/images/hopper.webp") as im:
|
||||
exif = im.getexif()
|
||||
|
|
|
@ -94,7 +94,6 @@ class TestImageFile:
|
|||
assert (48, 48) == p.image.size
|
||||
|
||||
@skip_unless_feature("webp")
|
||||
@skip_unless_feature("webp_anim")
|
||||
def test_incremental_webp(self) -> None:
|
||||
with ImageFile.Parser() as p:
|
||||
with open("Tests/images/hopper.webp", "rb") as f:
|
||||
|
|
|
@ -390,7 +390,7 @@ def test_colorize_3color_offset() -> None:
|
|||
|
||||
def test_exif_transpose() -> None:
|
||||
exts = [".jpg"]
|
||||
if features.check("webp") and features.check("webp_anim"):
|
||||
if features.check("webp"):
|
||||
exts.append(".webp")
|
||||
for ext in exts:
|
||||
with Image.open("Tests/images/hopper" + ext) as base_im:
|
||||
|
|
|
@ -126,6 +126,16 @@ JpegImageFile.huffman_ac and JpegImageFile.huffman_dc
|
|||
The ``huffman_ac`` and ``huffman_dc`` dictionaries on JPEG images were unused. They
|
||||
have been deprecated, and will be removed in Pillow 12 (2025-10-15).
|
||||
|
||||
Specific WebP Feature Checks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. deprecated:: 11.0.0
|
||||
|
||||
``features.check("transp_webp")``, ``features.check("webp_mux")`` and
|
||||
``features.check("webp_anim")`` are now deprecated. They will always return
|
||||
``True`` if the WebP module is installed, until they are removed in Pillow
|
||||
12.0.0 (2025-10-15).
|
||||
|
||||
Removed features
|
||||
----------------
|
||||
|
||||
|
|
|
@ -1220,8 +1220,7 @@ using the general tags available through tiffinfo.
|
|||
WebP
|
||||
^^^^
|
||||
|
||||
Pillow reads and writes WebP files. The specifics of Pillow's capabilities with
|
||||
this format are currently undocumented.
|
||||
Pillow reads and writes WebP files. Requires libwebp v0.5.0 or later.
|
||||
|
||||
.. _webp-saving:
|
||||
|
||||
|
@ -1249,29 +1248,19 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
|||
**exact**
|
||||
If true, preserve the transparent RGB values. Otherwise, discard
|
||||
invisible RGB values for better compression. Defaults to false.
|
||||
Requires libwebp 0.5.0 or later.
|
||||
|
||||
**icc_profile**
|
||||
The ICC Profile to include in the saved file. Only supported if
|
||||
the system WebP library was built with webpmux support.
|
||||
The ICC Profile to include in the saved file.
|
||||
|
||||
**exif**
|
||||
The exif data to include in the saved file. Only supported if
|
||||
the system WebP library was built with webpmux support.
|
||||
The exif data to include in the saved file.
|
||||
|
||||
**xmp**
|
||||
The XMP data to include in the saved file. Only supported if
|
||||
the system WebP library was built with webpmux support.
|
||||
The XMP data to include in the saved file.
|
||||
|
||||
Saving sequences
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
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
|
||||
runtime by calling ``features.check("webp_anim")``.
|
||||
|
||||
When calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, by default
|
||||
only the first frame of a multiframe image will be saved. If the ``save_all``
|
||||
argument is present and true, then all frames will be saved, and the following
|
||||
|
|
|
@ -55,10 +55,6 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libwebp** provides the WebP format.
|
||||
|
||||
* Pillow has been tested with version **0.1.3**, which does not read
|
||||
transparent WebP files. Versions **0.3.0** and above support
|
||||
transparency.
|
||||
|
||||
* **openjpeg** provides JPEG 2000 functionality.
|
||||
|
||||
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
|
||||
|
@ -275,18 +271,18 @@ Build Options
|
|||
|
||||
* Config settings: ``-C zlib=disable``, ``-C jpeg=disable``,
|
||||
``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``,
|
||||
``-C lcms=disable``, ``-C webp=disable``, ``-C webpmux=disable``,
|
||||
``-C lcms=disable``, ``-C webp=disable``,
|
||||
``-C jpeg2000=disable``, ``-C imagequant=disable``, ``-C xcb=disable``.
|
||||
Disable building the corresponding feature even if the development
|
||||
libraries are present on the building machine.
|
||||
|
||||
* Config settings: ``-C zlib=enable``, ``-C jpeg=enable``,
|
||||
``-C tiff=enable``, ``-C freetype=enable``, ``-C raqm=enable``,
|
||||
``-C lcms=enable``, ``-C webp=enable``, ``-C webpmux=enable``,
|
||||
``-C lcms=enable``, ``-C webp=enable``,
|
||||
``-C jpeg2000=enable``, ``-C imagequant=enable``, ``-C xcb=enable``.
|
||||
Require that the corresponding feature is built. The build will raise
|
||||
an exception if the libraries are not found. Webpmux (WebP metadata)
|
||||
relies on WebP support. Tcl and Tk also must be used together.
|
||||
an exception if the libraries are not found. Tcl and Tk must be used
|
||||
together.
|
||||
|
||||
* Config settings: ``-C raqm=vendor``, ``-C fribidi=vendor``.
|
||||
These flags are used to compile a modified version of libraqm and
|
||||
|
|
|
@ -54,12 +54,12 @@ Feature version numbers are available only where stated.
|
|||
Support for the following features can be checked:
|
||||
|
||||
* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available.
|
||||
* ``transp_webp``: Support for transparency in WebP images.
|
||||
* ``webp_mux``: (compile time) Support for EXIF data in WebP images.
|
||||
* ``webp_anim``: (compile time) Support for animated WebP images.
|
||||
* ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer.
|
||||
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available.
|
||||
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
|
||||
* ``transp_webp``: Deprecated. Always ``True`` if WebP module is installed.
|
||||
* ``webp_mux``: Deprecated. Always ``True`` if WebP module is installed.
|
||||
* ``webp_anim``: Deprecated. Always ``True`` if WebP module is installed.
|
||||
|
||||
.. autofunction:: PIL.features.check_feature
|
||||
.. autofunction:: PIL.features.version_feature
|
||||
|
|
|
@ -58,6 +58,14 @@ JpegImageFile.huffman_ac and JpegImageFile.huffman_dc
|
|||
The ``huffman_ac`` and ``huffman_dc`` dictionaries on JPEG images were unused. They
|
||||
have been deprecated, and will be removed in Pillow 12 (2025-10-15).
|
||||
|
||||
Specific WebP Feature Checks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``features.check("transp_webp")``, ``features.check("webp_mux")`` and
|
||||
``features.check("webp_anim")`` are now deprecated. They will always return
|
||||
``True`` if the WebP module is installed, until they are removed in Pillow
|
||||
12.0.0 (2025-10-15).
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
|
|
45
setup.py
45
setup.py
|
@ -295,7 +295,6 @@ class pil_build_ext(build_ext):
|
|||
"raqm",
|
||||
"lcms",
|
||||
"webp",
|
||||
"webpmux",
|
||||
"jpeg2000",
|
||||
"imagequant",
|
||||
"xcb",
|
||||
|
@ -794,28 +793,18 @@ class pil_build_ext(build_ext):
|
|||
|
||||
if feature.want("webp"):
|
||||
_dbg("Looking for webp")
|
||||
if _find_include_file(self, "webp/encode.h") and _find_include_file(
|
||||
self, "webp/decode.h"
|
||||
if all(
|
||||
_find_include_file(self, "webp/" + include)
|
||||
for include in ("encode.h", "decode.h", "mux.h", "demux.h")
|
||||
):
|
||||
# In Google's precompiled zip it is call "libwebp":
|
||||
if _find_library_file(self, "webp"):
|
||||
feature.webp = "webp"
|
||||
elif _find_library_file(self, "libwebp"):
|
||||
feature.webp = "libwebp"
|
||||
|
||||
if feature.want("webpmux"):
|
||||
_dbg("Looking for webpmux")
|
||||
if _find_include_file(self, "webp/mux.h") and _find_include_file(
|
||||
self, "webp/demux.h"
|
||||
):
|
||||
if _find_library_file(self, "webpmux") and _find_library_file(
|
||||
self, "webpdemux"
|
||||
):
|
||||
feature.webpmux = "webpmux"
|
||||
if _find_library_file(self, "libwebpmux") and _find_library_file(
|
||||
self, "libwebpdemux"
|
||||
):
|
||||
feature.webpmux = "libwebpmux"
|
||||
# In Google's precompiled zip it is called "libwebp"
|
||||
for prefix in ("", "lib"):
|
||||
if all(
|
||||
_find_library_file(self, prefix + library)
|
||||
for library in ("webp", "webpmux", "webpdemux")
|
||||
):
|
||||
feature.webp = prefix + "webp"
|
||||
break
|
||||
|
||||
if feature.want("xcb"):
|
||||
_dbg("Looking for xcb")
|
||||
|
@ -904,15 +893,8 @@ class pil_build_ext(build_ext):
|
|||
self._remove_extension("PIL._imagingcms")
|
||||
|
||||
if feature.webp:
|
||||
libs = [feature.webp]
|
||||
defs = []
|
||||
|
||||
if feature.webpmux:
|
||||
defs.append(("HAVE_WEBPMUX", None))
|
||||
libs.append(feature.webpmux)
|
||||
libs.append(feature.webpmux.replace("pmux", "pdemux"))
|
||||
|
||||
self._update_extension("PIL._webp", libs, defs)
|
||||
libs = [feature.webp, feature.webp + "mux", feature.webp + "demux"]
|
||||
self._update_extension("PIL._webp", libs)
|
||||
else:
|
||||
self._remove_extension("PIL._webp")
|
||||
|
||||
|
@ -953,7 +935,6 @@ class pil_build_ext(build_ext):
|
|||
(feature.raqm, "RAQM (Text shaping)", raqm_extra_info),
|
||||
(feature.lcms, "LITTLECMS2"),
|
||||
(feature.webp, "WEBP"),
|
||||
(feature.webpmux, "WEBPMUX"),
|
||||
(feature.xcb, "XCB (X protocol)"),
|
||||
]
|
||||
|
||||
|
|
|
@ -45,22 +45,6 @@ class WebPImageFile(ImageFile.ImageFile):
|
|||
__logical_frame = 0
|
||||
|
||||
def _open(self) -> None:
|
||||
if not _webp.HAVE_WEBPANIM:
|
||||
# Legacy mode
|
||||
data, width, height, self._mode, icc_profile, exif = _webp.WebPDecode(
|
||||
self.fp.read()
|
||||
)
|
||||
if icc_profile:
|
||||
self.info["icc_profile"] = icc_profile
|
||||
if exif:
|
||||
self.info["exif"] = exif
|
||||
self._size = width, height
|
||||
self.fp = BytesIO(data)
|
||||
self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
|
||||
self.n_frames = 1
|
||||
self.is_animated = False
|
||||
return
|
||||
|
||||
# Use the newer AnimDecoder API to parse the (possibly) animated file,
|
||||
# and access muxed chunks like ICC/EXIF/XMP.
|
||||
self._decoder = _webp.WebPAnimDecoder(self.fp.read())
|
||||
|
@ -145,21 +129,20 @@ class WebPImageFile(ImageFile.ImageFile):
|
|||
self._get_next() # Advance to the requested frame
|
||||
|
||||
def load(self) -> Image.core.PixelAccess | None:
|
||||
if _webp.HAVE_WEBPANIM:
|
||||
if self.__loaded != self.__logical_frame:
|
||||
self._seek(self.__logical_frame)
|
||||
if self.__loaded != self.__logical_frame:
|
||||
self._seek(self.__logical_frame)
|
||||
|
||||
# We need to load the image data for this frame
|
||||
data, timestamp, duration = self._get_next()
|
||||
self.info["timestamp"] = timestamp
|
||||
self.info["duration"] = duration
|
||||
self.__loaded = self.__logical_frame
|
||||
# We need to load the image data for this frame
|
||||
data, timestamp, duration = self._get_next()
|
||||
self.info["timestamp"] = timestamp
|
||||
self.info["duration"] = duration
|
||||
self.__loaded = self.__logical_frame
|
||||
|
||||
# Set tile
|
||||
if self.fp and self._exclusive_fp:
|
||||
self.fp.close()
|
||||
self.fp = BytesIO(data)
|
||||
self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)]
|
||||
# Set tile
|
||||
if self.fp and self._exclusive_fp:
|
||||
self.fp.close()
|
||||
self.fp = BytesIO(data)
|
||||
self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)]
|
||||
|
||||
return super().load()
|
||||
|
||||
|
@ -167,9 +150,6 @@ class WebPImageFile(ImageFile.ImageFile):
|
|||
pass
|
||||
|
||||
def tell(self) -> int:
|
||||
if not _webp.HAVE_WEBPANIM:
|
||||
return super().tell()
|
||||
|
||||
return self.__logical_frame
|
||||
|
||||
|
||||
|
@ -357,7 +337,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
|
||||
if SUPPORTED:
|
||||
Image.register_save(WebPImageFile.format, _save)
|
||||
if _webp.HAVE_WEBPANIM:
|
||||
Image.register_save_all(WebPImageFile.format, _save_all)
|
||||
Image.register_save_all(WebPImageFile.format, _save_all)
|
||||
Image.register_extension(WebPImageFile.format, ".webp")
|
||||
Image.register_mime(WebPImageFile.format, "image/webp")
|
||||
|
|
|
@ -9,6 +9,7 @@ from typing import IO
|
|||
import PIL
|
||||
|
||||
from . import Image
|
||||
from ._deprecate import deprecate
|
||||
|
||||
modules = {
|
||||
"pil": ("PIL._imaging", "PILLOW_VERSION"),
|
||||
|
@ -118,10 +119,10 @@ def get_supported_codecs() -> list[str]:
|
|||
return [f for f in codecs if check_codec(f)]
|
||||
|
||||
|
||||
features = {
|
||||
"webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None),
|
||||
"webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None),
|
||||
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None),
|
||||
features: dict[str, tuple[str, str | bool, str | None]] = {
|
||||
"webp_anim": ("PIL._webp", True, None),
|
||||
"webp_mux": ("PIL._webp", True, None),
|
||||
"transp_webp": ("PIL._webp", True, None),
|
||||
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
|
||||
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
|
||||
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
|
||||
|
@ -147,6 +148,9 @@ def check_feature(feature: str) -> bool | None:
|
|||
|
||||
try:
|
||||
imported_module = __import__(module, fromlist=["PIL"])
|
||||
if isinstance(flag, bool):
|
||||
deprecate(f'check_feature("{feature}")', 12)
|
||||
return flag
|
||||
return getattr(imported_module, flag)
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
|
@ -176,7 +180,17 @@ def get_supported_features() -> list[str]:
|
|||
"""
|
||||
:returns: A list of all supported features.
|
||||
"""
|
||||
return [f for f in features if check_feature(f)]
|
||||
supported_features = []
|
||||
for f, (module, flag, _) in features.items():
|
||||
if flag is True:
|
||||
for feature, (feature_module, _) in modules.items():
|
||||
if feature_module == module:
|
||||
if check_module(feature):
|
||||
supported_features.append(f)
|
||||
break
|
||||
elif check_feature(f):
|
||||
supported_features.append(f)
|
||||
return supported_features
|
||||
|
||||
|
||||
def check(feature: str) -> bool | None:
|
||||
|
@ -271,9 +285,6 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None:
|
|||
("freetype2", "FREETYPE2"),
|
||||
("littlecms2", "LITTLECMS2"),
|
||||
("webp", "WEBP"),
|
||||
("transp_webp", "WEBP Transparency"),
|
||||
("webp_mux", "WEBPMUX"),
|
||||
("webp_anim", "WEBP Animation"),
|
||||
("jpg", "JPEG"),
|
||||
("jpg_2000", "OPENJPEG (JPEG2000)"),
|
||||
("zlib", "ZLIB (PNG/ZIP)"),
|
||||
|
|
212
src/_webp.c
212
src/_webp.c
|
@ -4,8 +4,6 @@
|
|||
#include <webp/encode.h>
|
||||
#include <webp/decode.h>
|
||||
#include <webp/types.h>
|
||||
|
||||
#ifdef HAVE_WEBPMUX
|
||||
#include <webp/mux.h>
|
||||
#include <webp/demux.h>
|
||||
|
||||
|
@ -13,12 +11,10 @@
|
|||
* Check the versions from mux.h and demux.h, to ensure the WebPAnimEncoder and
|
||||
* WebPAnimDecoder APIs are present (initial support was added in 0.5.0). The
|
||||
* very early versions had some significant differences, so we require later
|
||||
* versions, before enabling animation support.
|
||||
* versions.
|
||||
*/
|
||||
#if WEBP_MUX_ABI_VERSION >= 0x0104 && WEBP_DEMUX_ABI_VERSION >= 0x0105
|
||||
#define HAVE_WEBPANIM
|
||||
#endif
|
||||
|
||||
#if WEBP_MUX_ABI_VERSION < 0x0106 || WEBP_DEMUX_ABI_VERSION < 0x0107
|
||||
#error libwebp 0.5.0 and above is required. Upgrade libwebp or build Pillow with --disable-webp flag
|
||||
#endif
|
||||
|
||||
void
|
||||
|
@ -35,8 +31,6 @@ ImagingSectionLeave(ImagingSectionCookie *cookie) {
|
|||
/* WebP Muxer Error Handling */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
#ifdef HAVE_WEBPMUX
|
||||
|
||||
static const char *const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
|
||||
"WEBP_MUX_NOT_FOUND",
|
||||
"WEBP_MUX_INVALID_ARGUMENT",
|
||||
|
@ -89,14 +83,10 @@ HandleMuxError(WebPMuxError err, char *chunk) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* WebP Animation Support */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
#ifdef HAVE_WEBPANIM
|
||||
|
||||
// Encoder type
|
||||
typedef struct {
|
||||
PyObject_HEAD WebPAnimEncoder *enc;
|
||||
|
@ -576,8 +566,6 @@ static PyTypeObject WebPAnimDecoder_Type = {
|
|||
0, /*tp_getset*/
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Legacy WebP Support */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@ -652,10 +640,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
|||
config.quality = quality_factor;
|
||||
config.alpha_quality = alpha_quality_factor;
|
||||
config.method = method;
|
||||
#if WEBP_ENCODER_ABI_VERSION >= 0x0209
|
||||
// the "exact" flag is only available in libwebp 0.5.0 and later
|
||||
config.exact = exact;
|
||||
#endif
|
||||
|
||||
// Validate the config
|
||||
if (!WebPValidateConfig(&config)) {
|
||||
|
@ -693,13 +678,6 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
|||
output = writer.mem;
|
||||
ret_size = writer.size;
|
||||
|
||||
#ifndef HAVE_WEBPMUX
|
||||
if (ret_size > 0) {
|
||||
PyObject *ret = PyBytes_FromStringAndSize((char *)output, ret_size);
|
||||
free(output);
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* 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
|
||||
|
@ -775,132 +753,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
|||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
WebPDecode_wrapper(PyObject *self, PyObject *args) {
|
||||
PyBytesObject *webp_string;
|
||||
const uint8_t *webp;
|
||||
Py_ssize_t size;
|
||||
PyObject *ret = Py_None, *bytes = NULL, *pymode = NULL, *icc_profile = NULL,
|
||||
*exif = NULL;
|
||||
WebPDecoderConfig config;
|
||||
VP8StatusCode vp8_status_code = VP8_STATUS_OK;
|
||||
char *mode = "RGB";
|
||||
|
||||
if (!PyArg_ParseTuple(args, "S", &webp_string)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!WebPInitDecoderConfig(&config)) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyBytes_AsStringAndSize((PyObject *)webp_string, (char **)&webp, &size);
|
||||
|
||||
vp8_status_code = WebPGetFeatures(webp, size, &config.input);
|
||||
if (vp8_status_code == VP8_STATUS_OK) {
|
||||
// If we don't set it, we don't get alpha.
|
||||
// Initialized to MODE_RGB
|
||||
if (config.input.has_alpha) {
|
||||
config.output.colorspace = MODE_RGBA;
|
||||
mode = "RGBA";
|
||||
}
|
||||
|
||||
#ifndef HAVE_WEBPMUX
|
||||
vp8_status_code = WebPDecode(webp, size, &config);
|
||||
#else
|
||||
{
|
||||
int copy_data = 0;
|
||||
WebPData data = {webp, size};
|
||||
WebPMuxFrameInfo image;
|
||||
WebPData icc_profile_data = {0};
|
||||
WebPData exif_data = {0};
|
||||
|
||||
WebPMux *mux = WebPMuxCreate(&data, copy_data);
|
||||
if (NULL == mux) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (WEBP_MUX_OK != WebPMuxGetFrame(mux, 1, &image)) {
|
||||
WebPMuxDelete(mux);
|
||||
goto end;
|
||||
}
|
||||
|
||||
webp = image.bitstream.bytes;
|
||||
size = image.bitstream.size;
|
||||
|
||||
vp8_status_code = WebPDecode(webp, size, &config);
|
||||
|
||||
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) {
|
||||
icc_profile = PyBytes_FromStringAndSize(
|
||||
(const char *)icc_profile_data.bytes, icc_profile_data.size
|
||||
);
|
||||
}
|
||||
|
||||
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) {
|
||||
exif = PyBytes_FromStringAndSize(
|
||||
(const char *)exif_data.bytes, exif_data.size
|
||||
);
|
||||
}
|
||||
|
||||
WebPDataClear(&image.bitstream);
|
||||
WebPMuxDelete(mux);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (vp8_status_code != VP8_STATUS_OK) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (config.output.colorspace < MODE_YUV) {
|
||||
bytes = PyBytes_FromStringAndSize(
|
||||
(char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size
|
||||
);
|
||||
} else {
|
||||
// Skipping YUV for now. Need Test Images.
|
||||
// UNDONE -- unclear if we'll ever get here if we set mode_rgb*
|
||||
bytes = PyBytes_FromStringAndSize(
|
||||
(char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size
|
||||
);
|
||||
}
|
||||
|
||||
pymode = PyUnicode_FromString(mode);
|
||||
ret = Py_BuildValue(
|
||||
"SiiSSS",
|
||||
bytes,
|
||||
config.output.width,
|
||||
config.output.height,
|
||||
pymode,
|
||||
NULL == icc_profile ? Py_None : icc_profile,
|
||||
NULL == exif ? Py_None : exif
|
||||
);
|
||||
|
||||
end:
|
||||
WebPFreeDecBuffer(&config.output);
|
||||
|
||||
Py_XDECREF(bytes);
|
||||
Py_XDECREF(pymode);
|
||||
Py_XDECREF(icc_profile);
|
||||
Py_XDECREF(exif);
|
||||
|
||||
if (Py_None == ret) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Return the decoder's version number, packed in hexadecimal using 8bits for
|
||||
// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
||||
PyObject *
|
||||
WebPDecoderVersion_wrapper() {
|
||||
return Py_BuildValue("i", WebPGetDecoderVersion());
|
||||
}
|
||||
|
||||
// Version as string
|
||||
const char *
|
||||
WebPDecoderVersion_str(void) {
|
||||
|
@ -916,85 +771,26 @@ WebPDecoderVersion_str(void) {
|
|||
return version;
|
||||
}
|
||||
|
||||
/*
|
||||
* The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well.
|
||||
* Files that are valid with 0.3 are reported as being invalid.
|
||||
*/
|
||||
int
|
||||
WebPDecoderBuggyAlpha(void) {
|
||||
return WebPGetDecoderVersion() == 0x0103;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
WebPDecoderBuggyAlpha_wrapper() {
|
||||
return Py_BuildValue("i", WebPDecoderBuggyAlpha());
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/* Module Setup */
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
static PyMethodDef webpMethods[] = {
|
||||
#ifdef HAVE_WEBPANIM
|
||||
{"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"},
|
||||
{"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"},
|
||||
#endif
|
||||
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
|
||||
{"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"},
|
||||
{"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_NOARGS, "WebPVersion"},
|
||||
{"WebPDecoderBuggyAlpha",
|
||||
WebPDecoderBuggyAlpha_wrapper,
|
||||
METH_NOARGS,
|
||||
"WebPDecoderBuggyAlpha"},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
void
|
||||
addMuxFlagToModule(PyObject *m) {
|
||||
PyObject *have_webpmux;
|
||||
#ifdef HAVE_WEBPMUX
|
||||
have_webpmux = Py_True;
|
||||
#else
|
||||
have_webpmux = Py_False;
|
||||
#endif
|
||||
Py_INCREF(have_webpmux);
|
||||
PyModule_AddObject(m, "HAVE_WEBPMUX", have_webpmux);
|
||||
}
|
||||
|
||||
void
|
||||
addAnimFlagToModule(PyObject *m) {
|
||||
PyObject *have_webpanim;
|
||||
#ifdef HAVE_WEBPANIM
|
||||
have_webpanim = Py_True;
|
||||
#else
|
||||
have_webpanim = Py_False;
|
||||
#endif
|
||||
Py_INCREF(have_webpanim);
|
||||
PyModule_AddObject(m, "HAVE_WEBPANIM", have_webpanim);
|
||||
}
|
||||
|
||||
void
|
||||
addTransparencyFlagToModule(PyObject *m) {
|
||||
PyObject *have_transparency = PyBool_FromLong(!WebPDecoderBuggyAlpha());
|
||||
if (PyModule_AddObject(m, "HAVE_TRANSPARENCY", have_transparency)) {
|
||||
Py_DECREF(have_transparency);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
setup_module(PyObject *m) {
|
||||
#ifdef HAVE_WEBPANIM
|
||||
/* Ready object types */
|
||||
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
|
||||
PyType_Ready(&WebPAnimEncoder_Type) < 0) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
PyObject *d = PyModule_GetDict(m);
|
||||
addMuxFlagToModule(m);
|
||||
addAnimFlagToModule(m);
|
||||
addTransparencyFlagToModule(m);
|
||||
|
||||
PyObject *d = PyModule_GetDict(m);
|
||||
PyObject *v = PyUnicode_FromString(WebPDecoderVersion_str());
|
||||
PyDict_SetItemString(d, "webpdecoder_version", v ? v : Py_None);
|
||||
Py_XDECREF(v);
|
||||
|
|
|
@ -201,7 +201,7 @@ DEPS = {
|
|||
},
|
||||
"build": [
|
||||
*cmds_cmake(
|
||||
"webp webpdemux webpmux",
|
||||
"webp webpmux webpdemux",
|
||||
"-DBUILD_SHARED_LIBS:BOOL=OFF",
|
||||
"-DWEBP_LINK_STATIC:BOOL=OFF",
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue
Block a user