代码之家  ›  专栏  ›  技术社区  ›  Thomas Vander Stichele

如何使用Python图像库将任何图像转换为4色调色板图像?

  •  11
  • Thomas Vander Stichele  · 技术社区  · 16 年前

    我有一个支持四色图形的设备(很像以前的CGA)。

    我想用 PIL 要读取图像并使用我的4色调色板(红、绿、黄、黑)转换它,但我无法确定是否有可能。我发现了一些邮件列表的存档邮件,这些邮件似乎表明其他人已经尝试过这样做,但失败了。

    一个简单的python示例将非常感谢!

    如果您添加了一些内容,然后将图像转换为字节字符串,其中每个字节代表4个像素的数据(每个两位代表从0到3的颜色)。

    3 回复  |  直到 6 年前
        1
  •  17
  •   Glorfindel Doug L.    6 年前

    第一:你的四种颜色(黑色、绿色、红色、黄色)有 蓝色成分。因此,您必须接受输出图像很难接近输入图像,除非没有蓝色组件可以开始。

    试试这个代码:

    import Image
    
    def estimate_color(c, bit, c_error):
        c_new= c -  c_error
        if c_new > 127:
            c_bit= bit
            c_error= 255 - c_new
        else:
            c_bit= 0
            c_error= -c_new
        return c_bit, c_error
    
    def image2cga(im):
        "Produce a sequence of CGA pixels from image im"
        im_width= im.size[0]
        for index, (r, g, b) in enumerate(im.getdata()):
            if index % im_width == 0: # start of a line
                r_error= g_error= 0
            r_bit, r_error= estimate_color(r, 1, r_error)
            g_bit, g_error= estimate_color(g, 2, g_error)
            yield r_bit|g_bit
    
    def cvt2cga(imgfn):
        "Convert an RGB image to (K, R, G, Y) CGA image"
        inp_im= Image.open(imgfn) # assume it's RGB
        out_im= Image.new("P", inp_im.size, None)
        out_im.putpalette( (
            0, 0, 0,
            255, 0, 0,
            0, 255, 0,
            255, 255, 0,
        ) )
        out_im.putdata(list(image2cga(inp_im)))
        return out_im
    
    if __name__ == "__main__":
        import sys, os
    
        for imgfn in sys.argv[1:]:
            im= cvt2cga(imgfn)
            dirname, filename= os.path.split(imgfn)
            name, ext= os.path.splitext(filename)
            newpathname= os.path.join(dirname, "cga-%s.png" % name)
            im.save(newpathname)
    

    这将创建一个PNG调色板图像,其中只有前四个调色板条目设置为您的颜色。此示例图像:

    变成

    获取 image2cga (生成一个0-3值的序列)并将每四个值打包为一个字节。

    如果您需要有关代码功能的帮助,请询问,我将解释。

    编辑1:不要重新发明轮子

    当然,当托马斯发现图像时,我太热情了。量化方法可以以调色板图像为论据,并以比我上面的特别方法更好的结果进行量化:

    def cga_quantize(image):
        pal_image= Image.new("P", (1,1))
        pal_image.putpalette( (0,0,0, 0,255,0, 255,0,0, 255,255,0) + (0,0,0)*252)
        return image.convert("RGB").quantize(palette=pal_image)
    

    edit1,cont:将像素打包成字节

    对于“附加值”,以下代码生成压缩字符串(每字节4个像素):

    import itertools as it
    
    # setup: create a map with tuples [(0,0,0,0)‥(3,3,3,3)] as keys
    # and values [chr(0)‥chr(255)], because PIL does not yet support
    # 4 colour palette images
    
    TUPLE2CHAR= {}
    
    # Assume (b7, b6) are pixel0, (b5, b4) are pixel1…
    # Call it "big endian"
    
    KEY_BUILDER= [
        (0, 64, 128, 192), # pixel0 value used as index
        (0, 16, 32, 48), # pixel1
        (0, 4, 8, 12), # pixel2
        (0, 1, 2, 3), # pixel3
    ]
    # For "little endian", uncomment the following line
    ## KEY_BUILDER.reverse()
    
    # python2.6 has itertools.product, but for compatibility purposes
    # let's do it verbosely:
    for ix0, px0 in enumerate(KEY_BUILDER[0]):
        for ix1, px1 in enumerate(KEY_BUILDER[1]):
            for ix2, px2 in enumerate(KEY_BUILDER[2]):
                for ix3, px3 in enumerate(KEY_BUILDER[3]):
                    TUPLE2CHAR[ix0,ix1,ix2,ix3]= chr(px0+px1+px2+px3)
    
    # Another helper function, copied almost verbatim from itertools docs
    def grouper(n, iterable, padvalue=None):
        "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
        return it.izip(*[it.chain(iterable, it.repeat(padvalue, n-1))]*n)
    
    # now the functions
    def seq2str(seq):
        """Takes a sequence of [0..3] values and packs them into bytes
        using two bits per value"""
        return ''.join(
            TUPLE2CHAR[four_pixel]
            for four_pixel in grouper(4, seq, 0))
    
    # and the image related function
    # Note that the following function is correct,
    # but is not useful for Windows 16 colour bitmaps,
    # which start at the *bottom* row…
    def image2str(img):
        return seq2str(img.getdata())
    
        2
  •  6
  •   Thomas Vander Stichele    16 年前

    约翰,我也发现了第一个链接,但它没有直接帮助我解决问题。不过,它确实让我更深入地研究了量子化。

    我昨天睡觉前想到这个:

    import sys
    
    import PIL
    import Image
    
    PALETTE = [
        0,   0,   0,  # black,  00
        0,   255, 0,  # green,  01
        255, 0,   0,  # red,    10
        255, 255, 0,  # yellow, 11
    ] + [0, ] * 252 * 3
    
    # a palette image to use for quant
    pimage = Image.new("P", (1, 1), 0)
    pimage.putpalette(PALETTE)
    
    # open the source image
    image = Image.open(sys.argv[1])
    image = image.convert("RGB")
    
    # quantize it using our palette image
    imagep = image.quantize(palette=pimage)
    
    # save
    imagep.save('/tmp/cga.png')
    

    Tz.Tzioy,你的解决方案似乎遵循同样的原则。尊敬的是,我本应该停止这方面的工作,等待你的答复。我的稍微简单一点,尽管显然没有你的逻辑。PIL使用起来很麻烦。你的解释了接下来要做什么。

        3
  •  0
  •   Lady_F    8 年前
    import sys
    import PIL
    from PIL import Image
    
    def quantizetopalette(silf, palette, dither=False):
        """Convert an RGB or L mode image to use a given P image's palette."""
    
        silf.load()
    
        # use palette from reference image
        palette.load()
        if palette.mode != "P":
            raise ValueError("bad mode for palette image")
        if silf.mode != "RGB" and silf.mode != "L":
            raise ValueError(
                "only RGB or L mode images can be quantized to a palette"
                )
        im = silf.im.convert("P", 1 if dither else 0, palette.im)
        # the 0 above means turn OFF dithering
        return silf._makeself(im)
    
    if __name__ == "__main__":
        import sys, os
    
    for imgfn in sys.argv[1:]:
        palettedata = [ 0, 0, 0, 0, 255, 0, 255, 0, 0, 255, 255, 0,] 
        palimage = Image.new('P', (16, 16))
        palimage.putpalette(palettedata + [0, ] * 252 * 3)
        oldimage = Image.open(sys.argv[1])
        newimage = quantizetopalette(oldimage, palimage, dither=False)
        dirname, filename= os.path.split(imgfn)
        name, ext= os.path.splitext(filename)
        newpathname= os.path.join(dirname, "cga-%s.png" % name)
        newimage.save(newpathname)
    

    对于那些不想抖动以获得纯色的人。我模仿: Convert image to specific palette using PIL without dithering 在这个线程中有两个解决方案。尽管这条线很旧,但我们中的一些人还是需要这些信息。库迪奥斯