When allocating a new color, repurpose an unused index if necessary

This commit is contained in:
Andrew Murray 2021-06-23 18:41:46 +10:00
parent f3451aefc6
commit fa559277fb
5 changed files with 19 additions and 10 deletions

View File

@ -760,7 +760,7 @@ def _get_background(im, infoBackground):
# WebPImagePlugin stores an RGBA value in info["background"] # WebPImagePlugin stores an RGBA value in info["background"]
# So it must be converted to the same format as GifImagePlugin's # So it must be converted to the same format as GifImagePlugin's
# info["background"] - a global color table index # info["background"] - a global color table index
background = im.palette.getcolor(background) background = im.palette.getcolor(background, im)
return background return background

View File

@ -978,7 +978,7 @@ class Image:
trns_im.putpalette(self.palette) trns_im.putpalette(self.palette)
if isinstance(t, tuple): if isinstance(t, tuple):
try: try:
t = trns_im.palette.getcolor(t) t = trns_im.palette.getcolor(t, self)
except Exception as e: except Exception as e:
raise ValueError( raise ValueError(
"Couldn't allocate a palette color for transparency" "Couldn't allocate a palette color for transparency"
@ -1016,7 +1016,7 @@ class Image:
del new.info["transparency"] del new.info["transparency"]
if trns is not None: if trns is not None:
try: try:
new.info["transparency"] = new.palette.getcolor(trns) new.info["transparency"] = new.palette.getcolor(trns, new)
except Exception: except Exception:
# if we can't make a transparent color, don't leave the old # if we can't make a transparent color, don't leave the old
# transparency hanging around to mess us up. # transparency hanging around to mess us up.
@ -1049,7 +1049,7 @@ class Image:
if trns is not None: if trns is not None:
if new_im.mode == "P": if new_im.mode == "P":
try: try:
new_im.info["transparency"] = new_im.palette.getcolor(trns) new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
except Exception: except Exception:
del new_im.info["transparency"] del new_im.info["transparency"]
warnings.warn("Couldn't allocate palette entry for transparency") warnings.warn("Couldn't allocate palette entry for transparency")
@ -1764,7 +1764,7 @@ class Image:
and len(value) in [3, 4] and len(value) in [3, 4]
): ):
# RGB or RGBA value for a P image # 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) return self.im.putpixel(xy, value)
def remap_palette(self, dest_map, source_palette=None): def remap_palette(self, dest_map, source_palette=None):

View File

@ -70,6 +70,7 @@ class ImageDraw:
self.palette = im.palette self.palette = im.palette
else: else:
self.palette = None self.palette = None
self._image = im
self.im = im.im self.im = im.im
self.draw = Image.core.draw(self.im, blend) self.draw = Image.core.draw(self.im, blend)
self.mode = mode self.mode = mode
@ -108,13 +109,13 @@ class ImageDraw:
if isinstance(ink, str): if isinstance(ink, str):
ink = ImageColor.getcolor(ink, self.mode) ink = ImageColor.getcolor(ink, self.mode)
if self.palette and not isinstance(ink, numbers.Number): 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) ink = self.draw.draw_ink(ink)
if fill is not None: if fill is not None:
if isinstance(fill, str): if isinstance(fill, str):
fill = ImageColor.getcolor(fill, self.mode) fill = ImageColor.getcolor(fill, self.mode)
if self.palette and not isinstance(fill, numbers.Number): 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) fill = self.draw.draw_ink(fill)
return ink, fill return ink, fill

View File

@ -97,7 +97,7 @@ class ImagePalette:
# Declare tostring as an alias for tobytes # Declare tostring as an alias for tobytes
tostring = tobytes tostring = tobytes
def getcolor(self, color): def getcolor(self, color, image=None):
"""Given an rgb tuple, allocate palette entry. """Given an rgb tuple, allocate palette entry.
.. warning:: This method is experimental. .. warning:: This method is experimental.
@ -119,7 +119,14 @@ class ImagePalette:
self._palette = bytearray(self.palette) self._palette = bytearray(self.palette)
index = len(self.palette) // 3 index = len(self.palette) // 3
if index >= 256: 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 self.colors[color] = index
if index * 3 < len(self.palette): if index * 3 < len(self.palette):
self._palette[index * 3] = color[0] self._palette[index * 3] = color[0]

View File

@ -54,6 +54,7 @@ class PyAccess:
self.image32 = ffi.cast("int **", vals["image32"]) self.image32 = ffi.cast("int **", vals["image32"])
self.image = ffi.cast("unsigned char **", vals["image"]) self.image = ffi.cast("unsigned char **", vals["image"])
self.xsize, self.ysize = img.im.size self.xsize, self.ysize = img.im.size
self._img = img
# Keep pointer to im object to prevent dereferencing. # Keep pointer to im object to prevent dereferencing.
self._im = img.im self._im = img.im
@ -93,7 +94,7 @@ class PyAccess:
and len(color) in [3, 4] and len(color) in [3, 4]
): ):
# RGB or RGBA value for a P image # 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) return self.set_pixel(x, y, color)