- Conditonally compile animation support, only if the mux.h and demux.h headers meet the ABI version requirements

- Add WEBPMUX support back to WebPDecode_wrapper (to support older versions of libwebp that have mux support, but not animation)
- Add HAVE_WEBPANIM flag, and use it appropriately
- Update documentation / tests
This commit is contained in:
Jason Douglas 2017-09-27 19:04:24 -07:00
parent e534991409
commit c18d26b04b
8 changed files with 96 additions and 24 deletions

View File

@ -33,7 +33,7 @@ class WebPImageFile(ImageFile.ImageFile):
format_description = "WebP image"
def _open(self):
if not _webp.HAVE_WEBPMUX:
if not _webp.HAVE_WEBPANIM:
# Legacy mode
data, width, height, self.mode = _webp.WebPDecode(self.fp.read())
self.size = width, height
@ -80,18 +80,18 @@ class WebPImageFile(ImageFile.ImageFile):
@property
def n_frames(self):
if not _webp.HAVE_WEBPMUX:
if not _webp.HAVE_WEBPANIM:
return 1
return self._n_frames
@property
def is_animated(self):
if not _webp.HAVE_WEBPMUX:
if not _webp.HAVE_WEBPANIM:
return False
return self._n_frames > 1
def seek(self, frame):
if not _webp.HAVE_WEBPMUX:
if not _webp.HAVE_WEBPANIM:
return super(WebPImageFile, self).seek(frame)
# Perform some simple checks first
@ -141,7 +141,7 @@ class WebPImageFile(ImageFile.ImageFile):
self._get_next()
def load(self):
if _webp.HAVE_WEBPMUX:
if _webp.HAVE_WEBPANIM:
if self.__loaded != self.__logical_frame:
self._seek(self.__logical_frame)
@ -158,7 +158,7 @@ class WebPImageFile(ImageFile.ImageFile):
return super(WebPImageFile, self).load()
def tell(self):
if not _webp.HAVE_WEBPMUX:
if not _webp.HAVE_WEBPANIM:
return super(WebPImageFile, self).tell()
return self.__logical_frame
@ -301,7 +301,7 @@ def _save(im, fp, filename):
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
Image.register_save(WebPImageFile.format, _save)
if _webp.HAVE_WEBPMUX:
if _webp.HAVE_WEBPANIM:
Image.register_save_all(WebPImageFile.format, _save_all)
Image.register_extension(WebPImageFile.format, ".webp")
Image.register_mime(WebPImageFile.format, "image/webp")

View File

