1.导入与全局变量

import tkinter as tk
导入 Python 的标准 GUI 库 Tkinter,并给它取别名 tk,便于后续使用如 tk.Tk()、tk.Button() 等控件。
作用:创建主窗口、按钮、标签、菜单等 GUI 元件,处理事件循环。
from tkinter import filedialog,messagebox 从 tkinter 模块中专门导入两个子模块:
filedialog:提供打开文件、保存文件等对话框,常用于让用户选择图片、视频等资源。
messagebox:提供简单的消息对话框,用于提示、确认和错误信息。 作用:实现文件选择和用户提示等交互功能。
from PIL import Image, ImageTk
从 Pillow 库导入 Image 和 ImageTk:
Image:用于图像的打开、处理、保存等操作(PIL 的核心对象)。
ImageTk:将 PIL 图像转换为 Tkinter 可以显示的图像对象(如 PhotoImage)。 作用:在 Tkinter
界面中显示和处理图像,结合 OpenCV 进行更丰富的图像处理。
import cv2
导入 OpenCV 库(通常以 cv2 为命名空间)。
作用:提供强大的计算机视觉和图像处理功能,如读取、解码、转换、滤镜、边缘检测、对象检测等。
import numpy as np
导入 NumPy 库并给出别名 np。
作用:在图像处理中广泛使用,用于处理数组、矩阵、图像数据的数值运算,OpenCV的大部分接口也返回/接受 NumPy 数组。
import os
导入 os 模块,用于跨平台的文件路径和操作系统相关功能(如路径拼接、检查文件存在性、环境变量等)。
作用:与文件系统交互,组合资源路径,读取/写入文件等。

import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
import cv2
import numpy as np
import os

定义全局变量 original_image,初始化为空,用于存储加载的原始 PIL 图片对象。 定义全局变量
processed_image,用于存储当前更换背景色后的最终图片对象。 定义全局变量
transparent_image,用于存储经过算法抠图后、去除背景的透明图片对象(RGBA 模式)。

# --- 全局变量 ---
original_image = None  # PIL 格式原图
processed_image = None  # PIL 格式处理后图片
transparent_image = None  # PIL 格式透明背景图(RGBA)

2.start_bg_tool 函数(抠图逻辑)

它的主要流程是:让用户选择图片 -> 弹出加载窗口 -> 使用 OpenCV 的 GrabCut 算法进行智能抠图 -> 对抠图结果进行优化(修补、去噪、羽化) -> 生成透明背景图 -> 调用界面窗口显示结果。

定义名为 start_bg_tool 的函数,文档字符串说明这是主界面调用的入口。
声明在函数内部将要修改这三个全局变量,否则 Python会将它们视为局部变量。

def start_bg_tool():
    """主界面调用的入口函数"""
    global original_image, processed_image, transparent_image

1. 让用户选择图片

打开一个文件选择对话框,标题为“选择证件照”,限制只能选择常见图片格式或所有文件,并将选中的路径赋值给 file_path。
如果用户取消了选择(路径为空字符串),则直接退出函数,不执行后续操作。

file_path = filedialog.askopenfilename(
        title="选择证件照",
        filetypes=[("图片文件", "*.jpg *.jpeg *.png"), ("所有文件", "*.*")]
    )

    if not file_path:
        return

2. 弹出处理提示窗口

创建一个新的顶级窗口(浮窗),用于显示处理状态。
设置该窗口的标题为“提示”,并设定窗口大小为 350x120 像素。
设置窗口属性-topmost 为 True,使该窗口始终显示在最前端,防止用户点击其他地方导致处理窗口被遮挡。
在处理窗口中创建一个标签,显示提示文字,并设置垂直内边距为 20。

 processing_window = tk.Toplevel()
    processing_window.title("提示")
    processing_window.geometry("350x120")
    # 设置窗口置顶,防止用户误操作
    processing_window.attributes('-topmost', True)
    tk.Label(processing_window, text="正在智能抠图与边缘精修...", font=("Arial", 12)).pack(pady=20)
    processing_window.update()  # 强制刷新界面

3.读取图片

