Repository: python-pillow/Pillow

Issue:
remap_palette loses transparency information
### What did you do?
I have palettized images that use "0xFF 0x00 0xFF" to mark transparency. After converting this into an alpha channel I want to reorder colours so that transparency is at the start of the palette.
I am using "image.remap_palette" to reorder colours.

### What did you expect to happen?
I expect the transparency information in a palette to be preserved when reordering colours.

### What actually happened?
Transparency information in a palette is lost when reordering colours

### What are your OS, Python and Pillow versions?

* OS: Windows 10
* Python: 3.9.5 (64 bit)
* Pillow: 9.1.1

```python
from PIL import Image

img = Image.new("P", (256, 256))
pixel = []
for y in range(256):
    for x in range(256):
        v = ((y // 16) & 1) ^ ((x // 16) & 1)
        pixel.append(v)
img.putdata(pixel)

pal = [255,0,0,0 ,0,255,0,255] + [255] * 248 * 4
img.putpalette(pal, "RGBA")
img.save("test_A.png")

img = img.remap_palette([1,0])
img.save("test_B.png")
```
This produces two images: test_A is the input, test_B is the output
Both images should look identical when opened in a program like GIMP. The colours (red and green) are correctly swapped, though test_B loses the transparency on red.



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
@@ -1867,10 +1867,15 @@ def remap_palette(self, dest_map, source_palette=None):
         if self.mode not in ("L", "P"):
             raise ValueError("illegal image mode")
 
+        bands = 3
+        palette_mode = "RGB"
         if source_palette is None:
             if self.mode == "P":
                 self.load()
-                source_palette = self.im.getpalette("RGB")[:768]
+                palette_mode = self.im.getpalettemode()
+                if palette_mode == "RGBA":
+                    bands = 4
+                source_palette = self.im.getpalette(palette_mode, palette_mode)
             else:  # L-mode
                 source_palette = bytearray(i // 3 for i in range(768))
 
@@ -1879,7 +1884,9 @@ def remap_palette(self, dest_map, source_palette=None):
 
         # pick only the used colors from the palette
         for i, oldPosition in enumerate(dest_map):
-            palette_bytes += source_palette[oldPosition * 3 : oldPosition * 3 + 3]
+            palette_bytes += source_palette[
+                oldPosition * bands : oldPosition * bands + bands
+            ]
             new_positions[oldPosition] = i
 
         # replace the palette color id of all pixel with the new id
@@ -1905,19 +1912,23 @@ def remap_palette(self, dest_map, source_palette=None):
         m_im = self.copy()
         m_im.mode = "P"
 
-        m_im.palette = ImagePalette.ImagePalette("RGB", palette=mapping_palette * 3)
+        m_im.palette = ImagePalette.ImagePalette(
+            palette_mode, palette=mapping_palette * bands
+        )
         # possibly set palette dirty, then
         # m_im.putpalette(mapping_palette, 'L')  # converts to 'P'
         # or just force it.
         # UNDONE -- this is part of the general issue with palettes
-        m_im.im.putpalette("RGB;L", m_im.palette.tobytes())
+        m_im.im.putpalette(palette_mode + ";L", m_im.palette.tobytes())
 
         m_im = m_im.convert("L")
 
-        # Internally, we require 768 bytes for a palette.
-        new_palette_bytes = palette_bytes + (768 - len(palette_bytes)) * b"\x00"
-        m_im.putpalette(new_palette_bytes)
-        m_im.palette = ImagePalette.ImagePalette("RGB", palette=palette_bytes)
+        # Internally, we require 256 palette entries.
+        new_palette_bytes = (
+            palette_bytes + ((256 * bands) - len(palette_bytes)) * b"\x00"
+        )
+        m_im.putpalette(new_palette_bytes, palette_mode)
+        m_im.palette = ImagePalette.ImagePalette(palette_mode, palette=palette_bytes)
 
         if "transparency" in self.info:
             try:


Test patch:
diff --git a/Tests/test_image.py b/Tests/test_image.py
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -604,6 +604,15 @@ def test_remap_palette(self):
         with Image.open("Tests/images/hopper.gif") as im:
             assert_image_equal(im, im.remap_palette(list(range(256))))
 
+        # Test identity transform with an RGBA palette
+        im = Image.new("P", (256, 1))
+        for x in range(256):
+            im.putpixel((x, 0), x)
+        im.putpalette(list(range(256)) * 4, "RGBA")
+        im_remapped = im.remap_palette(list(range(256)))
+        assert_image_equal(im, im_remapped)
+        assert im.palette.palette == im_remapped.palette.palette
+
         # Test illegal image mode
         with hopper() as im:
             with pytest.raises(ValueError):