Comment convertir une image en une image palettisée 4 couleurs à l'aide de la bibliothèque d'imagerie Python?

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

Question

J'ai un appareil qui prend en charge les graphiques 4 couleurs (un peu comme le CGA dans le passé).

Je voulais utiliser PIL pour lire l'image et la convertir à l'aide de mon 4- palette de couleurs (de rouge, vert, jaune, noir), mais je ne peux pas savoir si c'est même possible. J'ai trouvé des publications dans les archives de la liste de diffusion qui semblent suggérer que d'autres personnes ont essayé de le faire et ont échoué.

Un exemple simple en python serait très apprécié!

Points bonus si vous ajoutez quelque chose qui convertit ensuite l'image en une chaîne d'octets où chaque octet représente 4 pixels de données (chaque bit représentant une couleur de 0 à 3)

Était-ce utile?

La solution

Premièrement: votre palette de quatre couleurs (noir, vert, rouge, jaune) contient non composant bleu. Donc, vous devez accepter le fait que votre image de sortie sera à peine approximative, sauf s’il n’ya pas de composant bleu pour commencer.

Essayez ce code:

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)

Ceci crée une image de palette PNG avec uniquement les quatre premières entrées de palette définies sur vos couleurs. Cet exemple d'image:

devient

Il est facile de prendre la sortie de image2cga (donnant une séquence de 0 à 3 valeurs) et de compresser toutes les quatre valeurs dans un octet.

Si vous avez besoin d'aide concernant l'utilisation du code, demandez-le-moi et je vous expliquerai.

EDIT1: Ne réinventez pas la roue

Bien entendu, j’étais trop enthousiaste et & # 8212; comme Thomas l’a découvert "& # 8212; la méthode Image.quantize peut prendre une image de palette comme argument et effectuer la quantification avec des résultats bien meilleurs que ceux de la méthode ad-hoc ci-dessus:

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, suite: compresser les pixels en octets

Pour "valeur ajoutée", voici le code pour produire la chaîne compressée (4 pixels par octet):

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

Autres conseils

John, j'ai aussi trouvé ce premier lien, mais il ne m'a pas directement aidé à résoudre le problème. Cela m'a fait regarder plus profondément dans quantize cependant.

Je suis venu avec cela hier avant d'aller au lit:

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, votre solution semble fonctionner selon les mêmes principes. Bravo, j'aurais dû arrêter de travailler dessus et attendre votre réponse. Le mien est un peu plus simple, bien que ce ne soit certainement pas plus logique que le vôtre. La LIP est difficile à utiliser. Votre explique ce qui se passe pour le faire.

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)

Pour ceux qui ne souhaitaient AUCUN tramage pour obtenir des couleurs unies. i modded: Convertir une image en palette spécifique à l'aide de la technologie PIL sans Dithering avec les deux solutions dans ce fil. Même si ce fil est ancien, certains d’entre nous veulent cette information. Kudios

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top