Repository: python-pillow/Pillow

Issue:
Extra output from getpalette() and getcolors() in quantized image
### What did you do?
I am using `Image.quantize` to reduce an image down to a simple 12 color palette and count the prevalence of each color in quantized image.

### What did you expect to happen?
`getpalette()` should return a 36 integer list reflecting the R,G,B values of the 12 color palette
`getcolors()` should return a list of 12 tuples with the second index in the tuples ranging from 1-12 referencing the colors in the palette.

### What actually happened?
When I call `getpalette()` the first 36 items reflect my palette but the remaining items up to index 768 are a sequence of `12, 12, 12, 13, 13, 13` etc.  I'm guessing the code does this to avoid resizing the array and internally detects these additional items and discards them...so maybe not a big deal.

The real issue is when I call `getcolors()`.  Instead of receiving a list of 12 tuples (with second index in the tuples referencing a palette colors in the range 1-12) I receive a list of 208 tuples which reference palette colors as high as 252. I can't seem to make sense of the output. Am I supposed to just ignore anything that references an out of range palette index?

### What are your OS, Python and Pillow versions?
* OS: 
* Python: 3.9.10
* Pillow:  9.01

```python
RGB_flat = (2, 62, 255, 255, 124, 0, 26, 201, 56, 232, 0, 11, 139, 43, 226, 159, 72, 0, 241, 76, 193, 163, 163, 163, 255, 196, 0, 0, 215, 255, 0, 0, 0, 255, 255, 255)
img = Image.open('cherry.jpg')
pimg = Image.new('P',(16,16))
pimg.putpalette(RGB_flat)
imgq = img.quantize(palette=pimg)
colors = imgq.getcolors()
palette = imgq.getpalette()

```
![cherry](https://user-images.githubusercontent.com/13266108/153728546-51b76e7d-6499-4a44-b523-e8c61dd5170c.jpg)




Discussion:
I think this probably deserves a PR, but if you would like an immediate solution, try this code out.

```python
from PIL import Image
RGB_flat = (2, 62, 255, 255, 124, 0, 26, 201, 56, 232, 0, 11, 139, 43, 226, 159, 72, 0, 241, 76, 193, 163, 163, 163, 255, 196, 0, 0, 215, 255, 0, 0, 0, 255, 255, 255)
img = Image.open('cherry.jpg')
pimg = Image.new('P', (16,16))
pimg.putpalette(RGB_flat+RGB_flat[-3:]*(255 - len(RGB_flat)//3))
imgq = img.quantize(palette=pimg)
print(imgq.getcolors())
print(imgq.palette.colors)
```
Thanks that fix seems to work.  I'm not terribly familiar with interfacing C with Python programs but after staring at the code a bit I think my specific issue might stem from the `_putpalette()` function in the C code.  The palette array gets initialized to 256 colors using a ramp of increasing values:
https://github.com/python-pillow/Pillow/blob/95cff6e959bb3c37848158ed2145d49d49806a31/src/libImaging/Palette.c#L43-L47
Then in `_putpalette()` the call to unpack appears to only replace items up to the size of the palette:
https://github.com/python-pillow/Pillow/blob/164632650696429818fe9ff087b1189d38875b7b/src/_imaging.c#L1674
As a result you get your requested palette plus the remaining ramp items from the original palette initialization (or whatever  the palette was before).  There are a lot of places where the code assumes a 256 color palette so changing the size would probably break everything...so padding out the palette automatically might be the best fix. There does appear to be logic to skip duplicate colors in other sections of the code which is why the call to `getcolors()` seems to work with the padding.

I can try to come up with a PR for a fix, but you would prefer to fix this at the Python level or in the C code itself?
I expect a C solution would be better than a Python one, as it would have a broader effect.

Resolving patch:
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -821,7 +821,7 @@ def load(self):
         if self.im and self.palette and self.palette.dirty:
             # realize palette
             mode, arr = self.palette.getdata()
-            palette_length = self.im.putpalette(mode, arr)
+            self.im.putpalette(mode, arr)
             self.palette.dirty = 0
             self.palette.rawmode = None
             if "transparency" in self.info and mode in ("LA", "PA"):
@@ -833,9 +833,7 @@ def load(self):
             else:
                 palette_mode = "RGBA" if mode.startswith("RGBA") else "RGB"
                 self.palette.mode = palette_mode
-                self.palette.palette = self.im.getpalette(palette_mode, palette_mode)[
-                    : palette_length * len(palette_mode)
-                ]
+                self.palette.palette = self.im.getpalette(palette_mode, palette_mode)
 
         if self.im:
             if cffi and USE_CFFI_ACCESS:


Test patch:
diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py
--- a/Tests/test_image_putpalette.py
+++ b/Tests/test_image_putpalette.py
@@ -74,4 +74,5 @@ def test_putpalette_with_alpha_values():
 def test_rgba_palette(mode, palette):
     im = Image.new("P", (1, 1))
     im.putpalette(palette, mode)
+    assert im.getpalette() == [1, 2, 3]
     assert im.palette.colors == {(1, 2, 3, 4): 0}
diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py
--- a/Tests/test_image_quantize.py
+++ b/Tests/test_image_quantize.py
@@ -108,3 +108,18 @@ def test_palette(method, color):
     converted = im.quantize(method=method)
     converted_px = converted.load()
     assert converted_px[0, 0] == converted.palette.colors[color]
+
+
+def test_small_palette():
+    # Arrange
+    im = hopper()
+
+    colors = (255, 0, 0, 0, 0, 255)
+    p = Image.new("P", (1, 1))
+    p.putpalette(colors)
+
+    # Act
+    im = im.quantize(palette=p)
+
+    # Assert
+    assert len(im.getcolors()) == 2