[Image-SIG] Image.putpalette() bug
Derek Simkowiak
dereks@realloc.net
Wed, 30 Apr 2003 15:00:20 -0700
This is a multi-part message in MIME format.
--------------040708060501040601070102
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
I was testing my WalImagePlugin.py file, and got a strange message when
I tried to "show()" it:
[dereks@localhost src]$ cat wal_test.py
#!/usr/bin/env python
import Image
import WalImagePlugin
wal_image = Image.open('PAK_DATA/textures/e1u1/basemap.wal')
wal_image.show()
[dereks@localhost src]$ ./wal_test.py
Traceback (most recent call last):
File "./wal_test.py", line 7, in ?
wal_image.show()
File "/usr/lib/python2.2/site-packages/PIL/Image.py", line 770, in show
_showxv(self, title, command)
File "/usr/lib/python2.2/site-packages/PIL/Image.py", line 1079, in
_showxv
file = self.convert(base)._dump(format=format)
File "/usr/lib/python2.2/site-packages/PIL/Image.py", line 345, in _dump
self.load()
File "/usr/lib/python2.2/site-packages/PIL/Image.py", line 414, in load
self.im.putpalette(self.palette.rawmode, self.palette.data)
ValueError: unrecognized image mode
[dereks@localhost src]$
I did some digging and found this:
[root@localhost PIL]# grep -n "putpalette(" Image.py
414: self.im.putpalette(self.palette.rawmode, self.palette.data)
663: def putpalette(self, data, rawmode="RGB"):
Notice that in load(), where my test dies, it passes rawmode as arg1
and data as arg2. But in the method definition, data is arg1 and
rawmode is arg2. The file WmfImagePlugin.py also calls putpalette()
with (rawmode, data) instead of the other way around.
So I did the following change, hoping to fix my problem:
[root@localhost PIL]# diff -w ./Image.py ./Image-dist.py
414c414
< self.im.putpalette(self.palette.data, self.palette.rawmode)
---
> self.im.putpalette(self.palette.rawmode, self.palette.data)
But it did not. I noticed that the string "unrecognized image mode" is
only in the c module "_imaging.so", under the C string constant
"wrong_mode", and not in the Python code. In _imaging.c, I found this:
_putpalette(ImagingObject* self, PyObject* args)
{
[...]
if (!PyArg_ParseTuple(args, "ss#", &rawmode, &palette, &palettesize))
return NULL;
if (strcmp(self->image->mode, "L") != 0 &&
strcmp(self->image->mode, "P")) {
PyErr_SetString(PyExc_ValueError, wrong_mode);
return NULL;
}
[...]
}
It looks like the C module still expects rawmode first, and palette second.
Presumably, we need rawmode to be second, so that we can use the
default arg of "RGB". So I made this change:
[root@localhost Imaging-1.1.3]# diff ./_imaging.c _imaging-dist.c
1080c1080
< if (!PyArg_ParseTuple(args, "s#s", &palette, &palettesize, &rawmode))
---
> if (!PyArg_ParseTuple(args, "ss#", &rawmode, &palette, &palettesize))
That is, I changed the C code to match the arg list "putpalette()" in
Image.py. Unfortunately, it did not fix my problem. after a recompile,
I still get the same results:
[dereks@localhost src]$ ./wal_test.py
Traceback (most recent call last):
File "./wal_test.py", line 7, in ?
wal_image.show()
File "/usr/lib/python2.2/site-packages/PIL/Image.py", line 770, in show
_showxv(self, title, command)
File "/usr/lib/python2.2/site-packages/PIL/Image.py", line 1079, in
_showxv
file = self.convert(base)._dump(format=format)
File "/usr/lib/python2.2/site-packages/PIL/Image.py", line 345, in _dump
self.load()
File "/usr/lib/python2.2/site-packages/PIL/Image.py", line 414, in load
self.im.putpalette(self.palette.data, self.palette.rawmode)
ValueError: unrecognized image mode
[dereks@localhost src]$
A little more investigation revealed that the reason I'm getting the
"ValueError: unrecognized image mode" is that in _imaging.c, in the
function _putpalette(), at this test at the beginning of the function:
if (strcmp(self->image->mode, "L") != 0 &&
strcmp(self->image->mode, "P")) {
PyErr_SetString(PyExc_ValueError, wrong_mode);
return NULL;
}
...the value of self->image->mode is "RGB", which, being neither "L"
nor "P", results in the exception.
I do not know why it is "RGB", or what function sets it to that. But
it seems that there are two problems with the code:
First, inconsistent argument order for "putpalette()". Here are the
diffs that I think will fix this first problem, which will allow
"RGB" to be the default value of the second argument:
[root@localhost PIL]# diff Image.py Image-dist.py
414c414
< self.im.putpalette(self.palette.data, self.palette.rawmode)
---
> self.im.putpalette(self.palette.rawmode, self.palette.data)
[root@localhost PIL]# diff WmfImagePlugin.py WmfImagePlugin-dist.py
271c271
< self.im.putpalette(string.join(palette, ""), "RGB")
---
> self.im.putpalette("RGB", string.join(palette, ""))
[root@localhost PIL]# cd ..
[root@localhost Imaging-1.1.3]# diff _imaging.c _imaging-dist.c
1080c1080
< if (!PyArg_ParseTuple(args, "s#s", &palette, &palettesize, &rawmode))
---
> if (!PyArg_ParseTuple(args, "ss#", &rawmode, &palette, &palettesize))
[root@localhost Imaging-1.1.3]#
The second problem is the question, why can my Image have
"self->image->mode" set to "RGB" when I hit the function "putpalette()"
in _imaging.c? This one I have not been able to find yet, but appears
to be a bug within PIL itself, since the _open() of my WalImagePlugin
explicitly sets self->mode to "P".
Note: I have attached WalImagePlugin.py for reference, but I do not
expect the problem to be there.
Note2: I'll post my WalImagePlugin.py to this list again when it is
tested, and includes support for the Quake 33% and 66% transparency flag.
Please advise.
Thank You,
Derek Simkowiak
--------------040708060501040601070102
Content-Type: text/plain;
name="WalImagePlugin.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="WalImagePlugin.py"
#!/usr/bin/env python
import Image, ImageFile
import string
# Turns character byte data read from disk into an integer value.
# c is a PyString (of byte data), o is offset from the beginning
def i32(c, o=0):
return ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)
class WalImageFile(ImageFile.ImageFile):
format = "WAL"
format_description = "Id Software Quake WAL texture map"
def _open(self):
# The Quake2 default pallete is normally found
# in the file "pics/colormap.pcx" within PAK0.PAK.
# The palette is a list of 768 colour values,
# given as [R, G, B, R, G, B, ...]
self.mode = "P" # Pallete mode
header = self.fp.read(32+24+32+12)
self.size = i32(header, 32), i32(header, 36)
offset = i32(header, 40)
region = (0, 0) + self.size
parameters = (self.mode, 0, 1)
# A one-element list:
self.tile = [ ("raw", region, offset, parameters), ]
# Set WAL-specific information from the header:
# (Note: strings are null-terminated)
self.info["name"] = header[:32].split("\0", 1)[0]
self.info["next_name"] = header[56:56+32].split("\0", 1)[0]
self.info["flags"] = i32(header, (32+24+32) + 0)
self.info["contents"] = i32(header, (32+24+32) + 4)
self.info["value"] = i32(header, (32+24+32) + 8)
def load_end(self):
self.putpalette(QUAKE2PALETTE)
Image.register_open("WAL", WalImageFile)
Image.register_extension("WAL", ".wal")
QUAKE2PALETTE = (
"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e"
"\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f"
"\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c"
"\x24\x1e\x13\x22\x1c\x12\x20\x1b\x12\x1f\x1a\x10\x1d\x19\x10\x1b"
"\x17\x0f\x1a\x16\x0f\x18\x14\x0d\x17\x13\x0d\x16\x12\x0d\x14\x10"
"\x0b\x13\x0f\x0b\x10\x0d\x0a\x0f\x0b\x0a\x0d\x0b\x07\x0b\x0a\x07"
"\x23\x23\x26\x22\x22\x25\x22\x20\x23\x21\x1f\x22\x20\x1e\x20\x1f"
"\x1d\x1e\x1d\x1b\x1c\x1b\x1a\x1a\x1a\x19\x19\x18\x17\x17\x17\x16"
"\x16\x14\x14\x14\x13\x13\x13\x10\x10\x10\x0f\x0f\x0f\x0d\x0d\x0d"
"\x2d\x28\x20\x29\x24\x1c\x27\x22\x1a\x25\x1f\x17\x38\x2e\x1e\x31"
"\x29\x1a\x2c\x25\x17\x26\x20\x14\x3c\x30\x14\x37\x2c\x13\x33\x28"
"\x12\x2d\x24\x10\x28\x1f\x0f\x22\x1a\x0b\x1b\x14\x0a\x13\x0f\x07"
"\x31\x1a\x16\x30\x17\x13\x2e\x16\x10\x2c\x14\x0d\x2a\x12\x0b\x27"
"\x0f\x0a\x25\x0f\x07\x21\x0d\x01\x1e\x0b\x01\x1c\x0b\x01\x1a\x0b"
"\x01\x18\x0a\x01\x16\x0a\x01\x13\x0a\x01\x10\x07\x01\x0d\x07\x01"
"\x29\x23\x1e\x27\x21\x1c\x26\x20\x1b\x25\x1f\x1a\x23\x1d\x19\x21"
"\x1c\x18\x20\x1b\x17\x1e\x19\x16\x1c\x18\x14\x1b\x17\x13\x19\x14"
"\x10\x17\x13\x0f\x14\x10\x0d\x12\x0f\x0b\x0f\x0b\x0a\x0b\x0a\x07"
"\x26\x1a\x0f\x23\x19\x0f\x20\x17\x0f\x1c\x16\x0f\x19\x13\x0d\x14"
"\x10\x0b\x10\x0d\x0a\x0b\x0a\x07\x33\x22\x1f\x35\x29\x26\x37\x2f"
"\x2d\x39\x35\x34\x37\x39\x3a\x33\x37\x39\x30\x34\x36\x2b\x31\x34"
"\x27\x2e\x31\x22\x2b\x2f\x1d\x28\x2c\x17\x25\x2a\x0f\x20\x26\x0d"
"\x1e\x25\x0b\x1c\x22\x0a\x1b\x20\x07\x19\x1e\x07\x17\x1b\x07\x14"
"\x18\x01\x12\x16\x01\x0f\x12\x01\x0b\x0d\x01\x07\x0a\x01\x01\x01"
"\x2c\x21\x21\x2a\x1f\x1f\x29\x1d\x1d\x27\x1c\x1c\x26\x1a\x1a\x24"
"\x18\x18\x22\x17\x17\x21\x16\x16\x1e\x13\x13\x1b\x12\x12\x18\x10"
"\x10\x16\x0d\x0d\x12\x0b\x0b\x0d\x0a\x0a\x0a\x07\x07\x01\x01\x01"
"\x2e\x30\x29\x2d\x2e\x27\x2b\x2c\x26\x2a\x2a\x24\x28\x29\x23\x27"
"\x27\x21\x26\x26\x1f\x24\x24\x1d\x22\x22\x1c\x1f\x1f\x1a\x1c\x1c"
"\x18\x19\x19\x16\x17\x17\x13\x13\x13\x10\x0f\x0f\x0d\x0b\x0b\x0a"
"\x30\x1e\x1b\x2d\x1c\x19\x2c\x1a\x17\x2a\x19\x14\x28\x17\x13\x26"
"\x16\x10\x24\x13\x0f\x21\x12\x0d\x1f\x10\x0b\x1c\x0f\x0a\x19\x0d"
"\x0a\x16\x0b\x07\x12\x0a\x07\x0f\x07\x01\x0a\x01\x01\x01\x01\x01"
"\x28\x29\x38\x26\x27\x36\x25\x26\x34\x24\x24\x31\x22\x22\x2f\x20"
"\x21\x2d\x1e\x1f\x2a\x1d\x1d\x27\x1b\x1b\x25\x19\x19\x21\x17\x17"
"\x1e\x14\x14\x1b\x13\x12\x17\x10\x0f\x13\x0d\x0b\x0f\x0a\x07\x07"
"\x2f\x32\x29\x2d\x30\x26\x2b\x2e\x24\x29\x2c\x21\x27\x2a\x1e\x25"
"\x28\x1c\x23\x26\x1a\x21\x25\x18\x1e\x22\x14\x1b\x1f\x10\x19\x1c"
"\x0d\x17\x1a\x0a\x13\x17\x07\x10\x13\x01\x0d\x0f\x01\x0a\x0b\x01"
"\x01\x3f\x01\x13\x3c\x0b\x1b\x39\x10\x20\x35\x14\x23\x31\x17\x23"
"\x2d\x18\x23\x29\x18\x3f\x3f\x3f\x3f\x3f\x39\x3f\x3f\x31\x3f\x3f"
"\x2a\x3f\x3f\x20\x3f\x3f\x14\x3f\x3c\x12\x3f\x39\x0f\x3f\x35\x0b"
"\x3f\x32\x07\x3f\x2d\x01\x3d\x2a\x01\x3b\x26\x01\x39\x21\x01\x37"
"\x1d\x01\x34\x1a\x01\x32\x16\x01\x2f\x12\x01\x2d\x0f\x01\x2a\x0b"
"\x01\x27\x07\x01\x23\x01\x01\x1d\x01\x01\x17\x01\x01\x10\x01\x01"
"\x3d\x01\x01\x19\x19\x3f\x3f\x01\x01\x01\x01\x3f\x16\x16\x13\x10"
"\x10\x0f\x0d\x0d\x0b\x3c\x2e\x2a\x36\x27\x20\x30\x21\x18\x29\x1b"
"\x10\x3c\x39\x37\x37\x32\x2f\x31\x2c\x28\x2b\x26\x21\x30\x22\x20"
)
--------------040708060501040601070102--