mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	Merge pull request #5552 from radarhere/palette
This commit is contained in:
		
						commit
						50302231ed
					
				|  | @ -249,8 +249,8 @@ def test_apng_mode(): | |||
|         assert im.mode == "P" | ||||
|         im.seek(im.n_frames - 1) | ||||
|         im = im.convert("RGBA") | ||||
|         assert im.getpixel((0, 0)) == (0, 255, 0, 255) | ||||
|         assert im.getpixel((64, 32)) == (0, 255, 0, 255) | ||||
|         assert im.getpixel((0, 0)) == (255, 0, 0, 0) | ||||
|         assert im.getpixel((64, 32)) == (255, 0, 0, 0) | ||||
| 
 | ||||
|     with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im: | ||||
|         assert im.mode == "P" | ||||
|  |  | |||
|  | @ -766,10 +766,10 @@ def test_rgb_transparency(tmp_path): | |||
|     # Single frame | ||||
|     im = Image.new("RGB", (1, 1)) | ||||
|     im.info["transparency"] = (255, 0, 0) | ||||
|     pytest.warns(UserWarning, im.save, out) | ||||
|     im.save(out) | ||||
| 
 | ||||
|     with Image.open(out) as reloaded: | ||||
|         assert "transparency" not in reloaded.info | ||||
|         assert "transparency" in reloaded.info | ||||
| 
 | ||||
|     # Multiple frames | ||||
|     im = Image.new("RGB", (1, 1)) | ||||
|  |  | |||
|  | @ -582,6 +582,10 @@ class TestImage: | |||
|         assert ext_individual == ext_multiple | ||||
| 
 | ||||
|     def test_remap_palette(self): | ||||
|         # Test identity transform | ||||
|         with Image.open("Tests/images/hopper.gif") as im: | ||||
|             assert_image_equal(im, im.remap_palette(list(range(256)))) | ||||
| 
 | ||||
|         # Test illegal image mode | ||||
|         with hopper() as im: | ||||
|             with pytest.raises(ValueError): | ||||
|  | @ -606,7 +610,7 @@ class TestImage: | |||
|             else: | ||||
|                 assert new_im.palette is None | ||||
| 
 | ||||
|         _make_new(im, im_p, im_p.palette) | ||||
|         _make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3)) | ||||
|         _make_new(im_p, im, None) | ||||
|         _make_new(im, blank_p, ImagePalette.ImagePalette()) | ||||
|         _make_new(im, blank_pa, ImagePalette.ImagePalette()) | ||||
|  |  | |||
|  | @ -93,7 +93,7 @@ def test_trns_p(tmp_path): | |||
|     im_l.save(f) | ||||
| 
 | ||||
|     im_rgb = im.convert("RGB") | ||||
|     assert im_rgb.info["transparency"] == (0, 0, 0)  # undone | ||||
|     assert im_rgb.info["transparency"] == (0, 1, 2)  # undone | ||||
|     im_rgb.save(f) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -128,8 +128,8 @@ def test_trns_l(tmp_path): | |||
|     assert "transparency" in im_p.info | ||||
|     im_p.save(f) | ||||
| 
 | ||||
|     im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE) | ||||
|     assert "transparency" not in im_p.info | ||||
|     im_p = im.convert("P", palette=Image.ADAPTIVE) | ||||
|     assert "transparency" in im_p.info | ||||
|     im_p.save(f) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -155,13 +155,19 @@ def test_trns_RGB(tmp_path): | |||
|     assert "transparency" not in im_p.info | ||||
|     im_p.save(f) | ||||
| 
 | ||||
|     im = Image.new("RGB", (1, 1)) | ||||
|     im.info["transparency"] = im.getpixel((0, 0)) | ||||
|     im_p = im.convert("P", palette=Image.ADAPTIVE) | ||||
|     assert im_p.info["transparency"] == im_p.getpixel((0, 0)) | ||||
|     im_p.save(f) | ||||
| 
 | ||||
| 
 | ||||
| def test_gif_with_rgba_palette_to_p(): | ||||
|     # See https://github.com/python-pillow/Pillow/issues/2433 | ||||
|     with Image.open("Tests/images/hopper.gif") as im: | ||||
|         im.info["transparency"] = 255 | ||||
|         im.load() | ||||
|         assert im.palette.mode == "RGBA" | ||||
|         assert im.palette.mode == "RGB" | ||||
|         im_p = im.convert("P") | ||||
| 
 | ||||
