Convierte la imagen a una paleta específica usando PIL sin dithering

Estoy tratando de convertir una imagen RGB en formato PNG para usar una paleta indexada específica usando la biblioteca de almohadas (Python Image Library, PIL). Pero quiero convertir utilizando el método de “redondeo al color más cercano”, no el difuminado, ya que la imagen es un arte de píxeles y el distorsión distorsionaría los contornos de las áreas y agregaría ruido a las áreas que pretenden ser planas.

Image.Image.paste() , y usé los cuatro colores especificados, pero produjo una imagen difuminada:

 from PIL import Image oldimage = Image.open("oldimage.png") palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255] newimage = Image.new('P', oldimage.size) newimage.putpalette(palettedata * 64) newimage.paste(oldimage, (0, 0) + oldimage.size) newimage.show() 

Intenté Image.Image.quantize() como se menciona en la respuesta de pictu a una pregunta similar , pero también produjo vacilaciones:

 from PIL import Image palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255] palimage = Image.new('P', (16, 16)) palimage.putpalette(palettedata * 64) oldimage = Image.open("School_scrollable1.png") newimage = oldimage.quantize(palette=palimage) newimage.show() 

Image.Image.convert() , y convirtió la imagen sin distorsión, pero incluía colores distintos a los especificados, probablemente porque usaba una paleta web o una paleta adaptativa

 from PIL import Image oldimage = Image.open("oldimage.png") palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255] expanded_palettedata = palettedata * 64 newimage = oldimage.convert('P', dither=Image.NONE, palette=palettedata) newimage.show() 

¿Cómo convierto automáticamente una imagen a una paleta específica sin dithering? Me gustaría evitar una solución que procesa cada píxel individual en Python, como se sugiere en la respuesta de John La Rooy y sus comentarios, porque mi solución anterior que involucra un bucle interno escrito en Python ha demostrado ser notablemente lenta para imágenes grandes.

Las partes de PIL implementadas en C están en el módulo PIL._imaging , también disponible como Image.core después de que from PIL import Image . Las versiones actuales de Pillow dan a cada instancia de PIL.Image.Image un miembro llamado im que es una instancia de ImagingCore , una clase definida dentro de PIL._imaging . Puede enumerar sus métodos con help(oldimage.im) , pero los métodos en sí no están documentados desde Python.

El método de convert de objetos _imaging.c se implementa en _imaging.c . Toma de uno a tres argumentos y crea un nuevo objeto Imaging_Type (llamado Imaging_Type en _imaging.c ).

  • mode (requerido): cadena de modo (por ejemplo, "P" )
  • dither (opcional, predeterminado 0): PIL pasa 0 o 1
  • paletteimage (opcional): un ImagingCore con una paleta

El problema al que me enfrentaba es que quantize() en dist-packages/PIL/Image.py fuerza el argumento de dist-packages/PIL/Image.py a 1. Así que saqué una copia del método quantize() y la cambié. Es posible que esto no funcione en futuras versiones de Pillow, pero si no, es probable que implementen la “interfaz de cuantificación extendida en una versión posterior” que promete un comentario en quantize() .

 #!/usr/bin/env python3 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 # Later versions of Pillow (4.x) rename _makeself to _new try: return silf._new(im) except AttributeError: return silf._makeself(im) palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255] palimage = Image.new('P', (16, 16)) palimage.putpalette(palettedata * 64) oldimage = Image.open("School_scrollable1.png") newimage = quantizetopalette(oldimage, palimage, dither=False) newimage.show() 

Tomé todo esto y lo hice más rápido, añadí notas para que las entendiera y las convertí en almohadas en lugar de pil. Básicamente.

 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 made below palette.load() im = silf.im.convert("P", 0, palette.im) # the 0 above means turn OFF dithering making solid colors return silf._new(im) if __name__ == "__main__": import sys, os for imgfn in sys.argv[1:]: palettedata = [ 0, 0, 0, 255, 0, 0, 255, 255, 0, 0, 255, 0, 255, 255, 255,85,255,85, 255,85,85, 255,255,85] # palettedata = [ 0, 0, 0, 0,170,0, 170,0,0, 170,85,0,] # pallet 0 dark # palettedata = [ 0, 0, 0, 85,255,85, 255,85,85, 255,255,85] # pallet 0 light # palettedata = [ 0, 0, 0, 85,255,255, 255,85,255, 255,255,255,] #pallete 1 light # palettedata = [ 0, 0, 0, 0,170,170, 170,0,170, 170,170,170,] #pallete 1 dark # palettedata = [ 0,0,170, 0,170,170, 170,0,170, 170,170,170,] #pallete 1 dark sp # palettedata = [ 0, 0, 0, 0,170,170, 170,0,0, 170,170,170,] # pallet 3 dark # palettedata = [ 0, 0, 0, 85,255,255, 255,85,85, 255,255,255,] # pallet 3 light # grey 85,85,85) blue (85,85,255) green (85,255,85) cyan (85,255,255) lightred 255,85,85 magenta (255,85,255) yellow (255,255,85) # black 0, 0, 0, blue (0,0,170) darkred 170,0,0 green (0,170,0) cyan (0,170,170)magenta (170,0,170) brown(170,85,0) light grey (170,170,170) # # below is the meat we make an image and assign it a palette # after which it's used to quantize the input image, then that is saved palimage = Image.new('P', (16, 16)) palimage.putpalette(palettedata *32) oldimage = Image.open(sys.argv[1]) oldimage = oldimage.convert("RGB") 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) # palimage.putpalette(palettedata *64) 64 times 4 colors on the 256 index 4 times, == 256 colors, we made a 256 color pallet.