[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--