Pillow/src/_webp.c
Axel Huebl f79379ed7c Imaging.h: confusion with system
The file `libImaging/Imaging.h` gets installed flat on
Ubuntus into the public Python includes.

When building a newer version of Pillow from source and
hinting the Python includes "too early" in includes, e.g.
in package managers, this can confuse the two files and
pick up the external file over the internal one. With
different versions, this mismatch can lead to build errors,
e.g. undefined macros.

The most robust way to avoid this is to pre-fix the internal
include accordingly, so that the relative path to the including
file has to match as well.
2020-09-17 18:06:45 +10:00

921 lines
27 KiB
C

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "libImaging/Imaging.h"
#include <webp/encode.h>
#include <webp/decode.h>
#include <webp/types.h>
#ifdef HAVE_WEBPMUX
#include <webp/mux.h>
#include <webp/demux.h>
/*
* 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.
*/
#if WEBP_MUX_ABI_VERSION >= 0x0104 && WEBP_DEMUX_ABI_VERSION >= 0x0105
#define HAVE_WEBPANIM
#endif
#endif
void ImagingSectionEnter(ImagingSectionCookie* cookie) {
*cookie = (PyThreadState *) PyEval_SaveThread();
}
void ImagingSectionLeave(ImagingSectionCookie* cookie) {
PyEval_RestoreThread((PyThreadState*) *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", "WEBP_MUX_BAD_DATA",
"WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
};
PyObject* HandleMuxError(WebPMuxError err, char* chunk) {
char message[100];
int message_len;
assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
// Check for a memory error first
if (err == WEBP_MUX_MEMORY_ERROR) {
return PyErr_NoMemory();
}
// Create the error message
if (chunk == NULL) {
message_len = sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]);
} else {
message_len = sprintf(message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]);
}
if (message_len < 0) {
PyErr_SetString(PyExc_RuntimeError, "failed to construct error message");
return NULL;
}
// Set the proper error type
switch (err) {
case WEBP_MUX_NOT_FOUND:
case WEBP_MUX_INVALID_ARGUMENT:
PyErr_SetString(PyExc_ValueError, message);
break;
case WEBP_MUX_BAD_DATA:
case WEBP_MUX_NOT_ENOUGH_DATA:
PyErr_SetString(PyExc_OSError, message);
break;
default:
PyErr_SetString(PyExc_RuntimeError, message);
break;
}
return NULL;
}
#endif
/* -------------------------------------------------------------------- */
/* WebP Animation Support */
/* -------------------------------------------------------------------- */
#ifdef HAVE_WEBPANIM
// Encoder type
typedef struct {
PyObject_HEAD
WebPAnimEncoder* enc;
WebPPicture frame;
} WebPAnimEncoderObject;
static PyTypeObject WebPAnimEncoder_Type;
// Decoder type
typedef struct {
PyObject_HEAD
WebPAnimDecoder* dec;
WebPAnimInfo info;
WebPData data;
char* mode;
} WebPAnimDecoderObject;
static PyTypeObject WebPAnimDecoder_Type;
// Encoder functions
PyObject* _anim_encoder_new(PyObject* self, PyObject* args)
{
int width, height;
uint32_t bgcolor;
int loop_count;
int minimize_size;
int kmin, kmax;
int allow_mixed;
int verbose;
WebPAnimEncoderOptions enc_options;
WebPAnimEncoderObject* encp = NULL;
WebPAnimEncoder* enc = NULL;
if (!PyArg_ParseTuple(args, "iiIiiiiii",
&width, &height, &bgcolor, &loop_count, &minimize_size,
&kmin, &kmax, &allow_mixed, &verbose)) {
return NULL;
}
// Setup and configure the encoder's options (these are animation-specific)
if (!WebPAnimEncoderOptionsInit(&enc_options)) {
PyErr_SetString(PyExc_RuntimeError, "failed to initialize encoder options");
return NULL;
}
enc_options.anim_params.bgcolor = bgcolor;
enc_options.anim_params.loop_count = loop_count;
enc_options.minimize_size = minimize_size;
enc_options.kmin = kmin;
enc_options.kmax = kmax;
enc_options.allow_mixed = allow_mixed;
enc_options.verbose = verbose;
// Validate canvas dimensions
if (width <= 0 || height <= 0) {
PyErr_SetString(PyExc_ValueError, "invalid canvas dimensions");
return NULL;
}
// Create a new animation encoder and picture frame
encp = PyObject_New(WebPAnimEncoderObject, &WebPAnimEncoder_Type);
if (encp) {
if (WebPPictureInit(&(encp->frame))) {
enc = WebPAnimEncoderNew(width, height, &enc_options);
if (enc) {
encp->enc = enc;
return (PyObject*) encp;
}
WebPPictureFree(&(encp->frame));
}
PyObject_Del(encp);
}
PyErr_SetString(PyExc_RuntimeError, "could not create encoder object");
return NULL;
}
PyObject* _anim_encoder_dealloc(PyObject* self)
{
WebPAnimEncoderObject* encp = (WebPAnimEncoderObject*)self;
WebPPictureFree(&(encp->frame));
WebPAnimEncoderDelete(encp->enc);
Py_RETURN_NONE;
}
PyObject* _anim_encoder_add(PyObject* self, PyObject* args)
{
uint8_t* rgb;
Py_ssize_t size;
int timestamp;
int width;
int height;
char* mode;
int lossless;
float quality_factor;
int method;
WebPConfig config;
WebPAnimEncoderObject* encp = (WebPAnimEncoderObject*)self;
WebPAnimEncoder* enc = encp->enc;
WebPPicture* frame = &(encp->frame);
if (!PyArg_ParseTuple(args, "z#iiisifi",
(char**)&rgb, &size, &timestamp, &width, &height, &mode,
&lossless, &quality_factor, &method)) {
return NULL;
}
// Check for NULL frame, which sets duration of final frame
if (!rgb) {
WebPAnimEncoderAdd(enc, NULL, timestamp, NULL);
Py_RETURN_NONE;
}
// Setup config for this frame
if (!WebPConfigInit(&config)) {
PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!");
return NULL;
}
config.lossless = lossless;
config.quality = quality_factor;
config.method = method;
// Validate the config
if (!WebPValidateConfig(&config)) {
PyErr_SetString(PyExc_ValueError, "invalid configuration");
return NULL;
}
// Populate the frame with raw bytes passed to us
frame->width = width;
frame->height = height;
frame->use_argb = 1; // Don't convert RGB pixels to YUV
if (strcmp(mode, "RGBA")==0) {
WebPPictureImportRGBA(frame, rgb, 4 * width);
} else if (strcmp(mode, "RGBX")==0) {
WebPPictureImportRGBX(frame, rgb, 4 * width);
} else {
WebPPictureImportRGB(frame, rgb, 3 * width);
}
// Add the frame to the encoder
if (!WebPAnimEncoderAdd(enc, frame, timestamp, &config)) {
PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc));
return NULL;
}
Py_RETURN_NONE;
}
PyObject* _anim_encoder_assemble(PyObject* self, PyObject* args)
{
uint8_t* icc_bytes;
uint8_t* exif_bytes;
uint8_t* xmp_bytes;
Py_ssize_t icc_size;
Py_ssize_t exif_size;
Py_ssize_t xmp_size;
WebPData webp_data;
WebPAnimEncoderObject* encp = (WebPAnimEncoderObject*)self;
WebPAnimEncoder* enc = encp->enc;
WebPMux* mux = NULL;
PyObject* ret = NULL;
if (!PyArg_ParseTuple(args, "s#s#s#",
&icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) {
return NULL;
}
// Init the output buffer
WebPDataInit(&webp_data);
// Assemble everything into the output buffer
if (!WebPAnimEncoderAssemble(enc, &webp_data)) {
PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc));
return NULL;
}
// Re-mux to add metadata as needed
if (icc_size > 0 || exif_size > 0 || xmp_size > 0) {
WebPMuxError err = WEBP_MUX_OK;
int i_icc_size = (int)icc_size;
int i_exif_size = (int)exif_size;
int i_xmp_size = (int)xmp_size;
WebPData icc_profile = { icc_bytes, i_icc_size };
WebPData exif = { exif_bytes, i_exif_size };
WebPData xmp = { xmp_bytes, i_xmp_size };
mux = WebPMuxCreate(&webp_data, 1);
if (mux == NULL) {
PyErr_SetString(PyExc_RuntimeError, "could not re-mux to add metadata");
return NULL;
}
WebPDataClear(&webp_data);
// Add ICCP chunk
if (i_icc_size > 0) {
err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, 1);
if (err != WEBP_MUX_OK) {
return HandleMuxError(err, "ICCP");
}
}
// Add EXIF chunk
if (i_exif_size > 0) {
err = WebPMuxSetChunk(mux, "EXIF", &exif, 1);
if (err != WEBP_MUX_OK) {
return HandleMuxError(err, "EXIF");
}
}
// Add XMP chunk
if (i_xmp_size > 0) {
err = WebPMuxSetChunk(mux, "XMP ", &xmp, 1);
if (err != WEBP_MUX_OK) {
return HandleMuxError(err, "XMP");
}
}
err = WebPMuxAssemble(mux, &webp_data);
if (err != WEBP_MUX_OK) {
return HandleMuxError(err, NULL);
}
}
// Convert to Python bytes
ret = PyBytes_FromStringAndSize((char*)webp_data.bytes, webp_data.size);
WebPDataClear(&webp_data);
// If we had to re-mux, we should free it now that we're done with it
if (mux != NULL) {
WebPMuxDelete(mux);
}
return ret;
}
// Decoder functions
PyObject* _anim_decoder_new(PyObject* self, PyObject* args)
{
PyBytesObject *webp_string;
const uint8_t *webp;
Py_ssize_t size;
WebPData webp_src;
char* mode;
WebPDecoderConfig config;
WebPAnimDecoderObject* decp = NULL;
WebPAnimDecoder* dec = NULL;
if (!PyArg_ParseTuple(args, "S", &webp_string)) {
return NULL;
}
PyBytes_AsStringAndSize((PyObject *)webp_string, (char**)&webp, &size);
webp_src.bytes = webp;
webp_src.size = size;
// Sniff the mode, since the decoder API doesn't tell us
mode = "RGBA";
if (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) {
if (!config.input.has_alpha) {
mode = "RGBX";
}
}
// Create the decoder (default mode is RGBA, if no options passed)
decp = PyObject_New(WebPAnimDecoderObject, &WebPAnimDecoder_Type);
if (decp) {
decp->mode = mode;
if (WebPDataCopy(&webp_src, &(decp->data))) {
dec = WebPAnimDecoderNew(&(decp->data), NULL);
if (dec) {
if (WebPAnimDecoderGetInfo(dec, &(decp->info))) {
decp->dec = dec;
return (PyObject*)decp;
}
}
}
PyObject_Del(decp);
}
PyErr_SetString(PyExc_RuntimeError, "could not create decoder object");
return NULL;
}
PyObject* _anim_decoder_dealloc(PyObject* self)
{
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self;
WebPDataClear(&(decp->data));
WebPAnimDecoderDelete(decp->dec);
Py_RETURN_NONE;
}
PyObject* _anim_decoder_get_info(PyObject* self)
{
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self;
WebPAnimInfo* info = &(decp->info);
return Py_BuildValue("IIIIIs",
info->canvas_width, info->canvas_height,
info->loop_count,
info->bgcolor,
info->frame_count,
decp->mode
);
}
PyObject* _anim_decoder_get_chunk(PyObject* self, PyObject* args)
{
char* mode;
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self;
const WebPDemuxer* demux;
WebPChunkIterator iter;
PyObject *ret;
if (!PyArg_ParseTuple(args, "s", &mode)) {
return NULL;
}
demux = WebPAnimDecoderGetDemuxer(decp->dec);
if (!WebPDemuxGetChunk(demux, mode, 1, &iter)) {
Py_RETURN_NONE;
}
ret = PyBytes_FromStringAndSize((const char*)iter.chunk.bytes, iter.chunk.size);
WebPDemuxReleaseChunkIterator(&iter);
return ret;
}
PyObject* _anim_decoder_get_next(PyObject* self)
{
uint8_t* buf;
int timestamp;
PyObject* bytes;
PyObject* ret;
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject*)self;
if (!WebPAnimDecoderGetNext(decp->dec, &buf, &timestamp)) {
PyErr_SetString(PyExc_OSError, "failed to read next frame");
return NULL;
}
bytes = PyBytes_FromStringAndSize((char *)buf,
decp->info.canvas_width * 4 * decp->info.canvas_height);
ret = Py_BuildValue("Si", bytes, timestamp);
Py_DECREF(bytes);
return ret;
}
PyObject* _anim_decoder_reset(PyObject* self)
{
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self;
WebPAnimDecoderReset(decp->dec);
Py_RETURN_NONE;
}
/* -------------------------------------------------------------------- */
/* Type Definitions */
/* -------------------------------------------------------------------- */
// WebPAnimEncoder methods
static struct PyMethodDef _anim_encoder_methods[] = {
{"add", (PyCFunction)_anim_encoder_add, METH_VARARGS, "add"},
{"assemble", (PyCFunction)_anim_encoder_assemble, METH_VARARGS, "assemble"},
{NULL, NULL} /* sentinel */
};
// WebPAnimDecoder type definition
static PyTypeObject WebPAnimEncoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"WebPAnimEncoder", /*tp_name */
sizeof(WebPAnimEncoderObject), /*tp_size */
0, /*tp_itemsize */
/* methods */
(destructor)_anim_encoder_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number */
0, /*tp_as_sequence */
0, /*tp_as_mapping */
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
_anim_encoder_methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
};
// WebPAnimDecoder methods
static struct PyMethodDef _anim_decoder_methods[] = {
{"get_info", (PyCFunction)_anim_decoder_get_info, METH_NOARGS, "get_info"},
{"get_chunk", (PyCFunction)_anim_decoder_get_chunk, METH_VARARGS, "get_chunk"},
{"get_next", (PyCFunction)_anim_decoder_get_next, METH_NOARGS, "get_next"},
{"reset", (PyCFunction)_anim_decoder_reset, METH_NOARGS, "reset"},
{NULL, NULL} /* sentinel */
};
// WebPAnimDecoder type definition
static PyTypeObject WebPAnimDecoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"WebPAnimDecoder", /*tp_name */
sizeof(WebPAnimDecoderObject), /*tp_size */
0, /*tp_itemsize */
/* methods */
(destructor)_anim_decoder_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number */
0, /*tp_as_sequence */
0, /*tp_as_mapping */
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
_anim_decoder_methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
};
#endif
/* -------------------------------------------------------------------- */
/* Legacy WebP Support */
/* -------------------------------------------------------------------- */
PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
{
int width;
int height;
int lossless;
float quality_factor;
int method;
uint8_t* rgb;
uint8_t* icc_bytes;
uint8_t* exif_bytes;
uint8_t* xmp_bytes;
uint8_t* output;
char* mode;
Py_ssize_t size;
Py_ssize_t icc_size;
Py_ssize_t exif_size;
Py_ssize_t xmp_size;
size_t ret_size;
int rgba_mode;
int channels;
int ok;
ImagingSectionCookie cookie;
WebPConfig config;
WebPMemoryWriter writer;
WebPPicture pic;
if (!PyArg_ParseTuple(args, "y#iiifss#is#s#",
(char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode,
&icc_bytes, &icc_size, &method, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) {
return NULL;
}
rgba_mode = strcmp(mode, "RGBA") == 0;
if (!rgba_mode && strcmp(mode, "RGB") != 0) {
Py_RETURN_NONE;
}
channels = rgba_mode ? 4 : 3;
if (size < width * height * channels) {
Py_RETURN_NONE;
}
// Setup config for this frame
if (!WebPConfigInit(&config)) {
PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!");
return NULL;
}
config.lossless = lossless;
config.quality = quality_factor;
config.method = method;
// Validate the config
if (!WebPValidateConfig(&config)) {
PyErr_SetString(PyExc_ValueError, "invalid configuration");
return NULL;
}
if (!WebPPictureInit(&pic)) {
PyErr_SetString(PyExc_ValueError, "could not initialise picture");
return NULL;
}
pic.width = width;
pic.height = height;
pic.use_argb = 1; // Don't convert RGB pixels to YUV
if (rgba_mode) {
WebPPictureImportRGBA(&pic, rgb, channels * width);
} else {
WebPPictureImportRGB(&pic, rgb, channels * width);
}
WebPMemoryWriterInit(&writer);
pic.writer = WebPMemoryWrite;
pic.custom_ptr = &writer;
ImagingSectionEnter(&cookie);
ok = WebPEncode(&config, &pic);
ImagingSectionLeave(&cookie);
WebPPictureFree(&pic);
if (!ok) {
PyErr_SetString(PyExc_ValueError, "encoding error");
return NULL;
}
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
data in the upper byte. (Not sure why, it shouldn't have been there)
*/
int i_icc_size = (int)icc_size;
int i_exif_size = (int)exif_size;
int i_xmp_size = (int)xmp_size;
WebPData output_data = {0};
WebPData image = { output, ret_size };
WebPData icc_profile = { icc_bytes, i_icc_size };
WebPData exif = { exif_bytes, i_exif_size };
WebPData xmp = { xmp_bytes, i_xmp_size };
WebPMuxError err;
int dbg = 0;
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 (dbg) {
/* was getting %ld icc_size == 0, icc_size>0 was true */
fprintf(stderr, "icc size %d, %d \n", i_icc_size, i_icc_size > 0);
}
if (i_icc_size > 0) {
if (dbg) {
fprintf(stderr, "Adding ICC Profile\n");
}
err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data);
if (err != WEBP_MUX_OK) {
return HandleMuxError(err, "ICCP");
}
}
if (dbg) {
fprintf(stderr, "exif size %d \n", i_exif_size);
}
if (i_exif_size > 0) {
if (dbg) {
fprintf(stderr, "Adding Exif Data\n");
}
err = WebPMuxSetChunk(mux, "EXIF", &exif, copy_data);
if (err != WEBP_MUX_OK) {
return HandleMuxError(err, "EXIF");
}
}
if (dbg) {
fprintf(stderr, "xmp size %d \n", i_xmp_size);
}
if (i_xmp_size > 0) {
if (dbg){
fprintf(stderr, "Adding XMP Data\n");
}
err = WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data);
if (err != WEBP_MUX_OK) {
return HandleMuxError(err, "XMP ");
}
}
WebPMuxAssemble(mux, &output_data);
WebPMuxDelete(mux);
free(output);
ret_size = output_data.size;
if (ret_size > 0) {
PyObject *ret = PyBytes_FromStringAndSize((char*)output_data.bytes, ret_size);
WebPDataClear(&output_data);
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)
{
static char version[20];
int version_number = WebPGetDecoderVersion();
sprintf(version, "%d.%d.%d", version_number >> 16, (version_number >> 8) % 0x100, version_number % 0x100);
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) {
#ifdef HAVE_WEBPMUX
PyModule_AddObject(m, "HAVE_WEBPMUX", Py_True);
#else
PyModule_AddObject(m, "HAVE_WEBPMUX", Py_False);
#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()));
}
static int setup_module(PyObject* m) {
PyObject* d = PyModule_GetDict(m);
addMuxFlagToModule(m);
addAnimFlagToModule(m);
addTransparencyFlagToModule(m);
PyDict_SetItemString(d, "webpdecoder_version", PyUnicode_FromString(WebPDecoderVersion_str()));
#ifdef HAVE_WEBPANIM
/* Ready object types */
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
PyType_Ready(&WebPAnimEncoder_Type) < 0) {
return -1;
}
#endif
return 0;
}
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);
if (setup_module(m) < 0) {
return NULL;
}
return m;
}