开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
实现的最终效果
我们想要实现的一种如下所示的效果:
输入的原图
输出的效果图(由字符构成的图)
我们来看看如何通过Python的PIL库来实现。
具体实现代码
from PIL import Image, ImageDraw, ImageFont
# 将图片处理成字符画
def img2ascii(img, outName, ascii_chars, isgray, font, scale):
# 将图片转换为 RGB 模式
im = Image.open(img).convert('RGB')
# 设定处理后的字符画大小
print(im.width)
print(im.height)
raw_width = int(im.width * scale)
raw_height = int(im.height * scale)
# 获取设定的字体的尺寸
font_x, font_y = font.getsize(' ')
# 确定单元的大小
block_x = int(font_x * scale)
block_y = int(font_y * scale)
# 确定长宽各有几个单元
w = int(raw_width/block_x)
h = int(raw_height/block_y)
# 将每个单元缩小为一个像素
im = im.resize((w, h), Image.NEAREST)
# txts 和 colors 分别存储对应块的 ASCII 字符和 RGB 值
txts = []
colors = []
for i in range(h):
line = ''
lineColor = []
for j in range(w):
# 获取RGB的值,pixel格式[R, G, B]
pixel = im.getpixel((j, i))
lineColor.append((pixel[0], pixel[1], pixel[2]))
# 根据该点的RGB值,将其转换成对应的字符
line += get_char(ascii_chars, pixel[0], pixel[1], pixel[2])
txts.append(line)
colors.append(lineColor)
# 创建新画布
img_txt = Image.new('RGB', (raw_width, raw_height), (255, 255, 255))
# 创建 ImageDraw 对象以写入 ASCII
draw = ImageDraw.Draw(img_txt)
for j in range(len(txts)):
for i in range(len(txts[0])):
if isgray:
# 可以去https://www.sioe.cn/yingyong/yanse-rgb-16/ 查一下(119,136,153)是什么颜色的
draw.text((i * block_x, j * block_y), txts[j][i], (119,136,153))
else:
draw.text((i * block_x, j * block_y), txts[j][i], colors[j][i])
img_txt.save(outName)
# 将不同的灰度值映射为 ASCII 字符
def get_char(ascii_chars, r, g, b):
length = len(ascii_chars)
# 对于 sRGB 色彩空间,一种颜色的相对亮度定义为:
# L = 0.2126 * R + 0.7152 * G + 0.0722 * B
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
# gray在0-255之间,因此gray/256 * length 肯定是介于 [0, length - 1]之间
return ascii_chars[int(gray/(256/length))]
img2ascii('happy.jpg', 'after_happy.jpg', 'MNHQ$OC67+>!:-. ', True, ImageFont.load_default(), 1)
相关库
Python的Image库:Pillow
可以链接查看其各个模块的详细使用文档,该程序使用的主要是以下几个方法Image.open,Image.convert,Image.resize,Image.new,ImageDraw.Draw,image.save。
代码解读
首先通过Image.open读原图,并通过Image.convert将其转换为RGB模式。convert函数支持的具体模式列表可参考官方文档。
官方文档中对于L模式(8-bit pixels, black and white)与RGB模式的转换公式如下:
L = R * 299/1000 + G * 587/1000 + B * 114/1000
所以,如果我们不采用sRGB也可以尝试一下通过这种方式来将RGB转换为亮度,即上文代码中的get_char函数中的gray的计算方式。
获取图片后,紧接着获取图片的宽高,并根据scale值计算处理后图的宽高,并原图按照处理后的宽高进行缩放,即resize,第二个参数为缩放过程的取样方式,Image.NEAREST即取距离最近的像素而忽略其他的输入像素,其他可取的值还有Image.BOX等等,具体可以参考文档。
然后通过txts存储像素转换之后的字符,colors存储像素转换后的颜色。
最终,通过ImageDraw逐个字符的写入并保存。