From fa559277fb8bc1e59d154a0a45eafd3917d6e20a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 23 Jun 2021 18:41:46 +1000 Subject: [PATCH] When allocating a new color, repurpose an unused index if necessary --- src/PIL/GifImagePlugin.py | 2 +- src/PIL/Image.py | 8 ++++---- src/PIL/ImageDraw.py | 5 +++-- src/PIL/ImagePalette.py | 11 +++++++++-- src/PIL/PyAccess.py | 3 ++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 5c93de2c9..e84bd6fe1 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -760,7 +760,7 @@ def _get_background(im, infoBackground): # WebPImagePlugin stores an RGBA value in info["background"] # So it must be converted to the same format as GifImagePlugin's # info["background"] - a global color table index - background = im.palette.getcolor(background) + background = im.palette.getcolor(background, im) return background diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0df5e1218..fedfe9f0d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -978,7 +978,7 @@ class Image: trns_im.putpalette(self.palette) if isinstance(t, tuple): try: - t = trns_im.palette.getcolor(t) + t = trns_im.palette.getcolor(t, self) except Exception as e: raise ValueError( "Couldn't allocate a palette color for transparency" @@ -1016,7 +1016,7 @@ class Image: del new.info["transparency"] if trns is not None: try: - new.info["transparency"] = new.palette.getcolor(trns) + new.info["transparency"] = new.palette.getcolor(trns, new) except Exception: # if we can't make a transparent color, don't leave the old # transparency hanging around to mess us up. @@ -1049,7 +1049,7 @@ class Image: if trns is not None: if new_im.mode == "P": try: - new_im.info["transparency"] = new_im.palette.getcolor(trns) + new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im) except Exception: del new_im.info["transparency"] warnings.warn("Couldn't allocate palette entry for transparency") @@ -1764,7 +1764,7 @@ class Image: and len(value) in [3, 4] ): # RGB or RGBA value for a P image - value = self.palette.getcolor(value) + value = self.palette.getcolor(value, self) return self.im.putpixel(xy, value) def remap_palette(self, dest_map, source_palette=None): diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 717fb48a4..aea0cc680 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -70,6 +70,7 @@ class ImageDraw: self.palette = im.palette else: self.palette = None + self._image = im self.im = im.im self.draw = Image.core.draw(self.im, blend) self.mode = mode @@ -108,13 +109,13 @@ class ImageDraw: if isinstance(ink, str): ink = ImageColor.getcolor(ink, self.mode) if self.palette and not isinstance(ink, numbers.Number): - ink = self.palette.getcolor(ink) + ink = self.palette.getcolor(ink, self._image) ink = self.draw.draw_ink(ink) if fill is not None: if isinstance(fill, str): fill = ImageColor.getcolor(fill, self.mode) if self.palette and not isinstance(fill, numbers.Number): - fill = self.palette.getcolor(fill) + fill = self.palette.getcolor(fill, self._image) fill = self.draw.draw_ink(fill) return ink, fill diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 36b536fb6..f425a3c30 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -97,7 +97,7 @@ class ImagePalette: # Declare tostring as an alias for tobytes tostring = tobytes - def getcolor(self, color): + def getcolor(self, color, image=None): """Given an rgb tuple, allocate palette entry. .. warning:: This method is experimental. @@ -119,7 +119,14 @@ class ImagePalette: self._palette = bytearray(self.palette) index = len(self.palette) // 3 if index >= 256: - raise ValueError("cannot allocate more than 256 colors") from e + if image: + # Search for an unused index + for i, count in reversed(list(enumerate(image.histogram()))): + if count == 0: + index = i + break + if index >= 256: + raise ValueError("cannot allocate more than 256 colors") from e self.colors[color] = index if index * 3 < len(self.palette): self._palette[index * 3] = color[0] diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 494f5f9f4..5ceaa238a 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -54,6 +54,7 @@ class PyAccess: self.image32 = ffi.cast("int **", vals["image32"]) self.image = ffi.cast("unsigned char **", vals["image"]) self.xsize, self.ysize = img.im.size + self._img = img # Keep pointer to im object to prevent dereferencing. self._im = img.im @@ -93,7 +94,7 @@ class PyAccess: and len(color) in [3, 4] ): # RGB or RGBA value for a P image - color = self._palette.getcolor(color) + color = self._palette.getcolor(color, self._img) return self.set_pixel(x, y, color)