mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 09:26:16 +03:00
- 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:
parent
e534991409
commit
c18d26b04b
|
@ -33,7 +33,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
format_description = "WebP image"
|
format_description = "WebP image"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
if not _webp.HAVE_WEBPMUX:
|
if not _webp.HAVE_WEBPANIM:
|
||||||
# Legacy mode
|
# Legacy mode
|
||||||
data, width, height, self.mode = _webp.WebPDecode(self.fp.read())
|
data, width, height, self.mode = _webp.WebPDecode(self.fp.read())
|
||||||
self.size = width, height
|
self.size = width, height
|
||||||
|
@ -80,18 +80,18 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def n_frames(self):
|
def n_frames(self):
|
||||||
if not _webp.HAVE_WEBPMUX:
|
if not _webp.HAVE_WEBPANIM:
|
||||||
return 1
|
return 1
|
||||||
return self._n_frames
|
return self._n_frames
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_animated(self):
|
def is_animated(self):
|
||||||
if not _webp.HAVE_WEBPMUX:
|
if not _webp.HAVE_WEBPANIM:
|
||||||
return False
|
return False
|
||||||
return self._n_frames > 1
|
return self._n_frames > 1
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if not _webp.HAVE_WEBPMUX:
|
if not _webp.HAVE_WEBPANIM:
|
||||||
return super(WebPImageFile, self).seek(frame)
|
return super(WebPImageFile, self).seek(frame)
|
||||||
|
|
||||||
# Perform some simple checks first
|
# Perform some simple checks first
|
||||||
|
@ -141,7 +141,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
self._get_next()
|
self._get_next()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if _webp.HAVE_WEBPMUX:
|
if _webp.HAVE_WEBPANIM:
|
||||||
if self.__loaded != self.__logical_frame:
|
if self.__loaded != self.__logical_frame:
|
||||||
self._seek(self.__logical_frame)
|
self._seek(self.__logical_frame)
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
return super(WebPImageFile, self).load()
|
return super(WebPImageFile, self).load()
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
if not _webp.HAVE_WEBPMUX:
|
if not _webp.HAVE_WEBPANIM:
|
||||||
return super(WebPImageFile, self).tell()
|
return super(WebPImageFile, self).tell()
|
||||||
|
|
||||||
return self.__logical_frame
|
return self.__logical_frame
|
||||||
|
@ -301,7 +301,7 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
|
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
|
||||||
Image.register_save(WebPImageFile.format, _save)
|
Image.register_save(WebPImageFile.format, _save)
|
||||||
if _webp.HAVE_WEBPMUX:
|
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_extension(WebPImageFile.format, ".webp")
|
||||||
Image.register_mime(WebPImageFile.format, "image/webp")
|
Image.register_mime(WebPImageFile.format, "image/webp")
|
||||||
|
|
|
@ -43,6 +43,7 @@ def get_supported_codecs():
|
||||||
return [f for f in codecs if check_codec(f)]
|
return [f for f in codecs if check_codec(f)]
|
||||||
|
|
||||||
features = {
|
features = {
|
||||||
|
"webp_anim": ("PIL._webp", 'HAVE_WEBPANIM'),
|
||||||
"webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'),
|
"webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'),
|
||||||
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
|
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
|
||||||
"raqm": ("PIL._imagingft", "HAVE_RAQM")
|
"raqm": ("PIL._imagingft", "HAVE_RAQM")
|
||||||
|
|
|
@ -35,6 +35,11 @@ class TestFeatures(PillowTestCase):
|
||||||
self.assertEqual(features.check('webp_mux'),
|
self.assertEqual(features.check('webp_mux'),
|
||||||
_webp.HAVE_WEBPMUX)
|
_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):
|
def test_check_modules(self):
|
||||||
for feature in features.modules:
|
for feature in features.modules:
|
||||||
self.assertIn(features.check_module(feature), [True, False])
|
self.assertIn(features.check_module(feature), [True, False])
|
||||||
|
|
|
@ -17,8 +17,8 @@ class TestFileWebpAnimation(PillowTestCase):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.skipTest('WebP support not installed')
|
self.skipTest('WebP support not installed')
|
||||||
|
|
||||||
if not _webp.HAVE_WEBPMUX:
|
if not _webp.HAVE_WEBPANIM:
|
||||||
self.skipTest("WebP not compiled with mux support, "
|
self.skipTest("WebP library does not contain animation support, "
|
||||||
"not testing animation")
|
"not testing animation")
|
||||||
|
|
||||||
def test_n_frames(self):
|
def test_n_frames(self):
|
||||||
|
|
|
@ -2,17 +2,22 @@ from helper import unittest, PillowTestCase
|
||||||
|
|
||||||
from PIL import Image
|
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):
|
class TestFileWebpMetadata(PillowTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
try:
|
if not HAVE_WEBP:
|
||||||
from PIL import _webp
|
|
||||||
except ImportError:
|
|
||||||
self.skipTest('WebP support not installed')
|
self.skipTest('WebP support not installed')
|
||||||
return
|
return
|
||||||
|
|
||||||
if not _webp.HAVE_WEBPMUX:
|
if not HAVE_WEBPMUX:
|
||||||
self.skipTest('WebPMux support not installed')
|
self.skipTest('WebPMux support not installed')
|
||||||
|
|
||||||
def test_read_exif_metadata(self):
|
def test_read_exif_metadata(self):
|
||||||
|
@ -107,6 +112,7 @@ class TestFileWebpMetadata(PillowTestCase):
|
||||||
|
|
||||||
self.assertFalse(webp_image._getexif())
|
self.assertFalse(webp_image._getexif())
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAVE_WEBPANIM, 'WebP animation support not available')
|
||||||
def test_write_animated_metadata(self):
|
def test_write_animated_metadata(self):
|
||||||
iccp_data = '<iccp_data>'.encode('utf-8')
|
iccp_data = '<iccp_data>'.encode('utf-8')
|
||||||
exif_data = '<exif_data>'.encode('utf-8')
|
exif_data = '<exif_data>'.encode('utf-8')
|
||||||
|
|
71
_webp.c
71
_webp.c
|
@ -10,6 +10,19 @@
|
||||||
#include <webp/mux.h>
|
#include <webp/mux.h>
|
||||||
#include <webp/demux.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 */
|
/* WebP Muxer Error Codes */
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
@ -624,13 +637,12 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
|
PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
PyBytesObject* webp_string;
|
PyBytesObject* webp_string;
|
||||||
const uint8_t* webp;
|
const uint8_t* webp;
|
||||||
Py_ssize_t size;
|
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;
|
WebPDecoderConfig config;
|
||||||
VP8StatusCode vp8_status_code = VP8_STATUS_OK;
|
VP8StatusCode vp8_status_code = VP8_STATUS_OK;
|
||||||
char* mode = "RGB";
|
char* mode = "RGB";
|
||||||
|
@ -654,7 +666,41 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
|
||||||
mode = "RGBA";
|
mode = "RGBA";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_WEBPMUX
|
||||||
vp8_status_code = WebPDecode(webp, size, &config);
|
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)
|
if (vp8_status_code != VP8_STATUS_OK)
|
||||||
|
@ -675,14 +721,18 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
|
||||||
#else
|
#else
|
||||||
pymode = PyString_FromString(mode);
|
pymode = PyString_FromString(mode);
|
||||||
#endif
|
#endif
|
||||||
ret = Py_BuildValue("SiiS", bytes, config.output.width,
|
ret = Py_BuildValue("SiiSSS", bytes, config.output.width,
|
||||||
config.output.height, pymode);
|
config.output.height, pymode,
|
||||||
|
NULL == icc_profile ? Py_None : icc_profile,
|
||||||
|
NULL == exif ? Py_None : exif);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
WebPFreeDecBuffer(&config.output);
|
WebPFreeDecBuffer(&config.output);
|
||||||
|
|
||||||
Py_XDECREF(bytes);
|
Py_XDECREF(bytes);
|
||||||
Py_XDECREF(pymode);
|
Py_XDECREF(pymode);
|
||||||
|
Py_XDECREF(icc_profile);
|
||||||
|
Py_XDECREF(exif);
|
||||||
|
|
||||||
if (Py_None == ret)
|
if (Py_None == ret)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
@ -714,7 +764,7 @@ PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){
|
||||||
|
|
||||||
static PyMethodDef webpMethods[] =
|
static PyMethodDef webpMethods[] =
|
||||||
{
|
{
|
||||||
#ifdef HAVE_WEBPMUX
|
#ifdef HAVE_WEBPANIM
|
||||||
{"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"},
|
{"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"},
|
||||||
{"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"},
|
{"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"},
|
||||||
#endif
|
#endif
|
||||||
|
@ -733,6 +783,14 @@ void addMuxFlagToModule(PyObject* m) {
|
||||||
#endif
|
#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) {
|
void addTransparencyFlagToModule(PyObject* m) {
|
||||||
PyModule_AddObject(m, "HAVE_TRANSPARENCY",
|
PyModule_AddObject(m, "HAVE_TRANSPARENCY",
|
||||||
PyBool_FromLong(!WebPDecoderBuggyAlpha()));
|
PyBool_FromLong(!WebPDecoderBuggyAlpha()));
|
||||||
|
@ -740,9 +798,10 @@ void addTransparencyFlagToModule(PyObject* m) {
|
||||||
|
|
||||||
static int setup_module(PyObject* m) {
|
static int setup_module(PyObject* m) {
|
||||||
addMuxFlagToModule(m);
|
addMuxFlagToModule(m);
|
||||||
|
addAnimFlagToModule(m);
|
||||||
addTransparencyFlagToModule(m);
|
addTransparencyFlagToModule(m);
|
||||||
|
|
||||||
#ifdef HAVE_WEBPMUX
|
#ifdef HAVE_WEBPANIM
|
||||||
/* Ready object types */
|
/* Ready object types */
|
||||||
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
|
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
|
||||||
PyType_Ready(&WebPAnimEncoder_Type) < 0)
|
PyType_Ready(&WebPAnimEncoder_Type) < 0)
|
||||||
|
|
|
@ -683,8 +683,8 @@ 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 was built with webpmux support. You can check webpmux support
|
library is v0.5.0 or later. You can check webp animation support at
|
||||||
at runtime by inspecting the `_webp.HAVE_WEBPMUX` module flag.
|
runtime by inspecting the `_webp.HAVE_WEBPANIM` module flag.
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -178,8 +178,9 @@ if __name__ == "__main__":
|
||||||
("freetype2", "FREETYPE2"),
|
("freetype2", "FREETYPE2"),
|
||||||
("littlecms2", "LITTLECMS2"),
|
("littlecms2", "LITTLECMS2"),
|
||||||
("webp", "WEBP"),
|
("webp", "WEBP"),
|
||||||
("transp_webp", "Transparent WEBP"),
|
("transp_webp", "WEBP Transparency"),
|
||||||
("webp_mux", "WEBPMUX"),
|
("webp_mux", "WEBPMUX"),
|
||||||
|
("webp_anim", "WEBP Animation"),
|
||||||
("jpg", "JPEG"),
|
("jpg", "JPEG"),
|
||||||
("jpg_2000", "OPENJPEG (JPEG2000)"),
|
("jpg_2000", "OPENJPEG (JPEG2000)"),
|
||||||
("zlib", "ZLIB (PNG/ZIP)"),
|
("zlib", "ZLIB (PNG/ZIP)"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user