|     # Should not raise ValueError: unrecognized raw mode | ||||
|  |  | |||
|  | @ -157,9 +157,16 @@ def test_scale(): | |||
| 
 | ||||
| 
 | ||||
| def test_expand_palette(): | ||||
|     im = Image.open("Tests/images/hopper.gif") | ||||
|     im_expanded = ImageOps.expand(im) | ||||
|     assert_image_equal(im_expanded.convert("RGB"), im.convert("RGB")) | ||||
|     im = Image.open("Tests/images/p_16.tga") | ||||
|     im_expanded = ImageOps.expand(im, 10, (255, 0, 0)) | ||||
| 
 | ||||
|     px = im_expanded.convert("RGB").load() | ||||
|     assert px[0, 0] == (255, 0, 0) | ||||
| 
 | ||||
|     im_cropped = im_expanded.crop( | ||||
|         (10, 10, im_expanded.width - 10, im_expanded.height - 10) | ||||
|     ) | ||||
|     assert_image_equal(im_cropped, im) | ||||
| 
 | ||||
| 
 | ||||
| def test_colorize_2color(): | ||||
|  |  | |||
|  | @ -2,27 +2,47 @@ import pytest | |||
| 
 | ||||
| from PIL import Image, ImagePalette | ||||
| 
 | ||||
| from .helper import assert_image_equal_tofile | ||||
| from .helper import assert_image_equal, assert_image_equal_tofile | ||||
| 
 | ||||
| 
 | ||||
| def test_sanity(): | ||||
| 
 | ||||
|     ImagePalette.ImagePalette("RGB", list(range(256)) * 3) | ||||
|     palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) | ||||
|     assert len(palette.colors) == 256 | ||||
| 
 | ||||
|     with pytest.raises(ValueError): | ||||
|         ImagePalette.ImagePalette("RGB", list(range(256)) * 2) | ||||
|         ImagePalette.ImagePalette("RGB", list(range(256)) * 3, 10) | ||||
| 
 | ||||
| 
 | ||||
| def test_reload(): | ||||
|     im = Image.open("Tests/images/hopper.gif") | ||||
|     original = im.copy() | ||||
|     im.palette.dirty = 1 | ||||
|     assert_image_equal(im.convert("RGB"), original.convert("RGB")) | ||||
| 
 | ||||
| 
 | ||||
| def test_getcolor(): | ||||
| 
 | ||||
|     palette = ImagePalette.ImagePalette() | ||||
|     assert len(palette.palette) == 0 | ||||
|     assert len(palette.colors) == 0 | ||||
| 
 | ||||
|     test_map = {} | ||||
|     for i in range(256): | ||||
|         test_map[palette.getcolor((i, i, i))] = i | ||||
| 
 | ||||
|     assert len(test_map) == 256 | ||||
| 
 | ||||
|     # Colors can be converted between RGB and RGBA | ||||
|     rgba_palette = ImagePalette.ImagePalette("RGBA") | ||||
|     assert rgba_palette.getcolor((0, 0, 0)) == rgba_palette.getcolor((0, 0, 0, 255)) | ||||
| 
 | ||||
|     assert palette.getcolor((0, 0, 0)) == palette.getcolor((0, 0, 0, 255)) | ||||
| 
 | ||||
|     # An error is raised when the palette is full | ||||
|     with pytest.raises(ValueError): | ||||
|         palette.getcolor((1, 2, 3)) | ||||
|     # But not if the image is not using one of the palette entries | ||||
|     palette.getcolor((1, 2, 3), image=Image.new("P", (1, 1))) | ||||
| 
 | ||||
|     # Test unknown color specifier | ||||
|     with pytest.raises(ValueError): | ||||
|  | @ -116,7 +136,7 @@ def test_getdata(): | |||
|     mode, data_out = palette.getdata() | ||||
| 
 | ||||
|     # Assert | ||||
|     assert mode == "RGB;L" | ||||
|     assert mode == "RGB" | ||||
| 
 | ||||
| 
 | ||||
