首页 > 解决方案 > 如何告诉 Pillow 网格是什么以及如何在其中放置文本以使其不会超过网格块?

问题描述

我正在尝试用 Pillow 模块找出一些东西。对于宾果游戏式的东西,我想布置一个 5x5 的网格,并用我在列表中随机生成的问题填充它。到现在为止还挺好。

我已经想出了如何在 Pillow 中放置东西,所以它会在块的中间有文本,但这是手动完成的。我需要弄清楚我是否可以定义特定的块,这样我才能确保文本不会跨入其他块。

这就是我生成所附屏幕截图显示的图像的方式。

在此处输入图像描述

这是我到目前为止的代码(我知道字段的输入在它们周围有引号,这是为了在调试过程中更容易。)

def babbelbingo_file():
    file = gclient.open_by_key("obfuscated")
    babbelbingo = file.get_worksheet(1)
    values =  babbelbingo.get_all_values()
    list_values = [item for sublist in values for item in sublist]
    questions = random.sample(list_values, k=24)
    return questions

def make_bingocard(name, questions):
    image = Image.open('jeevesbot/files/bingokaart.png')
    font_name = ImageFont.truetype('jeevesbot/files/Overpass-regular.ttf', 20)
    draw = ImageDraw.Draw(image)
    wrapper = textwrap.TextWrapper(width=25)
    word_list = wrapper.wrap(text=questions[0])
    text_new = ''
    for ii in word_list[:-1]:
        text_new = text_new + ii + '\n'
    text_new += word_list[-1]
    draw.text((140, 275), 'questions[0]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((274, 275), 'questions[1]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((408, 275), 'questions[2]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((542, 275), 'questions[3]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((676, 275), 'questions[4]', (0, 0, 0), font=font_name, align='left', anchor='mm')

    draw.text((140, 394), 'questions[5]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((274, 394), 'questions[6]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((408, 394), 'questions[7]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((542, 394), 'questions[8]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((676, 394), 'questions[9]', (0, 0, 0), font=font_name, align='left', anchor='mm')

    draw.text((140, 513), 'questions[10]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((274, 513), 'questions[11]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((542, 513), 'questions[12]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((676, 513), 'questions[13]', (0, 0, 0), font=font_name, align='left', anchor='mm')

    draw.text((140, 632), 'questions[14]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((274, 632), 'questions[15]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((408, 632), 'questions[16]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((542, 632), 'questions[17]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((676, 632), 'questions[18]', (0, 0, 0), font=font_name, align='left', anchor='mm')

    draw.text((140, 751), 'questions[19]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((274, 751), 'questions[20]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((408, 751), 'questions[21]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((542, 751), 'questions[22]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    draw.text((676, 751), 'questions[23]', (0, 0, 0), font=font_name, align='left', anchor='mm')
    image.save('jeevesbot/files/generated_bingocards/' + name + '.png')

def bingo(name):
    questions = babbelbingo_file()
    make_bingocard(name, questions)

标签: pythonpython-imaging-library

解决方案


我创建了一个脚本,它结合了我之前评论中链接的答案以及您的代码来实现您正在寻找的内容(我认为)。我的解决方案与您的代码相比,关键是我正在为每个问题而不是一次进行文本换行。

import math
import textwrap
from PIL import Image, ImageDraw, ImageFont

def get_questions():
    return [
        'this is my first question',
        'a second question that wraps into three lines',
        'third question',
        'this is a very very long question that will wrap a couple of lines because it just wont end'
    ]

def draw_question(image, question, x_offset, y_offset):
    '''
    Based on Franck's answer in python PIL draw multiline text on
    image, https://stackoverflow.com/a/56205095/189362 .

    In this first approach we calculate the amount of lines that we're 
    going to need, calculate the offset from the top, and then draw  
    the text line by line, increasing the offset as we go.
    '''
    draw = ImageDraw.Draw(image)
    question_width = 300
    question_height = 300
    lines = textwrap.wrap(question, width=20)
    font = ImageFont.truetype("arial.ttf", 24)
    text_color = (255, 0, 0)

    line_height = font.getsize(lines[0])[1]
    y_text = y_offset + (question_height / 2) - (len(lines) * (line_height / 2))

    for line in lines:
        line_width = font.getsize(line)[0]
        draw.text(
            (
                x_offset + (question_width - line_width) / 2,
                y_text
            ), 
            line,
            font=font,
            fill=text_color
        )
        y_text += line_height

def draw_question_alt_approach(image, question, x_offset, y_offset):
    '''
    Another approach would be to first draw the text in a separate 
    image, calculate its bounding box, and then merge it with our main
    image. Result is the same, just a different approach (with better 
    readable code imho).
    '''
    draw = ImageDraw.Draw(image)
    question_width = 300
    question_height = 300
    lines = textwrap.wrap(question, width=20)
    font = ImageFont.truetype("arial.ttf", 24)
    text_color = (255, 0, 0)

    text_img = Image.new('RGBA', (question_width, question_height), (255, 255, 255, 0))
    text_draw = ImageDraw.Draw(text_img)
    text_draw.multiline_text((0, 0), "\n".join(lines), font=font, fill=text_color, align='center')
    bbox = text_img.getbbox()

    x = int(x_offset + (question_width / 2) - math.floor(bbox[2] / 2))
    y = int(y_offset + (question_height / 2) - math.floor(bbox[3] / 2))

    image.alpha_composite(text_img, (x,y))

def draw_card(name, questions):
    image = Image.open('bingokaart.png')
    draw = ImageDraw.Draw(image)

    question_positions = [
        [ 0, 0 ],
        [ 300, 0 ],
        [ 0, 300 ],
        [ 300, 300 ],
    ]

    for i, question in enumerate(questions):
        draw_question_alt_approach(image, question, question_positions[i][0], question_positions[i][1])

    image.save('generated_bingocards/' + name + '.png')

def bingo(name):
    questions = get_questions()
    draw_card(name, questions)

if __name__ == "__main__":
    bingo('foo')

给出:

此代码生成的bingokaart


推荐阅读