Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions Tests/test_file_webp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io
import re
import sys
import warnings
from typing import Any

Expand Down Expand Up @@ -158,21 +157,13 @@ def test_write_unsupported_mode_P(self, tmp_path: Path) -> None:

self._roundtrip(tmp_path, "P", 50.0)

@pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
def test_write_encoding_error_message(self, tmp_path: Path) -> None:
im = Image.new("RGB", (15000, 15000))
with pytest.raises(ValueError, match="encoding error 6"):
im.save(tmp_path / "temp.webp", method=0)

@pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
def test_write_encoding_error_bad_dimension(self, tmp_path: Path) -> None:
im = Image.new("L", (16384, 1))
with pytest.raises(ValueError) as e:
with pytest.raises(
ValueError,
match="encoding error 5: image size exceeds WebP limit of 16383 pixels",
):
im.save(tmp_path / "temp.webp")
assert (
str(e.value)
== "encoding error 5: Image size exceeds WebP limit of 16383 pixels"
)

def test_WebPEncode_with_invalid_args(self) -> None:
"""
Expand Down
6 changes: 6 additions & 0 deletions docs/releasenotes/13.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ escape sequences.
Other changes
=============

WebP encoding errors now include a description
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When libwebp fails to encode an image, the raised :py:exc:`ValueError` now
describes the error rather than only reporting a numeric code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#8322 was already doing this for one error, so could we include the word 'consistently' in here?


PNG palettes are no longer padded when saving with the ``bits`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
42 changes: 31 additions & 11 deletions src/_webp.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,36 @@ HandleMuxError(WebPMuxError err, char *chunk) {
return NULL;
}

/* -------------------------------------------------------------------- */
/* WebP Encoder Error Handling */
/* -------------------------------------------------------------------- */

#define WEBP_STR_HELPER(x) #x
#define WEBP_STR(x) WEBP_STR_HELPER(x)

static const char *const kEncoderErrorMessages[VP8_ENC_ERROR_LAST] = {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ok",
"out of memory allocating objects",
"out of memory re-allocating byte buffer",
"NULL parameter passed to function",
"configuration is invalid",
"image size exceeds WebP limit of " WEBP_STR(WEBP_MAX_DIMENSION) " pixels",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the story with WEBP_STR and WEBP_STR_HELPER? Why do they need to be defined separately, and why are there two of them?

@akx akx Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's just C preprocessor expansion rules and standard procedure:

a.c

#define WEBP_MAX_DIMENSION 123

// Doesn't work like we want
#define WEBP_STR_1(x) #x
WEBP_STR_1(WEBP_MAX_DIMENSION)

// Works like we want
#define WEBP_STR_HELPER(x) #x
#define WEBP_STR_2(x) WEBP_STR_HELPER(x)
WEBP_STR_2(WEBP_MAX_DIMENSION)

Preprocessing:

$ gcc -E a.c
# 1 "a.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 466 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "a.c" 2



"WEBP_MAX_DIMENSION"




"123"

This shows up in Pillow e.g. here (#4700):

Pillow/src/_imaging.c

Lines 4372 to 4374 in 4b03987

#define tostr1(a) #a
#define tostr(a) tostr1(a)
PyObject *v = PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION));

"partition #0 is bigger than 512K",
"partition is bigger than 16M",
"picture writer returned an I/O error",
"file would be bigger than 4G",
"encoding aborted by user"
};

static PyObject *
HandleEncoderError(WebPEncodingError error_code) {
const char *message = error_code > VP8_ENC_OK && error_code < VP8_ENC_ERROR_LAST
? kEncoderErrorMessages[error_code]
: "unknown error";
PyErr_Format(PyExc_ValueError, "encoding error %d: %s", error_code, message);
return NULL;
}

/* -------------------------------------------------------------------- */
/* Frame import */
/* -------------------------------------------------------------------- */
Expand Down Expand Up @@ -647,18 +677,8 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
ret_size = writer.size;

if (!ok) {
int error_code = (&pic)->error_code;
char message[50] = "";
if (error_code == VP8_ENC_ERROR_BAD_DIMENSION) {
sprintf(
message,
": Image size exceeds WebP limit of %d pixels",
WEBP_MAX_DIMENSION
);
}
PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
free(output);
return NULL;
return HandleEncoderError(pic.error_code);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does HandleEncoderError need to be a separate function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't, but it follows the form of HandleMuxError, above. Would you prefer it inlined?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out.

It could go either way, but my preference would be to inline it, since HandleMuxError is repeatedly called, and this isn't.

}

{
Expand Down
Loading