字符画生成的种类
- 生成图片:文字大小不一样大,像词云一样
- 生成文字:纯文本
图像的种类:
- 黑白
- 灰度图
之前用Java实现过一个用字符写字符,那个只能画出黑白图像。本文用python PIL库实现纯文本字符画,它能够用字符画出灰度图像。
原理如下:
- 首先定义一个字符集,本程序使用ASCII码中的可打印字符:32~126
- 其次,每一个字符都对应一个灰度值,不同字符灰度值不同,具体如何计算一个字符的灰度值见下文
- 传入一张彩色图片,先对它进行灰度化、放缩处理(能够使得宽度合适,每行字符串避免太长)、直方图均衡化(使得图像灰度均匀,增强对比度)。经过以上步骤,得到一个灰度数组。
- 根据图像的灰度数组和字符的灰度值,将灰度数组映射为字符串
如何计算一个字符的灰度值?
将字符画在一张纸上,统计这个字符所占的面积,面积越大,说明字符灰度值越大(或者恰恰相反也是可以的)。
关于直方图均衡化,请查看直方图均衡化
本程序可以进行如下配置:
- 更改字符集,可以包含汉字
- 更改导出图片的字体、字符间距
先放上一张大大的帅照:
from PIL import ImageFont, Image, ImageDraw
# 字符集使用ascii码中的可打印字符
charset = [chr(i) for i in range(32, 127)]
# 计算字符灰度时,字体使用默认字体
font = ImageFont.load_default()
def histogram(a):
# 统计各个颜色出现的频率
cnt = [0] * 256
for i in a:
cnt[i] += 1
return cnt
def transform(a):
# 为各个颜色赋予新的颜色值
su = sum(a)
ans = [0] * 256
s = 0
for i in range(len(a)):
s += a[i]
ans[i] = int(255 * s / su)
return ans
def map_by(a, b):
# 根据映射b,将a数组中的元素映射为新的数组
ans = []
for i in a:
ans.append(b[i])
return ans
def get_grey(char):
# 获取单个字符的灰度
sz = font.getsize(char)
img = Image.new('1', sz)
draw = ImageDraw.Draw(img)
draw.text((0, 0), char, fill='white')
white_cnt = 0
for i in range(sz[0]):
for j in range(sz[1]):
if img.getpixel((i, j)):
white_cnt += 1
return white_cnt / (sz[0] * sz[1])
def get_charset_grey():
# 获取字符集中各个字符的灰度
charset_grey = []
for i in charset:
grey = get_grey(i)
charset_grey.append((i, grey))
charset_grey = sorted(charset_grey, key=lambda it: it[1])
max_grey = charset_grey[-1][1] # 最大灰度的字符
charset_grey = list(map(lambda it: (it[0], it[1] / max_grey * 255), charset_grey))
return charset_grey
def near(a, x):
# 根据灰度x在“字符-灰度”列表中查找灰度最接近的字符,此处使用二分查找
lo, hi = 0, len(a) - 1
while lo < hi:
mid = (hi + lo) // 2
if a[mid][1] == x:
return a[mid][0]
elif a[mid][1] < x:
lo = mid + 1
else:
hi = mid
ind = lo
if ind == 0: return a[0][0]
if abs(a[ind][1] - x) < abs(a[ind + 1][1] - x):
return a[ind][0]
else:
return a[ind + 1][0]
def draw_char(charset_grey, img_data):
# 根据“字符-灰度”列表将图像数据映射成字符串
s = ""
for i in img_data:
s += near(charset_grey, i)
return s
def char_image(img_path, line_chars=100):
# 传入图片路径,将图片映射成为字符串
# 首先将原图片进行灰度化、放缩、直方图均衡化
img = Image.open(img_path).convert('L')
height = int(line_chars / img.size[0] * img.size[1])
img = img.resize((line_chars, height))
data = list(img.getdata())
new_data = map_by(data, transform(histogram(data)))
charset_grey = get_charset_grey()
s = draw_char(charset_grey, new_data)
s = '
'.join([s[i * img.size[0]:(i + 1) * img.size[0]] for i in range(img.size[1])])
return s
def toimg(s):
# 将一个多行字符串画到图片上
s = s.split('
')
ch_sz = font.getsize(' ') # 先测试一下单字符宽高(以空格为例)
ch_sz = (ch_sz[0] + 2, ch_sz[1] + 2) # 字符之间空闲两格
img = Image.new('1', (ch_sz[0] * len(s[0]), ch_sz[1] * len(s))) # 创建新图片
draw = ImageDraw.Draw(img)
for i in range(len(s)):
for j in range(len(s[0])):
draw.text((j * ch_sz[0], i * ch_sz[1]), s[i][j], fill='white')
return img
s = char_image("bitch.jpg", line_chars=200)
img = toimg(s)
img.save("haha.jpg")
print(s)