Skip to content

Commit

Permalink
Merge pull request #8213 from uploadcare/webp-require-anim
Browse files Browse the repository at this point in the history
Remove WebP support without anim, mux/demux, and with buggy alpha
  • Loading branch information
homm authored Aug 14, 2024
2 parents a5b415b + 4d544da commit 35a70e4
Show file tree
Hide file tree
Showing 20 changed files with 111 additions and 377 deletions.
30 changes: 16 additions & 14 deletions Tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@

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
Expand All @@ -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:
Expand All @@ -48,23 +47,26 @@ def test(name: str, function: Callable[[str], str | None]) -> 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")
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_file_gif.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 7 additions & 20 deletions Tests/test_file_webp.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ def setup_method(self) -> None:
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)
Expand Down Expand Up @@ -117,7 +115,6 @@ def test_write_method(self, tmp_path: Path) -> None:
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))
Expand All @@ -132,10 +129,9 @@ def test_save_all(self, tmp_path: Path) -> None:

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:
"""
Expand Down Expand Up @@ -165,23 +161,17 @@ def test_WebPEncode_with_invalid_args(self) -> None:
"""
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"
Expand All @@ -200,7 +190,6 @@ def test_file_pointer_could_be_reused(self) -> None:
"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:
Expand All @@ -209,7 +198,6 @@ def test_invalid_background(
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:
Expand All @@ -234,7 +222,6 @@ def test_background_from_gif(self, tmp_path: Path) -> None:
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
Expand Down
17 changes: 2 additions & 15 deletions Tests/test_file_webp_alpha.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@
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:
Expand Down Expand Up @@ -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()

Expand All @@ -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:
Expand Down
5 changes: 1 addition & 4 deletions Tests/test_file_webp_animated.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
skip_unless_feature,
)

pytestmark = [
skip_unless_feature("webp"),
skip_unless_feature("webp_anim"),
]
pytestmark = skip_unless_feature("webp")


def test_n_frames() -> None:
Expand Down
5 changes: 1 addition & 4 deletions Tests/test_file_webp_lossless.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@

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)
Expand Down
6 changes: 1 addition & 5 deletions Tests/test_file_webp_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@

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:
Expand Down Expand Up @@ -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>"
Expand Down
1 change: 0 additions & 1 deletion Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,6 @@ def test_exif_jpeg(self, tmp_path: Path) -> None:
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()
Expand Down
1 change: 0 additions & 1 deletion Tests/test_imagefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ def test_ico(self) -> None:
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:
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_imageops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions docs/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------

Expand Down
19 changes: 4 additions & 15 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand Down
12 changes: 4 additions & 8 deletions docs/installation/building-from-source.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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**,
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions docs/reference/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions docs/releasenotes/11.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
===========

Expand Down
Loading

0 comments on commit 35a70e4

Please sign in to comment.