You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Pillow中定义网格区块并控制文本不超出网格块范围

Pillow宾果卡网格布局优化方案

核心优化点

  • 提前定义网格基础参数,明确每个单元格的边界范围,彻底避免文本跨区块问题
  • 新增文本自适应逻辑:动态计算文本尺寸,自动调整换行规则和字号,保证内容完全容纳在单元格内
  • 用循环替换重复硬编码的坐标写法,大幅降低代码冗余度,后续调整布局只需要修改基础参数即可

完整实现代码

import random
import textwrap
from PIL import Image, ImageFont, ImageDraw
# 此处保留你原有gclient相关的导入逻辑

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 get_fit_text(text, font_path, max_width, max_height, initial_font_size=20):
    """
    自适应调整文本字号和换行,保证文本在指定宽高范围内显示
    """
    font_size = initial_font_size
    # 最小字号限制,避免文字过小无法识别
    while font_size > 8:
        font = ImageFont.truetype(font_path, font_size)
        # 计算适配当前字号的换行宽度
        avg_char_width = font.getlength("中") if "\u4e00" <= text <= "\u9fff" else font.getlength("x")
        wrap_width = int(max_width // avg_char_width)
        wrapped_text = "\n".join(textwrap.wrap(text, width=wrap_width))
        # 计算换行后文本的总尺寸
        _, _, text_w, text_h = ImageDraw.Draw(Image.new("RGB", (1,1))).multiline_textbbox(
            (0,0), wrapped_text, font=font
        )
        if text_w <= max_width and text_h <= max_height:
            return wrapped_text, font
        font_size -= 1
    # 降到最小字号仍放不下时返回最小字号适配结果
    font = ImageFont.truetype(font_path, 8)
    wrapped_text = "\n".join(textwrap.wrap(text, width=int(max_width//font.getlength("x"))))
    return wrapped_text, font

def make_bingocard(name, questions):
    # 网格基础参数,可根据你的底图实际尺寸调整
    GRID_START_X = 140
    GRID_START_Y = 275
    CELL_WIDTH = 134 # 对应你原有代码里同列相邻格子的x坐标差
    CELL_HEIGHT = 119 # 对应你原有代码里同行相邻格子的y坐标差
    PADDING = 10 # 单元格内边距,避免文字贴边
    FONT_PATH = 'jeevesbot/files/Overpass-regular.ttf'
    
    image = Image.open('jeevesbot/files/bingokaart.png')
    draw = ImageDraw.Draw(image)
    
    q_idx = 0
    # 遍历5x5网格
    for row in range(5):
        for col in range(5):
            # 跳过中间的免费格子(索引从0开始,第3行第3列对应row=2、col=2)
            if row == 2 and col == 2:
                continue
            # 计算当前格子的中心坐标、可用显示范围
            cell_center_x = GRID_START_X + col * CELL_WIDTH
            cell_center_y = GRID_START_Y + row * CELL_HEIGHT
            available_w = CELL_WIDTH - 2 * PADDING
            available_h = CELL_HEIGHT - 2 * PADDING
            # 获取适配当前格子的文本和对应字体
            current_q = questions[q_idx]
            fit_text, fit_font = get_fit_text(current_q, FONT_PATH, available_w, available_h)
            # 渲染文本
            draw.text(
                (cell_center_x, cell_center_y), 
                fit_text, 
                fill=(0,0,0), 
                font=fit_font, 
                align='center', 
                anchor='mm'
            )
            q_idx += 1
    image.save(f'jeevesbot/files/generated_bingocards/{name}.png')

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

调整说明

如果后续更换宾果卡底图,只需要修改make_bingocard函数开头的网格基础参数,即可快速适配新模板,不需要逐个调整每个文本的坐标。


内容的提问来源于stack exchange,提问作者Peter van Arkel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.13 09:00:05