Come posso convertire qualsiasi immagine in un'immagine con palette a 4 colori usando la libreria di imaging Python?

StackOverflow https://stackoverflow.com/questions/236692

Domanda

Ho un dispositivo che supporta la grafica a 4 colori (molto simile alla CGA ai vecchi tempi).

Volevo usare PIL per leggere l'immagine e convertirla usando il mio 4- tavolozza dei colori (di rosso, verde, giallo, nero), ma non riesco a capire se sia possibile. Ho trovato alcuni post dell'archivio delle mailing list che sembrano suggerire che altre persone hanno provato a farlo e non ci sono riusciti.

Un semplice esempio di pitone sarebbe molto apprezzato!

Punti bonus se aggiungi qualcosa che converte l'immagine in una stringa di byte in cui ogni byte rappresenta 4 pixel di dati (con ogni due bit che rappresentano un colore da 0 a 3)

È stato utile?

Soluzione

Primo: la tua palette di quattro colori (nero, verde, rosso, giallo) ha no componente blu. Quindi, devi accettare che l'immagine di output difficilmente si avvicina all'immagine di input, a meno che non ci sia un componente blu con cui iniziare.

Prova questo codice:

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)

Questo crea un'immagine della palette PNG con solo le prime quattro voci della palette impostate sui colori. Questa immagine di esempio:

diventa

È banale prendere l'output di image2cga (produce una sequenza di valori 0-3) e impacchettare ogni quattro valori in un byte.

Se hai bisogno di aiuto su ciò che fa il codice, chiedi e ti spiegherò.

EDIT1: non reinventare la ruota

Certo, risulta che ero troppo entusiasta e & # 8212; come Thomas ha scoperto & # 8212; il metodo Image.quantize può prendere un'immagine della tavolozza come argomento e fare la quantizzazione con risultati molto migliori rispetto al mio metodo ad hoc sopra:

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: impacchetta i pixel in byte

Per "valore aggiunto", ecco il codice che segue per produrre la stringa compressa (4 pixel per byte):

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

Altri suggerimenti

John, ho trovato anche quel primo link, ma non mi ha aiutato direttamente con il problema. Mi ha fatto sembrare più approfondito nel quantizzare.

Me ne sono inventato ieri prima di andare a letto:

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, la tua soluzione sembra funzionare secondo gli stessi principi. Complimenti, avrei dovuto smettere di lavorarci e aspettare la tua risposta. Il mio è un po 'più semplice, anche se sicuramente non più logico del tuo. PIL è complicato da usare. Il tuo spiega cosa sta succedendo per farlo.

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)

Per coloro che non desideravano il dithering per ottenere colori solidi. ho modificato: Converti l'immagine in una tavolozza specifica usando PIL senza dithering con le due soluzioni in questo thread. Anche se questo thread è vecchio, alcuni di noi vogliono tali informazioni. Kudios

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top