@ -43,6 +43,7 @@ def get_supported_codecs():
return [f for f in codecs if check_codec(f)]
features = {
"webp_anim": ("PIL._webp", 'HAVE_WEBPANIM'),
"webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'),
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
"raqm": ("PIL._imagingft", "HAVE_RAQM")

View File

@ -35,6 +35,11 @@ class TestFeatures(PillowTestCase):
self.assertEqual(features.check('webp_mux'),
_webp.HAVE_WEBPMUX)
@unittest.skipUnless(HAVE_WEBP, True)
def check_webp_anim(self):
self.assertEqual(features.check('webp_anim'),
_webp.HAVE_WEBPANIM)
def test_check_modules(self):
for feature in features.modules:
self.assertIn(features.check_module(feature), [True, False])

View File

@ -17,8 +17,8 @@ class TestFileWebpAnimation(PillowTestCase):
except ImportError:
self.skipTest('WebP support not installed')
if not _webp.HAVE_WEBPMUX:
self.skipTest("WebP not compiled with mux support, "
if not _webp.HAVE_WEBPANIM:
self.skipTest("WebP library does not contain animation support, "
"not testing animation")
def test_n_frames(self):

View File

@ -2,17 +2,22 @@ from helper import unittest, PillowTestCase
from PIL import Image
try:
from PIL import _webp
HAVE_WEBP = True
HAVE_WEBPMUX = _webp.HAVE_WEBPMUX
HAVE_WEBPANIM = _webp.HAVE_WEBPANIM
except ImportError:
HAVE_WEBP = False
class TestFileWebpMetadata(PillowTestCase):
def setUp(self):
try:
from PIL import _webp
except ImportError:
if not HAVE_WEBP:
self.skipTest('WebP support not installed')
return
if not _webp.HAVE_WEBPMUX:
if not HAVE_WEBPMUX:
self.skipTest('WebPMux support not installed')
def test_read_exif_metadata(self):
@ -107,6 +112,7 @@ class TestFileWebpMetadata(PillowTestCase):
self.assertFalse(webp_image._getexif())
@unittest.skipUnless(HAVE_WEBPANIM, 'WebP animation support not available')
def test_write_animated_metadata(self):
iccp_data = '<iccp_data>'.encode('utf-8')
exif_data = '<exif_data>'.encode('utf-8')

71
_webp.c
View File

@ -10,6 +10,19 @@
#include <webp/mux.h>
#include <webp/demux.h>
/*
* 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
* very early versions added had some significant differences, so we require
* later versions, before enabling animation support.
*/
#if WEBP_MUX_ABI_VERSION >= 0x0104 && WEBP_DEMUX_ABI_VERSION >= 0x0105
#define HAVE_WEBPANIM
#endif
#endif
#ifdef HAVE_WEBPANIM
/* -------------------------------------------------------------------- */
/* WebP Muxer Error Codes */
/* -------------------------------------------------------------------- */
@ -624,13 +637,12 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
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;
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";
@ -654,7 +666,41 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
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)
@ -675,14 +721,18 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
#else
pymode = PyString_FromString(mode);
#endif
ret = Py_BuildValue("SiiS", bytes, config.output.width,
config.output.height, pymode);
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;
@ -714,7 +764,7 @@ PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){
static PyMethodDef webpMethods[] =
{
#ifdef HAVE_WEBPMUX
#ifdef HAVE_WEBPANIM
{"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"},
{"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"},
#endif
@ -733,6 +783,14 @@ void addMuxFlagToModule(PyObject* m) {
#endif
}
void addAnimFlagToModule(PyObject* m) {
#ifdef HAVE_WEBPANIM
PyModule_AddObject(m, "HAVE_WEBPANIM", Py_True);
#else
PyModule_AddObject(m, "HAVE_WEBPANIM", Py_False);
#endif
}
void addTransparencyFlagToModule(PyObject* m) {
PyModule_AddObject(m, "HAVE_TRANSPARENCY",
PyBool_FromLong(!WebPDecoderBuggyAlpha()));
@ -740,9 +798,10 @@ void addTransparencyFlagToModule(PyObject* m) {
static int setup_module(PyObject* m) {
addMuxFlagToModule(m);
addAnimFlagToModule(m);
addTransparencyFlagToModule(m);
#ifdef HAVE_WEBPMUX
#ifdef HAVE_WEBPANIM
/* Ready object types */
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
PyType_Ready(&WebPAnimEncoder_Type) < 0)

View File

@ -683,8 +683,8 @@ Saving sequences
.. note::
Support for animated WebP files will only be enabled if the system webp
library was built with webpmux support. You can check webpmux support
at runtime by inspecting the `_webp.HAVE_WEBPMUX` module flag.
library is v0.5.0 or later. You can check webp animation support at
runtime by inspecting the `_webp.HAVE_WEBPANIM` module flag.
When calling :py:meth:`~PIL.Image.Image.save`, the following options
are available when the save_all argument is present and true.

View File

@ -178,8 +178,9 @@ if __name__ == "__main__":
("freetype2", "FREETYPE2"),
("littlecms2", "LITTLECMS2"),
("webp", "WEBP"),
("transp_webp", "Transparent WEBP"),
("transp_webp", "WEBP Transparency"),
("webp_mux", "WEBPMUX"),
("webp_anim", "WEBP Animation"),
("jpg", "JPEG"),
("jpg_2000", "OPENJPEG (JPEG2000)"),
("zlib", "ZLIB (PNG/ZIP)"),