Changing palette's of 8-bit .png images using python PIL
Solution 1
If you want to change just the palette, then PIL will just get in your way. Luckily, the PNG file format was designed to be easy to deal with when you only are interested in some of the data chunks. The format of the PLTE chunk is just an array of RGB triples, with a CRC at the end. To change the palette on a file in-place without reading or writing the whole file:
import struct
from zlib import crc32
import os
# PNG file format signature
pngsig = '\x89PNG\r\n\x1a\n'
def swap_palette(filename):
# open in read+write mode
with open(filename, 'r+b') as f:
f.seek(0)
# verify that we have a PNG file
if f.read(len(pngsig)) != pngsig:
raise RuntimeError('not a png file!')
while True:
chunkstr = f.read(8)
if len(chunkstr) != 8:
# end of file
break
# decode the chunk header
length, chtype = struct.unpack('>L4s', chunkstr)
# we only care about palette chunks
if chtype == 'PLTE':
curpos = f.tell()
paldata = f.read(length)
# change the 3rd palette entry to cyan
paldata = paldata[:6] + '\x00\xff\xde' + paldata[9:]
# go back and write the modified palette in-place
f.seek(curpos)
f.write(paldata)
f.write(struct.pack('>L', crc32(chtype+paldata)&0xffffffff))
else:
# skip over non-palette chunks
f.seek(length+4, os.SEEK_CUR)
if __name__ == '__main__':
import shutil
shutil.copyfile('redghost.png', 'blueghost.png')
swap_palette('blueghost.png')
This code copies redghost.png over to blueghost.png and modifies the palette of blueghost.png in-place.
->
Solution 2
im.palette
is not callable -- it's an instance of the ImagePalette
class, in mode P
, otherwise None
. im.putpalette(...)
is a method, so callable: the argument must be a sequence of 768 integers giving R, G and B value at each index.
Comments
-
Jack Ha almost 2 years
I'm looking for a fast way to apply a new palette to an existing 8-bit .png image. How can I do that? Is the .png re-encoded when I save the image? (Own answer: it seems so)
What I have tried (edited):
import Image, ImagePalette output = StringIO.StringIO() palette = (.....) #long palette of 768 items im = Image.open('test_palette.png') #8 bit image im.putpalette(palette) im.save(output, format='PNG')
With my testimage the save function takes about 65 millis. My thought: without the decoding and encoding, it can be a lot faster??