Pillow/_webp.c
Bernardo Heynemann b4735f7829 Adding support for metadata in webp images.
Pillow now uses the webpmux library to envelop the webp images in RIFF. This allows for easy support of exif and icc_profile metadata.

Also included tests that verify compatibility with jpeg for exif and icc_profile metadata.

If the user does not have webp with webpmux enabled, pillow will fall back to the previous approach, meaning no exif or icc_profile metadata will be read or written to.
2013-07-04 18:04:07 -03:00

231 lines
6.3 KiB
C

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "py3.h"
#include <webp/encode.h>
#include <webp/decode.h>
#include <webp/types.h>
#ifdef HAVE_WEBPMUX
#include <webp/mux.h>
#endif
PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
{
int width;
int height;
float quality_factor;
uint8_t *rgb;
uint8_t *icc_bytes;
uint8_t *exif_bytes;
uint8_t *output;
char *mode;
Py_ssize_t size;
Py_ssize_t icc_size;
Py_ssize_t exif_size;
size_t ret_size;
if (!PyArg_ParseTuple(args, "s#nifss#s#",
(char**)&rgb, &size, &width, &height, &quality_factor, &mode,
&icc_bytes, &icc_size, &exif_bytes, &exif_size)) {
Py_RETURN_NONE;
}
if (strcmp(mode, "RGBA")==0){
if (size < width * height * 4){
Py_RETURN_NONE;
}
ret_size = WebPEncodeRGBA(rgb, width, height, 4* width, quality_factor, &output);
} else if (strcmp(mode, "RGB")==0){
if (size < width * height * 3){
Py_RETURN_NONE;
}
ret_size = WebPEncodeRGB(rgb, width, height, 3* width, quality_factor, &output);
} else {
Py_RETURN_NONE;
}
#ifdef HAVE_WEBPMUX
WebPData output_data = {0};
WebPData image = { output, ret_size };
int copy_data = 0; // value 1 indicates given data WILL be copied to the mux
// and value 0 indicates data will NOT be copied.
WebPMux* mux = WebPMuxNew();
WebPMuxSetImage(mux, &image, copy_data);
if (icc_size > 0) {
WebPData icc_profile = { icc_bytes, icc_size };
WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data);
}
if (exif_size > 0) {
WebPData exif = { exif_bytes, exif_size };
WebPMuxSetChunk(mux, "EXIF", &exif, copy_data);
}
WebPMuxAssemble(mux, &output_data);
WebPMuxDelete(mux);
output = (uint8_t*)output_data.bytes;
ret_size = output_data.size;
#endif
if (ret_size > 0) {
PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size);
#ifdef HAVE_WEBPMUX
WebPDataClear(&output_data);
#else
free(output);
#endif
return ret;
}
Py_RETURN_NONE;
}
PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args)
{
PyBytesObject *webp_string;
uint8_t *webp;
Py_ssize_t size;
PyObject *ret, *bytes, *pymode, *icc_profile = Py_None, *exif = Py_None;
WebPDecoderConfig config;
VP8StatusCode vp8_status_code = VP8_STATUS_OK;
char* mode = "RGB";
if (!PyArg_ParseTuple(args, "S", &webp_string)) {
Py_RETURN_NONE;
}
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";
}
#ifdef HAVE_WEBPMUX
int copy_data = 0;
WebPData data = { webp, size };
WebPMuxFrameInfo image;
WebPMux* mux = WebPMuxCreate(&data, copy_data);
WebPMuxGetFrame(mux, 1, &image);
webp = (uint8_t*)image.bitstream.bytes;
size = image.bitstream.size;
#endif
vp8_status_code = WebPDecode(webp, size, &config);
#ifdef HAVE_WEBPMUX
WebPData icc_profile_data = {0};
WebPMuxGetChunk(mux, "ICCP", &icc_profile_data);
if (icc_profile_data.size > 0) {
icc_profile = PyBytes_FromStringAndSize((const char*)icc_profile_data.bytes, icc_profile_data.size);
}
WebPData exif_data = {0};
WebPMuxGetChunk(mux, "EXIF", &exif_data);
if (exif_data.size > 0) {
exif = PyBytes_FromStringAndSize((const char*)exif_data.bytes, exif_data.size);
}
WebPMuxDelete(mux);
#endif
}
if (vp8_status_code != VP8_STATUS_OK) {
WebPFreeDecBuffer(&config.output);
Py_RETURN_NONE;
}
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);
}
#if PY_VERSION_HEX >= 0x03000000
pymode = PyUnicode_FromString(mode);
#else
pymode = PyString_FromString(mode);
#endif
ret = Py_BuildValue("SiiSSS", bytes, config.output.width,
config.output.height, pymode, icc_profile, exif);
WebPFreeDecBuffer(&config.output);
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(PyObject* self, PyObject* args){
return Py_BuildValue("i", WebPGetDecoderVersion());
}
/*
* 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.
*/
PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){
return Py_BuildValue("i", WebPGetDecoderVersion()==0x0103);
}
static PyMethodDef webpMethods[] =
{
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
{"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"},
{"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_VARARGS, "WebPVersion"},
{"WebPDecoderBuggyAlpha", WebPDecoderBuggyAlpha_wrapper, METH_VARARGS, "WebPDecoderBuggyAlpha"},
{NULL, NULL}
};
void addMuxFlagToModule(PyObject* m) {
#ifdef HAVE_WEBPMUX
PyModule_AddObject(m, "HAVE_WEBPMUX", Py_True);
#else
PyModule_AddObject(m, "HAVE_WEBPMUX", Py_False);
#endif
}
#if PY_VERSION_HEX >= 0x03000000
PyMODINIT_FUNC
PyInit__webp(void) {
PyObject* m;
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
"_webp", /* m_name */
NULL, /* m_doc */
-1, /* m_size */
webpMethods, /* m_methods */
};
m = PyModule_Create(&module_def);
addMuxFlagToModule(m);
return m;
}
#else
PyMODINIT_FUNC
init_webp(void)
{
PyObject* m = Py_InitModule("_webp", webpMethods);
addMuxFlagToModule(m);
}
#endif