1
#define PY_SSIZE_T_CLEAN
3
#include "libImaging/Imaging.h"
4
#include <webp/encode.h>
5
#include <webp/decode.h>
16
#if WEBP_MUX_ABI_VERSION < 0x0106 || WEBP_DEMUX_ABI_VERSION < 0x0107
17
#error libwebp 0.5.0 and above is required. Upgrade libwebp or build Pillow with --disable-webp flag
21
ImagingSectionEnter(ImagingSectionCookie *cookie) {
22
*cookie = (PyThreadState *)PyEval_SaveThread();
26
ImagingSectionLeave(ImagingSectionCookie *cookie) {
27
PyEval_RestoreThread((PyThreadState *)*cookie);
34
static const char *const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
36
"WEBP_MUX_INVALID_ARGUMENT",
38
"WEBP_MUX_MEMORY_ERROR",
39
"WEBP_MUX_NOT_ENOUGH_DATA"
43
HandleMuxError(WebPMuxError err, char *chunk) {
46
assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
49
if (err == WEBP_MUX_MEMORY_ERROR) {
50
return PyErr_NoMemory();
56
sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]);
58
message_len = sprintf(
59
message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]
62
if (message_len < 0) {
63
PyErr_SetString(PyExc_RuntimeError, "failed to construct error message");
69
case WEBP_MUX_NOT_FOUND:
70
case WEBP_MUX_INVALID_ARGUMENT:
71
PyErr_SetString(PyExc_ValueError, message);
74
case WEBP_MUX_BAD_DATA:
75
case WEBP_MUX_NOT_ENOUGH_DATA:
76
PyErr_SetString(PyExc_OSError, message);
80
PyErr_SetString(PyExc_RuntimeError, message);
92
PyObject_HEAD WebPAnimEncoder *enc;
94
} WebPAnimEncoderObject;
96
static PyTypeObject WebPAnimEncoder_Type;
100
PyObject_HEAD WebPAnimDecoder *dec;
104
} WebPAnimDecoderObject;
106
static PyTypeObject WebPAnimDecoder_Type;
110
_anim_encoder_new(PyObject *self, PyObject *args) {
118
WebPAnimEncoderOptions enc_options;
119
WebPAnimEncoderObject *encp = NULL;
120
WebPAnimEncoder *enc = NULL;
122
if (!PyArg_ParseTuple(
139
if (!WebPAnimEncoderOptionsInit(&enc_options)) {
140
PyErr_SetString(PyExc_RuntimeError, "failed to initialize encoder options");
143
enc_options.anim_params.bgcolor = bgcolor;
144
enc_options.anim_params.loop_count = loop_count;
145
enc_options.minimize_size = minimize_size;
146
enc_options.kmin = kmin;
147
enc_options.kmax = kmax;
148
enc_options.allow_mixed = allow_mixed;
149
enc_options.verbose = verbose;
152
if (width <= 0 || height <= 0) {
153
PyErr_SetString(PyExc_ValueError, "invalid canvas dimensions");
158
encp = PyObject_New(WebPAnimEncoderObject, &WebPAnimEncoder_Type);
160
if (WebPPictureInit(&(encp->frame))) {
161
enc = WebPAnimEncoderNew(width, height, &enc_options);
164
return (PyObject *)encp;
166
WebPPictureFree(&(encp->frame));
170
PyErr_SetString(PyExc_RuntimeError, "could not create encoder object");
175
_anim_encoder_dealloc(PyObject *self) {
176
WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
177
WebPPictureFree(&(encp->frame));
178
WebPAnimEncoderDelete(encp->enc);
182
_anim_encoder_add(PyObject *self, PyObject *args) {
190
float quality_factor;
191
float alpha_quality_factor;
194
WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
195
WebPAnimEncoder *enc = encp->enc;
196
WebPPicture *frame = &(encp->frame);
198
if (!PyArg_ParseTuple(
209
&alpha_quality_factor,
217
WebPAnimEncoderAdd(enc, NULL, timestamp, NULL);
222
if (!WebPConfigInit(&config)) {
223
PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!");
226
config.lossless = lossless;
227
config.quality = quality_factor;
228
config.alpha_quality = alpha_quality_factor;
229
config.method = method;
232
if (!WebPValidateConfig(&config)) {
233
PyErr_SetString(PyExc_ValueError, "invalid configuration");
238
frame->width = width;
239
frame->height = height;
241
if (strcmp(mode, "RGBA") == 0) {
242
WebPPictureImportRGBA(frame, rgb, 4 * width);
243
} else if (strcmp(mode, "RGBX") == 0) {
244
WebPPictureImportRGBX(frame, rgb, 4 * width);
246
WebPPictureImportRGB(frame, rgb, 3 * width);
250
if (!WebPAnimEncoderAdd(enc, frame, timestamp, &config)) {
251
PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc));
259
_anim_encoder_assemble(PyObject *self, PyObject *args) {
264
Py_ssize_t exif_size;
267
WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
268
WebPAnimEncoder *enc = encp->enc;
270
PyObject *ret = NULL;
272
if (!PyArg_ParseTuple(
286
WebPDataInit(&webp_data);
289
if (!WebPAnimEncoderAssemble(enc, &webp_data)) {
290
PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc));
295
if (icc_size > 0 || exif_size > 0 || xmp_size > 0) {
296
WebPMuxError err = WEBP_MUX_OK;
297
int i_icc_size = (int)icc_size;
298
int i_exif_size = (int)exif_size;
299
int i_xmp_size = (int)xmp_size;
300
WebPData icc_profile = {icc_bytes, i_icc_size};
301
WebPData exif = {exif_bytes, i_exif_size};
302
WebPData xmp = {xmp_bytes, i_xmp_size};
304
mux = WebPMuxCreate(&webp_data, 1);
306
PyErr_SetString(PyExc_RuntimeError, "could not re-mux to add metadata");
309
WebPDataClear(&webp_data);
312
if (i_icc_size > 0) {
313
err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, 1);
314
if (err != WEBP_MUX_OK) {
315
return HandleMuxError(err, "ICCP");
320
if (i_exif_size > 0) {
321
err = WebPMuxSetChunk(mux, "EXIF", &exif, 1);
322
if (err != WEBP_MUX_OK) {
323
return HandleMuxError(err, "EXIF");
328
if (i_xmp_size > 0) {
329
err = WebPMuxSetChunk(mux, "XMP ", &xmp, 1);
330
if (err != WEBP_MUX_OK) {
331
return HandleMuxError(err, "XMP");
335
err = WebPMuxAssemble(mux, &webp_data);
336
if (err != WEBP_MUX_OK) {
337
return HandleMuxError(err, NULL);
342
ret = PyBytes_FromStringAndSize((char *)webp_data.bytes, webp_data.size);
343
WebPDataClear(&webp_data);
355
_anim_decoder_new(PyObject *self, PyObject *args) {
356
PyBytesObject *webp_string;
361
WebPDecoderConfig config;
362
WebPAnimDecoderObject *decp = NULL;
363
WebPAnimDecoder *dec = NULL;
365
if (!PyArg_ParseTuple(args, "S", &webp_string)) {
368
PyBytes_AsStringAndSize((PyObject *)webp_string, (char **)&webp, &size);
369
webp_src.bytes = webp;
370
webp_src.size = size;
374
if (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) {
375
if (!config.input.has_alpha) {
381
decp = PyObject_New(WebPAnimDecoderObject, &WebPAnimDecoder_Type);
384
if (WebPDataCopy(&webp_src, &(decp->data))) {
385
dec = WebPAnimDecoderNew(&(decp->data), NULL);
387
if (WebPAnimDecoderGetInfo(dec, &(decp->info))) {
389
return (PyObject *)decp;
392
WebPDataClear(&(decp->data));
396
PyErr_SetString(PyExc_OSError, "could not create decoder object");
401
_anim_decoder_dealloc(PyObject *self) {
402
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
403
WebPDataClear(&(decp->data));
404
WebPAnimDecoderDelete(decp->dec);
408
_anim_decoder_get_info(PyObject *self) {
409
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
410
WebPAnimInfo *info = &(decp->info);
412
return Py_BuildValue(
424
_anim_decoder_get_chunk(PyObject *self, PyObject *args) {
426
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
427
const WebPDemuxer *demux;
428
WebPChunkIterator iter;
431
if (!PyArg_ParseTuple(args, "s", &mode)) {
435
demux = WebPAnimDecoderGetDemuxer(decp->dec);
436
if (!WebPDemuxGetChunk(demux, mode, 1, &iter)) {
440
ret = PyBytes_FromStringAndSize((const char *)iter.chunk.bytes, iter.chunk.size);
441
WebPDemuxReleaseChunkIterator(&iter);
447
_anim_decoder_get_next(PyObject *self) {
453
ImagingSectionCookie cookie;
454
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
456
ImagingSectionEnter(&cookie);
457
ok = WebPAnimDecoderGetNext(decp->dec, &buf, ×tamp);
458
ImagingSectionLeave(&cookie);
460
PyErr_SetString(PyExc_OSError, "failed to read next frame");
464
bytes = PyBytes_FromStringAndSize(
465
(char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height
468
ret = Py_BuildValue("Si", bytes, timestamp);
475
_anim_decoder_reset(PyObject *self) {
476
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
477
WebPAnimDecoderReset(decp->dec);
486
static struct PyMethodDef _anim_encoder_methods[] = {
487
{"add", (PyCFunction)_anim_encoder_add, METH_VARARGS, "add"},
488
{"assemble", (PyCFunction)_anim_encoder_assemble, METH_VARARGS, "assemble"},
493
static PyTypeObject WebPAnimEncoder_Type = {
494
PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimEncoder",
495
sizeof(WebPAnimEncoderObject),
498
(destructor)_anim_encoder_dealloc,
521
_anim_encoder_methods,
527
static struct PyMethodDef _anim_decoder_methods[] = {
528
{"get_info", (PyCFunction)_anim_decoder_get_info, METH_NOARGS, "get_info"},
529
{"get_chunk", (PyCFunction)_anim_decoder_get_chunk, METH_VARARGS, "get_chunk"},
530
{"get_next", (PyCFunction)_anim_decoder_get_next, METH_NOARGS, "get_next"},
531
{"reset", (PyCFunction)_anim_decoder_reset, METH_NOARGS, "reset"},
536
static PyTypeObject WebPAnimDecoder_Type = {
537
PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimDecoder",
538
sizeof(WebPAnimDecoderObject),
541
(destructor)_anim_decoder_dealloc,
564
_anim_decoder_methods,
574
WebPEncode_wrapper(PyObject *self, PyObject *args) {
578
float quality_factor;
579
float alpha_quality_factor;
590
Py_ssize_t exif_size;
596
ImagingSectionCookie cookie;
598
WebPMemoryWriter writer;
601
if (!PyArg_ParseTuple(
610
&alpha_quality_factor,
624
rgba_mode = strcmp(mode, "RGBA") == 0;
625
if (!rgba_mode && strcmp(mode, "RGB") != 0) {
629
channels = rgba_mode ? 4 : 3;
630
if (size < width * height * channels) {
635
if (!WebPConfigInit(&config)) {
636
PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!");
639
config.lossless = lossless;
640
config.quality = quality_factor;
641
config.alpha_quality = alpha_quality_factor;
642
config.method = method;
643
config.exact = exact;
646
if (!WebPValidateConfig(&config)) {
647
PyErr_SetString(PyExc_ValueError, "invalid configuration");
651
if (!WebPPictureInit(&pic)) {
652
PyErr_SetString(PyExc_ValueError, "could not initialise picture");
660
WebPPictureImportRGBA(&pic, rgb, channels * width);
662
WebPPictureImportRGB(&pic, rgb, channels * width);
665
WebPMemoryWriterInit(&writer);
666
pic.writer = WebPMemoryWrite;
667
pic.custom_ptr = &writer;
669
ImagingSectionEnter(&cookie);
670
ok = WebPEncode(&config, &pic);
671
ImagingSectionLeave(&cookie);
673
WebPPictureFree(&pic);
675
int error_code = (&pic)->error_code;
676
char message[50] = "";
677
if (error_code == VP8_ENC_ERROR_BAD_DIMENSION) {
680
": Image size exceeds WebP limit of %d pixels",
684
PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
688
ret_size = writer.size;
695
int i_icc_size = (int)icc_size;
696
int i_exif_size = (int)exif_size;
697
int i_xmp_size = (int)xmp_size;
698
WebPData output_data = {0};
699
WebPData image = {output, ret_size};
700
WebPData icc_profile = {icc_bytes, i_icc_size};
701
WebPData exif = {exif_bytes, i_exif_size};
702
WebPData xmp = {xmp_bytes, i_xmp_size};
709
WebPMux *mux = WebPMuxNew();
710
WebPMuxSetImage(mux, &image, copy_data);
714
fprintf(stderr, "icc size %d, %d \n", i_icc_size, i_icc_size > 0);
717
if (i_icc_size > 0) {
719
fprintf(stderr, "Adding ICC Profile\n");
721
err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data);
722
if (err != WEBP_MUX_OK) {
723
return HandleMuxError(err, "ICCP");
728
fprintf(stderr, "exif size %d \n", i_exif_size);
730
if (i_exif_size > 0) {
732
fprintf(stderr, "Adding Exif Data\n");
734
err = WebPMuxSetChunk(mux, "EXIF", &exif, copy_data);
735
if (err != WEBP_MUX_OK) {
736
return HandleMuxError(err, "EXIF");
741
fprintf(stderr, "xmp size %d \n", i_xmp_size);
743
if (i_xmp_size > 0) {
745
fprintf(stderr, "Adding XMP Data\n");
747
err = WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data);
748
if (err != WEBP_MUX_OK) {
749
return HandleMuxError(err, "XMP ");
753
WebPMuxAssemble(mux, &output_data);
757
ret_size = output_data.size;
760
PyBytes_FromStringAndSize((char *)output_data.bytes, ret_size);
761
WebPDataClear(&output_data);
770
WebPDecoderVersion_str(void) {
771
static char version[20];
772
int version_number = WebPGetDecoderVersion();
776
version_number >> 16,
777
(version_number >> 8) % 0x100,
778
version_number % 0x100
787
static PyMethodDef webpMethods[] = {
788
{"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"},
789
{"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"},
790
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
795
setup_module(PyObject *m) {
797
if (PyType_Ready(&WebPAnimDecoder_Type) < 0 ||
798
PyType_Ready(&WebPAnimEncoder_Type) < 0) {
802
PyObject *d = PyModule_GetDict(m);
803
PyObject *v = PyUnicode_FromString(WebPDecoderVersion_str());
804
PyDict_SetItemString(d, "webpdecoder_version", v ? v : Py_None);
814
static PyModuleDef module_def = {
815
PyModuleDef_HEAD_INIT,
822
m = PyModule_Create(&module_def);
823
if (setup_module(m) < 0) {
828
#ifdef Py_GIL_DISABLED
829
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);