| def test_rawmode_getdata(): | ||||
|  |  | |||
|  | @ -472,10 +472,10 @@ def _write_multiple_frames(im, fp, palette): | |||
|                 previous = im_frames[-1] | ||||
|                 if encoderinfo.get("disposal") == 2: | ||||
|                     if background_im is None: | ||||
|                         background = _get_background( | ||||
|                             im, | ||||
|                             im.encoderinfo.get("background", im.info.get("background")), | ||||
|                         color = im.encoderinfo.get( | ||||
|                             "transparency", im.info.get("transparency", (0, 0, 0)) | ||||
|                         ) | ||||
|                         background = _get_background(im_frame, color) | ||||
|                         background_im = Image.new("P", im_frame.size, background) | ||||
|                         background_im.putpalette(im_frames[0]["im"].palette) | ||||
|                     base_im = background_im | ||||
|  | @ -771,7 +771,15 @@ 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) | ||||
|             try: | ||||
|                 background = im.palette.getcolor(background, im) | ||||
|             except ValueError as e: | ||||
|                 if str(e) == "cannot allocate more than 256 colors": | ||||
|                     # If all 256 colors are in use, | ||||
|                     # then there is no need for the background color | ||||
|                     return 0 | ||||
|                 else: | ||||
|                     raise | ||||
|     return background | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -833,7 +833,7 @@ class Image: | |||
|             palette_length = self.im.putpalette(mode, arr) | ||||
|             self.palette.dirty = 0 | ||||
|             self.palette.rawmode = None | ||||
|             if "transparency" in self.info: | ||||
|             if "transparency" in self.info and mode in ("RGBA", "LA", "PA"): | ||||
|                 if isinstance(self.info["transparency"], int): | ||||
|                     self.im.putpalettealpha(self.info["transparency"], 0) | ||||
|                 else: | ||||
|  | @ -977,21 +977,28 @@ class Image: | |||
|                     if self.mode == "P": | ||||
|                         trns_im.putpalette(self.palette) | ||||
|                         if isinstance(t, tuple): | ||||
|                             err = "Couldn't allocate a palette color for transparency" | ||||
|                             try: | ||||
|                                 t = trns_im.palette.getcolor(t) | ||||
|                             except Exception as e: | ||||
|                                 raise ValueError( | ||||
|                                     "Couldn't allocate a palette color for transparency" | ||||
|                                 ) from e | ||||
|                     trns_im.putpixel((0, 0), t) | ||||
| 
 | ||||
|                     if mode in ("L", "RGB"): | ||||
|                         trns_im = trns_im.convert(mode) | ||||
|                                 t = trns_im.palette.getcolor(t, self) | ||||
|                             except ValueError as e: | ||||
|                                 if str(e) == "cannot allocate more than 256 colors": | ||||
|                                     # If all 256 colors are in use, | ||||
|                                     # then there is no need for transparency | ||||
|                                     t = None | ||||
|                                 else: | ||||
|                                     raise ValueError(err) from e | ||||
|                     if t is None: | ||||
|                         trns = None | ||||
|                     else: | ||||
|                         # can't just retrieve the palette number, got to do it | ||||
|                         # after quantization. | ||||
|                         trns_im = trns_im.convert("RGB") | ||||
|                     trns = trns_im.getpixel((0, 0)) | ||||
|                         trns_im.putpixel((0, 0), t) | ||||
| 
 | ||||
|                         if mode in ("L", "RGB"): | ||||
|                             trns_im = trns_im.convert(mode) | ||||
|                         else: | ||||
|                             # can't just retrieve the palette number, got to do it | ||||
|                             # after quantization. | ||||
|                             trns_im = trns_im.convert("RGB") | ||||
|                         trns = trns_im.getpixel((0, 0)) | ||||
| 
 | ||||
|             elif self.mode == "P" and mode == "RGBA": | ||||
|                 t = self.info["transparency"] | ||||
|  | @ -1009,14 +1016,14 @@ class Image: | |||
|             new = self._new(im) | ||||
|             from . import ImagePalette | ||||
| 
 | ||||
|             new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB")) | ||||
|             new.palette = ImagePalette.ImagePalette("RGB", new.im.getpalette("RGB")) | ||||
|             if delete_trns: | ||||
|                 # This could possibly happen if we requantize to fewer colors. | ||||
|                 # The transparency would be totally off in that case. | ||||
|                 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. | ||||
|  | @ -1039,16 +1046,25 @@ class Image: | |||
|                 raise ValueError("illegal conversion") from e | ||||
| 
 | ||||
|         new_im = self._new(im) | ||||
|         if mode == "P" and palette != ADAPTIVE: | ||||
|             from . import ImagePalette | ||||
| 
 | ||||
