Repository: python-pillow/Pillow

Issue:
ImagePalette: loading a translucent RGBA color into an RGB palette will break the palette
This is a minor thing:

In PIL/ImagePalette.py, we have
https://github.com/python-pillow/Pillow/blob/243402e78e2bf2b7af478c6d891816e372b5c3f9/src/PIL/ImagePalette.py#L118-L119

And then, this `color` is consumed in line 151, always assuming it is 3-bytes long (and having 4 would make colors from that point on "out of sync") . I was about to send a small PR raising value error in the unchecked case: 

 ```python
    if len(color) == 4 and color[3] < 255:
           raise ValueError(...)
```
But then, why not just truncate the alpha away, and always use the RGB value if an RGBA color is added to an RGB palette?
Can we do that?

(the sole purpose of this issue is to ask this question - I should submit fix + test afterwards)
ImagePalette: loading a translucent RGBA color into an RGB palette will break the palette
This is a minor thing:

In PIL/ImagePalette.py, we have
https://github.com/python-pillow/Pillow/blob/243402e78e2bf2b7af478c6d891816e372b5c3f9/src/PIL/ImagePalette.py#L118-L119

And then, this `color` is consumed in line 151, always assuming it is 3-bytes long (and having 4 would make colors from that point on "out of sync") . I was about to send a small PR raising value error in the unchecked case: 

 ```python
    if len(color) == 4 and color[3] < 255:
           raise ValueError(...)
```
But then, why not just truncate the alpha away, and always use the RGB value if an RGBA color is added to an RGB palette?
Can we do that?

(the sole purpose of this issue is to ask this question - I should submit fix + test afterwards)


Discussion:
For context, the code to handle opaque RGBA colors was added in #5552.

I think that raising a ValueError is the better solution. If Pillow just accepted the input, this could surprise the user later when they discover that the RGBA color isn't really there.
For context, the code to handle opaque RGBA colors was added in #5552.

I think that raising a ValueError is the better solution. If Pillow just accepted the input, this could surprise the user later when they discover that the RGBA color isn't really there.

Resolving patch:
diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py
--- a/src/PIL/ImagePalette.py
+++ b/src/PIL/ImagePalette.py
@@ -115,7 +115,11 @@ def getcolor(self, color, image=None):
             raise ValueError("palette contains raw palette data")
         if isinstance(color, tuple):
             if self.mode == "RGB":
-                if len(color) == 4 and color[3] == 255:
+                if len(color) == 4:
+                    if color[3] != 255:
+                        raise ValueError(
+                            "cannot add non-opaque RGBA color to RGB palette"
+                        )
                     color = color[:3]
             elif self.mode == "RGBA":
                 if len(color) == 3:


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
@@ -345,13 +345,14 @@ def test_reference_counting(self):
 
     @pytest.mark.parametrize("mode", ("P", "PA"))
     def test_p_putpixel_rgb_rgba(self, mode):
-        for color in [(255, 0, 0), (255, 0, 0, 127)]:
+        for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)):
             im = Image.new(mode, (1, 1))
             access = PyAccess.new(im, False)
             access.putpixel((0, 0), color)
 
-            alpha = color[3] if len(color) == 4 and mode == "PA" else 255
-            assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
+            if len(color) == 3:
+                color += (255,)
+            assert im.convert("RGBA").getpixel((0, 0)) == color
 
 
 class TestImagePutPixelError(AccessTest):
diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py
--- a/Tests/test_imagepalette.py
+++ b/Tests/test_imagepalette.py
@@ -50,6 +50,16 @@ def test_getcolor():
         palette.getcolor("unknown")
 
 
+def test_getcolor_rgba_color_rgb_palette():
+    palette = ImagePalette.ImagePalette("RGB")
+
+    # Opaque RGBA colors are converted
+    assert palette.getcolor((0, 0, 0, 255)) == palette.getcolor((0, 0, 0))
+
+    with pytest.raises(ValueError):
+        palette.getcolor((0, 0, 0, 128))
+
+
 @pytest.mark.parametrize(
     "index, palette",
     [