Repository: python-pillow/Pillow

Issue:
Saving still WebP image does not use method argument
### What did you do?

Add the `method` argument (as documented in [the handbook](https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html#webp)) with `0` as value to the `Image.save()` function with webp as format.

### What did you expect to happen?

Pillow/WebP encoding the image with that method.

### What actually happened?

The image always got encoded with the default method.

### What are your OS, Python and Pillow versions?

* OS: Debian testing (bullseye)
* Python: 3.7.4
* Pillow: 6.1.0 (from pypi)

<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.

The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->

Used image: https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Python-logo-notext.svg/512px-Python-logo-notext.svg.png (as `image.png`)

```python
from PIL import Image
from io import BytesIO

image = Image.open('image.png')

buffer = BytesIO()
image.save(buffer, format='webp', method=0)
print(buffer.getbuffer().nbytes) # 9212

buffer = BytesIO()
image.save(buffer, format='webp', method=6)
print(buffer.getbuffer().nbytes) # 9212
```

Both of these have the exact same size, which should not be the case given the different method.
Example of different sizes (encoding with `cwebp` tool):
<pre>
$ cwebp -m 0 image.png 2>&1 | grep Output
Output:    <strong>11396</strong> bytes Y-U-V-All-PSNR 52.79 45.11 45.20   48.64 dB
$ cwebp -m 6 image.png 2>&1 | grep Output
Output:    <strong>8408</strong> bytes Y-U-V-All-PSNR 53.12 44.73 44.82   48.43 dB
</pre>

Encoding as animation, however, uses the set method for encoding, as you can see from the different sizes:

```python
# (continuation from above code snippet)

buffer = BytesIO()
image.save(buffer, format='webp', save_all=True, append_images=[image], method=0)
print(buffer.getbuffer().nbytes) # 12310

buffer = BytesIO()
image.save(buffer, format='webp', save_all=True, append_images=[image], method=6)
print(buffer.getbuffer().nbytes) # 9220
```

This pull request might be related: #1263


Discussion:
Yes, I would conclude that #1263 is the solution to this.
Minor note: At the beginning of your description, you talk about saving with 0, instead of the default. The documentation states that 0 is the default.
I've created PR #4547 to resolve this.

Resolving patch:
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -325,6 +325,7 @@ def _save(im, fp, filename):
     if isinstance(exif, Image.Exif):
         exif = exif.tobytes()
     xmp = im.encoderinfo.get("xmp", "")
+    method = im.encoderinfo.get("method", 0)
 
     if im.mode not in _VALID_WEBP_LEGACY_MODES:
         alpha = (
@@ -342,6 +343,7 @@ def _save(im, fp, filename):
         float(quality),
         im.mode,
         icc_profile,
+        method,
         exif,
         xmp,
     )


Test patch:
diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py
--- a/Tests/test_file_webp.py
+++ b/Tests/test_file_webp.py
@@ -1,3 +1,5 @@
+import io
+
 import pytest
 from PIL import Image, WebPImagePlugin
 
@@ -54,15 +56,10 @@ def test_read_rgb(self):
             # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
             assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
 
-    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?
-        """
-
+    def _roundtrip(self, tmp_path, mode, epsilon, args={}):
         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:
             assert image.mode == self.rgb_mode
             assert image.size == (128, 128)
@@ -70,18 +67,38 @@ def test_write_rgb(self, tmp_path):
             image.load()
             image.getdata()
 
-            # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
-            assert_image_similar_tofile(
-                image, "Tests/images/hopper_webp_write.ppm", 12.0
-            )
+            if mode == self.rgb_mode:
+                # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
+                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
             # 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
-            # the image. The old lena images for WebP are showing ~16 on
-            # Ubuntu, the jpegs are showing ~18.
-            target = hopper(self.rgb_mode)
-            assert_image_similar(image, target, 12.0)
+            # the image.
+            target = hopper(mode)
+            if mode != self.rgb_mode:
+                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):
         """
@@ -89,18 +106,7 @@ def test_write_unsupported_mode_L(self, tmp_path):
         similar to the original file.
         """
 
-        temp_file = str(tmp_path / "temp.webp")
-        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)
+        self._roundtrip(tmp_path, "L", 10.0)
 
     def test_write_unsupported_mode_P(self, tmp_path):
         """
@@ -108,18 +114,7 @@ def test_write_unsupported_mode_P(self, tmp_path):
         similar to the original file.
         """
 
-        temp_file = str(tmp_path / "temp.webp")
-        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)
+        self._roundtrip(tmp_path, "P", 50.0)
 
     def test_WebPEncode_with_invalid_args(self):
         """