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:
Alexander Karpinsky 2024-08-14 15:27:23 +04:00 committed by GitHub
commit 35a70e4b97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 109 additions and 375 deletions

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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>"

View File

@ -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()

View File

@ -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:

View File

@ -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:

View File

@ -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
----------------

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
===========

View File

@ -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)"),
]

View File

@ -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")

View File

@ -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)"),

View File

@ -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);

View File

@ -201,7 +201,7 @@ DEPS = {
},
"build": [
*cmds_cmake(
"webp webpdemux webpmux",
"webp webpmux webpdemux",
"-DBUILD_SHARED_LIBS:BOOL=OFF",
"-DWEBP_LINK_STATIC:BOOL=OFF",
),