使用 python PIL 更改 8 位 .png 图像的调色板

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1158736/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 21:36:48  来源:igfitidea点击:

Changing palette's of 8-bit .png images using python PIL

pythonimage-processingpython-imaging-library

提问by Hyman Ha

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)

我正在寻找一种将新调色板应用于现有 8 位 .png 图像的快速方法。我怎样才能做到这一点?保存图像时 .png 是否重新编码?(自己的答案:似乎是这样)

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

使用我的 testimage,保存功能大约需要 65 毫秒。我的想法:没有解码和编码,它可以快很多??

回答by Theran

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 chunkis 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:

如果您只想更改调色板,那么 PIL 会妨碍您。幸运的是,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')

This code copies redghost.png over to blueghost.png and modifies the palette of blueghost.png in-place.

此代码将 redghost.png 复制到 blueghost.png 并就地修改 blueghost.png 的调色板。

red ghost-> blue ghost

红鬼-> 蓝幽灵

回答by Alex Martelli

im.paletteis not callable -- it's an instance of the ImagePaletteclass, 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.

im.palette不可调用——它是ImagePalette类的一个实例,在 mode 中P,否则为Noneim.putpalette(...)是一种方法,因此可调用:参数必须是 768 个整数的序列,在每个索引处给出 R、G 和 B 值。

回答by Hyman Ha

Changing palette's without decoding and (re)encoding does not seem possible. The method in the question seems best (for now). If performance is important, encoding to GIF seems a lot faster.

在不解码和(重新)编码的情况下更改调色板似乎是不可能的。问题中的方法似乎最好(目前)。如果性能很重要,那么编码为 GIF 似乎要快得多。