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
5 changes: 5 additions & 0 deletions Tests/test_image_putalpha.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import pytest

from PIL import Image


Expand Down Expand Up @@ -39,6 +41,9 @@ def test_promote() -> None:
assert im.mode == "RGBA"
assert im.getpixel((0, 0)) == (1, 2, 3, 4)

with pytest.raises(ValueError, match="image has wrong mode"):
im.im.setalpha()


def test_readonly() -> None:
im = Image.new("RGB", (1, 1), (1, 2, 3))
Expand Down
34 changes: 0 additions & 34 deletions Tests/test_lib_image.py

This file was deleted.

22 changes: 11 additions & 11 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -2044,19 +2044,19 @@ def putalpha(self, alpha: Image | int) -> None:

self._ensure_mutable()

if self.mode not in ("LA", "PA", "RGBA"):
# attempt to promote self to a matching alpha mode
if self.mode in ("RGB", "RGBX"):
# promote self to RGBA
self.im.setalpha()
self._mode = "RGBA"
elif self.mode not in ("LA", "PA", "RGBA"):
try:
# do things the hard way
mode = getmodebase(self.mode) + "A"
try:
self.im.setmode(mode)
except (AttributeError, ValueError) as e:
# do things the hard way
im = self.im.convert(mode)
if im.mode not in ("LA", "PA", "RGBA"):
msg = "alpha channel could not be added"
raise ValueError(msg) from e # sanity check
self.im = im
im = self.im.convert(mode)
if im.mode not in ("LA", "PA", "RGBA"):
msg = "alpha channel could not be added"
raise ValueError(msg) # sanity check
self.im = im
self._mode = self.im.mode
except KeyError as e:
msg = "illegal image mode"
Expand Down
45 changes: 8 additions & 37 deletions src/_imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -2052,45 +2052,16 @@ _reduce(ImagingObject *self, PyObject *args) {
return PyImagingNew(imOut);
}

static int
isRGB(const ModeID mode) {
return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA ||
mode == IMAGING_MODE_RGBX;
}

static PyObject *
im_setmode(ImagingObject *self, PyObject *args) {
im_setalpha(ImagingObject *self, PyObject *args) {
/* attempt to modify the mode of an image in place */

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.

This comment is a little stale now?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Is it? The method is still modifying the mode of an image in place. It's more specific now, is all.

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.

I suppose...

Should this function know how to do P-to-PA and L-to-LA too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The thought occurred to me. It is a bit different to RGB, since RGB already has the same pixelsize as RGBA.

Do you mind if that is a follow-up PR? I would rather tidy things up first.


Imaging im;

char *mode_name;
Py_ssize_t modelen;
if (!PyArg_ParseTuple(args, "s#:setmode", &mode_name, &modelen)) {
return NULL;
}

const ModeID mode = findModeID(mode_name);

im = self->image;

/* move all logic in here to the libImaging primitive */

if (im->mode == mode) {
; /* same mode; always succeeds */
} else if (isRGB(im->mode) && isRGB(mode)) {
/* color to color */
im->mode = mode;
im->bands = modelen;
if (mode == IMAGING_MODE_RGBA) {
(void)ImagingFillBand(im, 3, 255);
}
} else {
/* trying doing an in-place conversion */
if (!ImagingConvertInPlace(im, mode)) {
return NULL;
}
Imaging im = self->image;
if (im->mode != IMAGING_MODE_RGB && im->mode != IMAGING_MODE_RGBX) {
return ImagingError_ModeError();
}
im->mode = IMAGING_MODE_RGBA;
im->bands = 4;
(void)ImagingFillBand(im, 3, 255);

if (self->access) {
ImagingAccessDelete(im, self->access);
Expand Down Expand Up @@ -3739,7 +3710,7 @@ static struct PyMethodDef methods[] = {
{"split", (PyCFunction)_split, METH_NOARGS},
{"fillband", (PyCFunction)_fillband, METH_VARARGS},

{"setmode", (PyCFunction)im_setmode, METH_VARARGS},
{"setalpha", (PyCFunction)im_setalpha, METH_NOARGS},
Comment on lines -3742 to +3713

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.

What's the deprecation policy for methods on the core image objects in general (is it documented)?

GitHub's Code Search isn't making it easy to find whether there are external users of im.im.setmode.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There is no deprecation period.

#4532 (comment)

Pillow makes a commitment to stable public interfaces, which are defined at the Python layer. The C interfaces are explicitly internal, and no effort is made to keep them stable even between minor releases, and no support or warning is given when they change.

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.

That's an useful comment, thanks! Should maybe codify it into development documentation if it isn't already (for instance, I feel much better about touching the palette code even harder in #9829).

And I'll understand this as "the Python-facing interfaces defined in C extension module(s) in Pillow are also malleable" (since this is exactly that). 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've created #9879


{"getpalette", (PyCFunction)_getpalette, METH_VARARGS},
{"getpalettemode", (PyCFunction)_getpalettemode, METH_NOARGS},
Expand Down
24 changes: 0 additions & 24 deletions src/libImaging/Convert.c
Original file line number Diff line number Diff line change
Expand Up @@ -1734,27 +1734,3 @@ ImagingConvertTransparent(Imaging imIn, const ModeID mode, int r, int g, int b)

return imOut;
}

Imaging
ImagingConvertInPlace(Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
int y;

/* limited support for inplace conversion */
if (imIn->mode == IMAGING_MODE_L && mode == IMAGING_MODE_1) {
convert = l2bit;
} else if (imIn->mode == IMAGING_MODE_1 && mode == IMAGING_MODE_L) {
convert = bit2l;
} else {
return ImagingError_ModeError();
}

ImagingSectionEnter(&cookie);
for (y = 0; y < imIn->ysize; y++) {
(*convert)((UINT8 *)imIn->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
}
ImagingSectionLeave(&cookie);

return imIn;
}
2 changes: 0 additions & 2 deletions src/libImaging/Imaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,6 @@ ImagingCopy(Imaging im);
extern Imaging
ImagingConvert(Imaging im, ModeID mode, ImagingPalette palette, int dither);
extern Imaging
ImagingConvertInPlace(Imaging im, ModeID mode);
extern Imaging
ImagingConvertMatrix(Imaging im, ModeID mode, const float m[12]);
extern Imaging
ImagingConvertTransparent(Imaging im, ModeID mode, int r, int g, int b);
Expand Down
Loading