mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 01:47:47 +03:00 
			
		
		
		
	Merge pull request #7568 from radarhere/gif_transparency
This commit is contained in:
		
						commit
						18907b5f7e
					
				| 
						 | 
					@ -217,6 +217,27 @@ def test_optimize_if_palette_can_be_reduced_by_half():
 | 
				
			||||||
            assert len(reloaded.palette.palette) // 3 == colors
 | 
					            assert len(reloaded.palette.palette) // 3 == colors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_full_palette_second_frame(tmp_path):
 | 
				
			||||||
 | 
					    out = str(tmp_path / "temp.gif")
 | 
				
			||||||
 | 
					    im = Image.new("P", (1, 256))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    full_palette_im = Image.new("P", (1, 256))
 | 
				
			||||||
 | 
					    for i in range(256):
 | 
				
			||||||
 | 
					        full_palette_im.putpixel((0, i), i)
 | 
				
			||||||
 | 
					    full_palette_im.palette = ImagePalette.ImagePalette(
 | 
				
			||||||
 | 
					        "RGB", bytearray(i // 3 for i in range(768))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    full_palette_im.palette.dirty = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    im.save(out, save_all=True, append_images=[full_palette_im])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Image.open(out) as reloaded:
 | 
				
			||||||
 | 
					        reloaded.seek(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(256):
 | 
				
			||||||
 | 
					            reloaded.getpixel((0, i)) == i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_roundtrip(tmp_path):
 | 
					def test_roundtrip(tmp_path):
 | 
				
			||||||
    out = str(tmp_path / "temp.gif")
 | 
					    out = str(tmp_path / "temp.gif")
 | 
				
			||||||
    im = hopper()
 | 
					    im = hopper()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -266,13 +266,14 @@ following options are available::
 | 
				
			||||||
    :py:class:`PIL.ImagePalette.ImagePalette` object.
 | 
					    :py:class:`PIL.ImagePalette.ImagePalette` object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**optimize**
 | 
					**optimize**
 | 
				
			||||||
    Whether to attempt to compress the palette by eliminating unused colors.
 | 
					    Whether to attempt to compress the palette by eliminating unused colors
 | 
				
			||||||
 | 
					    (this is only useful if the palette can be compressed to the next smaller
 | 
				
			||||||
 | 
					    power of 2 elements) and whether to mark all pixels that are not new in the
 | 
				
			||||||
 | 
					    next frame as transparent.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    This is attempted by default, unless a palette is specified as an option or
 | 
					    This is attempted by default, unless a palette is specified as an option or
 | 
				
			||||||
    as part of the first image's :py:attr:`~PIL.Image.Image.info` dictionary.
 | 
					    as part of the first image's :py:attr:`~PIL.Image.Image.info` dictionary.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    This is only useful if the palette can be compressed to the next smaller
 | 
					 | 
				
			||||||
    power of 2 elements.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Note that if the image you are saving comes from an existing GIF, it may have
 | 
					Note that if the image you are saving comes from an existing GIF, it may have
 | 
				
			||||||
the following properties in its :py:attr:`~PIL.Image.Image.info` dictionary.
 | 
					the following properties in its :py:attr:`~PIL.Image.Image.info` dictionary.
 | 
				
			||||||
For these options, if you do not pass them in, they will default to
 | 
					For these options, if you do not pass them in, they will default to
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,15 @@ import os
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
from enum import IntEnum
 | 
					from enum import IntEnum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
 | 
					from . import (
 | 
				
			||||||
 | 
					    Image,
 | 
				
			||||||
 | 
					    ImageChops,
 | 
				
			||||||
 | 
					    ImageFile,
 | 
				
			||||||
 | 
					    ImageMath,
 | 
				
			||||||
 | 
					    ImageOps,
 | 
				
			||||||
 | 
					    ImagePalette,
 | 
				
			||||||
 | 
					    ImageSequence,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from ._binary import i16le as i16
 | 
					from ._binary import i16le as i16
 | 
				
			||||||
from ._binary import o8
 | 
					from ._binary import o8
 | 
				
			||||||
from ._binary import o16le as o16
 | 
					from ._binary import o16le as o16
 | 
				
			||||||
| 
						 | 
					@ -536,7 +544,15 @@ def _normalize_palette(im, palette, info):
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        used_palette_colors = _get_optimize(im, info)
 | 
					        used_palette_colors = _get_optimize(im, info)
 | 
				
			||||||
        if used_palette_colors is not None:
 | 
					        if used_palette_colors is not None:
 | 
				
			||||||
            return im.remap_palette(used_palette_colors, source_palette)
 | 
					            im = im.remap_palette(used_palette_colors, source_palette)
 | 
				
			||||||
 | 
					            if "transparency" in info:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    info["transparency"] = used_palette_colors.index(
 | 
				
			||||||
 | 
					                        info["transparency"]
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                except ValueError:
 | 
				
			||||||
 | 
					                    del info["transparency"]
 | 
				
			||||||
 | 
					            return im
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im.palette.palette = source_palette
 | 
					    im.palette.palette = source_palette
 | 
				
			||||||
    return im
 | 
					    return im
 | 
				
			||||||
| 
						 | 
					@ -564,13 +580,11 @@ def _write_single_frame(im, fp, palette):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _getbbox(base_im, im_frame):
 | 
					def _getbbox(base_im, im_frame):
 | 
				
			||||||
    if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im):
 | 
					    if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im):
 | 
				
			||||||
 | 
					        im_frame = im_frame.convert("RGBA")
 | 
				
			||||||
 | 
					        base_im = base_im.convert("RGBA")
 | 
				
			||||||
    delta = ImageChops.subtract_modulo(im_frame, base_im)
 | 
					    delta = ImageChops.subtract_modulo(im_frame, base_im)
 | 
				
			||||||
    else:
 | 
					    return delta, delta.getbbox(alpha_only=False)
 | 
				
			||||||
        delta = ImageChops.subtract_modulo(
 | 
					 | 
				
			||||||
            im_frame.convert("RGBA"), base_im.convert("RGBA")
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    return delta.getbbox(alpha_only=False)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _write_multiple_frames(im, fp, palette):
 | 
					def _write_multiple_frames(im, fp, palette):
 | 
				
			||||||
| 
						 | 
					@ -578,6 +592,7 @@ def _write_multiple_frames(im, fp, palette):
 | 
				
			||||||
    disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
 | 
					    disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    im_frames = []
 | 
					    im_frames = []
 | 
				
			||||||
 | 
					    previous_im = None
 | 
				
			||||||
    frame_count = 0
 | 
					    frame_count = 0
 | 
				
			||||||
    background_im = None
 | 
					    background_im = None
 | 
				
			||||||
    for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
 | 
					    for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
 | 
				
			||||||
| 
						 | 
					@ -591,9 +606,9 @@ def _write_multiple_frames(im, fp, palette):
 | 
				
			||||||
                    im.encoderinfo.setdefault(k, v)
 | 
					                    im.encoderinfo.setdefault(k, v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            encoderinfo = im.encoderinfo.copy()
 | 
					            encoderinfo = im.encoderinfo.copy()
 | 
				
			||||||
            im_frame = _normalize_palette(im_frame, palette, encoderinfo)
 | 
					 | 
				
			||||||
            if "transparency" in im_frame.info:
 | 
					            if "transparency" in im_frame.info:
 | 
				
			||||||
                encoderinfo.setdefault("transparency", im_frame.info["transparency"])
 | 
					                encoderinfo.setdefault("transparency", im_frame.info["transparency"])
 | 
				
			||||||
 | 
					            im_frame = _normalize_palette(im_frame, palette, encoderinfo)
 | 
				
			||||||
            if isinstance(duration, (list, tuple)):
 | 
					            if isinstance(duration, (list, tuple)):
 | 
				
			||||||
                encoderinfo["duration"] = duration[frame_count]
 | 
					                encoderinfo["duration"] = duration[frame_count]
 | 
				
			||||||
            elif duration is None and "duration" in im_frame.info:
 | 
					            elif duration is None and "duration" in im_frame.info:
 | 
				
			||||||
| 
						 | 
					@ -602,14 +617,16 @@ def _write_multiple_frames(im, fp, palette):
 | 
				
			||||||
                encoderinfo["disposal"] = disposal[frame_count]
 | 
					                encoderinfo["disposal"] = disposal[frame_count]
 | 
				
			||||||
            frame_count += 1
 | 
					            frame_count += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            diff_frame = None
 | 
				
			||||||
            if im_frames:
 | 
					            if im_frames:
 | 
				
			||||||
                # delta frame
 | 
					                # delta frame
 | 
				
			||||||
                previous = im_frames[-1]
 | 
					                delta, bbox = _getbbox(previous_im, im_frame)
 | 
				
			||||||
                bbox = _getbbox(previous["im"], im_frame)
 | 
					 | 
				
			||||||
                if not bbox:
 | 
					                if not bbox:
 | 
				
			||||||
                    # This frame is identical to the previous frame
 | 
					                    # This frame is identical to the previous frame
 | 
				
			||||||
                    if encoderinfo.get("duration"):
 | 
					                    if encoderinfo.get("duration"):
 | 
				
			||||||
                        previous["encoderinfo"]["duration"] += encoderinfo["duration"]
 | 
					                        im_frames[-1]["encoderinfo"]["duration"] += encoderinfo[
 | 
				
			||||||
 | 
					                            "duration"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                if encoderinfo.get("disposal") == 2:
 | 
					                if encoderinfo.get("disposal") == 2:
 | 
				
			||||||
                    if background_im is None:
 | 
					                    if background_im is None:
 | 
				
			||||||
| 
						 | 
					@ -619,10 +636,44 @@ def _write_multiple_frames(im, fp, palette):
 | 
				
			||||||
                        background = _get_background(im_frame, color)
 | 
					                        background = _get_background(im_frame, color)
 | 
				
			||||||
                        background_im = Image.new("P", im_frame.size, background)
 | 
					                        background_im = Image.new("P", im_frame.size, background)
 | 
				
			||||||
                        background_im.putpalette(im_frames[0]["im"].palette)
 | 
					                        background_im.putpalette(im_frames[0]["im"].palette)
 | 
				
			||||||
                    bbox = _getbbox(background_im, im_frame)
 | 
					                    delta, bbox = _getbbox(background_im, im_frame)
 | 
				
			||||||
 | 
					                if encoderinfo.get("optimize") and im_frame.mode != "1":
 | 
				
			||||||
 | 
					                    if "transparency" not in encoderinfo:
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            encoderinfo[
 | 
				
			||||||
 | 
					                                "transparency"
 | 
				
			||||||
 | 
					                            ] = im_frame.palette._new_color_index(im_frame)
 | 
				
			||||||
 | 
					                        except ValueError:
 | 
				
			||||||
 | 
					                            pass
 | 
				
			||||||
 | 
					                    if "transparency" in encoderinfo:
 | 
				
			||||||
 | 
					                        # When the delta is zero, fill the image with transparency
 | 
				
			||||||
 | 
					                        diff_frame = im_frame.copy()
 | 
				
			||||||
 | 
					                        fill = Image.new(
 | 
				
			||||||
 | 
					                            "P", diff_frame.size, encoderinfo["transparency"]
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        if delta.mode == "RGBA":
 | 
				
			||||||
 | 
					                            r, g, b, a = delta.split()
 | 
				
			||||||
 | 
					                            mask = ImageMath.eval(
 | 
				
			||||||
 | 
					                                "convert(max(max(max(r, g), b), a) * 255, '1')",
 | 
				
			||||||
 | 
					                                r=r,
 | 
				
			||||||
 | 
					                                g=g,
 | 
				
			||||||
 | 
					                                b=b,
 | 
				
			||||||
 | 
					                                a=a,
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            if delta.mode == "P":
 | 
				
			||||||
 | 
					                                # Convert to L without considering palette
 | 
				
			||||||
 | 
					                                delta_l = Image.new("L", delta.size)
 | 
				
			||||||
 | 
					                                delta_l.putdata(delta.getdata())
 | 
				
			||||||
 | 
					                                delta = delta_l
 | 
				
			||||||
 | 
					                            mask = ImageMath.eval("convert(im * 255, '1')", im=delta)
 | 
				
			||||||
 | 
					                        diff_frame.paste(fill, mask=ImageOps.invert(mask))
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                bbox = None
 | 
					                bbox = None
 | 
				
			||||||
            im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
 | 
					            previous_im = im_frame
 | 
				
			||||||
 | 
					            im_frames.append(
 | 
				
			||||||
 | 
					                {"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo}
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if len(im_frames) == 1:
 | 
					    if len(im_frames) == 1:
 | 
				
			||||||
        if "duration" in im.encoderinfo:
 | 
					        if "duration" in im.encoderinfo:
 | 
				
			||||||
| 
						 | 
					@ -680,22 +731,10 @@ def get_interlace(im):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _write_local_header(fp, im, offset, flags):
 | 
					def _write_local_header(fp, im, offset, flags):
 | 
				
			||||||
    transparent_color_exists = False
 | 
					 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        transparency = int(im.encoderinfo["transparency"])
 | 
					        transparency = im.encoderinfo["transparency"]
 | 
				
			||||||
    except (KeyError, ValueError):
 | 
					    except KeyError:
 | 
				
			||||||
        pass
 | 
					        transparency = None
 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        # optimize the block away if transparent color is not used
 | 
					 | 
				
			||||||
        transparent_color_exists = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        used_palette_colors = _get_optimize(im, im.encoderinfo)
 | 
					 | 
				
			||||||
        if used_palette_colors is not None:
 | 
					 | 
				
			||||||
            # adjust the transparency index after optimize
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                transparency = used_palette_colors.index(transparency)
 | 
					 | 
				
			||||||
            except ValueError:
 | 
					 | 
				
			||||||
                transparent_color_exists = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if "duration" in im.encoderinfo:
 | 
					    if "duration" in im.encoderinfo:
 | 
				
			||||||
        duration = int(im.encoderinfo["duration"] / 10)
 | 
					        duration = int(im.encoderinfo["duration"] / 10)
 | 
				
			||||||
| 
						 | 
					@ -704,11 +743,9 @@ def _write_local_header(fp, im, offset, flags):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    disposal = int(im.encoderinfo.get("disposal", 0))
 | 
					    disposal = int(im.encoderinfo.get("disposal", 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if transparent_color_exists or duration != 0 or disposal:
 | 
					    if transparency is not None or duration != 0 or disposal:
 | 
				
			||||||
        packed_flag = 1 if transparent_color_exists else 0
 | 
					        packed_flag = 1 if transparency is not None else 0
 | 
				
			||||||
        packed_flag |= disposal << 2
 | 
					        packed_flag |= disposal << 2
 | 
				
			||||||
        if not transparent_color_exists:
 | 
					 | 
				
			||||||
            transparency = 0
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fp.write(
 | 
					        fp.write(
 | 
				
			||||||
            b"!"
 | 
					            b"!"
 | 
				
			||||||
| 
						 | 
					@ -716,7 +753,7 @@ def _write_local_header(fp, im, offset, flags):
 | 
				
			||||||
            + o8(4)  # length
 | 
					            + o8(4)  # length
 | 
				
			||||||
            + o8(packed_flag)  # packed fields
 | 
					            + o8(packed_flag)  # packed fields
 | 
				
			||||||
            + o16(duration)  # duration
 | 
					            + o16(duration)  # duration
 | 
				
			||||||
            + o8(transparency)  # transparency index
 | 
					            + o8(transparency or 0)  # transparency index
 | 
				
			||||||
            + o8(0)
 | 
					            + o8(0)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -804,7 +841,7 @@ def _get_optimize(im, info):
 | 
				
			||||||
    :param info: encoderinfo
 | 
					    :param info: encoderinfo
 | 
				
			||||||
    :returns: list of indexes of palette entries in use, or None
 | 
					    :returns: list of indexes of palette entries in use, or None
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if im.mode in ("P", "L") and info and info.get("optimize", 0):
 | 
					    if im.mode in ("P", "L") and info and info.get("optimize"):
 | 
				
			||||||
        # Potentially expensive operation.
 | 
					        # Potentially expensive operation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The palette saves 3 bytes per color not used, but palette
 | 
					        # The palette saves 3 bytes per color not used, but palette
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,6 +102,30 @@ class ImagePalette:
 | 
				
			||||||
    # Declare tostring as an alias for tobytes
 | 
					    # Declare tostring as an alias for tobytes
 | 
				
			||||||
    tostring = tobytes
 | 
					    tostring = tobytes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _new_color_index(self, image=None, e=None):
 | 
				
			||||||
 | 
					        if not isinstance(self.palette, bytearray):
 | 
				
			||||||
 | 
					            self._palette = bytearray(self.palette)
 | 
				
			||||||
 | 
					        index = len(self.palette) // 3
 | 
				
			||||||
 | 
					        special_colors = ()
 | 
				
			||||||
 | 
					        if image:
 | 
				
			||||||
 | 
					            special_colors = (
 | 
				
			||||||
 | 
					                image.info.get("background"),
 | 
				
			||||||
 | 
					                image.info.get("transparency"),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            while index in special_colors:
 | 
				
			||||||
 | 
					                index += 1
 | 
				
			||||||
 | 
					        if index >= 256:
 | 
				
			||||||
 | 
					            if image:
 | 
				
			||||||
 | 
					                # Search for an unused index
 | 
				
			||||||
 | 
					                for i, count in reversed(list(enumerate(image.histogram()))):
 | 
				
			||||||
 | 
					                    if count == 0 and i not in special_colors:
 | 
				
			||||||
 | 
					                        index = i
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					            if index >= 256:
 | 
				
			||||||
 | 
					                msg = "cannot allocate more than 256 colors"
 | 
				
			||||||
 | 
					                raise ValueError(msg) from e
 | 
				
			||||||
 | 
					        return index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def getcolor(self, color, image=None):
 | 
					    def getcolor(self, color, image=None):
 | 
				
			||||||
        """Given an rgb tuple, allocate palette entry.
 | 
					        """Given an rgb tuple, allocate palette entry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,27 +148,7 @@ class ImagePalette:
 | 
				
			||||||
                return self.colors[color]
 | 
					                return self.colors[color]
 | 
				
			||||||
            except KeyError as e:
 | 
					            except KeyError as e:
 | 
				
			||||||
                # allocate new color slot
 | 
					                # allocate new color slot
 | 
				
			||||||
                if not isinstance(self.palette, bytearray):
 | 
					                index = self._new_color_index(image, e)
 | 
				
			||||||
                    self._palette = bytearray(self.palette)
 | 
					 | 
				
			||||||
                index = len(self.palette) // 3
 | 
					 | 
				
			||||||
                special_colors = ()
 | 
					 | 
				
			||||||
                if image:
 | 
					 | 
				
			||||||
                    special_colors = (
 | 
					 | 
				
			||||||
                        image.info.get("background"),
 | 
					 | 
				
			||||||
                        image.info.get("transparency"),
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                while index in special_colors:
 | 
					 | 
				
			||||||
                    index += 1
 | 
					 | 
				
			||||||
                if index >= 256:
 | 
					 | 
				
			||||||
                    if image:
 | 
					 | 
				
			||||||
                        # Search for an unused index
 | 
					 | 
				
			||||||
                        for i, count in reversed(list(enumerate(image.histogram()))):
 | 
					 | 
				
			||||||
                            if count == 0 and i not in special_colors:
 | 
					 | 
				
			||||||
                                index = i
 | 
					 | 
				
			||||||
                                break
 | 
					 | 
				
			||||||
                    if index >= 256:
 | 
					 | 
				
			||||||
                        msg = "cannot allocate more than 256 colors"
 | 
					 | 
				
			||||||
                        raise ValueError(msg) from e
 | 
					 | 
				
			||||||
                self.colors[color] = index
 | 
					                self.colors[color] = index
 | 
				
			||||||
                if index * 3 < len(self.palette):
 | 
					                if index * 3 < len(self.palette):
 | 
				
			||||||
                    self._palette = (
 | 
					                    self._palette = (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user