Skip to content

Instantly share code, notes, and snippets.

@r1235613
Forked from oldj/text_to_image.py
Created October 28, 2017 09:25
Show Gist options
  • Save r1235613/b6fdc641f24b185b4681e79308c86d45 to your computer and use it in GitHub Desktop.
Save r1235613/b6fdc641f24b185b4681e79308c86d45 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
#
# Author: oldj
# Email: oldj.wu@gmail.com
# Blog: http://oldj.net
#
import os
import re
import StringIO
from PIL import Image
from PIL import ImageDraw
import pygame
g_script_folder = os.path.dirname(os.path.abspath(__file__))
g_fonts_folder = os.path.join(g_script_folder, "fonts")
g_re_first_word = re.compile((u""
+ u"(%(prefix)s+\S%(postfix)s+)" # 标点
+ u"|(%(prefix)s*\w+%(postfix)s*)" # 单词
+ u"|(%(prefix)s+\S)|(\S%(postfix)s+)" # 标点
+ u"|(\d+%%)" # 百分数
) % {
"prefix": u"['\"\(<\[\{‘“(《「『]",
"postfix": u"[:'\"\)>\]\}:’”)》」』,;\.\?!,、;。?!]",
})
pygame.init()
def getFontForPyGame(font_name="wqy-zenhei.ttc", font_size=14):
return pygame.font.Font(os.path.join(g_fonts_folder, font_name), font_size)
def makeConfig(cfg=None):
if not cfg or type(cfg) != dict:
cfg = {}
default_cfg = {
"width": 440, # px
"padding": (15, 18, 20, 18),
"line-height": 20, #px
"title-line-height": 32, #px
"font-size": 14, # px
"title-font-size": 24, # px
"font-family": "wqy-zenhei.ttc",
# "font-family": "msyh.ttf",
"font-color": (0, 0, 0),
"font-antialiasing": True, # 字体是否反锯齿
"background-color": (255, 255, 255),
"border-size": 1,
"border-color": (192, 192, 192),
"copyright": u"本图文由 txt2.im 自动生成,但不代表 txt2.im 赞同其内容或立场。",
"copyright-center": False, # 版权信息居中显示,如为 False 则居左显示
"first-line-as-title": True,
"break-word": False,
}
default_cfg.update(cfg)
return default_cfg
def makeLineToWordsList(line, break_word=False):
u"""将一行文本转为单词列表"""
if break_word:
return [c for c in line]
lst = []
while line:
ro = g_re_first_word.match(line)
end = 1 if not ro else ro.end()
lst.append(line[:end])
line = line[end:]
return lst
def makeLongLineToLines(long_line, start_x, start_y, width, line_height, font, cn_char_width=0):
u"""将一个长行分成多个可显示的短行"""
txt = long_line
# txt = u"测试汉字abc123"
# txt = txt.decode("utf-8")
if not txt:
return [None]
words = makeLineToWordsList(txt)
lines = []
if not cn_char_width:
cn_char_width, h = font.size(u"汉")
avg_char_per_line = width / cn_char_width
if avg_char_per_line <= 1:
avg_char_per_line = 1
line_x = start_x
line_y = start_y
while words:
tmp_words = words[:avg_char_per_line]
tmp_ln = "".join(tmp_words)
w, h = font.size(tmp_ln)
wc = len(tmp_words)
while w < width and wc < len(words):
wc += 1
tmp_words = words[:wc]
tmp_ln = "".join(tmp_words)
w, h = font.size(tmp_ln)
while w > width and len(tmp_words) > 1:
tmp_words = tmp_words[:-1]
tmp_ln = "".join(tmp_words)
w, h = font.size(tmp_ln)
if w > width and len(tmp_words) == 1:
# 处理一个长单词或长数字
line_y = makeLongWordToLines(
tmp_words[0], line_x, line_y, width, line_height, font, lines
)
words = words[len(tmp_words):]
continue
line = {
"x": line_x,
"y": line_y,
"text": tmp_ln,
"font": font,
}
line_y += line_height
words = words[len(tmp_words):]
lines.append(line)
if len(lines) >= 1:
# 去掉长行的第二行开始的行首的空白字符
while len(words) > 0 and not words[0].strip():
words = words[1:]
return lines
def makeLongWordToLines(long_word, line_x, line_y, width, line_height, font, lines):
if not long_word:
return line_y
c = long_word[0]
char_width, char_height = font.size(c)
default_char_num_per_line = width / char_width
while long_word:
tmp_ln = long_word[:default_char_num_per_line]
w, h = font.size(tmp_ln)
l = len(tmp_ln)
while w < width and l < len(long_word):
l += 1
tmp_ln = long_word[:l]
w, h = font.size(tmp_ln)
while w > width and len(tmp_ln) > 1:
tmp_ln = tmp_ln[:-1]
w, h = font.size(tmp_ln)
l = len(tmp_ln)
long_word = long_word[l:]
line = {
"x": line_x,
"y": line_y,
"text": tmp_ln,
"font": font,
}
line_y += line_height
lines.append(line)
return line_y
def makeMatrix(txt, font, title_font, cfg):
width = cfg["width"]
data = {
"width": width,
"height": 0,
"lines": [],
}
a = txt.split("\n")
cur_x = cfg["padding"][3]
cur_y = cfg["padding"][0]
cn_char_width, h = font.size(u"汉")
for ln_idx, ln in enumerate(a):
ln = ln.rstrip()
if ln_idx == 0 and cfg["first-line-as-title"]:
f = title_font
line_height = cfg["title-line-height"]
else:
f = font
line_height = cfg["line-height"]
current_width = width - cur_x - cfg["padding"][1]
lines = makeLongLineToLines(ln, cur_x, cur_y, current_width, line_height, f, cn_char_width=cn_char_width)
cur_y += line_height * len(lines)
data["lines"].extend(lines)
data["height"] = cur_y + cfg["padding"][2]
return data
def makeImage(data, cfg):
u"""
"""
width, height = data["width"], data["height"]
if cfg["copyright"]:
height += 48
im = Image.new("RGB", (width, height), cfg["background-color"])
dr = ImageDraw.Draw(im)
for ln_idx, line in enumerate(data["lines"]):
__makeLine(im, line, cfg)
# dr.text((line["x"], line["y"]), line["text"], font=font, fill=cfg["font-color"])
# 缩放
# im = im.resize((width / 2, height / 2), Image.ANTIALIAS)
drawBorder(im, dr, cfg)
drawCopyright(im, dr, cfg)
return im
def drawCopyright(im, dr, cfg):
u"""绘制版权信息"""
if not cfg["copyright"]:
return
font = getFontForPyGame(font_name=cfg["font-family"], font_size=12)
rtext = font.render(cfg["copyright"],
cfg["font-antialiasing"], (128, 128, 128), cfg["background-color"]
)
sio = StringIO.StringIO()
pygame.image.save(rtext, sio)
sio.seek(0)
copyright_im = Image.open(sio)
iw, ih = im.size
cw, ch = rtext.get_size()
padding = cfg["padding"]
offset_y = ih - 32 - padding[2]
if cfg["copyright-center"]:
cx = (iw - cw) / 2
else:
cx = cfg["padding"][3]
cy = offset_y + 12
dr.line([(padding[3], offset_y), (iw - padding[1], offset_y)], width=1, fill=(192, 192, 192))
im.paste(copyright_im, (cx, cy))
def drawBorder(im, dr, cfg):
u"""绘制边框"""
if not cfg["border-size"]:
return
w, h = im.size
x, y = w - 1, h - 1
dr.line(
[(0, 0), (x, 0), (x, y), (0, y), (0, 0)],
width=cfg["border-size"],
fill=cfg["border-color"],
)
def __makeLine(im, line, cfg):
if not line:
return
sio = StringIO.StringIO()
x, y = line["x"], line["y"]
text = line["text"]
font = line["font"]
rtext = font.render(text, cfg["font-antialiasing"], cfg["font-color"], cfg["background-color"])
pygame.image.save(rtext, sio)
sio.seek(0)
ln_im = Image.open(sio)
im.paste(ln_im, (x, y))
def txt2im(txt, outfn, cfg=None, show=False):
# print(cfg)
cfg = makeConfig(cfg)
# print(cfg)
font = getFontForPyGame(cfg["font-family"], cfg["font-size"])
title_font = getFontForPyGame(cfg["font-family"], cfg["title-font-size"])
data = makeMatrix(txt, font, title_font, cfg)
im = makeImage(data, cfg)
im.save(outfn)
if os.name == "nt" and show:
im.show()
def test():
c = open("test.txt", "rb").read().decode("utf-8")
txt2im(c, "test.png", show=True)
if __name__ == "__main__":
test()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment