Top: mean WebP file size at each method setting, averaged across quality 60 through 90. The curve does almost all of its work between method 0 and method 2 and then flattens. Bottom: mean encode time, best of three runs, which does the opposite. It stays cheap through method 2 and then triples at method 3. The dotted line is where those two shapes cross, and it is the whole article. Charts generated from the measurement run described below.
Everybody sets quality and almost nobody touches method, which is the knob that decides how hard the encoder looks for a smaller file. It is worth touching. It is also worth not turning all the way up, because the last four steps of it cost seven times the time of the first two and buy a fifth of the bytes.
The WebP encoder has two knobs that matter and everyone only turns one of them. Quality is the famous one. Method, an integer from 0 to 6, is the other, and it does not change the target quality at all. It changes how long the encoder is willing to search for a cheaper way to hit it.
Pillow defaults it to 4. The cwebp command line defaults it to 4. A lot of build pipelines set it to 6 on the theory that more effort is free quality, and a lot of others leave it wherever it landed. So I measured what each step is actually buying.
Eight source images from this gallery, the same eight used in every measurement piece here. Each one encoded as WebP at quality 60, 70, 75, 80, 85 and 90, and at every method value from 0 to 6. That is 336 encodes.
Each encode is timed best of three runs, because encode time on a desktop is noisy and the minimum is the honest floor. Each result is scored with SSIM over 11x11 Gaussian windows, sigma 1.5, on the luma channel, against the original source rather than against another encode.
Nothing else changes between rows. Same image, same quality number, same library. Only the method integer moves.
| Method | Mean KB | vs method 6 | Mean encode time | Mean SSIM |
|---|---|---|---|---|
| 0 | 149.4 | +30.59% | 41.6 ms | 0.97231 |
| 1 | 144.9 | +26.62% | 55.2 ms | 0.97232 |
| 2 | 121.8 | +6.42% | 67.2 ms | 0.97290 |
| 3 | 118.0 | +3.16% | 187.8 ms | 0.97513 |
| 4 | 118.3 | +3.37% | 191.5 ms | 0.97525 |
| 5 | 117.1 | +2.37% | 203.2 ms | 0.97464 |
| 6 | 114.4 | 0.00% | 260.8 ms | 0.97441 |
Read the size column and the time column as two different shapes, because that is what they are.
Size falls off a cliff between method 1 and method 2, from 144.9 KB to 121.8, and then barely moves again. The entire distance from method 2 to method 6 is 7.4 KB, which is less than half of what the single step from 1 to 2 gave up.
Time does the opposite. It crawls from 41.6 ms to 67.2 through the first three settings and then jumps to 187.8 at method 3, which is where libwebp turns on the expensive search. From there to method 6 it climbs another 73 ms for 3.6 KB.
Take method 0 as the floor and method 6 as the ceiling. Across the whole run that is 35,839 bytes saved per image and 219.2 extra milliseconds spent.
Method 2 collects 28,319 of those bytes, which is 79.02 percent, for 25.6 extra milliseconds, which is 11.69 percent of the added time.
Put another way, in bytes saved per extra millisecond: method 2 returns 1,105 bytes per ms. Method 3 returns 220. Method 6 returns 164. The first useful setting is almost seven times more efficient than the last one.
An average can hide a split, so here is each image on its own at quality 80, and the share of the total method 0 to method 6 saving that method 2 already collected.
| Image | m0 KB | m2 KB | m6 KB | m2 share of the saving |
|---|---|---|---|---|
| 00006 | 92.0 | 73.4 | 68.4 | 78.6% |
| 00010 | 88.0 | 70.3 | 65.8 | 79.6% |
| 00012 | 313.4 | 251.3 | 235.5 | 79.6% |
| 00015 | 107.8 | 89.5 | 84.0 | 76.9% |
| 00022 | 287.9 | 231.5 | 213.7 | 76.1% |
| 00027 | 98.5 | 78.0 | 72.7 | 79.5% |
| 00029 | 35.6 | 27.9 | 25.9 | 79.5% |
| 00032 | 139.3 | 118.7 | 110.8 | 72.4% |
Eight images out of eight land between 72.4 and 79.6 percent. That is a tighter band than anything else measured on this site, and it is the reason I am comfortable saying this is a property of the encoder rather than a property of these files.
| Quality | m0 KB | m2 KB | m6 KB |
|---|---|---|---|
| 60 | 108.6 | 81.1 | 74.7 |
| 70 | 117.7 | 90.9 | 84.0 |
| 75 | 126.7 | 97.0 | 90.0 |
| 80 | 145.3 | 117.6 | 109.6 |
| 85 | 181.1 | 146.5 | 137.9 |
| 90 | 217.1 | 197.5 | 190.2 |
The gap narrows at quality 90, where the encoder is being asked to keep almost everything and has less room to be clever, but the shape never changes. Method 2 is most of the way down at every quality setting in the run.
Look at the SSIM column in the first table again. It is not flat.
Method 0 scores 0.97231 and method 4 scores 0.97525, which is the highest in the run. Then method 5 drops to 0.97464 and method 6 to 0.97441.
So the smallest file is not the best looking one. Method 6 gets under method 4 on bytes by giving back a sliver of structural agreement, which is exactly what a harder rate distortion search is supposed to do: it is allowed to trade a little quality for a lot of size, and at the top of the ladder that is the trade it makes.
The differences here are in the fourth decimal place and nobody is going to see them. It is worth knowing anyway, because it kills the idea that method is a free quality knob. It is a size and time knob, and above method 4 it is very slightly a quality knob in the wrong direction.
Method 4 for anything a person is waiting on. It is the library default for a reason, it has the best SSIM in the run, and it is 3.37 percent off the size floor. If an image is being encoded inside a request, method 6 is spending 69 extra milliseconds per image on 3.9 KB.
Method 2 for bulk. Re-encoding a gallery, a migration, a batch of a few thousand files: method 2 finishes in a quarter of the time and lands within 6.42 percent of the floor. On a thousand images that is 193 seconds saved against about 7 MB spent.
Method 6 for anything that gets served more than once. A file encoded once and downloaded a hundred thousand times should take the 261 milliseconds. The time is paid once and the bytes are paid every time.
Never method 0 or method 1. Method 1 costs 13.7 ms more than method 0 and saves 4.6 KB, then method 2 costs another 12 ms and saves 23.7 KB. There is no build anywhere that is so tight on time that method 1 is the right stopping point.
Eight images from one gallery, all AI generated art with a lot of smooth gradient and a similar palette. Photographs with heavy fine detail give the encoder more to search through and the method curve may sit differently on them.
Timings are from one desktop, single threaded through Pillow, best of three. The absolute milliseconds mean nothing on anybody else's machine. The ratios are the part worth carrying.
This is also Pillow's binding to libwebp rather than cwebp directly, and it does not touch the newer -sharp_yuv or segment options, which are their own knobs and their own article.
336 encodes, reported exactly as they came out, including the part where the smallest file was not the best scoring one.