mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 18:26:17 +03:00
a3e3639a59
for more information, see https://pre-commit.ci
834 lines
25 KiB
C
834 lines
25 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>
|
|
#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.
|
|
*/
|
|
#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
|
|
ImagingSectionEnter(ImagingSectionCookie *cookie) {
|
|
*cookie = (PyThreadState *)PyEval_SaveThread();
|
|
}
|
|
|
|
void
|
|
ImagingSectionLeave(ImagingSectionCookie *cookie) {
|
|
PyEval_RestoreThread((PyThreadState *)*cookie);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* WebP Muxer Error Handling */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
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;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* WebP Animation Support */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
// 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;
|
|
}
|
|
|
|
void
|
|
_anim_encoder_dealloc(PyObject *self) {
|
|
WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
|
|
WebPPictureFree(&(encp->frame));
|
|
WebPAnimEncoderDelete(encp->enc);
|
|
}
|
|
|
|
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;
|
|
float alpha_quality_factor;
|
|
int method;
|
|
WebPConfig config;
|
|
WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
|
|
WebPAnimEncoder *enc = encp->enc;
|
|
WebPPicture *frame = &(encp->frame);
|
|
|
|
if (!PyArg_ParseTuple(
|
|
args,
|
|
"z#iiisiffi",
|
|
(char **)&rgb,
|
|
&size,
|
|
×tamp,
|
|
&width,
|
|
&height,
|
|
&mode,
|
|
&lossless,
|
|
&quality_factor,
|
|
&alpha_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.alpha_quality = alpha_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;
|
|
}
|
|
}
|
|
WebPDataClear(&(decp->data));
|
|
}
|
|
PyObject_Del(decp);
|
|
}
|
|
PyErr_SetString(PyExc_OSError, "could not create decoder object");
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
_anim_decoder_dealloc(PyObject *self) {
|
|
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
|
|
WebPDataClear(&(decp->data));
|
|
WebPAnimDecoderDelete(decp->dec);
|
|
}
|
|
|
|
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;
|
|
int ok;
|
|
PyObject *bytes;
|
|
PyObject *ret;
|
|
ImagingSectionCookie cookie;
|
|
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
|
|
|
|
ImagingSectionEnter(&cookie);
|
|
ok = WebPAnimDecoderGetNext(decp->dec, &buf, ×tamp);
|
|
ImagingSectionLeave(&cookie);
|
|
if (!ok) {
|
|
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 */
|
|
};
|
|
|
|
// WebPAnimEncoder type definition
|
|
static PyTypeObject WebPAnimEncoder_Type = {
|
|
PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimEncoder", /*tp_name */
|
|
sizeof(WebPAnimEncoderObject), /*tp_basicsize */
|
|
0, /*tp_itemsize */
|
|
/* methods */
|
|
(destructor)_anim_encoder_dealloc, /*tp_dealloc*/
|
|
0, /*tp_vectorcall_offset*/
|
|
0, /*tp_getattr*/
|
|
0, /*tp_setattr*/
|
|
0, /*tp_as_async*/
|
|
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_basicsize */
|
|
0, /*tp_itemsize */
|
|
/* methods */
|
|
(destructor)_anim_decoder_dealloc, /*tp_dealloc*/
|
|
0, /*tp_vectorcall_offset*/
|
|
0, /*tp_getattr*/
|
|
0, /*tp_setattr*/
|
|
0, /*tp_as_async*/
|
|
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*/
|
|
};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Legacy WebP Support */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
PyObject *
|
|
WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
|
int width;
|
|
int height;
|
|
int lossless;
|
|
float quality_factor;
|
|
float alpha_quality_factor;
|
|
int method;
|
|
int exact;
|
|
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#iiiffss#iis#s#",
|
|
(char **)&rgb,
|
|
&size,
|
|
&width,
|
|
&height,
|
|
&lossless,
|
|
&quality_factor,
|
|
&alpha_quality_factor,
|
|
&mode,
|
|
&icc_bytes,
|
|
&icc_size,
|
|
&method,
|
|
&exact,
|
|
&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.alpha_quality = alpha_quality_factor;
|
|
config.method = method;
|
|
config.exact = exact;
|
|
|
|
// 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) {
|
|
int error_code = (&pic)->error_code;
|
|
char message[50] = "";
|
|
if (error_code == VP8_ENC_ERROR_BAD_DIMENSION) {
|
|
sprintf(
|
|
message,
|
|
": Image size exceeds WebP limit of %d pixels",
|
|
WEBP_MAX_DIMENSION
|
|
);
|
|
}
|
|
PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
|
|
return NULL;
|
|
}
|
|
output = writer.mem;
|
|
ret_size = writer.size;
|
|
|
|
{
|
|
/* 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;
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Module Setup */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
static PyMethodDef webpMethods[] = {
|
|
{"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"},
|
|
{"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"},
|
|
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static int
|
|
setup_module(PyObject *m) {
|
|
/* Ready object types */
|
|
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
|
|
PyType_Ready(&WebPAnimEncoder_Type) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
PyObject *d = PyModule_GetDict(m);
|
|
PyObject *v = PyUnicode_FromString(WebPDecoderVersion_str());
|
|
PyDict_SetItemString(d, "webpdecoder_version", v ? v : Py_None);
|
|
Py_XDECREF(v);
|
|
|
|
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) {
|
|
Py_DECREF(m);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef Py_GIL_DISABLED
|
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
#endif
|
|
|
|
return m;
|
|
}
|