|             new_im.palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) | ||||
|         if delete_trns: | ||||
|             # crash fail if we leave a bytes transparency in an rgb/l mode. | ||||
|             del new_im.info["transparency"] | ||||
|         if trns is not None: | ||||
|             if new_im.mode == "P": | ||||
|                 try: | ||||
|                     new_im.info["transparency"] = new_im.palette.getcolor(trns) | ||||
|                 except Exception: | ||||
|                     new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im) | ||||
|                 except ValueError as e: | ||||
|                     del new_im.info["transparency"] | ||||
|                     warnings.warn("Couldn't allocate palette entry for transparency") | ||||
|                     if str(e) != "cannot allocate more than 256 colors": | ||||
|                         # If all 256 colors are in use, | ||||
|                         # then there is no need for transparency | ||||
|                         warnings.warn( | ||||
|                             "Couldn't allocate palette entry for transparency" | ||||
|                         ) | ||||
|             else: | ||||
|                 new_im.info["transparency"] = trns | ||||
|         return new_im | ||||
|  | @ -1732,7 +1748,7 @@ class Image: | |||
|         Attaches a palette to this image.  The image must be a "P", "PA", "L" | ||||
|         or "LA" image. | ||||
| 
 | ||||
|         The palette sequence must contain either 768 integer values, or 1024 | ||||
|         The palette sequence must contain at most 768 integer values, or 1024 | ||||
|         integer values if alpha is included. Each group of values represents | ||||
|         the red, green, blue (and alpha if included) values for the | ||||
|         corresponding pixel index. Instead of an integer sequence, you can use | ||||
|  | @ -1745,7 +1761,6 @@ class Image: | |||
| 
 | ||||
|         if self.mode not in ("L", "LA", "P", "PA"): | ||||
|             raise ValueError("illegal image mode") | ||||
|         self.load() | ||||
|         if isinstance(data, ImagePalette.ImagePalette): | ||||
|             palette = ImagePalette.raw(data.rawmode, data.palette) | ||||
|         else: | ||||
|  | @ -1792,7 +1807,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): | ||||
|  | @ -1813,6 +1828,7 @@ class Image: | |||
| 
 | ||||
|         if source_palette is None: | ||||
|             if self.mode == "P": | ||||
|                 self.load() | ||||
|                 real_source_palette = self.im.getpalette("RGB")[:768] | ||||
|             else:  # L-mode | ||||
|                 real_source_palette = bytearray(i // 3 for i in range(768)) | ||||
|  | @ -1850,23 +1866,19 @@ class Image: | |||
|         m_im = self.copy() | ||||
|         m_im.mode = "P" | ||||
| 
 | ||||
|         m_im.palette = ImagePalette.ImagePalette( | ||||
|             "RGB", palette=mapping_palette * 3, size=768 | ||||
|         ) | ||||
|         m_im.palette = ImagePalette.ImagePalette("RGB", palette=mapping_palette * 3) | ||||
|         # 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(*m_im.palette.getdata()) | ||||
|         m_im.im.putpalette("RGB;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, size=len(palette_bytes) | ||||
|         ) | ||||
|         m_im.palette = ImagePalette.ImagePalette("RGB", palette=palette_bytes) | ||||
| 
 | ||||
|         return m_im | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ | |||
| import functools | ||||
| import operator | ||||
| 
 | ||||
| from . import Image | ||||
| from . import Image, ImageDraw | ||||
| 
 | ||||
| # | ||||
| # helpers | ||||
|  | @ -392,10 +392,17 @@ def expand(image, border=0, fill=0): | |||
|     left, top, right, bottom = _border(border) | ||||
|     width = left + image.size[0] + right | ||||
|     height = top + image.size[1] + bottom | ||||
|     out = Image.new(image.mode, (width, height), _color(fill, image.mode)) | ||||
|     color = _color(fill, image.mode) | ||||
|     if image.mode == "P" and image.palette: | ||||
|         out = Image.new(image.mode, (width, height)) | ||||
|         out.putpalette(image.palette) | ||||
|     out.paste(image, (left, top)) | ||||
|         out.paste(image, (left, top)) | ||||
| 
 | ||||
|         draw = ImageDraw.Draw(out) | ||||
|         draw.rectangle((0, 0, width, height), outline=color, width=border) | ||||
|     else: | ||||
|         out = Image.new(image.mode, (width, height), color) | ||||
|         out.paste(image, (left, top)) | ||||
|     return out | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,14 +39,27 @@ class ImagePalette: | |||
|     def __init__(self, mode="RGB", palette=None, size=0): | ||||
|         self.mode = mode | ||||
|         self.rawmode = None  # if set, palette contains raw data | ||||
|         self.palette = palette or bytearray(range(256)) * len(self.mode) | ||||
|         self.colors = {} | ||||
|         self.palette = palette or bytearray() | ||||
|         self.dirty = None | ||||
|         if (size == 0 and len(self.mode) * 256 != len(self.palette)) or ( | ||||
|             size != 0 and size != len(self.palette) | ||||
|         ): | ||||
|         if size != 0 and size != len(self.palette): | ||||
|             raise ValueError("wrong palette size") | ||||
| 
 | ||||
|     @property | ||||
|     def palette(self): | ||||
|         return self._palette | ||||
| 
 | ||||
|     @palette.setter | ||||
|     def palette(self, palette): | ||||
|         self._palette = 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]) | ||||
|             if color in self.colors: | ||||
|                 continue | ||||
|             self.colors[color] = i // mode_len | ||||
| 
 | ||||
