From bf508a7e46a5145e28d7c60fb55e07e060aef768 Mon Sep 17 00:00:00 2001 From: nightcityblade Date: Thu, 23 Jul 2026 23:17:19 +0800 Subject: [PATCH 1/3] Fix ImagePalette colors for raw palettes --- Tests/test_imagepalette.py | 14 ++++++++++++++ src/PIL/ImagePalette.py | 14 ++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 3f37c23c5ae..6c3ddad65de 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -15,6 +15,20 @@ def test_sanity() -> None: assert len(palette.colors) == 256 +def test_colors_rawmode() -> None: + palette = ImagePalette.raw( + "BGRX", + bytes.fromhex("00000000ffffff000000ff0000ff0000ff000000"), + ) + assert palette.colors == { + (0, 0, 0): 0, + (255, 255, 255): 1, + (255, 0, 0): 2, + (0, 255, 0): 3, + (0, 0, 255): 4, + } + + def test_reload() -> None: with Image.open("Tests/images/hopper.gif") as im: original = im.copy() diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 03fb4d8c4a4..952bfad6245 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -62,10 +62,20 @@ def palette(self, palette: Sequence[int] | bytes | bytearray) -> None: @property def colors(self) -> dict[tuple[int, ...], int]: if self._colors is None: + palette = self.palette + if self.rawmode: + from . import Image + + image = Image.new("P", (1, 1)) + image.putpalette(palette, self.rawmode) + converted_palette = image.getpalette(self.mode) + assert converted_palette is not None + palette = converted_palette + mode_len = len(self.mode) self._colors = {} - for i in range(0, len(self.palette), mode_len): - color = tuple(self.palette[i : i + mode_len]) + for i in range(0, len(palette), mode_len): + color = tuple(palette[i : i + mode_len]) if color in self._colors: continue self._colors[color] = i // mode_len From 84571abc8a62a56469a50bb9e8bb9e0af6153da8 Mon Sep 17 00:00:00 2001 From: nightcityblade Date: Fri, 24 Jul 2026 11:06:33 +0800 Subject: [PATCH 2/3] Use core palette conversion for raw colors --- src/PIL/ImagePalette.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 952bfad6245..ed4ec7d2f0c 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -66,11 +66,9 @@ def colors(self) -> dict[tuple[int, ...], int]: if self.rawmode: from . import Image - image = Image.new("P", (1, 1)) - image.putpalette(palette, self.rawmode) - converted_palette = image.getpalette(self.mode) - assert converted_palette is not None - palette = converted_palette + im = Image.core.new("P", (0, 0)) + im.putpalette(self.mode, self.rawmode, palette) + palette = im.getpalette(self.mode, self.mode) mode_len = len(self.mode) self._colors = {} From b3bbfe52faa2e96c280803319aff60576f69477a Mon Sep 17 00:00:00 2001 From: nightcityblade Date: Fri, 24 Jul 2026 23:07:26 +0800 Subject: [PATCH 3/3] Support sequence data in raw palette colors --- Tests/test_imagepalette.py | 2 +- src/PIL/ImagePalette.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 6c3ddad65de..da0f652df00 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -18,7 +18,7 @@ def test_sanity() -> None: def test_colors_rawmode() -> None: palette = ImagePalette.raw( "BGRX", - bytes.fromhex("00000000ffffff000000ff0000ff0000ff000000"), + (0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0), ) assert palette.colors == { (0, 0, 0): 0, diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index ed4ec7d2f0c..1d4a2f04bb8 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -67,7 +67,7 @@ def colors(self) -> dict[tuple[int, ...], int]: from . import Image im = Image.core.new("P", (0, 0)) - im.putpalette(self.mode, self.rawmode, palette) + im.putpalette(self.mode, self.rawmode, bytes(palette)) palette = im.getpalette(self.mode, self.mode) mode_len = len(self.mode)