Repository: python-pillow/Pillow

Issue:
RFC - allow setting of a RGB color on P images
The current implementation of putpixel requires that the color passed in for P mode images be an integer. 

It would be a useful and reasonable to accept an RGB(A) color, look it up in the palette (or allocate an entry if possible) and then pass that integer into the core putpixel method. 



Discussion:

Resolving patch:
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -1651,7 +1651,8 @@ 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.
+        multi-band images. In addition to this, RGB and RGBA tuples are
+        accepted for P 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`
@@ -1674,6 +1675,11 @@ def putpixel(self, xy, value):
 
         if self.pyaccess:
             return self.pyaccess.putpixel(xy, value)
+
+        if self.mode == "P" and \
+           isinstance(value, (list, tuple)) and len(value) in [3, 4]:
+            # RGB or RGBA value for a P image
+            value = self.palette.getcolor(value)
         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
--- a/src/PIL/PyAccess.py
+++ b/src/PIL/PyAccess.py
@@ -53,6 +53,8 @@ def __init__(self, img, readonly=False):
 
         # Keep pointer to im object to prevent dereferencing.
         self._im = img.im
+        if self._im.mode == "P":
+            self._palette = img.palette
 
         # Debugging is polluting test traces, only useful here
         # when hacking on PyAccess
@@ -80,6 +82,12 @@ def __setitem__(self, xy, color):
         if y < 0:
             y = self.ysize + y
         (x, y) = self.check_xy((x, y))
+
+        if self._im.mode == "P" and \
+           isinstance(color, (list, tuple)) and len(color) in [3, 4]:
+            # RGB or RGBA value for a P image
+            color = self._palette.getcolor(color)
+
         return self.set_pixel(x, y, color)
 
     def __getitem__(self, xy):


Test patch:
diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py
--- a/Tests/test_image_access.py
+++ b/Tests/test_image_access.py
@@ -175,6 +175,12 @@ 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)
+            im.putpixel((0, 0), color)
+            self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0))
+
 
 @unittest.skipIf(cffi is None, "No cffi")
 class TestCffiPutPixel(TestImagePutPixel):
@@ -294,6 +300,13 @@ def test_reference_counting(self):
                 # pixels can contain garbage if image is released
                 self.assertEqual(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)
+            access = PyAccess.new(im, False)
+            access.putpixel((0, 0), color)
+            self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0))
+
 
 class TestEmbeddable(unittest.TestCase):
     @unittest.skipIf(not sys.platform.startswith('win32') or