Skip to content

Instantly share code, notes, and snippets.

@oldj
Last active April 6, 2024 11:17
Show Gist options
  • Select an option

  • Save oldj/9c4d012d6fff059ccea7 to your computer and use it in GitHub Desktop.

Select an option

Save oldj/9c4d012d6fff059ccea7 to your computer and use it in GitHub Desktop.

Revisions

  1. oldj revised this gist Jun 25, 2022. No changes.
  2. oldj revised this gist Oct 25, 2017. No changes.
  3. oldj revised this gist Oct 25, 2017. No changes.
  4. oldj revised this gist Oct 25, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions text_to_image.py
    Original file line number Diff line number Diff line change
    @@ -8,8 +8,8 @@
    import os
    import re
    import StringIO
    import Image
    import ImageDraw
    from PIL import Image
    from PIL import ImageDraw
    import pygame

    g_script_folder = os.path.dirname(os.path.abspath(__file__))
  5. oldj created this gist Jan 6, 2015.
    328 changes: 328 additions & 0 deletions text_to_image.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,328 @@
    # -*- coding: utf-8 -*-
    #
    # Author: oldj
    # Email: [email protected]
    # Blog: http://oldj.net
    #

    import os
    import re
    import StringIO
    import Image
    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()