字符画生成的种类
- 生成图片:文字大小不一样大,像词云一样
- 生成文字:纯文本
图像的种类:
- 黑白
- 灰度图
之前用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 cntdef 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 ansdef map_by(a, b): # 根据映射b,将a数组中的元素映射为新的数组 ans = [] for i in a: ans.append(b[i]) return ansdef 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_greydef 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 sdef 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 = '\n'.join([s[i * img.size[0]:(i + 1) * img.size[0]] for i in range(img.size[1])]) return sdef toimg(s): # 将一个多行字符串画到图片上 s = s.split('\n') 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 imgs = char_image("bitch.jpg", line_chars=200)img = toimg(s)img.save("haha.jpg")print(s)