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

Tkinter中叠加图像导出时Logo位置偏移问题求助

问题描述
  • 开发目标:实现一款具备以下功能的程序
    • 上传背景图像
    • 叠加Logo(第二张图像)
    • 拖拽及调整Logo大小
    • 将结果保存为PNG格式
  • 遇到的问题:导出时Logo位置偏移,放在右下角会向左上方偏移,越靠近中间误差越小。尝试通过将.paste()坐标除以画布精确尺寸解决,但无效。
  • 导出函数逻辑:先利用bg_img的宽高创建空白Image,将bg_img粘贴到(0,0)(此部分功能正常);获取Logo在画布上的中心坐标,转换为相对于bg_img的坐标,减去Logo尺寸的一半得到左上角坐标,将Logo转为RGBA格式后粘贴到img_export的计算坐标处。
问题代码
from tkinter import *
from tkinter import filedialog
from PIL import ImageTk, ImageOps, Image

# Save PNG File _______________________

def export():
    global bg_img, logo_img, logo_id

    width = bg_img.width
    height = bg_img.height

    img_export = Image.new('RGBA', (width, height))
    img_export.paste(bg_img, (0,0))

    logo_x, logo_y = canvas.logo_coords
    logo_x = int(width / 1001 * (logo_x - logo_img.width // 2))
    logo_y = int(height / 1001 * (logo_y - logo_img.height // 2))
    print(f"coordinates = {logo_x, logo_y}")

    l_img_alpha = logo_img.convert("RGBA")
    img_export.paste(logo_img, (logo_x, logo_y))

    file = filedialog.asksaveasfilename(defaultextension='.png')
    img_export.save(file, "PNG")

# Logo Upload & Placement ______________________

def start_drag(event):
    widget = event.widget
    widget.startX = event.x
    widget.startY = event.y
    widget.logo_coords = (widget.startX, widget.startY)

def drag(event):
    widget = event.widget
    deltaX = event.x - widget.startX
    deltaY = event.y - widget.startY
    widget.move(widget.logo_id, deltaX, deltaY)

    widget.logo_coords = (widget.startX, widget.startY)
    print(widget.logo_coords)
    widget.startX = event.x
    widget.startY = event.y

def render_logo():
    global logo_img, logo_id

    logo_rendered = ImageTk.PhotoImage(logo_img)
    if hasattr(canvas, 'logo_id'):
        x, y = canvas.coords(logo_id)
        canvas.delete(logo_id)
        logo_id = canvas.create_image(x, y, image=logo_rendered)
    else:
        logo_id = canvas.create_image(500, 500, image=logo_rendered)

    canvas.logo_rendered = logo_rendered
    canvas.logo_id = logo_id

    canvas.tag_bind(logo_id, "<ButtonPress-1>", start_drag)
    canvas.tag_bind(logo_id, "<B1-Motion>", drag)

def enlarge():
    global logo_img

    incr_width = int(logo_img.width * 1.33)
    incr_height = int(incr_width*logo_img.height/logo_img.width)
    logo_img = logo_img.resize((incr_width, incr_height), Image.LANCZOS)
    render_logo()

def reduce():
    global logo_img

    smaller = (int(logo_img.height * 0.88), int(logo_img.width * 0.88))
    logo_img.thumbnail(smaller)
    render_logo()

def upload_logo(event=None):
    global logo_img, bg_img, user_prompt

    file_formats = [('Jpg', '*.jpg'), ('PNG', '*.png'), ('JPEG', '*.jpeg')]
    logo_path = filedialog.askopenfilename(filetypes=file_formats)

    logo_img = Image.open(logo_path)
    logo_img.thumbnail((100,100))
    render_logo()

    canvas.logo_coords = (500,500)

    if logo_img:
        user_prompt.destroy()
        enlarge_button = Button(text="enlarge logo", command=enlarge, highlightbackground="#6D3939")
        enlarge_button.place(x=555, y=0)
        reduce_button = Button(text="reduce logo", command=reduce, highlightbackground="#6D3939")
        reduce_button.place(x=333, y=0)
        export_button = Button(text="Export", command=export, highlightbackground="#6D3939")
        export_button.place(x=444, y=955)

# Image Upload & Render Background ______________________

def upload_picture(event=None):
    global bg_img, user_prompt

    file_formats = [('Jpg', '*.jpg'), ('PNG', '*.png'), ('JPEG', '*.jpeg')]
    bg_path = filedialog.askopenfilename(filetypes=file_formats)

    bg_img = Image.open(bg_path)
    if bg_img.height > 909 or bg_img.width > 909:
        bg_img.thumbnail((909, 909))
    else:
        bg_img = ImageOps.contain(bg_img, (909, 909))
    bg_img_rendered = ImageTk.PhotoImage(bg_img)
    canvas.bg_img_rendered = bg_img_rendered
    canvas.create_image(500,500, image=bg_img_rendered)
    print(bg_img.width, bg_img.height, bg_img)

    upload_button.destroy()
    user_prompt = Button(text="add your logo", command=upload_logo, highlightbackground="#6D3939")
    user_prompt.place(x=444, y=0)

# User Interface ______________________

window = Tk()
window.title("Watermark")
window.resizable(True, True)

canvas = Canvas(window, width=1001, height=1001, bg="#6D3939")
canvas.grid(column=1, row=1)

upload_button = Button(text='upload your image', command=upload_picture, highlightbackground="#6D3939")
upload_button.place(x=444, y=444)

window.mainloop()
解决方案

偏移问题的核心是画布上背景图的显示逻辑与实际导出图像的坐标映射错误,同时存在拖拽时坐标记录错误、尺寸参数顺序错误两个次要问题,修复如下:

1. 修正Logo坐标记录逻辑

拖拽时需直接获取Logo的中心坐标,而非鼠标点击位置:

def start_drag(event):
    widget = event.widget
    widget.startX = event.x
    widget.startY = event.y
    # 直接获取Logo的中心坐标
    widget.logo_coords = widget.coords(widget.logo_id)

def drag(event):
    widget = event.widget
    deltaX = event.x - widget.startX
    deltaY = event.y - widget.startY
    widget.move(widget.logo_id, deltaX, deltaY)
    # 更新为最新的Logo中心坐标
    widget.logo_coords = widget.coords(widget.logo_id)
    widget.startX = event.x
    widget.startY = event.y

2. 修正导出时的坐标映射

背景图在1001x1001画布上是居中显示的,需先计算背景图的偏移量,再将Logo的画布坐标转换为背景图相对坐标:

def export():
    global bg_img, logo_img, logo_id

    width = bg_img.width
    height = bg_img.height

    img_export = Image.new('RGBA', (width, height))
    img_export.paste(bg_img, (0,0))

    # 获取Logo在画布上的中心坐标
    canvas_logo_x, canvas_logo_y = canvas.logo_coords
    # 计算背景图在画布上的左上角偏移量
    bg_offset_x = (1001 - bg_img.width) // 2
    bg_offset_y = (1001 - bg_img.height) // 2
    # 转换为背景图上的中心坐标
    logo_center_x = canvas_logo_x - bg_offset_x
    logo_center_y = canvas_logo_y - bg_offset_y
    # 计算Logo左上角坐标
    logo_x = int(logo_center_x - logo_img.width / 2)
    logo_y = int(logo_center_y - logo_img.height / 2)

    # 可选:限制坐标不超出背景图范围
    logo_x = max(0, min(logo_x, width - logo_img.width))
    logo_y = max(0, min(logo_y, height - logo_img.height))

    print(f"coordinates = {logo_x, logo_y}")

    # 带透明通道粘贴,确保Logo透明区域生效
    l_img_alpha = logo_img.convert("RGBA")
    img_export.paste(l_img_alpha, (logo_x, logo_y), mask=l_img_alpha)

    file = filedialog.asksaveasfilename(defaultextension='.png')
    img_export.save(file, "PNG")

3. 修正缩小Logo的尺寸参数顺序

thumbnail方法的参数是(width, height),原代码写反了:

def reduce():
    global logo_img
    # 修正为(width, height)顺序
    smaller = (int(logo_img.width * 0.88), int(logo_img.height * 0.88))
    logo_img.thumbnail(smaller)
    render_logo()

内容的提问来源于stack exchange,提问作者Dominic J

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 21:55:18