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"]
# 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

View File

@ -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):

View File

@ -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

View File

@ -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]

View File

@ -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)