Repository: python-pillow/Pillow

Issue:
[Regression] image save() corrupted the image color start from Pillow 8.3.0
### What did you do?

```python
import io
import numpy as np
from PIL import Image

#generate random data with 21 classes
num_classes = 21
height = 200
width = 300
data = np.random.rand(num_classes, height, width)

#prepare and generate image from numpy array use mode P
pred = np.squeeze(data)
pred = np.argmax(pred, axis=0)
img_with_correct_colors = Image.fromarray(pred.astype('uint8'), mode='P')
print(img_with_correct_colors.getcolors())

#save image uses BytesIO
fp = io.BytesIO()
img_with_correct_colors.save(fp, 'png')
img_with_wrong_colors = Image.open(io.BytesIO(fp.getvalue()))
img_with_wrong_colors.getcolors()
```

### What did you expect to happen?

I expect image contains correct number of class before and after saving (which should have 21 number of classes)


### What actually happened?

I run the exact same commands on Pillow 8.3.0 and Pillow 8.2.0

Pillow 8.2.0 gives the correct result

The `img_with_correct_colors` produce correct number of class

Image before saving(container 21 classes):

```pycon
>>> print(img_with_correct_colors.getcolors())
[(2883, 0), (2847, 1), (2852, 2), (2889, 3), (2850, 4), (2870, 5), (2826, 6), (2846, 7), (2861, 8), (2836, 9), (2764, 10), (2915, 11), (2831, 12), (2852, 13), (2873, 14), (2826, 15), (2880, 16), (2866, 17), (2756, 18), (2928, 19), (2949, 20)]
```

After save, the image saved preserves the correct number of class as well (container 21 classes):

```pycon
>>> img_with_wrong_colors.getcolors()
[(2883, 0), (2847, 1), (2852, 2), (2889, 3), (2850, 4), (2870, 5), (2826, 6), (2846, 7), (2861, 8), (2836, 9), (2764, 10), (2915, 11), (2831, 12), (2852, 13), (2873, 14), (2826, 15), (2880, 16), (2866, 17), (2756, 18), (2928, 19), (2949, 20)]
```

However, starting with Pillow >= 8.3.0 (Also tested on latest Pillow version 9.1.0)

Image before saving is still correct but image after saving produces wrong number of colors. The result looks like except the class 0 everything else got aggregate to the class 1.

Image before saving:

```pycon
>>> print(img_with_correct_colors.getcolors())
[(2846, 0), (2940, 1), (2861, 2), (2834, 3), (2886, 4), (2911, 5), (2739, 6), (2797, 7), (2751, 8), (2960, 9), (2900, 10), (2880, 11), (2893, 12), (2810, 13), (2851, 14), (2870, 15), (2780, 16), (2775, 17), (2863, 18), (2954, 19), (2899, 20)]
```

Image after saving:

```pycon
>>> print(img_with_wrong_colors.getcolors())
[(2846, 0), (57154, 1)]
```

### What are your OS, Python and Pillow versions?

* OS: Mac OS
* Python: Python 3.7
* Pillow: Pillow 8.2.0 / Pillow 8.3.0 / Pillow 9.1.0




Discussion:
Bisecting between 8.2.0 and 8.3.0 points to 4d36feefc7cda3fc3a77ef873f3c4c166aa69352 from PR https://github.com/python-pillow/Pillow/pull/5552.
If you would like an immediate fix, I've found the following options work -
1. Specify the number of colors when saving to PNG - `img_with_correct_colors.save(fp, 'png', bits=8)`.
2. Change the mode to "L". That may not work for the rest of your scenario, but it fixes the example you've provided here.
Thanks for providing the immediate workaround! Going to try and see if that works.
The workaround works! Thanks for the help! Curious about what change causing this issue and do you have any ETA for the fix. Thanks!
Just to be clear - I meant that you could do either 1 or 2 as a workaround, you don't have to do both.
Yes, we did option 1 as option 2 doesn't apply to us. Thanks for clarify though.

Resolving patch:
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -2838,6 +2838,10 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
         if args[0] in _MAPMODES:
             im = new(mode, (1, 1))
             im = im._new(core.map_buffer(data, size, decoder_name, 0, args))
+            if mode == "P":
+                from . import ImagePalette
+
+                im.palette = ImagePalette.ImagePalette("RGB", im.im.getpalette("RGB"))
             im.readonly = 1
             return im
 


Test patch:
diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py
--- a/Tests/test_image_array.py
+++ b/Tests/test_image_array.py
@@ -80,3 +80,15 @@ def test(mode):
     with pytest.raises(TypeError):
         wrapped = Wrapper(test("L"), {"shape": (100, 128)})
         Image.fromarray(wrapped)
+
+
+def test_fromarray_palette():
+    # Arrange
+    i = im.convert("L")
+    a = numpy.array(i)
+
+    # Act
+    out = Image.fromarray(a, "P")
+
+    # Assert that the Python and C palettes match
+    assert len(out.palette.colors) == len(out.im.getpalette()) / 3