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

如何用Python基于单字符PNG图片生成弧形文字?

基于单字符PNG生成弧形文字的改进方案

需求说明

需要使用Python将单字符PNG图片(背景透明、字符带特定颜色)排列成指定文本的弧形效果,例如生成"STACKOVERFLOW"的弧形排版。

现有代码

from PIL import Image
import math

def generate_curve_text(input_text, spacing):
    letter_images = []
    arc_radius = 100  # Adjust the radius of the arc as needed
    total_angle = math.pi  # Adjust the total angle of the arc as needed

    angle_per_letter = total_angle / (len(input_text) - 1) if len(input_text) > 1 else 0

    for i, char in enumerate(input_text):
        angle = i * angle_per_letter - total_angle / 2
        x = int(arc_radius * math.cos(angle))
        y = int(arc_radius * math.sin(angle))

        letter_image = Image.open("./images/" + char.upper() + ".png").convert("RGBA")
        transparent_image = Image.new("RGBA", letter_image.size)

        for px in range(letter_image.width):
            for py in range(letter_image.height):
                pixel = letter_image.getpixel((px, py))
                if pixel[:3] == (0, 0, 0):
                    transparent_image.putpixel((px, py), (0, 0, 0, 0))
                else:
                    transparent_image.putpixel((px, py), pixel)

        letter_images.append((transparent_image, (x, y)))

    # Calculate the bounding box of all letter images
    min_x = min(x for _, (x, _) in letter_images)
    max_x = max(x + image.width for image, (x, _) in letter_images)
    min_y = min(y for _, (_, y) in letter_images)
    max_y = max(y + image.height for image, (_, y) in letter_images)

    # Create a new image with the size of the bounding box
    width = max_x - min_x + spacing * (len(letter_images) - 1)
    height = max_y - min_y
    output_image = Image.new('RGBA', (width, height))

    # Paste each letter image at its respective position
    for image, (x, y) in letter_images:
        output_image.paste(image, (x - min_x, y - min_y))

    return output_image

# Save or display the result
generated_curve_image = generate_curve_text('STACKOVERFLOW', 20)
generated_curve_image.save('curve_output.png')

当前问题

  • 字符仅以左上角定位在圆弧上,排列混乱,未形成流畅弧形
  • 未旋转字符,不符合弧形文字的阅读朝向
  • 间距处理逻辑错误,导致字符重叠或间距不均
  • 多余的像素遍历处理,降低代码效率

核心改进方案

1. 准确计算字符位置与朝向

位置计算

应该将字符的中心点定位在圆弧上,而不是左上角:

  • 确定每个字符对应的圆心角,保证弧长方向的间距均匀
  • 根据圆心角计算圆弧上的中心点坐标:x = arc_radius * cos(angle),y = arc_radius * sin(angle)
  • 粘贴位置为中心点坐标减去旋转后字符宽高的一半,确保字符中心落在圆弧点上

字符旋转

弧形文字需要垂直于圆弧切线方向(朝向圆心),旋转角度计算公式:
rotation_angle = math.degrees(current_angle) - 90
使用Image.rotate()时添加expand=True避免裁剪字符,resample=Image.BILINEAR保证旋转后的清晰度。

2. 调整间距避免重叠

不再使用固定像素间距,而是将间距转换为圆心角,保证弧长方向的均匀性:

  • 先计算所有字符的总宽度,加上总间距像素值
  • 总圆心角分配给字符宽度和间距,计算每个像素对应的角度:angle_per_pixel = total_angle / (total_char_width + total_spacing_pixels)
  • 每个字符占据的角度为字符宽度 * angle_per_pixel,间距占据的角度为spacing_pixels * angle_per_pixel

3. 代码优化技巧

  • 移除冗余透明处理:原PNG已为背景透明,直接使用convert("RGBA")即可,无需遍历像素修改
  • 粘贴时使用mask:粘贴字符时传入mask=img,保证透明通道正常生效
  • 预加载字符图片:提前加载所有字符,避免重复IO操作
  • 动态计算边界框:根据所有字符的位置和尺寸,精准计算输出图片的大小,减少留白

改进后的代码

from PIL import Image
import math

def generate_curve_text(input_text, arc_radius=150, total_angle=math.pi, spacing=10):
    letter_data = []
    total_char_width = 0

    # 预加载所有字符图片并计算总宽度
    for char in input_text:
        img = Image.open(f"./images/{char.upper()}.png").convert("RGBA")
        letter_data.append(img)
        total_char_width += img.width

    # 计算每个像素对应的圆心角,保证弧长间距均匀
    total_spacing_pixels = spacing * (len(input_text) - 1)
    angle_per_pixel = total_angle / (total_char_width + total_spacing_pixels)

    current_angle = -total_angle / 2
    positioned_letters = []

    for img in letter_data:
        # 计算字符中心点的圆弧坐标
        center_x = arc_radius * math.cos(current_angle)
        center_y = arc_radius * math.sin(current_angle)

        # 旋转字符,使其贴合弧形方向
        rotation_angle = math.degrees(current_angle) - 90
        rotated_img = img.rotate(rotation_angle, expand=True, resample=Image.BILINEAR)

        # 计算粘贴位置:中心点偏移半宽高
        paste_x = int(center_x - rotated_img.width / 2)
        paste_y = int(center_y - rotated_img.height / 2)

        positioned_letters.append((rotated_img, (paste_x, paste_y)))

        # 更新下一个字符的角度
        char_angle = img.width * angle_per_pixel
        spacing_angle = spacing * angle_per_pixel
        current_angle += char_angle + spacing_angle

    # 计算输出图片的边界框
    min_x = min(x for _, (x, _) in positioned_letters)
    max_x = max(x + img.width for img, (x, _) in positioned_letters)
    min_y = min(y for _, (_, y) in positioned_letters)
    max_y = max(y + img.height for img, (_, y) in positioned_letters)

    output_width = max_x - min_x
    output_height = max_y - min_y
    output_image = Image.new('RGBA', (output_width, output_height), (0, 0, 0, 0))

    # 粘贴所有字符,保留透明效果
    for img, (x, y) in positioned_letters:
        output_image.paste(img, (x - min_x, y - min_y), mask=img)

    return output_image

# 生成并保存结果
result = generate_curve_text('STACKOVERFLOW', arc_radius=150, spacing=10)
result.save('curve_output.png')

额外优化建议

  • 可添加参数控制弧形方向(向上/向下凸),只需修改center_y的符号或角度起始值
  • 预缓存字符图片,避免重复加载,提升批量生成效率
  • 针对不同字体,可调整旋转基准点(如字符基线),让排版更整齐
  • 添加对齐选项(左/中/右对齐),通过调整起始角度实现

内容的提问来源于stack exchange,提问作者Dhruw Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 06:32:12