开始异常捕获块。如果后续代码出错(如文件损坏),程序不会崩溃,而是跳转到 except 块。
检查文件路径是否真实存在,如果不存在则手动抛出异常。 以二进制流形式读取文件内容到 numpy
数组。关键点:这种方法支持包含中文的文件路径,解决了 cv2.imread 对中文不友好的问题。 将 numpy 数组解码为 OpenCV
图像格式(img_cv),以彩色模式读取。 检查解码结果是否为空(None),如果为空说明文件可能不是有效图片,抛出异常。
获取图像的高度和宽度。

try:
    # --- 读取图片 (支持中文路径) ---
    if not os.path.exists(file_path):
        raise Exception("文件不存在")

    img_array = np.fromfile(file_path, dtype=np.uint8)
    img_cv = cv2.imdecode(img_array, cv2.IMREAD_COLOR)

    if img_cv is None:
        raise Exception("无法解码图片,文件可能已损坏")

    height, width = img_cv.shape[:2]

4.GrabCut 算法

检查解码结果是否为空(None),如果为空说明文件可能不是有效图片,抛出异常。 获取图像的高度和宽度。 自动计算一个矩形框,距离图片边缘
10%。假设人物在图片中间,这个框作为算法的初始参考。 初始化 GrabCut 算法所需的临时变量:掩码、背景模型和前景模型数组。 执行
GrabCut 算法。输入图像、掩码、矩形框等,迭代 10 次,利用矩形框模式进行初始化计算。
处理算法输出的掩码,将背景类(0和2)转为0(黑),前景类(1和3)转为1(白),得到一个黑白二值图。
进行形态学闭运算(先膨胀后腐蚀),用于填补前景物体内部可能出现的黑洞(如衣服纹理)。
进行形态学开运算(先腐蚀后膨胀),用于去除边缘细小的白色噪点。 对掩码进行高斯模糊,使锐利的边缘产生灰度渐变,实现羽化效果,让合成更自然。
将原图与掩码相乘。背景部分(掩码0)变黑,前景部分(掩码1)保留。利用广播机制将二维掩码扩展到三维。

# 1. 自动计算 ROI (感兴趣区域)
        margin_x = int(width * 0.1)
        margin_y = int(height * 0.1)
        rect = (margin_x, margin_y, width - margin_x, height - margin_y)

        # 2. 初始化 GrabCut 模型
        mask = np.zeros(img_cv.shape[:2], np.uint8)
        bgd_model = np.zeros((1, 65), np.float64)
        fgd_model = np.zeros((1, 65), np.float64)

        # 3. 运行 GrabCut (迭代次数 15)
        cv2.grabCut(img_cv, mask, rect, bgd_model, fgd_model, 10, cv2.GC_INIT_WITH_RECT)

        # 4. 修改掩码:将“可能是背景”(0,2) 设为 0,将“前景”(1,3) 设为 1
        mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')

        # 5. 缺陷修复:闭运算(填补前景内部空洞)
        kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
        mask2 = cv2.morphologyEx(mask2, cv2.MORPH_CLOSE, kernel_close, iterations=2)

        # 6. 边缘形态学处理 (去噪)
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
        mask2 = cv2.morphologyEx(mask2, cv2.MORPH_OPEN, kernel, iterations=1)

        # 7. 边缘羽化 (使边缘平滑)
        mask2 = cv2.GaussianBlur(mask2, (3, 3), 0)

        # 8. 应用掩码提取前景
        foreground = img_cv * mask2[:, :, np.newaxis]

5.转换为 PIL 格式

