Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IndexError while drawing on an image #1987

Closed
obskyr opened this issue Jun 25, 2016 · 5 comments
Closed

IndexError while drawing on an image #1987

obskyr opened this issue Jun 25, 2016 · 5 comments
Labels
Bug Any unexpected behavior, until confirmed feature. GIF Palette

Comments

@obskyr
Copy link

obskyr commented Jun 25, 2016

With Pillow 3.2.0 on Python 3 (and on Python 2, with the fix in #1985), running the very same code as in #1592 with the very same image slightly edited, the following traceback occurs:

Traceback (most recent call last):
  File "C:\Python34\lib\site-packages\PIL\ImagePalette.py", line 97, in getcolor
    return self.colors[color]
KeyError: (255, 255, 255)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "deborder.py", line 30, in <module>
    deborder(sys.argv[1])
  File "deborder.py", line 20, in deborder
    draw.line((topLeft, topRight),       fill=(255, 255, 255))
  File "C:\Python34\lib\site-packages\PIL\ImageDraw.py", line 177, in line
    ink, fill = self._getink(fill)
  File "C:\Python34\lib\site-packages\PIL\ImageDraw.py", line 124, in _getink
    ink = self.palette.getcolor(ink)
  File "C:\Python34\lib\site-packages\PIL\ImagePalette.py", line 108, in getcolor
    self.palette[index+256] = color[1]
IndexError: list assignment index out of range

Before the #1985 fix, the error is an IndexError: list assignment index out of range. After, it persists as an IndexError: bytearray index out of range, however.

From what I can tell, self.palette is shorter than 256 elements, and can therefore not take an assignment to index index+256. In this case, index is 0, as len(self.colors) in getcolor for this image is 0, which I find a bit odd.

Since the image is almost exactly the one used in #1592, this may be related. The difference is this one is edited in ImageMagick to make the outer 1px border white (#FFFFFF).


The very same code as in #1592 causes the error:

# -*- coding: utf-8 -*-

from PIL import Image, ImageDraw

def deborder(filename):
    frame = Image.open(filename)
    frameNo = 0
    while True:
        try:
            frame.seek(frameNo)
        except EOFError:
            break
        draw = ImageDraw.Draw(frame)

        topLeft = (0, 0)
        topRight = (frame.size[0] - 1, 0)
        bottomLeft = (0, frame.size[1] - 1)
        bottomRight = (frame.size[0] - 1, frame.size[1] - 1)

        draw.line((topLeft, topRight),       fill=(255, 255, 255))
        draw.line((topLeft, bottomLeft),     fill=(255, 255, 255))
        draw.line((bottomLeft, bottomRight), fill=(255, 255, 255))
        draw.line((topRight, bottomRight),   fill=(255, 255, 255))

        frameNo += 1

    frame.save('resave-'+filename, save_all=True)

deborder('no.gif')

With the following image, for example (while the error occurs with the exact image in #1592 edited a bit, I chose one with a different glyph to avoid future confusion):

no

@wiredfool
Copy link
Member

wiredfool commented Jun 26, 2016

So, there are at least three problems here.

Edit -- no, not this, there are just a bunch of null bytes at the end, and they're probably just extra unused colors.

  1. The raw palette is 192 bytes long, and it appears to be in RRRGGGBBBAAA mode. Internally though, the palette is listed as RGB.

Edit -- no, the palette is set 'correctly' image.im.getpalette, but image.palette is not right.
2) The palette is loaded with a raw mode, and when Image.load() is called on the image (or frame), Image.putpalette(*im.palette.getdata()) is called, which sets the new, non-raw, byte identical palette in _imaging.c without checking for length, so while the python version has a n*256 length, this is allowed to have any length. This later fails because we expect a palette of length 768.

  1. When added in a test in test_imagedraw.py, it passes. When run separately, it fails.

additional edits:

There's some severe disconnect between the lower level palette information and the python level palettes. I don't see where the python colors dict is ever populated, so when draw happens on a newly loaded image, there are no colors loaded, so it starts overwriting at the first one. The palette at that point may be scrambled if it's come from a raw palette, and would be in the RGB mode instead of RGB;L.

It's possible that on load we want to take the palette from the c layer and bring it back up to the python layer, but we'd probably want to know how long it is to prepopulate the colors dictionary.

@ghost
Copy link

ghost commented Jun 26, 2016

f7d8466 This fixes the IndexError. The palette is still screwed up though.

@radarhere
Copy link
Member

The image is in P mode. Should the fill value not also be in P mode, rather than being supplied as RGB?

@siddharth-ncl-work
Copy link

siddharth-ncl-work commented Aug 11, 2019

I fixed the error by converting the image with 'P' mode into 'RGB' mode.
I got a similar error during the processing of multiple images. It turns out that one of my images was in 'P' mode while the rest of them were in 'RGB' mode. I was assuming all my images were in 'RGB' mode. I think because 'P' mode only has one channel as compared to 3 channels in 'RGB', I got index error for the 2 missing channels.
I will suggest please check modes of your images and keep them consistent

Check the following link to convert the mode:
#513

@radarhere
Copy link
Member

Trying again after #5552, I find that I just need to copy() each frame before drawing to make this work. Here is modified version of the original code.

from PIL import Image, ImageDraw

def deborder(filename):
    frame = Image.open(filename)
    frameNo = 0
    frames = []
    while True:
        try:
            frame.seek(frameNo)
        except EOFError:
            break
        image = frame.copy()
        draw = ImageDraw.Draw(image)

        topLeft = (0, 0)
        topRight = (frame.size[0] - 1, 0)
        bottomLeft = (0, frame.size[1] - 1)
        bottomRight = (frame.size[0] - 1, frame.size[1] - 1)

        draw.line((topLeft, topRight),       fill=(255, 255, 255))
        draw.line((topLeft, bottomLeft),     fill=(255, 255, 255))
        draw.line((bottomLeft, bottomRight), fill=(255, 255, 255))
        draw.line((topRight, bottomRight),   fill=(255, 255, 255))

        frameNo += 1
        frames.append(image)

    frames[0].save('resave-'+filename, save_all=True, append_images=frames[1:])

deborder('no.gif')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Any unexpected behavior, until confirmed feature. GIF Palette
Projects
None yet
Development

No branches or pull requests

4 participants