mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-09 06:44:45 +03:00
webp: add decoder
- support incremental decoding - support Image.draft - support reading Exif/ICC profile data even if Mux API is not available
This commit is contained in:
parent
2ae69f1dc2
commit
76c8dda681
|
@ -1,19 +1,11 @@
|
||||||
from PIL import Image
|
from PIL import Image, ImageFile, _webp
|
||||||
from PIL import ImageFile
|
from PIL._binary import i8, i32le
|
||||||
from io import BytesIO
|
import os
|
||||||
from PIL import _webp
|
|
||||||
|
|
||||||
|
|
||||||
_VALID_WEBP_MODES = {
|
_VALID_WEBP_MODES = ("RGB", "RGBA")
|
||||||
"RGB": True,
|
|
||||||
"RGBA": True,
|
|
||||||
}
|
|
||||||
|
|
||||||
_VP8_MODES_BY_IDENTIFIER = {
|
_VP8_MODES_BY_IDENTIFIER = (b"VP8 ", b"VP8X", b"VP8L")
|
||||||
b"VP8 ": "RGB",
|
|
||||||
b"VP8X": "RGBA",
|
|
||||||
b"VP8L": "RGBA", # lossless
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
|
@ -23,6 +15,63 @@ def _accept(prefix):
|
||||||
|
|
||||||
return is_riff_file_format and is_webp_file and is_valid_vp8_mode
|
return is_riff_file_format and is_webp_file and is_valid_vp8_mode
|
||||||
|
|
||||||
|
# VP8X/VP8L/VP8 chunk parsing routines.
|
||||||
|
# Arguments:
|
||||||
|
# - chunk_size: RIFF chunk size
|
||||||
|
# - fp: file
|
||||||
|
# Return:
|
||||||
|
# - mode: RGB/RGBA
|
||||||
|
# - size: width/height
|
||||||
|
# - skip: how many bytes to skip to next chunk
|
||||||
|
|
||||||
|
def _parse_vp8x(chunk_size, fp):
|
||||||
|
if chunk_size < 10:
|
||||||
|
raise SyntaxError("bad VP8X chunk")
|
||||||
|
vp8x = fp.read(10)
|
||||||
|
if 10 != len(vp8x):
|
||||||
|
raise SyntaxError("bad VP8X chunk")
|
||||||
|
flags = i8(vp8x[0])
|
||||||
|
if (flags & 0b10000):
|
||||||
|
mode = 'RGBA'
|
||||||
|
else:
|
||||||
|
mode = 'RGB'
|
||||||
|
width = (i8(vp8x[4]) | (i8(vp8x[5]) << 8) | (i8(vp8x[6]) << 16)) + 1
|
||||||
|
height = (i8(vp8x[7]) | (i8(vp8x[8]) << 8) | (i8(vp8x[9]) << 16)) + 1
|
||||||
|
return mode, (width, height), chunk_size - 10
|
||||||
|
|
||||||
|
def _parse_vp8l(chunk_size, fp):
|
||||||
|
if chunk_size < 5:
|
||||||
|
raise SyntaxError("bad VP8L chunk")
|
||||||
|
vp8l = fp.read(5)
|
||||||
|
if 5 != len(vp8l):
|
||||||
|
raise SyntaxError("bad VP8L chunk")
|
||||||
|
vp8l = [i8(b) for b in vp8l]
|
||||||
|
# Check signature.
|
||||||
|
if 0x2f != vp8l[0] or 0 != (vp8l[4] >> 5):
|
||||||
|
raise SyntaxError("bad VP8L chunk")
|
||||||
|
if (vp8l[4] & 0b10000):
|
||||||
|
mode = 'RGBA'
|
||||||
|
else:
|
||||||
|
mode = 'RGB'
|
||||||
|
# 14 bits for each...
|
||||||
|
width = (vp8l[1] | ((vp8l[2] & 0b111111) << 8)) + 1
|
||||||
|
height = ((vp8l[2] >> 6) | (vp8l[3] << 2) | ((vp8l[4] & 0b1111) << 10)) + 1
|
||||||
|
return mode, (width, height), chunk_size - 5
|
||||||
|
|
||||||
|
def _parse_vp8(chunk_size, fp):
|
||||||
|
if chunk_size < 10:
|
||||||
|
raise SyntaxError("bad VP8 chunk")
|
||||||
|
vp8 = fp.read(10)
|
||||||
|
if 10 != len(vp8):
|
||||||
|
raise SyntaxError("bad VP8 chunk")
|
||||||
|
# Check signature.
|
||||||
|
if 0x9d != i8(vp8[3]) or 0x01 != i8(vp8[4]) or 0x2a != i8(vp8[5]):
|
||||||
|
raise SyntaxError("bad VP8 chunk")
|
||||||
|
mode = 'RGB'
|
||||||
|
width = (i8(vp8[6]) | (i8(vp8[7]) << 8)) & 0x3fff
|
||||||
|
height = (i8(vp8[8]) | (i8(vp8[9]) << 8)) & 0x3fff
|
||||||
|
return mode, (width, height), chunk_size - 10
|
||||||
|
|
||||||
|
|
||||||
class WebPImageFile(ImageFile.ImageFile):
|
class WebPImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
|
@ -30,17 +79,102 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
format_description = "WebP image"
|
format_description = "WebP image"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
data, width, height, self.mode, icc_profile, exif = \
|
|
||||||
_webp.WebPDecode(self.fp.read())
|
|
||||||
|
|
||||||
if icc_profile:
|
header = self.fp.read(12)
|
||||||
self.info["icc_profile"] = icc_profile
|
if 12 != len(header):
|
||||||
if exif:
|
raise SyntaxError("not a WebP file")
|
||||||
self.info["exif"] = exif
|
|
||||||
|
|
||||||
self.size = width, height
|
if b'RIFF' != header[0:4] or b'WEBP' != header[8:12]:
|
||||||
self.fp = BytesIO(data)
|
raise SyntaxError("not a WebP file")
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
|
|
||||||
|
mode = None
|
||||||
|
size = None
|
||||||
|
lossy = None
|
||||||
|
|
||||||
|
first_chunk = True
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
chunk_header = self.fp.read(8)
|
||||||
|
if 8 != len(chunk_header):
|
||||||
|
if first_chunk:
|
||||||
|
raise SyntaxError("not a WebP file")
|
||||||
|
break
|
||||||
|
|
||||||
|
chunk_fourcc = chunk_header[0:4]
|
||||||
|
chunk_size = i32le(chunk_header[4:8])
|
||||||
|
|
||||||
|
if first_chunk:
|
||||||
|
if chunk_fourcc not in _VP8_MODES_BY_IDENTIFIER:
|
||||||
|
raise SyntaxError("not a WebP file")
|
||||||
|
|
||||||
|
if b'VP8X' == chunk_fourcc:
|
||||||
|
if first_chunk:
|
||||||
|
mode, size, chunk_size = _parse_vp8x(chunk_size, self.fp)
|
||||||
|
|
||||||
|
elif b'VP8L' == chunk_fourcc:
|
||||||
|
if first_chunk:
|
||||||
|
mode, size, chunk_size = _parse_vp8l(chunk_size, self.fp)
|
||||||
|
lossy = False
|
||||||
|
|
||||||
|
elif b'VP8 ' == chunk_fourcc:
|
||||||
|
if first_chunk:
|
||||||
|
mode, size, chunk_size = _parse_vp8(chunk_size, self.fp)
|
||||||
|
lossy = True
|
||||||
|
|
||||||
|
elif b'ALPH' == chunk_fourcc:
|
||||||
|
mode = 'RGBA'
|
||||||
|
|
||||||
|
elif b'EXIF' == chunk_fourcc:
|
||||||
|
exif = self.fp.read(chunk_size)
|
||||||
|
if chunk_size != len(exif):
|
||||||
|
raise SyntaxError("bad EXIF chunk")
|
||||||
|
self.info["exif"] = exif
|
||||||
|
chunk_size = 0
|
||||||
|
|
||||||
|
elif b'ICCP' == chunk_fourcc:
|
||||||
|
icc_profile = self.fp.read(chunk_size)
|
||||||
|
if chunk_size != len(icc_profile):
|
||||||
|
raise SyntaxError("bad ICCP chunk")
|
||||||
|
self.info["icc_profile"] = icc_profile
|
||||||
|
chunk_size = 0
|
||||||
|
|
||||||
|
if chunk_size > 0:
|
||||||
|
# Skip to next chunk.
|
||||||
|
pos = self.fp.tell()
|
||||||
|
self.fp.seek(chunk_size, os.SEEK_CUR)
|
||||||
|
if self.fp.tell() != (pos + chunk_size):
|
||||||
|
raise SyntaxError("not a WebP file")
|
||||||
|
|
||||||
|
first_chunk = False
|
||||||
|
|
||||||
|
if None in (mode, size, lossy):
|
||||||
|
raise SyntaxError("not a WebP file")
|
||||||
|
|
||||||
|
self.mode = mode
|
||||||
|
self.size = size
|
||||||
|
self.tile = [('webp', (0, 0) + size, 0,
|
||||||
|
# Decoder params: rawmode, has_alpha, width, height.
|
||||||
|
(mode, 1 if 'RGBA' == mode else 0, size[0], size[1]))]
|
||||||
|
|
||||||
|
def draft(self, mode, size):
|
||||||
|
|
||||||
|
if 1 != len(self.tile):
|
||||||
|
return
|
||||||
|
|
||||||
|
d, e, o, a = self.tile[0]
|
||||||
|
|
||||||
|
if mode in _VALID_WEBP_MODES:
|
||||||
|
a = mode, a[1], a[2], a[3]
|
||||||
|
self.mode = mode
|
||||||
|
|
||||||
|
if size:
|
||||||
|
e = e[0], e[1], size[0], size[1]
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
self.tile = [(d, e, o, a)]
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
from PIL.JpegImagePlugin import _getexif
|
from PIL.JpegImagePlugin import _getexif
|
||||||
|
|
56
Tests/test_file_webp_draft.py
Normal file
56
Tests/test_file_webp_draft.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
from helper import unittest, PillowTestCase, fromstring, tostring
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
CODECS = dir(Image.core)
|
||||||
|
FILENAME = "Tests/images/hopper.webp"
|
||||||
|
DATA = tostring(Image.open(FILENAME).resize((512, 512)), "WEBP")
|
||||||
|
ALPHA_FILENAME = "Tests/images/transparent.webp"
|
||||||
|
ALPHA_DATA = tostring(Image.open(ALPHA_FILENAME).resize((512, 512)), "WEBP")
|
||||||
|
|
||||||
|
|
||||||
|
def draft(mode, size):
|
||||||
|
im = fromstring(DATA)
|
||||||
|
im.draft(mode, size)
|
||||||
|
return im
|
||||||
|
|
||||||
|
def alpha_draft(mode, size):
|
||||||
|
im = fromstring(ALPHA_DATA)
|
||||||
|
im.draft(mode, size)
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestImageDraft(PillowTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
if "webp_decoder" not in CODECS:
|
||||||
|
self.skipTest("WebP support not available")
|
||||||
|
|
||||||
|
def test_size(self):
|
||||||
|
# Upscaling/downscaling to any size is supported.
|
||||||
|
self.assertEqual(draft("RGB", (1024, 1024)).size, (1024, 1024))
|
||||||
|
self.assertEqual(draft("RGB", (512, 512)).size, (512, 512))
|
||||||
|
self.assertEqual(draft("RGB", (256, 256)).size, (256, 256))
|
||||||
|
self.assertEqual(draft("RGB", (128, 128)).size, (128, 128))
|
||||||
|
self.assertEqual(draft("RGB", (64, 64)).size, (64, 64))
|
||||||
|
self.assertEqual(draft("RGB", (32, 32)).size, (32, 32))
|
||||||
|
|
||||||
|
def test_mode(self):
|
||||||
|
# Decoder only support RGB/RGBA output.
|
||||||
|
self.assertEqual(draft("1", (512, 512)).mode, "RGB")
|
||||||
|
self.assertEqual(draft("L", (512, 512)).mode, "RGB")
|
||||||
|
self.assertEqual(draft("RGB", (512, 512)).mode, "RGB")
|
||||||
|
self.assertEqual(draft("RGBA", (512, 512)).mode, "RGBA")
|
||||||
|
self.assertEqual(draft("YCbCr", (512, 512)).mode, "RGB")
|
||||||
|
self.assertEqual(alpha_draft("1", (512, 512)).mode, "RGBA")
|
||||||
|
self.assertEqual(alpha_draft("L", (512, 512)).mode, "RGBA")
|
||||||
|
self.assertEqual(alpha_draft("RGB", (512, 512)).mode, "RGB")
|
||||||
|
self.assertEqual(alpha_draft("RGBA", (512, 512)).mode, "RGBA")
|
||||||
|
self.assertEqual(alpha_draft("YCbCr", (512, 512)).mode, "RGBA")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
|
@ -3343,6 +3343,7 @@ extern PyObject* PyImaging_BitDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_FliDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_FliDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_GifDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_GifDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args);
|
||||||
|
extern PyObject* PyImaging_WebPDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args);
|
||||||
extern PyObject* PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args);
|
extern PyObject* PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args);
|
||||||
|
@ -3411,6 +3412,9 @@ static PyMethodDef functions[] = {
|
||||||
{"gif_encoder", (PyCFunction)PyImaging_GifEncoderNew, 1},
|
{"gif_encoder", (PyCFunction)PyImaging_GifEncoderNew, 1},
|
||||||
{"hex_decoder", (PyCFunction)PyImaging_HexDecoderNew, 1},
|
{"hex_decoder", (PyCFunction)PyImaging_HexDecoderNew, 1},
|
||||||
{"hex_encoder", (PyCFunction)PyImaging_EpsEncoderNew, 1}, /* EPS=HEX! */
|
{"hex_encoder", (PyCFunction)PyImaging_EpsEncoderNew, 1}, /* EPS=HEX! */
|
||||||
|
#ifdef HAVE_LIBWEBP
|
||||||
|
{"webp_decoder", (PyCFunction)PyImaging_WebPDecoderNew, 1},
|
||||||
|
#endif
|
||||||
#ifdef HAVE_LIBJPEG
|
#ifdef HAVE_LIBJPEG
|
||||||
{"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1},
|
{"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1},
|
||||||
{"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1},
|
{"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1},
|
||||||
|
|
113
_webp.c
113
_webp.c
|
@ -9,7 +9,7 @@
|
||||||
#include <webp/mux.h>
|
#include <webp/mux.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
|
static PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
|
@ -132,113 +132,9 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
|
||||||
Py_RETURN_NONE;
|
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)) {
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
#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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#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,
|
|
||||||
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
|
// Return the decoder's version number, packed in hexadecimal using 8bits for
|
||||||
// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
||||||
PyObject* WebPDecoderVersion_wrapper(PyObject* self, PyObject* args){
|
static PyObject* WebPDecoderVersion_wrapper(PyObject* self, PyObject* args){
|
||||||
return Py_BuildValue("i", WebPGetDecoderVersion());
|
return Py_BuildValue("i", WebPGetDecoderVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,20 +142,19 @@ PyObject* WebPDecoderVersion_wrapper(PyObject* self, PyObject* args){
|
||||||
* The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well.
|
* 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.
|
* Files that are valid with 0.3 are reported as being invalid.
|
||||||
*/
|
*/
|
||||||
PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){
|
static PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){
|
||||||
return Py_BuildValue("i", WebPGetDecoderVersion()==0x0103);
|
return Py_BuildValue("i", WebPGetDecoderVersion()==0x0103);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyMethodDef webpMethods[] =
|
static PyMethodDef webpMethods[] =
|
||||||
{
|
{
|
||||||
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
|
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
|
||||||
{"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"},
|
|
||||||
{"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_VARARGS, "WebPVersion"},
|
{"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_VARARGS, "WebPVersion"},
|
||||||
{"WebPDecoderBuggyAlpha", WebPDecoderBuggyAlpha_wrapper, METH_VARARGS, "WebPDecoderBuggyAlpha"},
|
{"WebPDecoderBuggyAlpha", WebPDecoderBuggyAlpha_wrapper, METH_VARARGS, "WebPDecoderBuggyAlpha"},
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
void addMuxFlagToModule(PyObject* m) {
|
static void addMuxFlagToModule(PyObject* m) {
|
||||||
#ifdef HAVE_WEBPMUX
|
#ifdef HAVE_WEBPMUX
|
||||||
PyModule_AddObject(m, "HAVE_WEBPMUX", Py_True);
|
PyModule_AddObject(m, "HAVE_WEBPMUX", Py_True);
|
||||||
#else
|
#else
|
||||||
|
|
48
decode.c
48
decode.c
|
@ -722,6 +722,54 @@ PyImaging_ZipDecoderNew(PyObject* self, PyObject* args)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* WebP */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBWEBP
|
||||||
|
|
||||||
|
#include "WebP.h"
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
PyImaging_WebPDecoderNew(PyObject* self, PyObject* args)
|
||||||
|
{
|
||||||
|
ImagingDecoderObject* decoder;
|
||||||
|
WEBPSTATE* context;
|
||||||
|
|
||||||
|
char* mode;
|
||||||
|
char* rawmode; /* what we wan't from the decoder */
|
||||||
|
int has_alpha;
|
||||||
|
int width, height;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "ssiii",
|
||||||
|
&mode, &rawmode,
|
||||||
|
&has_alpha, &width, &height))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
decoder = PyImaging_DecoderNew(sizeof(WEBPSTATE));
|
||||||
|
if (decoder == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (get_unpacker(decoder, mode, rawmode) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
decoder->decode = ImagingWebPDecode;
|
||||||
|
decoder->cleanup = ImagingWebPDecodeCleanup;
|
||||||
|
|
||||||
|
context = (WEBPSTATE*)decoder->state.context;
|
||||||
|
|
||||||
|
strncpy(context->rawmode, rawmode, IMAGING_MODE_LENGTH - 1);
|
||||||
|
context->rawmode[IMAGING_MODE_LENGTH - 1] = '\0';
|
||||||
|
context->has_alpha = has_alpha;
|
||||||
|
context->width = width;
|
||||||
|
context->height = height;
|
||||||
|
context->decoder = NULL;
|
||||||
|
|
||||||
|
return (PyObject*) decoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* JPEG */
|
/* JPEG */
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
|
@ -419,6 +419,11 @@ extern int ImagingGifEncode(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
extern int ImagingHexDecode(Imaging im, ImagingCodecState state,
|
extern int ImagingHexDecode(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
|
#ifdef HAVE_LIBWEBP
|
||||||
|
extern int ImagingWebPDecode(Imaging im, ImagingCodecState state,
|
||||||
|
UINT8* buffer, int bytes);
|
||||||
|
extern int ImagingWebPDecodeCleanup(ImagingCodecState state);
|
||||||
|
#endif
|
||||||
#ifdef HAVE_LIBJPEG
|
#ifdef HAVE_LIBJPEG
|
||||||
extern int ImagingJpegDecode(Imaging im, ImagingCodecState state,
|
extern int ImagingJpegDecode(Imaging im, ImagingCodecState state,
|
||||||
UINT8* buffer, int bytes);
|
UINT8* buffer, int bytes);
|
||||||
|
|
31
libImaging/WebP.h
Normal file
31
libImaging/WebP.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library.
|
||||||
|
*
|
||||||
|
* declarations for the WebP codec interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <webp/encode.h>
|
||||||
|
#include <webp/decode.h>
|
||||||
|
#include <webp/types.h>
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Decoder */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
/* CONFIGURATION */
|
||||||
|
|
||||||
|
/* Decoder output mode (input to the shuffler). */
|
||||||
|
char rawmode[IMAGING_MODE_LENGTH];
|
||||||
|
|
||||||
|
/* Original image information. */
|
||||||
|
int has_alpha;
|
||||||
|
int width, height;
|
||||||
|
|
||||||
|
/* PRIVATE CONTEXT (set by decoder) */
|
||||||
|
|
||||||
|
WebPDecoderConfig config;
|
||||||
|
WebPIDecoder *decoder;
|
||||||
|
|
||||||
|
} WEBPSTATE;
|
||||||
|
|
145
libImaging/WebPDecode.c
Normal file
145
libImaging/WebPDecode.c
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* The Python Imaging Library.
|
||||||
|
*
|
||||||
|
* decoder for WebP image data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBWEBP
|
||||||
|
|
||||||
|
#include "Imaging.h"
|
||||||
|
#include "WebP.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
static int _vp8_status_to_codec_status(VP8StatusCode code)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case VP8_STATUS_OK:
|
||||||
|
return 0;
|
||||||
|
case VP8_STATUS_OUT_OF_MEMORY:
|
||||||
|
return IMAGING_CODEC_MEMORY;
|
||||||
|
case VP8_STATUS_BITSTREAM_ERROR:
|
||||||
|
case VP8_STATUS_NOT_ENOUGH_DATA:
|
||||||
|
case VP8_STATUS_SUSPENDED:
|
||||||
|
return IMAGING_CODEC_BROKEN;
|
||||||
|
case VP8_STATUS_INVALID_PARAM:
|
||||||
|
case VP8_STATUS_UNSUPPORTED_FEATURE:
|
||||||
|
return IMAGING_CODEC_CONFIG;
|
||||||
|
default:
|
||||||
|
case VP8_STATUS_USER_ABORT:
|
||||||
|
return IMAGING_CODEC_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Decoder */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
int ImagingWebPDecode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes)
|
||||||
|
{
|
||||||
|
WEBPSTATE *context = (WEBPSTATE *)state->context;
|
||||||
|
VP8StatusCode vp8_status_code;
|
||||||
|
|
||||||
|
if (!state->state)
|
||||||
|
{
|
||||||
|
WebPDecoderConfig *config = &context->config;
|
||||||
|
|
||||||
|
if (!WebPInitDecoderConfig(config))
|
||||||
|
{
|
||||||
|
/* Mismatched version. */
|
||||||
|
state->errcode = IMAGING_CODEC_CONFIG;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == strcmp("RGBA", context->rawmode))
|
||||||
|
config->output.colorspace = MODE_RGBA;
|
||||||
|
else
|
||||||
|
config->output.colorspace = MODE_RGB;
|
||||||
|
|
||||||
|
if (state->xsize != context->width || state->ysize != context->height)
|
||||||
|
{
|
||||||
|
config->options.scaled_width = state->xsize;
|
||||||
|
config->options.scaled_height = state->ysize;
|
||||||
|
config->options.use_scaling = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->decoder = WebPIDecode(NULL, 0, config);
|
||||||
|
if (NULL == context->decoder)
|
||||||
|
{
|
||||||
|
state->errcode = _vp8_status_to_codec_status(vp8_status_code);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->state = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Consume the buffer, decoding as much as possible. */
|
||||||
|
vp8_status_code = WebPIAppend(context->decoder, buf, bytes);
|
||||||
|
if (VP8_STATUS_NOT_ENOUGH_DATA != vp8_status_code &&
|
||||||
|
VP8_STATUS_SUSPENDED != vp8_status_code &&
|
||||||
|
VP8_STATUS_OK != vp8_status_code)
|
||||||
|
{
|
||||||
|
state->errcode = _vp8_status_to_codec_status(vp8_status_code);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VP8_STATUS_NOT_ENOUGH_DATA != vp8_status_code)
|
||||||
|
{
|
||||||
|
/* Check progress, and unpack available data. */
|
||||||
|
|
||||||
|
const uint8_t *rgba;
|
||||||
|
int last_y;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int stride;
|
||||||
|
|
||||||
|
rgba = WebPIDecGetRGB(context->decoder, &last_y, &width, &height, &stride);
|
||||||
|
if (NULL != rgba)
|
||||||
|
{
|
||||||
|
assert(width == state->xsize);
|
||||||
|
assert(height == state->ysize);
|
||||||
|
assert(last_y <= state->ysize);
|
||||||
|
|
||||||
|
for (; state->y < last_y; ++state->y)
|
||||||
|
{
|
||||||
|
assert(state->y < state->ysize);
|
||||||
|
state->shuffle((UINT8*) im->image[state->y + state->yoff] +
|
||||||
|
state->xoff * im->pixelsize,
|
||||||
|
rgba + state->y * stride, state->xsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VP8_STATUS_OK == vp8_status_code)
|
||||||
|
{
|
||||||
|
/* We're finished! */
|
||||||
|
state->errcode = IMAGING_CODEC_END;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return number of bytes consumed. */
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Cleanup */
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
int ImagingWebPDecodeCleanup(ImagingCodecState state)
|
||||||
|
{
|
||||||
|
WEBPSTATE* context = (WEBPSTATE*) state->context;
|
||||||
|
|
||||||
|
if (NULL != context->decoder)
|
||||||
|
{
|
||||||
|
WebPFreeDecBuffer(&context->config.output);
|
||||||
|
WebPIDelete(context->decoder);
|
||||||
|
context->decoder = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
20
setup.py
20
setup.py
|
@ -37,7 +37,7 @@ _LIB_IMAGING = (
|
||||||
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
|
"RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode",
|
||||||
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
|
"TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode",
|
||||||
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental",
|
"XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental",
|
||||||
"Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur")
|
"Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur", "WebPDecode")
|
||||||
|
|
||||||
|
|
||||||
def _add_directory(path, dir, where=None):
|
def _add_directory(path, dir, where=None):
|
||||||
|
@ -535,6 +535,14 @@ class pil_build_ext(build_ext):
|
||||||
libs.extend(["kernel32", "user32", "gdi32"])
|
libs.extend(["kernel32", "user32", "gdi32"])
|
||||||
if struct.unpack("h", "\0\1".encode('ascii'))[0] == 1:
|
if struct.unpack("h", "\0\1".encode('ascii'))[0] == 1:
|
||||||
defs.append(("WORDS_BIGENDIAN", None))
|
defs.append(("WORDS_BIGENDIAN", None))
|
||||||
|
if os.path.isfile("_webp.c") and feature.webp:
|
||||||
|
webp_defs = [("HAVE_LIBWEBP", None)]
|
||||||
|
webp_libs = [feature.webp]
|
||||||
|
if feature.webpmux:
|
||||||
|
webp_defs.append(("HAVE_WEBPMUX", None))
|
||||||
|
webp_libs.append(feature.webpmux)
|
||||||
|
libs.extend(webp_libs)
|
||||||
|
defs.extend(webp_defs)
|
||||||
|
|
||||||
exts = [(Extension(
|
exts = [(Extension(
|
||||||
"PIL._imaging", files, libraries=libs, define_macros=defs))]
|
"PIL._imaging", files, libraries=libs, define_macros=defs))]
|
||||||
|
@ -564,16 +572,8 @@ class pil_build_ext(build_ext):
|
||||||
libraries=["lcms2"] + extra))
|
libraries=["lcms2"] + extra))
|
||||||
|
|
||||||
if os.path.isfile("_webp.c") and feature.webp:
|
if os.path.isfile("_webp.c") and feature.webp:
|
||||||
libs = [feature.webp]
|
|
||||||
defs = []
|
|
||||||
|
|
||||||
if feature.webpmux:
|
|
||||||
defs.append(("HAVE_WEBPMUX", None))
|
|
||||||
libs.append(feature.webpmux)
|
|
||||||
libs.append(feature.webpmux.replace('pmux','pdemux'))
|
|
||||||
|
|
||||||
exts.append(Extension(
|
exts.append(Extension(
|
||||||
"PIL._webp", ["_webp.c"], libraries=libs, define_macros=defs))
|
"PIL._webp", ["_webp.c"], libraries=webp_libs, define_macros=webp_defs))
|
||||||
|
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
# locate Tcl/Tk frameworks
|
# locate Tcl/Tk frameworks
|
||||||
|
|
Loading…
Reference in New Issue
Block a user