Repository: python-pillow/Pillow

Issue:
Odd color change in GIF after overlaying text
## What did you do?
Tried to overlay some text over a GIF file

### What did you expect to happen?
I expected the outcome to look almost identical to the original in terms of quality.

### What actually happened?
The final result quality text looks odd, and the GIF looks like it had it's color inverted

### What are your OS, Python and Pillow versions?

* OS: Windows
* Python: 3.8
* Pillow: 6.2.1

Code used to modify the GIF from a URL: 
```python
image = Image.open(BytesIO(await request.read()))
frames = []
for frame in ImageSequence.Iterator(image):
    frame_bytes = BytesIO()
    draw = ImageDraw.Draw(frame)
    draw_text(frame, draw, top_text, 'top')
    draw_text(frame, draw, bottom_text, 'bottom')
    frame.save(frame_bytes, format='GIF')
    frames.append(Image.open(frame_bytes))
    del draw
edited_image_bytes = BytesIO()
frames[0].save(edited_image_bytes, format='GIF', save_all=True, append_images=frames[1:])
```
draw_text and draw_text_with_outline methods: https://hastebin.com/xoxecosawu.py

Files: [Gifs.zip](https://github.com/python-pillow/Pillow/files/3822773/Gifs.zip)




Discussion:
For reference, `draw_text` and `draw_text_with_outline methods`:
```python
font = ImageFont.truetype("impact.ttf", 42)

def draw_text_with_outline(draw, text, x, y):
    draw.text((x - 2, y - 2), text, (0, 0, 0), font=font)
    draw.text((x + 2, y - 2), text, (0, 0, 0), font=font)
    draw.text((x + 2, y + 2), text, (0, 0, 0), font=font)
    draw.text((x - 2, y + 2), text, (0, 0, 0), font=font)
    draw.text((x, y), text, (255, 255, 255), font=font)

def draw_text(img, draw, text, pos):
    text = text.upper()
    w, h = draw.textsize(text, font)
    line_count = int(round((w / img.width) + 1)) if w > img.width else 1

    lines = []
    if line_count > 1:
        last_cut = 0
        is_last = False
        for i in range(0, line_count):
            cut = (len(text) / line_count) * i if last_cut == 0 else last_cut

            if i < line_count - 1:
                next_cut = (len(text) / line_count) * (i + 1)
            else:
                next_cut = len(text)
                is_last = True

            if not (next_cut == len(text) or text[next_cut] == " "):
                while text[next_cut] != " ":
                    next_cut += 1

            line = text[cut:next_cut].strip()

            w, h = draw.textsize(line, font)
            if not is_last and w > img.width:
                next_cut -= 1
                while text[next_cut] != " ":
                    next_cut -= 1

            last_cut = next_cut
            lines.append(text[cut:next_cut].strip())
    else:
        lines.append(text)

    last_y = img.height - h * (line_count + 1) - 10 if pos == 'bottom' else -h

    for i in range(0, line_count):
        w, h = draw.textsize(lines[i], font)
        x = img.width / 2 - w / 2
        y = last_y + h
        draw_text_with_outline(draw, lines[i], x, y)
        last_y = y
```
For clarity, when you're talking about color inversion, I presume you're just talking about the first frame.

Regarding the text, I'm confused. You're saying it should be similar in quality to the original - what are you referring to? There is no original for the text.
This looks related to the palette. If I replace the RGB values with P values like so -
```python
def draw_text_with_outline(draw, text, x, y):
    draw.text((x - 2, y - 2), text, 2, font=font)
    draw.text((x + 2, y - 2), text, 2, font=font)
    draw.text((x + 2, y + 2), text, 2, font=font)
    draw.text((x - 2, y + 2), text, 2, font=font)
    draw.text((x, y), text, 17, font=font)
```
I find that the output image does not have the color inversion problem.
That seems to have fixed it. Now it seems to be filling in the background of clear background gifs with white.
Are you referring to files not mentioned here when you say 'clear background gifs'? Because with the change from my last comment, the output GIF looks fine to me.
Looking at the first frame, there is no local palette, and (0, 0, 0) and (255, 255, 255) are not part of the global palette. So to allow those colors to be added, we will have to implement a system to start discarding unused colors.

Resolving patch:
diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py
--- a/src/PIL/ImagePalette.py
+++ b/src/PIL/ImagePalette.py
@@ -118,11 +118,19 @@ def getcolor(self, color, image=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:
+                            if count == 0 and i not in special_colors:
                                 index = i
                                 break
                     if index >= 256:


Test patch:
diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py
--- a/Tests/test_imagepalette.py
+++ b/Tests/test_imagepalette.py
@@ -49,6 +49,29 @@ def test_getcolor():
         palette.getcolor("unknown")
 
 
+@pytest.mark.parametrize(
+    "index, palette",
+    [
+        # Test when the palette is not full
+        (0, ImagePalette.ImagePalette()),
+        # Test when the palette is full
+        (255, ImagePalette.ImagePalette("RGB", list(range(256)) * 3)),
+    ],
+)
+def test_getcolor_not_special(index, palette):
+    im = Image.new("P", (1, 1))
+
+    # Do not use transparency index as a new color
+    im.info["transparency"] = index
+    index1 = palette.getcolor((0, 0, 0), im)
+    assert index1 != index
+
+    # Do not use background index as a new color
+    im.info["background"] = index1
+    index2 = palette.getcolor((0, 0, 1), im)
+    assert index2 not in (index, index1)
+
+
 def test_file(tmp_path):
 
     palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)