将 OpenCV 默认的 BGR 颜色顺序转换为 PIL 需要的 RGB 顺序。 将 numpy 数组转换为 PIL 图像对象。
将处理后的浮点掩码转为 0-255 的灰度图,作为透明度通道的基础。 查找掩码中的所有轮廓。创建一个全黑的新掩码用于存放清理后的结果。
如果存在轮廓,找出面积最大的那个(通常是人物主体),将其填充到新掩码中。这步操作切掉了所有非主体的杂质。最后再次高斯模糊保持边缘羽化。
将清理后的掩码作为 Alpha 通道添加到 PIL 图片中,生成最终的透明背景图(RGBA)。 将生成的图片赋值给全局变量,供后续使用。
处理完成,销毁“正在处理…”的提示窗口。

 # 1. BGR 转 RGB
        foreground_rgb = cv2.cvtColor(foreground, cv2.COLOR_BGR2RGB)
        # 2. 转为 PIL Image
        pil_img = Image.fromarray(foreground_rgb)
        # 3. 生成 Alpha 通道
        alpha_mask = Image.fromarray((mask2 * 255).astype(np.uint8))
        # 4. 计算总轮廓以消除内部噪点
        contours, _ = cv2.findContours(mask2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        mask_clean = np.zeros_like(mask2)
        if contours:
            max_contour = max(contours, key=cv2.contourArea)
            cv2.drawContours(mask_clean, [max_contour], -1, 255, -1)
            mask_clean = cv2.GaussianBlur(mask_clean, (3, 3), 0)

        # 合并 RGB 和 Alpha 通道
        pil_img.putalpha(Image.fromarray(mask_clean))

        transparent_image = pil_img
        original_image = pil_img
        processed_image = pil_img.copy()

        # 关闭处理窗口
        processing_window.destroy()

6.异常处理

异常处理代码。如果出错,销毁提示窗口,弹出错误对话框,并在控制台打印详细错误堆栈,然后退出函数。

    except Exception as e:
        processing_window.destroy()
        messagebox.showerror("处理失败", f"抠图出错:\n{str(e)}")
        import traceback
        traceback.print_exc()
        return

7. 打开操作窗口

抠图成功后,调用 open_tool_window 函数,打开操作界面,并将透明图传进去。

open_tool_window(transparent_image)

3.open_tool_window 函数(界面与交互)

该函数负责构建用户的操作界面。界面分为左(原图)、中(按钮)、右(预览)三部分。它实现了将透明图与不同颜色的背景合成,并支持保存结果。

定义界面显示函数,声明使用全局变量 processed_image。创建新的顶级窗口。
设置标题和大小。

def open_tool_window(img_no_bg):
    """显示操作窗口 - 原图垂直偏移版"""
    global processed_image

    top = tk.Toplevel()
    top.title("证件照背景处理工具 (OpenCV优化版)")
    top.geometry("800x500")

1.创建透明棋盘格背景

定义内部函数,用于生成灰白相间的棋盘格图像,这是图像处理中表示“透明”的通用方式,方便用户看清抠图边缘。

 def create_transparent_grid(width, height):
        img = Image.new("RGB", (width, height), "#e0e0e0")
        pixels = img.load()
        for y in range(height):
            for x in range(width):
                if (x // 20 + y // 20) % 2 == 0:
                    pixels[x, y] = (200, 200, 200)
        return img

4.页面布局

左侧:原图显示

创建左侧容器 Frame,设置背景色,并将其放置在窗口左边。
创建一个空白的标签作为占位符,通过设置顶部内边距将下方的内容“挤”下去,实现垂直位置的调整。 添加“原图 (已去背景)”的标题标签。

中间:控制按钮

创建中间容器 Frame,放置在窗口中间,用于放置操作按钮。

右侧:效果图显示

创建右侧容器 Frame,放置在窗口右边,用于显示换底后的效果图。

图片显示参数

定义图片显示的固定最大高度和宽度,确保图片在不同分辨率下都能良好显示且保持对齐。

左侧显示逻辑

复制透明图,并使用 thumbnail 方法按比例缩放图片以适应显示区域。
生成与缩放后图片等大的棋盘格背景,并将透明图粘贴上去。第三个参数 display_img_left 是掩码,用于实现透明叠加。
将合成后的图片转换为 Tkinter 可显示的对象,创建 Label 显示它。label_original.image = photo_original 防止图片被垃圾回收。

# 左侧:原图显示
    frame_left = tk.Frame(top, bg="#e0e0e0", width=280)
    frame_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)

    # 【布局修改】在上方增加一个垂直占位符,使原图位置下移
    spacer_label = tk.Label(frame_left, text="", bg="#e0e0e0")
    spacer_label.pack(pady=(60, 0))  # 调整这个 60 可以改变下移距离

    tk.Label(frame_left, text="原图 (已去背景)", bg="#e0e0e0", font=("Arial", 10, "bold")).pack(pady=5)

    # 中间:控制按钮
    frame_mid = tk.Frame(top, bg="#f0f0f0", width=120)
    frame_mid.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=10)

    # 右侧:效果图显示
    frame_right = tk.Frame(top, bg="#e0e0e0", width=280)
    frame_right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
    tk.Label(frame_right, text="换底预览", bg="#e0e0e0", font=("Arial", 10, "bold")).pack(pady=5)

    # --- 图片显示参数 (确保Y轴对齐) ---
    FIXED_DISPLAY_HEIGHT = 450
    MAX_DISPLAY_WIDTH = 260

    # --- 左侧显示逻辑 ---
    display_img_left = img_no_bg.copy()
    display_img_left.thumbnail((MAX_DISPLAY_WIDTH, FIXED_DISPLAY_HEIGHT))

    grid_bg = create_transparent_grid(display_img_left.width, display_img_left.height)
    grid_bg.paste(display_img_left, (0, 0), display_img_left)

    photo_original = ImageTk.PhotoImage(grid_bg)
    label_original = tk.Label(frame_left, image=photo_original, bg="#e0e0e0")
    label_original.image = photo_original
    label_original.pack()

