Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow RGB and RGBA values for PA image putpixel #6504

Merged
merged 2 commits into from Aug 31, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 13 additions & 7 deletions Tests/test_image_access.py
Expand Up @@ -215,11 +215,14 @@ def test_signedness(self, mode):
self.check(mode, 2**15 + 1)
self.check(mode, 2**16 - 1)

@pytest.mark.parametrize("mode", ("P", "PA"))
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
def test_p_putpixel_rgb_rgba(self, color):
im = Image.new("P", (1, 1), 0)
def test_p_putpixel_rgb_rgba(self, mode, color):
im = Image.new(mode, (1, 1))
im.putpixel((0, 0), color)
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)

alpha = color[3] if len(color) == 4 and mode == "PA" else 255
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)


@pytest.mark.skipif(cffi is None, reason="No CFFI")
Expand Down Expand Up @@ -340,12 +343,15 @@ def test_reference_counting(self):
# pixels can contain garbage if image is released
assert px[i, 0] == 0

def test_p_putpixel_rgb_rgba(self):
for color in [(255, 0, 0), (255, 0, 0, 255)]:
im = Image.new("P", (1, 1), 0)
@pytest.mark.parametrize("mode", ("P", "PA"))
def test_p_putpixel_rgb_rgba(self, mode):
for color in [(255, 0, 0), (255, 0, 0, 127)]:
im = Image.new(mode, (1, 1))
access = PyAccess.new(im, False)
access.putpixel((0, 0), color)
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)

alpha = color[3] if len(color) == 4 and mode == "PA" else 255
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)


class TestImagePutPixelError(AccessTest):
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/PixelAccess.rst
Expand Up @@ -73,7 +73,7 @@ Access using negative indexes is also possible.
Modifies the pixel at x,y. The color is given as a single
numerical value for single band images, and a tuple for
multi-band images. In addition to this, RGB and RGBA tuples
are accepted for P images.
are accepted for P and PA images.

:param xy: The pixel coordinate, given as (x, y).
:param color: The pixel value according to its mode. e.g. tuple (r, g, b) for RGB mode)
Expand Down
11 changes: 8 additions & 3 deletions src/PIL/Image.py
Expand Up @@ -1839,7 +1839,7 @@ def putpixel(self, xy, value):
Modifies the pixel at the given position. The color is given as
a single numerical value for single-band images, and a tuple for
multi-band images. In addition to this, RGB and RGBA tuples are
accepted for P images.
accepted for P and PA images.

Note that this method is relatively slow. For more extensive changes,
use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw`
Expand All @@ -1864,12 +1864,17 @@ def putpixel(self, xy, value):
return self.pyaccess.putpixel(xy, value)

if (
self.mode == "P"
self.mode in ("P", "PA")
and isinstance(value, (list, tuple))
and len(value) in [3, 4]
):
# RGB or RGBA value for a P image
# RGB or RGBA value for a P or PA image
if self.mode == "PA":
alpha = value[3] if len(value) == 4 else 255
value = value[:3]
value = self.palette.getcolor(value, self)
if self.mode == "PA":
value = (value, alpha)
return self.im.putpixel(xy, value)

def remap_palette(self, dest_map, source_palette=None):
Expand Down
11 changes: 8 additions & 3 deletions src/PIL/PyAccess.py
Expand Up @@ -58,7 +58,7 @@ def __init__(self, img, readonly=False):

# Keep pointer to im object to prevent dereferencing.
self._im = img.im
if self._im.mode == "P":
if self._im.mode in ("P", "PA"):
self._palette = img.palette

# Debugging is polluting test traces, only useful here
Expand Down Expand Up @@ -89,12 +89,17 @@ def __setitem__(self, xy, color):
(x, y) = self.check_xy((x, y))

if (
self._im.mode == "P"
self._im.mode in ("P", "PA")
and isinstance(color, (list, tuple))
and len(color) in [3, 4]
):
# RGB or RGBA value for a P image
# RGB or RGBA value for a P or PA image
if self._im.mode == "PA":
alpha = color[3] if len(color) == 4 else 255
color = color[:3]
color = self._palette.getcolor(color, self._img)
if self._im.mode == "PA":
color = (color, alpha)

return self.set_pixel(x, y, color)

Expand Down