如何使用 Python Imaging Library 将任何图像转换为 4 色调色板图像?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/236692/
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
How do I convert any image to a 4-color paletted image using the Python Imaging Library?
提问by Thomas Vander Stichele
I have a device that supports 4-color graphics (much like CGA in the old days).
我有一个支持 4 色图形的设备(很像过去的 CGA)。
I wanted to use PILto read the image and convert it using my 4-color palette (of red, green, yellow, black), but I can't figure out if it's even possible at all. I found some mailing list archive posts that seem to suggest other people have tried to do so and failed.
我想使用PIL读取图像并使用我的 4 色调色板(红色、绿色、黄色、黑色)转换它,但我不知道它是否可能。我发现一些邮件列表存档帖子似乎暗示其他人尝试这样做但失败了。
A simple python example would be much appreciated!
一个简单的python示例将不胜感激!
Bonus points if you add something that then converts the image to a byte string where each byte represents 4 pixels of data (with each two bits representing a color from 0 to 3)
如果您添加一些内容,然后将图像转换为字节字符串,其中每个字节代表 4 个数据像素(每两位代表从 0 到 3 的颜色),则奖励积分
回答by tzot
First: your four colour palette (black, green, red, yellow) has noblue component. So, you have to accept that your output image will hardly approximate the input image, unless there is no blue component to start with.
第一:您的四种调色板(黑色、绿色、红色、黄色)没有蓝色成分。因此,您必须接受您的输出图像几乎不会接近输入图像,除非开始时没有蓝色分量。
Try this 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)
This creates a PNG palette image with only the first four palette entries set to your colours. This sample image:
这将创建一个 PNG 调色板图像,其中只有前四个调色板条目设置为您的颜色。此示例图像:
becomes
变成
It's trivial to take the output of image2cga
(yields a sequence of 0-3 values) and pack every four values to a byte.
获取image2cga
(产生 0-3 个值的序列)的输出并将每四个值打包成一个字节是微不足道的。
If you need help about what the code does, please ask and I will explain.
如果您需要有关代码功能的帮助,请询问,我会解释。
EDIT1: Do not reinvent the wheel
EDIT1:不要重新发明轮子
Of course, turns out I was too enthusiastic and —as Thomas discovered— the Image.quantize method can take a palette image as argument and do the quantization with far better results than my ad-hoc method above:
当然,事实证明我太热情了——正如 Thomas 发现的那样——Image.quantize 方法可以将调色板图像作为参数,并以比我上面的临时方法好得多的结果进行量化:
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: Pack the pixels into bytes
EDIT1, cont: 将像素打包成字节
For "added value", here follows code to produce the packed string (4 pixels per byte):
对于“附加值”,下面是生成压缩字符串的代码(每字节 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())
回答by Thomas Vander Stichele
John, I found that first link as well, but it didn't directly help me with the problem. It did make me look deeper into quantize though.
约翰,我也找到了第一个链接,但它并没有直接帮助我解决问题。不过,它确实让我更深入地研究了量化。
I came up with this yesterday before going to bed:
我昨天睡前想出了这个:
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, your solution seems to work along the same principles. Kudos, I should have stopped working on it and waited for your reply. Mine is a bit simpler, although definately not more logical than yours. PIL is cumbersome to use. Yours explains what's going on to do it.
TZ.TZIOY,您的解决方案似乎遵循相同的原则。荣誉,我应该停止工作并等待您的回复。我的有点简单,虽然绝对不比你的更合乎逻辑。PIL使用起来很麻烦。你的解释了要做什么。
回答by Lady_F
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)
For those that wanted NO dithering to get solid colors. i modded: Convert image to specific palette using PIL without ditheringwith the two solutions in this thread. Even though this thread is old, some of us want that information. Kudios
对于那些不想抖动以获得纯色的人。我修改了:使用 PIL 将图像转换为特定的调色板,而无需使用此线程中的两种解决方案进行抖动。尽管这个帖子很旧,但我们中的一些人想要这些信息。库迪奥斯