5.换背景色

定义换色函数。创建纯色背景层,如果抠图是 RGBA 模式,使用 alpha_composite 将背景层和人像层混合,利用 Alpha通道实现完美的背景合成。
保存合成结果到全局变量,并调用 update_display 更新右侧预览。

    def change_color(color_rgb):
        global processed_image
        width, height = img_no_bg.size
        color_bg = Image.new("RGB", (width, height), color_rgb)
        if img_no_bg.mode == 'RGBA':
            color_bg_rgba = color_bg.convert("RGBA")
            final_img = Image.alpha_composite(color_bg_rgba, img_no_bg)
        else:
            final_img = img_no_bg

        processed_image = final_img
        update_display(final_img)

6.显示新图像

定义更新显示的函数。为了预览效果,将 RGBA 图片合成为白底 RGB 图片(防止预览窗口看起来是透明黑底)。
缩放预览图,转换为 Tkinter 对象,并更新右侧 Label 的显示内容。

    def update_display(img_pil):
        if img_pil.mode == 'RGBA':
            display_img = Image.new("RGB", img_pil.size, (255, 255, 255))
            display_img.paste(img_pil, mask=img_pil.split()[3])
        else:
            display_img = img_pil

        display_img.thumbnail((MAX_DISPLAY_WIDTH, FIXED_DISPLAY_HEIGHT))
        photo_new = ImageTk.PhotoImage(display_img)
        label_result.config(image=photo_new)
        label_result.image = photo_new

7.保存图像

定义保存函数。如果没有处理好的图片,弹出警告。
弹出保存文件对话框,默认扩展名为 .jpg。
如果用户确认了保存路径: 如果是 JPG格式(不支持透明),先将背景合成为白色,然后转换为 RGB 模式保存,质量设为 95。 这样可以避免保存出来的照片背景变黑。
如果是 PNG格式,直接保存(保留透明度)。捕获保存过程中的异常并提示。

    def save_img():
        global processed_image
        if not processed_image:
            messagebox.showwarning("提示", "没有可保存的图片")
            return

        path = filedialog.asksaveasfilename(
            defaultextension=".jpg",
            filetypes=[("JPEG 图片", "*.jpg"), ("PNG 透明图", "*.png")]
        )
        if path:
            try:
                ext = os.path.splitext(path)[1].lower()
                if ext in ['.jpg', '.jpeg']:
                    if processed_image.mode == 'RGBA':
                        white_bg = Image.new("RGB", processed_image.size, (255, 255, 255))
                        white_bg.paste(processed_image, mask=processed_image.split()[3])
                        save_img_obj = white_bg
                    else:
                        save_img_obj = processed_image.convert("RGB")
                    save_img_obj.save(path, quality=95)
                else:
                    processed_image.save(path)
                messagebox.showinfo("成功", "图片已保存!")
            except Exception as e:
                messagebox.showerror("保存失败", f"错误:{str(e)}")

