diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index dc82fb742b2..5970fd2a371 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -97,6 +97,35 @@ def test_write_rgba(tmp_path): assert_image_similar(image, pil_image, 1.0) +def test_keep_rgb_values_when_transparent(tmp_path): + """ + Saving transparent pixels should retain their original RGB values + when using the "exact" parameter. + """ + + image = hopper("RGB") + + # create a copy of the image + # with the left half transparent + half_transparent_image = image.copy() + new_alpha = Image.new("L", (128, 128), 255) + new_alpha.paste(0, (0, 0, 64, 128)) + half_transparent_image.putalpha(new_alpha) + + # save with transparent area preserved + temp_file = str(tmp_path / "temp.webp") + half_transparent_image.save(temp_file, exact=True, lossless=True) + + with Image.open(temp_file) as reloaded: + assert reloaded.mode == "RGBA" + assert reloaded.format == "WEBP" + + # even though it is lossless, if we don't use exact=True + # in libwebp >= 0.5, the transparent area will be filled with black + # (or something more conducive to compression) + assert_image_equal(reloaded.convert("RGB"), image) + + def test_write_unsupported_mode_PA(tmp_path): """ Saving a palette-based file with transparency to WebP format diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 1e79db68bcb..ac39625a27a 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1124,6 +1124,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **method** Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4. +**exact** + If true, preserve the transparent RGB values. Otherwise, discard + invisible RGB values for better compression. Defaults to false. + Requires libwebp 0.5.0 or later. + **icc_profile** The ICC Profile to include in the saved file. Only supported if the system WebP library was built with webpmux support. diff --git a/docs/releasenotes/9.4.0.rst b/docs/releasenotes/9.4.0.rst index 46c7e2f2285..0f47f5ad6d0 100644 --- a/docs/releasenotes/9.4.0.rst +++ b/docs/releasenotes/9.4.0.rst @@ -37,6 +37,14 @@ support a ``start`` argument. This tuple of horizontal and vertical offset will be used internally by :py:meth:`.ImageDraw.text` to more accurately place text at the ``xy`` coordinates. +Added the ``exact`` encoding option for WebP +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``exact`` encoding option for WebP is now supported. The WebP encoder +removes the hidden RGB values for better compression by default in libwebp 0.5 +or later. By setting this option to ``True``, the encoder will keep the hidden +RGB values. + Security ======== diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 5eaeb10ccd5..e3c19db3dbf 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -318,6 +318,7 @@ def _save(im, fp, filename): exif = exif[6:] xmp = im.encoderinfo.get("xmp", "") method = im.encoderinfo.get("method", 4) + exact = 1 if im.encoderinfo.get("exact") else 0 if im.mode not in _VALID_WEBP_LEGACY_MODES: alpha = ( @@ -336,6 +337,7 @@ def _save(im, fp, filename): im.mode, icc_profile, method, + exact, exif, xmp, ) diff --git a/src/_webp.c b/src/_webp.c index fd99116cb41..c2532a49687 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -576,6 +576,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { int lossless; float quality_factor; int method; + int exact; uint8_t *rgb; uint8_t *icc_bytes; uint8_t *exif_bytes; @@ -597,7 +598,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "y#iiifss#is#s#", + "y#iiifss#iis#s#", (char **)&rgb, &size, &width, @@ -608,6 +609,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { &icc_bytes, &icc_size, &method, + &exact, &exif_bytes, &exif_size, &xmp_bytes, @@ -633,6 +635,10 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { config.lossless = lossless; config.quality = quality_factor; config.method = method; +#if WEBP_ENCODER_ABI_VERSION >= 0x0209 + // the "exact" flag is only available in libwebp 0.5.0 and later + config.exact = exact; +#endif // Validate the config if (!WebPValidateConfig(&config)) {