Skip to content
Merged
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
10 changes: 9 additions & 1 deletion Tests/test_image_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ def test_trns_RGB(tmp_path: Path) -> None:
assert "transparency" not in im_p.info
im_p.save(f)

im.info["transparency"] = list(im.info["transparency"])
im_rgba = im.convert("RGBA")
assert "transparency" not in im_rgba.info

im = Image.new("RGB", (1, 1))
im.info["transparency"] = im.getpixel((0, 0))
im_p = im.convert("P", palette=Image.Palette.ADAPTIVE)
Expand Down Expand Up @@ -360,8 +364,12 @@ def test_matrix_identity() -> None:

# Act
# Convert with an identity matrix
converted_im = im.convert(mode="RGB", matrix=identity_matrix)
converted_im = im.convert("RGB", identity_matrix)

# Assert
# No change
assert_image_equal(converted_im, im)

# Test list
converted_im = im.convert("RGB", list(identity_matrix))
assert_image_equal(converted_im, im)
6 changes: 3 additions & 3 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ def verify(self) -> None:
def convert(
self,
mode: str | None = None,
matrix: tuple[float, ...] | None = None,
matrix: list[float] | tuple[float, ...] | None = None,
dither: Dither | None = None,
palette: Palette = Palette.WEB,
colors: int = 256,
Expand Down Expand Up @@ -1056,7 +1056,7 @@ def convert(

:param mode: The requested mode. See: :ref:`concept-modes`.
:param matrix: An optional conversion matrix. If given, this
should be 4- or 12-tuple containing floating point values.
should be 4- or 12-sequence containing floating point values.
:param dither: Dithering method, used when converting from
mode "RGB" to "P" or from "RGB" or "L" to "1".
Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG`
Expand Down Expand Up @@ -1094,7 +1094,7 @@ def convert(
transparency = new_im.info["transparency"]

def convert_transparency(
m: tuple[float, ...], v: tuple[int, int, int]
m: list[float] | tuple[float, ...], v: tuple[int, int, int]
) -> int:
value = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * 0.5
return max(0, min(255, int(value)))
Expand Down
45 changes: 32 additions & 13 deletions src/_imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -1059,8 +1059,15 @@ static PyObject *
_convert_matrix(ImagingObject *self, PyObject *args) {
char *mode_name;
float m[12];
if (!PyArg_ParseTuple(args, "s(ffff)", &mode_name, m + 0, m + 1, m + 2, m + 3)) {
PyErr_Clear();
PyObject *matrix;
if (!PyArg_ParseTuple(args, "sO", &mode_name, &matrix)) {
return NULL;
}
Py_ssize_t size = PySequence_Size(matrix);
Comment on lines +1062 to +1066

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review note to self: it's OK to not decref matrix, since "O" in ParseTuple doesn't create a strong reference.

if (size == -1) {
return NULL;
}
if (size == 12) {
if (!PyArg_ParseTuple(
args,
"s(ffffffffffff)",
Expand All @@ -1078,30 +1085,42 @@ _convert_matrix(ImagingObject *self, PyObject *args) {
m + 10,
m + 11
)) {
PyErr_SetString(PyExc_TypeError, "matrix must be tuple of length 4 or 12");
return NULL;
}
} else if (size == 4) {
if (!PyArg_ParseTuple(
args, "s(ffff)", &mode_name, m + 0, m + 1, m + 2, m + 3
)) {
return NULL;
}
} else {
PyErr_SetString(PyExc_TypeError, "matrix must be tuple of length 4 or 12");
return NULL;
}

const ModeID mode = findModeID(mode_name);

return PyImagingNew(ImagingConvertMatrix(self->image, mode, m));
}

static PyObject *
_convert_transparent(ImagingObject *self, PyObject *args) {
char *mode_name;
int r, g, b;
if (PyArg_ParseTuple(args, "s(iii)", &mode_name, &r, &g, &b)) {
const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b));
int r, g = 0, b = 0;
PyObject *transparency;
if (!PyArg_ParseTuple(args, "sO", &mode_name, &transparency)) {
return NULL;
}
PyErr_Clear();
if (PyArg_ParseTuple(args, "si", &mode_name, &r)) {
const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0));

if (PySequence_Check(transparency)) {
if (!PyArg_ParseTuple(args, "s(iii)", &mode_name, &r, &g, &b)) {
return NULL;
}
} else if (!PyArg_ParseTuple(args, "si", &mode_name, &r)) {
return NULL;
}
return NULL;
Comment on lines +1114 to -1104

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The same comment as above about confusing error messages stands here (the allowed values are either a single integer, or a 3-sequence of integers), but it's much harder to reach from userland, since you'd either need to call i.im.convert_transparent() by hand, or have explicitly set some image's .info["transparency"] to an invalid value. Might not be worth addressing.

>>> i.im.convert_transparent("RGBA", "hello")
Traceback (most recent call last):
  File "<python-input-22>", line 1, in <module>
    i.im.convert_transparent("RGBA", "hello")
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
TypeError: argument 2 must be 3-item tuple, not str
>>> i.im.convert_transparent("RGBA", 123)
<ImagingCore object at 0x1034fca50>
>>> i.im.convert_transparent("RGBA", [1,2,4])
<ImagingCore object at 0x1034ffeb0>


const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b));
}

static PyObject *
Expand Down
Loading