From 1f6df76c42dab44e00b128b40fd6657185925aca Mon Sep 17 00:00:00 2001 From: Alireza Shafaei Date: Thu, 17 Nov 2022 13:58:07 -0800 Subject: [PATCH 01/12] updated webp with exact parameter. --- Tests/test_file_webp_alpha.py | 34 ++++++++++++++++++++++++++++ docs/handbook/image-file-formats.rst | 4 ++++ src/PIL/WebPImagePlugin.py | 2 ++ src/_webp.c | 5 +++- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index dc82fb742b2..07df7a06856 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -96,6 +96,40 @@ def test_write_rgba(tmp_path): else: assert_image_similar(image, pil_image, 1.0) +def test_write_rgba_keep_transparent(tmp_path): + """ + Can we write a RGBA mode file to WebP while preserving + the transparent RGB without error. + Does it have the bits we expect? + """ + + temp_output_file = str(tmp_path / "temp.webp") + + input_image = hopper("RGB") + # make a copy of the image + output_image = input_image.copy() + # make a single channel image with the same size as input_image + new_alpha = Image.new("L", input_image.size, 255) + # make the left half transparent + new_alpha.paste((0,), (0, 0, new_alpha.size[0]//2, new_alpha.size[1])) + # putalpha on output_image + output_image.putalpha(new_alpha) + + # now save with transparent area preserved. + output_image.save(temp_output_file, "WEBP", exact=True, lossless=True) + # even though it is lossless, if we don't put exact=True, the transparent + # area will be filled with black (or something more conducive to compression) + + with Image.open(temp_output_file) as image: + image.load() + + assert image.mode == "RGBA" + assert image.format == "WEBP" + image.load() + image = image.convert("RGB") + assert_image_similar(image, input_image, 1.0) + + def test_write_unsupported_mode_PA(tmp_path): """ diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 1e79db68bcb..ffc94914873 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1124,6 +1124,10 @@ 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. + **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/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 5eaeb10ccd5..c88f730a2a7 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 = im.encoderinfo.get("exact", False) if im.mode not in _VALID_WEBP_LEGACY_MODES: alpha = ( @@ -336,6 +337,7 @@ def _save(im, fp, filename): im.mode, icc_profile, method, + 1 if exact else 0, exif, xmp, ) diff --git a/src/_webp.c b/src/_webp.c index fd99116cb41..ec9425d3684 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,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { config.lossless = lossless; config.quality = quality_factor; config.method = method; + config.exact = exact; // Validate the config if (!WebPValidateConfig(&config)) { From 770560d8e4972d69193a816713be2bda5ff3ed94 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 22:06:14 +0000 Subject: [PATCH 02/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_webp_alpha.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index 07df7a06856..5a57d591add 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -96,6 +96,7 @@ def test_write_rgba(tmp_path): else: assert_image_similar(image, pil_image, 1.0) + def test_write_rgba_keep_transparent(tmp_path): """ Can we write a RGBA mode file to WebP while preserving @@ -111,7 +112,7 @@ def test_write_rgba_keep_transparent(tmp_path): # make a single channel image with the same size as input_image new_alpha = Image.new("L", input_image.size, 255) # make the left half transparent - new_alpha.paste((0,), (0, 0, new_alpha.size[0]//2, new_alpha.size[1])) + new_alpha.paste((0,), (0, 0, new_alpha.size[0] // 2, new_alpha.size[1])) # putalpha on output_image output_image.putalpha(new_alpha) @@ -130,7 +131,6 @@ def test_write_rgba_keep_transparent(tmp_path): assert_image_similar(image, input_image, 1.0) - def test_write_unsupported_mode_PA(tmp_path): """ Saving a palette-based file with transparency to WebP format From 3587f27780a5be7d02d0c781b39e13919c88d8d6 Mon Sep 17 00:00:00 2001 From: Alireza Shafaei Date: Fri, 18 Nov 2022 10:15:24 -0800 Subject: [PATCH 03/12] Added version check for WebP --- src/_webp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/_webp.c b/src/_webp.c index ec9425d3684..9231150aa55 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -635,7 +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)) { From fdf074b050f272e60e37bbd7f6ff52f3fc95a299 Mon Sep 17 00:00:00 2001 From: Alireza Shafaei Date: Fri, 18 Nov 2022 10:22:33 -0800 Subject: [PATCH 04/12] added a note to the docs for webp --- docs/handbook/image-file-formats.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index ffc94914873..9c2319b44e3 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1127,6 +1127,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **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 From 96a4d98abc265dabc1442a3e7b0cfdd5043f90b5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2022 17:07:43 +1100 Subject: [PATCH 05/12] Simplified code --- src/PIL/WebPImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index c88f730a2a7..e3c19db3dbf 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -318,7 +318,7 @@ def _save(im, fp, filename): exif = exif[6:] xmp = im.encoderinfo.get("xmp", "") method = im.encoderinfo.get("method", 4) - exact = im.encoderinfo.get("exact", False) + exact = 1 if im.encoderinfo.get("exact") else 0 if im.mode not in _VALID_WEBP_LEGACY_MODES: alpha = ( @@ -337,7 +337,7 @@ def _save(im, fp, filename): im.mode, icc_profile, method, - 1 if exact else 0, + exact, exif, xmp, ) From 7e5e843d5cd9f4a17361853138816773da28d8c9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2022 17:12:51 +1100 Subject: [PATCH 06/12] Note that the fill behaviour only affects libwebp >= 0.5 --- Tests/test_file_webp_alpha.py | 45 ++++++++++++++++------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index 5a57d591add..df6cffb1751 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -97,38 +97,33 @@ def test_write_rgba(tmp_path): assert_image_similar(image, pil_image, 1.0) -def test_write_rgba_keep_transparent(tmp_path): +def test_keep_rgb_values_when_transparent(tmp_path): """ - Can we write a RGBA mode file to WebP while preserving - the transparent RGB without error. - Does it have the bits we expect? + Saving transparent pixels should retain their original RGB values + when using the "exact" parameter. """ - temp_output_file = str(tmp_path / "temp.webp") + image = hopper("RGB") - input_image = hopper("RGB") - # make a copy of the image - output_image = input_image.copy() - # make a single channel image with the same size as input_image - new_alpha = Image.new("L", input_image.size, 255) - # make the left half transparent - new_alpha.paste((0,), (0, 0, new_alpha.size[0] // 2, new_alpha.size[1])) - # putalpha on output_image - output_image.putalpha(new_alpha) + # 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) - # now save with transparent area preserved. - output_image.save(temp_output_file, "WEBP", exact=True, lossless=True) - # even though it is lossless, if we don't put exact=True, the transparent - # area will be filled with black (or something more conducive to compression) + # 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_output_file) as image: - image.load() + with Image.open(temp_file) as reloaded: + assert reloaded.mode == "RGBA" + assert reloaded.format == "WEBP" - assert image.mode == "RGBA" - assert image.format == "WEBP" - image.load() - image = image.convert("RGB") - assert_image_similar(image, input_image, 1.0) + # 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_similar(reloaded.convert("RGB"), image, 1) def test_write_unsupported_mode_PA(tmp_path): From 3c7aa133eb62d75c0f96360ba54c7c4ed19d5c6f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Nov 2022 17:18:27 +1100 Subject: [PATCH 07/12] Assert that image is equal --- Tests/test_file_webp_alpha.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index df6cffb1751..5970fd2a371 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -123,7 +123,7 @@ def test_keep_rgb_values_when_transparent(tmp_path): # 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_similar(reloaded.convert("RGB"), image, 1) + assert_image_equal(reloaded.convert("RGB"), image) def test_write_unsupported_mode_PA(tmp_path): From 690446050a1963599dc926b06e0360aea0da3f67 Mon Sep 17 00:00:00 2001 From: Alireza Shafaei Date: Fri, 18 Nov 2022 23:26:08 -0800 Subject: [PATCH 08/12] minor fix in the comments --- src/_webp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_webp.c b/src/_webp.c index 9231150aa55..c2532a49687 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -636,7 +636,7 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { 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 + // the "exact" flag is only available in libwebp 0.5.0 and later config.exact = exact; #endif From d6f10d4876e4f3e1126d3a1d6eac1f75b9d675c2 Mon Sep 17 00:00:00 2001 From: Alireza Shafaei Date: Fri, 18 Nov 2022 23:51:06 -0800 Subject: [PATCH 09/12] doc update for libwebp --- docs/handbook/image-file-formats.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 9c2319b44e3..ac39625a27a 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1127,7 +1127,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **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. + Requires libwebp 0.5.0 or later. **icc_profile** The ICC Profile to include in the saved file. Only supported if From 55a75b9a696e968ab56a19a207959211cd33adf9 Mon Sep 17 00:00:00 2001 From: Alireza Shafaei Date: Sat, 19 Nov 2022 23:14:59 -0800 Subject: [PATCH 10/12] added RN for the new exact option. --- docs/releasenotes/9.4.0.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/releasenotes/9.4.0.rst b/docs/releasenotes/9.4.0.rst index 46c7e2f2285..3a9c3977fa2 100644 --- a/docs/releasenotes/9.4.0.rst +++ b/docs/releasenotes/9.4.0.rst @@ -37,6 +37,13 @@ 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. By setting +this option to ``True``, the encoder will keep the hidden RGB values. + Security ======== From 9c5b00ef7e03b921fd55cd6c432bac3a00562d46 Mon Sep 17 00:00:00 2001 From: Alireza Shafaei Date: Sat, 19 Nov 2022 23:19:08 -0800 Subject: [PATCH 11/12] RN trailing space fix --- docs/releasenotes/9.4.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/9.4.0.rst b/docs/releasenotes/9.4.0.rst index 3a9c3977fa2..ad79022fef3 100644 --- a/docs/releasenotes/9.4.0.rst +++ b/docs/releasenotes/9.4.0.rst @@ -41,7 +41,7 @@ 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. By setting +removes the hidden RGB values for better compression by default. By setting this option to ``True``, the encoder will keep the hidden RGB values. Security From 8f73a895ec29a8f824466812ef6dae9c90ad0397 Mon Sep 17 00:00:00 2001 From: Alireza Shafaei Date: Sun, 20 Nov 2022 16:00:24 -0800 Subject: [PATCH 12/12] Update docs/releasenotes/9.4.0.rst Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/releasenotes/9.4.0.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/releasenotes/9.4.0.rst b/docs/releasenotes/9.4.0.rst index ad79022fef3..0f47f5ad6d0 100644 --- a/docs/releasenotes/9.4.0.rst +++ b/docs/releasenotes/9.4.0.rst @@ -41,8 +41,9 @@ 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. By setting -this option to ``True``, the encoder will keep the hidden RGB values. +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 ========