8.定义按钮

定义标准的证件照背景色 RGB 值。
btn_style:定义按钮的统一样式字典。
创建“换成蓝\红\白底”按钮,点击时调用change_color 并传入蓝色 RGB 值。
tk.Frame:添加一个空的 Frame作为占位,在上方按钮和保存按钮之间留出空隙。
创建“保存结果”按钮。

    color_blue = (67, 142, 219)
    color_red = (220, 50, 47)
    color_white = (255, 255, 255)

    btn_style = {"width": 12, "height": 2, "font": ("Arial", 10)}

    tk.Button(frame_mid, text="换成蓝底", bg="#add8e6", **btn_style,
              command=lambda: change_color(color_blue)).pack(pady=15)
    tk.Button(frame_mid, text="换成红底", bg="#ffcccb", **btn_style,
              command=lambda: change_color(color_red)).pack(pady=15)
    tk.Button(frame_mid, text="换成白底", bg="white", relief="solid", **btn_style,
              command=lambda: change_color(color_white)).pack(pady=15)

    tk.Frame(frame_mid, height=20).pack()  # 占位符

    tk.Button(frame_mid, text="保存结果", bg="#dddddd", **btn_style,
              command=save_img).pack(pady=30)

9.右侧初始化

初始化右侧预览 Label。程序启动时默认执行一次 change_color(color_white),让窗口一打开就显示白底效果图。

label_result = tk.Label(frame_right, bg="#e0e0e0")
    label_result.pack(expand=True)
    change_color(color_white)

10.主程序入口

程序运行的起始点。它创建了一个隐藏的主窗口,并直接启动抠图工具的主逻辑。

判断当前脚本是否作为主程序运行(而不是被作为模块导入)。
root = tk.Tk,root.withdraw:创建 Tkinter根窗口,并立即调用 withdraw 将其隐藏。因为我们的界面是通过 Toplevel 弹出的,不需要这个空的主窗口。
start_bg_tool:调用抠图工具的主入口函数,开始运行程序。
root.mainloop:进入 Tkinter的主事件循环,保持程序运行,等待用户操作。

if __name__ == "__main__":
    root = tk.Tk()
    root.withdraw()  # 隐藏主窗口,作为模块使用时
    start_bg_tool()
    root.mainloop()

11.运行结果

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

12.总代码

# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
import cv2
import numpy as np
import os

# --- 全局变量 ---
original_image = None  # PIL 格式原图
processed_image = None  # PIL 格式处理后图片
transparent_image = None  # PIL 格式透明背景图(RGBA)


