mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-13 05:06:49 +03:00
Merge pull request #4547 from radarhere/webp
Added method argument to single frame WebP saving
This commit is contained in:
commit
713dd17c8f
|
@ -1,3 +1,5 @@
|
||||||
|
import io
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image, WebPImagePlugin
|
from PIL import Image, WebPImagePlugin
|
||||||
|
|
||||||
|
@ -54,15 +56,10 @@ class TestFileWebp:
|
||||||
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
|
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
|
||||||
assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
|
assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
|
||||||
|
|
||||||
def test_write_rgb(self, tmp_path):
|
def _roundtrip(self, tmp_path, mode, epsilon, args={}):
|
||||||
"""
|
|
||||||
Can we write a RGB mode file to webp without error.
|
|
||||||
Does it have the bits we expect?
|
|
||||||
"""
|
|
||||||
|
|
||||||
temp_file = str(tmp_path / "temp.webp")
|
temp_file = str(tmp_path / "temp.webp")
|
||||||
|
|
||||||
hopper(self.rgb_mode).save(temp_file)
|
hopper(mode).save(temp_file, **args)
|
||||||
with Image.open(temp_file) as image:
|
with Image.open(temp_file) as image:
|
||||||
assert image.mode == self.rgb_mode
|
assert image.mode == self.rgb_mode
|
||||||
assert image.size == (128, 128)
|
assert image.size == (128, 128)
|
||||||
|
@ -70,18 +67,38 @@ class TestFileWebp:
|
||||||
image.load()
|
image.load()
|
||||||
image.getdata()
|
image.getdata()
|
||||||
|
|
||||||
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
|
if mode == self.rgb_mode:
|
||||||
assert_image_similar_tofile(
|
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
|
||||||
image, "Tests/images/hopper_webp_write.ppm", 12.0
|
assert_image_similar_tofile(
|
||||||
)
|
image, "Tests/images/hopper_webp_write.ppm", 12.0
|
||||||
|
)
|
||||||
|
|
||||||
# This test asserts that the images are similar. If the average pixel
|
# This test asserts that the images are similar. If the average pixel
|
||||||
# difference between the two images is less than the epsilon value,
|
# difference between the two images is less than the epsilon value,
|
||||||
# then we're going to accept that it's a reasonable lossy version of
|
# then we're going to accept that it's a reasonable lossy version of
|
||||||
# the image. The old lena images for WebP are showing ~16 on
|
# the image.
|
||||||
# Ubuntu, the jpegs are showing ~18.
|
target = hopper(mode)
|
||||||
target = hopper(self.rgb_mode)
|
if mode != self.rgb_mode:
|
||||||
assert_image_similar(image, target, 12.0)
|
target = target.convert(self.rgb_mode)
|
||||||
|
assert_image_similar(image, target, epsilon)
|
||||||
|
|
||||||
|
def test_write_rgb(self, tmp_path):
|
||||||
|
"""
|
||||||
|
Can we write a RGB mode file to webp without error?
|
||||||
|
Does it have the bits we expect?
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._roundtrip(tmp_path, self.rgb_mode, 12.5)
|
||||||
|
|
||||||
|
def test_write_method(self, tmp_path):
|
||||||
|
self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6})
|
||||||
|
|
||||||
|
buffer_no_args = io.BytesIO()
|
||||||
|
hopper().save(buffer_no_args, format="WEBP")
|
||||||
|
|
||||||
|
buffer_method = io.BytesIO()
|
||||||
|
hopper().save(buffer_method, format="WEBP", method=6)
|
||||||
|
assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
|
||||||
|
|
||||||
def test_write_unsupported_mode_L(self, tmp_path):
|
def test_write_unsupported_mode_L(self, tmp_path):
|
||||||
"""
|
"""
|
||||||
|
@ -89,18 +106,7 @@ class TestFileWebp:
|
||||||
similar to the original file.
|
similar to the original file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
temp_file = str(tmp_path / "temp.webp")
|
self._roundtrip(tmp_path, "L", 10.0)
|
||||||
hopper("L").save(temp_file)
|
|
||||||
with Image.open(temp_file) as image:
|
|
||||||
assert image.mode == self.rgb_mode
|
|
||||||
assert image.size == (128, 128)
|
|
||||||
assert image.format == "WEBP"
|
|
||||||
|
|
||||||
image.load()
|
|
||||||
image.getdata()
|
|
||||||
target = hopper("L").convert(self.rgb_mode)
|
|
||||||
|
|
||||||
assert_image_similar(image, target, 10.0)
|
|
||||||
|
|
||||||
def test_write_unsupported_mode_P(self, tmp_path):
|
def test_write_unsupported_mode_P(self, tmp_path):
|
||||||
"""
|
"""
|
||||||
|
@ -108,18 +114,7 @@ class TestFileWebp:
|
||||||
similar to the original file.
|
similar to the original file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
temp_file = str(tmp_path / "temp.webp")
|
self._roundtrip(tmp_path, "P", 50.0)
|
||||||
hopper("P").save(temp_file)
|
|
||||||
with Image.open(temp_file) as image:
|
|
||||||
assert image.mode == self.rgb_mode
|
|
||||||
assert image.size == (128, 128)
|
|
||||||
assert image.format == "WEBP"
|
|
||||||
|
|
||||||
image.load()
|
|
||||||
image.getdata()
|
|
||||||
target = hopper("P").convert(self.rgb_mode)
|
|
||||||
|
|
||||||
assert_image_similar(image, target, 50.0)
|
|
||||||
|
|
||||||
def test_WebPEncode_with_invalid_args(self):
|
def test_WebPEncode_with_invalid_args(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -314,6 +314,7 @@ def _save(im, fp, filename):
|
||||||
if isinstance(exif, Image.Exif):
|
if isinstance(exif, Image.Exif):
|
||||||
exif = exif.tobytes()
|
exif = exif.tobytes()
|
||||||
xmp = im.encoderinfo.get("xmp", "")
|
xmp = im.encoderinfo.get("xmp", "")
|
||||||
|
method = im.encoderinfo.get("method", 0)
|
||||||
|
|
||||||
if im.mode not in _VALID_WEBP_LEGACY_MODES:
|
if im.mode not in _VALID_WEBP_LEGACY_MODES:
|
||||||
alpha = (
|
alpha = (
|
||||||
|
@ -331,6 +332,7 @@ def _save(im, fp, filename):
|
||||||
float(quality),
|
float(quality),
|
||||||
im.mode,
|
im.mode,
|
||||||
icc_profile,
|
icc_profile,
|
||||||
|
method,
|
||||||
exif,
|
exif,
|
||||||
xmp,
|
xmp,
|
||||||
)
|
)
|
||||||
|
|
97
src/_webp.c
97
src/_webp.c
|
@ -545,6 +545,7 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
|
||||||
int height;
|
int height;
|
||||||
int lossless;
|
int lossless;
|
||||||
float quality_factor;
|
float quality_factor;
|
||||||
|
int method;
|
||||||
uint8_t* rgb;
|
uint8_t* rgb;
|
||||||
uint8_t* icc_bytes;
|
uint8_t* icc_bytes;
|
||||||
uint8_t* exif_bytes;
|
uint8_t* exif_bytes;
|
||||||
|
@ -556,49 +557,75 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args)
|
||||||
Py_ssize_t exif_size;
|
Py_ssize_t exif_size;
|
||||||
Py_ssize_t xmp_size;
|
Py_ssize_t xmp_size;
|
||||||
size_t ret_size;
|
size_t ret_size;
|
||||||
|
int rgba_mode;
|
||||||
|
int channels;
|
||||||
|
int ok;
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
|
WebPConfig config;
|
||||||
|
WebPMemoryWriter writer;
|
||||||
|
WebPPicture pic;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "y#iiifss#s#s#",
|
if (!PyArg_ParseTuple(args, "y#iiifss#is#s#",
|
||||||
(char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode,
|
(char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode,
|
||||||
&icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) {
|
&icc_bytes, &icc_size, &method, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (strcmp(mode, "RGBA")==0){
|
|
||||||
if (size < width * height * 4){
|
rgba_mode = strcmp(mode, "RGBA") == 0;
|
||||||
Py_RETURN_NONE;
|
if (!rgba_mode && strcmp(mode, "RGB") != 0) {
|
||||||
}
|
|
||||||
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
|
|
||||||
if (lossless) {
|
|
||||||
ImagingSectionEnter(&cookie);
|
|
||||||
ret_size = WebPEncodeLosslessRGBA(rgb, width, height, 4 * width, &output);
|
|
||||||
ImagingSectionLeave(&cookie);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
ImagingSectionEnter(&cookie);
|
|
||||||
ret_size = WebPEncodeRGBA(rgb, width, height, 4 * width, quality_factor, &output);
|
|
||||||
ImagingSectionLeave(&cookie);
|
|
||||||
}
|
|
||||||
} else if (strcmp(mode, "RGB")==0){
|
|
||||||
if (size < width * height * 3){
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
|
|
||||||
if (lossless) {
|
|
||||||
ImagingSectionEnter(&cookie);
|
|
||||||
ret_size = WebPEncodeLosslessRGB(rgb, width, height, 3 * width, &output);
|
|
||||||
ImagingSectionLeave(&cookie);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
ImagingSectionEnter(&cookie);
|
|
||||||
ret_size = WebPEncodeRGB(rgb, width, height, 3 * width, quality_factor, &output);
|
|
||||||
ImagingSectionLeave(&cookie);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Py_RETURN_NONE;
|
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
|
#ifndef HAVE_WEBPMUX
|
||||||
if (ret_size > 0) {
|
if (ret_size > 0) {
|
||||||
PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size);
|
PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user