From a37593f004247ebf69d5582524da6dc5143cb023 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 14 Aug 2022 14:34:42 +1000 Subject: [PATCH] Allow RGB and RGBA values for PA image putpixel --- Tests/test_image_access.py | 22 ++++++++++++++-------- docs/reference/PixelAccess.rst | 2 +- src/PIL/Image.py | 11 ++++++++--- src/PIL/PyAccess.py | 11 ++++++++--- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 617274a576d..58e78475340 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -212,11 +212,14 @@ def test_signedness(self): self.check(mode, 2**15 + 1) self.check(mode, 2**16 - 1) - 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)) 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") @@ -337,12 +340,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): diff --git a/docs/reference/PixelAccess.rst b/docs/reference/PixelAccess.rst index d2e80fb8cb7..b234b7b4efb 100644 --- a/docs/reference/PixelAccess.rst +++ b/docs/reference/PixelAccess.rst @@ -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) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 4eb2dead655..f3f158db8bf 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -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` @@ -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): diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 2a48c53f723..9a2ec48fc60 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -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 @@ -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)