|     def copy(self): | ||||
|         new = ImagePalette() | ||||
| 
 | ||||
|  | @ -54,7 +67,6 @@ class ImagePalette: | |||
|         new.rawmode = self.rawmode | ||||
|         if self.palette is not None: | ||||
|             new.palette = self.palette[:] | ||||
|         new.colors = self.colors.copy() | ||||
|         new.dirty = self.dirty | ||||
| 
 | ||||
|         return new | ||||
|  | @ -68,7 +80,7 @@ class ImagePalette: | |||
|         """ | ||||
|         if self.rawmode: | ||||
|             return self.rawmode, self.palette | ||||
|         return self.mode + ";L", self.tobytes() | ||||
|         return self.mode, self.tobytes() | ||||
| 
 | ||||
|     def tobytes(self): | ||||
|         """Convert palette to bytes. | ||||
|  | @ -80,14 +92,12 @@ class ImagePalette: | |||
|         if isinstance(self.palette, bytes): | ||||
|             return self.palette | ||||
|         arr = array.array("B", self.palette) | ||||
|         if hasattr(arr, "tobytes"): | ||||
|             return arr.tobytes() | ||||
|         return arr.tostring() | ||||
|         return arr.tobytes() | ||||
| 
 | ||||
|     # 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. | ||||
|  | @ -95,19 +105,37 @@ class ImagePalette: | |||
|         if self.rawmode: | ||||
|             raise ValueError("palette contains raw palette data") | ||||
|         if isinstance(color, tuple): | ||||
|             if self.mode == "RGB": | ||||
|                 if len(color) == 4 and color[3] == 255: | ||||
|                     color = color[:3] | ||||
|             elif self.mode == "RGBA": | ||||
|                 if len(color) == 3: | ||||
|                     color += (255,) | ||||
|             try: | ||||
|                 return self.colors[color] | ||||
|             except KeyError as e: | ||||
|                 # allocate new color slot | ||||
|                 if isinstance(self.palette, bytes): | ||||
|                     self.palette = bytearray(self.palette) | ||||
|                 index = len(self.colors) | ||||
|                 if not isinstance(self.palette, bytearray): | ||||
|                     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 | ||||
|                 self.palette[index] = color[0] | ||||
|                 self.palette[index + 256] = color[1] | ||||
|                 self.palette[index + 512] = color[2] | ||||
|                 if index * 3 < len(self.palette): | ||||
|                     self._palette = ( | ||||
|                         self.palette[: index * 3] | ||||
|                         + bytes(color) | ||||
|                         + self.palette[index * 3 + 3 :] | ||||
|                     ) | ||||
|                 else: | ||||
|                     self._palette += bytes(color) | ||||
|                 self.dirty = 1 | ||||
|                 return index | ||||
|         else: | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -320,7 +320,7 @@ def _save(im, fp, filename): | |||
|         alpha = ( | ||||
|             "A" in im.mode | ||||
|             or "a" in im.mode | ||||
|             or (im.mode == "P" and "A" in im.im.getpalettemode()) | ||||
|             or (im.mode == "P" and "transparency" in im.info) | ||||
|         ) | ||||
|         im = im.convert("RGBA" if alpha else "RGB") | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user