使用Python PIL更改8位PNG图像的调色板

4 投票
3 回答
14940 浏览
提问于 2025-04-15 13:03

我想找一个快速的方法,把新的调色板应用到一个已有的8位.pn图像上。我该怎么做呢?保存图像的时候,.png文件会重新编码吗?(我自己的回答:看起来是这样的)

我尝试过的(编辑过的):

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')

在我的测试图像中,保存功能大约需要65毫秒。我在想:如果不进行解码和编码,速度会不会快很多呢?

3 个回答

0

不解码和重新编码就更换调色板似乎是不可能的。问题中提到的方法看起来是目前最好的选择。如果性能很重要,编码成GIF格式似乎要快很多。

1

im.palette 不能被调用——它是 ImagePalette 类的一个实例,属于 P 模式,否则就是 None。而 im.putpalette(...) 是一个方法,所以可以被调用:这个方法的参数必须是一个包含 768 个整数的序列,这些整数分别代表每个索引位置的红色、绿色和蓝色的值。

8

如果你只想改变调色板,那么使用PIL(Python Imaging Library)可能会让事情变得复杂。不过幸运的是,PNG文件格式的设计就是为了方便处理一些特定的数据部分。PLTE块的格式其实就是一组RGB颜色值,最后还有一个CRC校验码。要在不读取或写入整个文件的情况下直接修改文件中的调色板,可以使用以下代码:

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')

这段代码会把redghost.png文件复制到blueghost.png,并在这个过程中直接修改blueghost.png的调色板。

red ghost -> blue ghost

撰写回答