def start_bg_tool():
    """主界面调用的入口函数"""
    global original_image, processed_image, transparent_image

    # 1. 让用户选择图片
    file_path = filedialog.askopenfilename(
        title="选择证件照",
        filetypes=[("图片文件", "*.jpg *.jpeg *.png"), ("所有文件", "*.*")]
    )

    if not file_path:
        return

    # 2. 弹出处理提示窗口
    processing_window = tk.Toplevel()
    processing_window.title("提示")
    processing_window.geometry("350x120")
    # 设置窗口置顶,防止用户误操作
    processing_window.attributes('-topmost', True)
    tk.Label(processing_window, text="正在智能抠图与边缘精修...", font=("Arial", 12)).pack(pady=20)
    processing_window.update()  # 强制刷新界面

    try:
        # --- 读取图片 (支持中文路径) ---
        if not os.path.exists(file_path):
            raise Exception("文件不存在")

        img_array = np.fromfile(file_path, dtype=np.uint8)
        img_cv = cv2.imdecode(img_array, cv2.IMREAD_COLOR)

        if img_cv is None:
            raise Exception("无法解码图片,文件可能已损坏")

        height, width = img_cv.shape[:2]

        # --- 核心:改进的 GrabCut 算法 ---

        # 1. 自动计算 ROI (感兴趣区域)
        margin_x = int(width * 0.1)
        margin_y = int(height * 0.1)
        rect = (margin_x, margin_y, width - margin_x, height - margin_y)

        # 2. 初始化 GrabCut 模型
        mask = np.zeros(img_cv.shape[:2], np.uint8)
        bgd_model = np.zeros((1, 65), np.float64)
        fgd_model = np.zeros((1, 65), np.float64)

        # 3. 运行 GrabCut (迭代次数 15)
        cv2.grabCut(img_cv, mask, rect, bgd_model, fgd_model, 10, cv2.GC_INIT_WITH_RECT)

        # 4. 修改掩码:将“可能是背景”(0,2) 设为 0,将“前景”(1,3) 设为 1
        mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')

        # 5. 缺陷修复:闭运算(填补前景内部空洞)
        kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
        mask2 = cv2.morphologyEx(mask2, cv2.MORPH_CLOSE, kernel_close, iterations=2)

        # 6. 边缘形态学处理 (去噪)
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
        mask2 = cv2.morphologyEx(mask2, cv2.MORPH_OPEN, kernel, iterations=1)

        # 7. 边缘羽化 (使边缘平滑)
        mask2 = cv2.GaussianBlur(mask2, (3, 3), 0)

        # 8. 应用掩码提取前景
        foreground = img_cv * mask2[:, :, np.newaxis]

        # --- 转换为 PIL 格式 ---
        # 1. BGR 转 RGB
        foreground_rgb = cv2.cvtColor(foreground, cv2.COLOR_BGR2RGB)
        # 2. 转为 PIL Image
        pil_img = Image.fromarray(foreground_rgb)
        # 3. 生成 Alpha 通道
        alpha_mask = Image.fromarray((mask2 * 255).astype(np.uint8))
        # 4. 计算总轮廓以消除内部噪点
        contours, _ = cv2.findContours(mask2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        mask_clean = np.zeros_like(mask2)
        if contours:
            max_contour = max(contours, key=cv2.contourArea)
            cv2.drawContours(mask_clean, [max_contour], -1, 255, -1)
            mask_clean = cv2.GaussianBlur(mask_clean, (3, 3), 0)

        # 合并 RGB 和 Alpha 通道
        pil_img.putalpha(Image.fromarray(mask_clean))

        transparent_image = pil_img
        original_image = pil_img
        processed_image = pil_img.copy()

        # 关闭处理窗口
        processing_window.destroy()

    except Exception as e:
        processing_window.destroy()
        messagebox.showerror("处理失败", f"抠图出错:\n{str(e)}")
        import traceback
        traceback.print_exc()
        return

    # 3. 打开操作窗口
    open_tool_window(transparent_image)


def open_tool_window(img_no_bg):
    """显示操作窗口 - 原图垂直偏移版"""
    global processed_image

    top = tk.Toplevel()
    top.title("证件照背景处理工具 (OpenCV优化版)")
    top.geometry("800x500")

    # --- 辅助函数:创建透明棋盘格背景 ---
    def create_transparent_grid(width, height):
        img = Image.new("RGB", (width, height), "#e0e0e0")
        pixels = img.load()
        for y in range(height):
            for x in range(width):
                if (x // 20 + y // 20) % 2 == 0:
                    pixels[x, y] = (200, 200, 200)
        return img

    # --- 布局 ---
    # 左侧:原图显示
    frame_left = tk.Frame(top, bg="#e0e0e0", width=280)
    frame_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)

    # 【布局修改】在上方增加一个垂直占位符,使原图位置下移
    spacer_label = tk.Label(frame_left, text="", bg="#e0e0e0")
    spacer_label.pack(pady=(60, 0))  # 调整这个 60 可以改变下移距离

    tk.Label(frame_left, text="原图 (已去背景)", bg="#e0e0e0", font=("Arial", 10, "bold")).pack(pady=5)

    # 中间:控制按钮
    frame_mid = tk.Frame(top, bg="#f0f0f0", width=120)
    frame_mid.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=10)

    # 右侧:效果图显示
    frame_right = tk.Frame(top, bg="#e0e0e0", width=280)
    frame_right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
    tk.Label(frame_right, text="换底预览", bg="#e0e0e0", font=("Arial", 10, "bold")).pack(pady=5)

    # --- 图片显示参数 (确保Y轴对齐) ---
    FIXED_DISPLAY_HEIGHT = 450
    MAX_DISPLAY_WIDTH = 260

    # --- 左侧显示逻辑 ---
    display_img_left = img_no_bg.copy()
    display_img_left.thumbnail((MAX_DISPLAY_WIDTH, FIXED_DISPLAY_HEIGHT))

    grid_bg = create_transparent_grid(display_img_left.width, display_img_left.height)
    grid_bg.paste(display_img_left, (0, 0), display_img_left)

    photo_original = ImageTk.PhotoImage(grid_bg)
    label_original = tk.Label(frame_left, image=photo_original, bg="#e0e0e0")
    label_original.image = photo_original
    label_original.pack()

    # --- 核心功能:换色 ---
    def change_color(color_rgb):
        global processed_image
        width, height = img_no_bg.size
        color_bg = Image.new("RGB", (width, height), color_rgb)
        if img_no_bg.mode == 'RGBA':
            color_bg_rgba = color_bg.convert("RGBA")
            final_img = Image.alpha_composite(color_bg_rgba, img_no_bg)
        else:
            final_img = img_no_bg

        processed_image = final_img
        update_display(final_img)

    def update_display(img_pil):
        if img_pil.mode == 'RGBA':
            display_img = Image.new("RGB", img_pil.size, (255, 255, 255))
            display_img.paste(img_pil, mask=img_pil.split()[3])
        else:
            display_img = img_pil

        display_img.thumbnail((MAX_DISPLAY_WIDTH, FIXED_DISPLAY_HEIGHT))
        photo_new = ImageTk.PhotoImage(display_img)
        label_result.config(image=photo_new)
        label_result.image = photo_new

    def save_img():
        global processed_image
        if not processed_image:
            messagebox.showwarning("提示", "没有可保存的图片")
            return

        path = filedialog.asksaveasfilename(
            defaultextension=".jpg",
            filetypes=[("JPEG 图片", "*.jpg"), ("PNG 透明图", "*.png")]
        )
        if path:
            try:
                ext = os.path.splitext(path)[1].lower()
                if ext in ['.jpg', '.jpeg']:
                    if processed_image.mode == 'RGBA':
                        white_bg = Image.new("RGB", processed_image.size, (255, 255, 255))
                        white_bg.paste(processed_image, mask=processed_image.split()[3])
                        save_img_obj = white_bg
                    else:
                        save_img_obj = processed_image.convert("RGB")
                    save_img_obj.save(path, quality=95)
                else:
                    processed_image.save(path)
                messagebox.showinfo("成功", "图片已保存!")
            except Exception as e:
                messagebox.showerror("保存失败", f"错误:{str(e)}")

    # --- 按钮定义 ---
    color_blue = (67, 142, 219)
    color_red = (220, 50, 47)
    color_white = (255, 255, 255)

    btn_style = {"width": 12, "height": 2, "font": ("Arial", 10)}

    tk.Button(frame_mid, text="换成蓝底", bg="#add8e6", **btn_style,
              command=lambda: change_color(color_blue)).pack(pady=15)
    tk.Button(frame_mid, text="换成红底", bg="#ffcccb", **btn_style,
              command=lambda: change_color(color_red)).pack(pady=15)
    tk.Button(frame_mid, text="换成白底", bg="white", relief="solid", **btn_style,
              command=lambda: change_color(color_white)).pack(pady=15)

    tk.Frame(frame_mid, height=20).pack()  # 占位符

    tk.Button(frame_mid, text="保存结果", bg="#dddddd", **btn_style,
              command=save_img).pack(pady=30)

    # --- 右侧初始化 ---
    label_result = tk.Label(frame_right, bg="#e0e0e0")
    label_result.pack(expand=True)
    change_color(color_white)


if __name__ == "__main__":
    root = tk.Tk()
    root.withdraw()  # 隐藏主窗口,作为模块使用时
    start_bg_tool()
    root.mainloop()

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