乐于分享
好东西不私藏

Python证件照批量处理工具下载链接版

Python证件照批量处理工具下载链接版

Python证件照批量处理工具  从此告别照相馆

通过网盘分享的文件:证件照排版生成器小助手.exe

链接: https://pan.baidu.com/s/1MudM4D-jFxQGHAnEKLpj1g?pwd=4444 提取码: 4444 复制这段内容后打开百度网盘手机App,操作更方便哦

“授人以鱼不如授人以渔,授人以渔不如给他写个脚本。”  某不愿透露姓名的程序员

你有没有算过,这辈子在证件照上花了多少冤枉钱?

办身份证,拍一次。办护照,再拍一次。考驾照、入职、办社保、报名考试每次都是同一个流程:走进照相馆,坐在那把被无数人坐过的椅子上,面对一个让你”笑一下、再自然一点”的摄影师,然后花30到80块钱,拿到一版你自己都不太认识的照片。更离谱的是,每次要求的尺寸还不一样一寸、二寸、小一寸、大一寸,蓝底、白底、红底,排列组合下来能凑一副扑克牌。

作为一个写代码的人,我实在无法忍受这种重复劳动。于是我花了一个下午,用 Python + Tkinter 写了这个证件照批量处理工具。它能做什么?简单说:你丢进去一堆照片,选好尺寸和背景色,点一下按钮,它就帮你全部处理好,批量导出。不用排队,不用花钱,不用忍受摄影师的尬聊。

这个工具的界面我花了不少心思左右分栏布局,蓝色主色调,扁平化设计,看起来就像一个正经的商业软件。左边是控制面板(尺寸选择、背景换色、亮度对比度调节),右边是实时预览区(原图和处理后对比,处理完还有绿色对勾)。整体风格干净利落,没有任何多余的装饰,打开就能用,用完就关,绝不浪费你一秒钟。

更重要的是,这个项目的代码量只有不到400行,结构清晰,注释完整,非常适合作为 Tkinter GUI 开发的学习案例。无论你是想学桌面应用开发,还是想给自己做个实用工具,这篇文章都值得你花10分钟读完。


一、界面架构  左右分栏 + 卡片式设计

整个界面采用经典的左右分栏布局

  • 左侧(320px固定宽度):控制面板,包含所有参数设置和操作按钮
  • 右侧(自适应宽度):预览区,表格式展示原图和处理结果

配色方案以 品牌蓝 #1677ff 为主色调,搭配浅灰背景 #f0f2f5 和白色卡片 #ffffff,视觉上清爽专业。

classIDPhotoTool:
    PRIMARY = "#1677ff"# 主色调-蓝
    BG_COLOR = "#f0f2f5"# 页面背景-浅灰
    CARD_BG = "#ffffff"# 卡片背景-白
    TEXT_COLOR = "#333333"# 主文字-深灰
    TEXT2_COLOR = "#666666"# 次要文字
    BORDER_COLOR = "#e8e8e8"# 边框色
    SUCCESS_COLOR = "#52c41a"# 成功标记-绿

左侧面板分为6个功能模块,每个模块之间用细线分隔,层次分明:

  1. 图片管理(添加/清空)
  2. 证件照尺寸(下拉选择)
  3. 处理选项(裁剪开关 + 亮度/对比度滑块)
  4. 背景颜色(20+色块网格选择)
  5. 背景识别容差(滑动条)
  6. 底部操作(刷新预览 + 批量保存)

二、核心功能  背景替换 + 尺寸裁剪

背景替换的算法思路很直接:取图片四角像素的平均色作为原背景色参考,然后遍历所有像素,与参考色的RGB差值在容差范围内的,替换为目标背景色。

def_process_image(self, item):
    img = item["original"].copy()

# 亮度调节
if self.brightness.get() != 1.0:
        img = ImageEnhance.Brightness(img).enhance(self.brightness.get())
# 对比度调节
if self.contrast.get() != 1.0:
        img = ImageEnhance.Contrast(img).enhance(self.contrast.get())

# 背景替换:基于四角平均色 + 容差
    target_rgb = BG_COLORS[self.selected_bg_idx][2]
    tolerance = self.tolerance.get()
    w, h = img.size
    corners = [img.getpixel((0,0)), img.getpixel((w-1,0)),
               img.getpixel((0,h-1)), img.getpixel((w-1,h-1))]
    avg_bg = tuple(sum(c[i] for c in corners)//4for i in range(3))

    pixels = img.load()
for y in range(h):
for x in range(w):
            r, g, b = pixels[x, y][:3]
if (abs(r-avg_bg[0]) < tolerance and
                abs(g-avg_bg[1]) < tolerance and
                abs(b-avg_bg[2]) < tolerance):
                pixels[x, y] = target_rgb

# 裁剪到目标尺寸
    size_cfg = PHOTO_SIZES[self.selected_size_idx.get()]
    target_w, target_h = size_cfg[3], size_cfg[4]
    img = img.resize((target_w, target_h), Image.LANCZOS)
    item["processed"] = img

这个方法简单高效,对于纯色背景的证件照效果很好。容差滑块让用户可以根据实际情况微调背景不够干净就调大,误伤主体就调小。


三、预览系统  动态渲染 + 滚动列表

右侧预览区使用 Canvas + Scrollbar 实现可滚动列表,每添加一张图片就动态渲染一行:

def_render_preview_list(self):
for w in self.preview_inner.winfo_children():
        w.destroy()

for i, item in enumerate(self.images):
        row = tk.Frame(self.preview_inner, bg=self.CARD_BG,
                      highlightbackground=self.BORDER_COLOR,
                      highlightthickness=1, pady=6, padx=8)
        row.pack(fill=tk.X, padx=8, pady=4)

# 序号
        tk.Label(row, text=str(i+1), ...).pack(side=tk.LEFT)

# 原图缩略图
        orig_thumb = item["original"].copy()
        orig_thumb.thumbnail((80100))
        tk_img = ImageTk.PhotoImage(orig_thumb)
        item["tk_orig"] = tk_img  # 防止GC回收
        tk.Label(row, image=tk_img, ...).pack(side=tk.LEFT)

# 箭头 
        tk.Label(row, text="    ", ...).pack(side=tk.LEFT)

# 处理后缩略图 + 绿色对勾
if item["processed"]:
            proc_thumb = item["processed"].copy()
            proc_thumb.thumbnail((80100))
            tk_proc = ImageTk.PhotoImage(proc_thumb)
            item["tk_proc"] = tk_proc
            tk.Label(row, image=tk_proc, ...).pack(side=tk.LEFT)
            tk.Label(row, text="  ", fg="#52c41a", ...).pack(side=tk.LEFT)

关键点:ImageTk.PhotoImage 对象必须保存引用(存到 item["tk_orig"]),否则会被 Python 垃圾回收导致图片不显示这是 Tkinter 图片显示的经典坑。


四、完整代码

以下为完整可运行代码,复制保存为 photo_gui.py,安装 pillow 后即可运行。

"""
证件照批量处理工具 - 专业桌面端GUI
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk, ImageEnhance
import os

PHOTO_SIZES = [
    ("小一寸 22x32mm"2232260378),
    ("一寸 25x35mm"2535295413),
    ("大一寸 33x48mm"3348390567),
    ("二寸 35x49mm"3549413579),
    ("小二寸 35x45mm"3545413531),
    ("五寸 89x127mm"8912710501500),
]

BG_COLORS = [
    ("纯白""#FFFFFF", (255,255,255)),
    ("红色""#FF0000", (255,0,0)),
    ("深红""#8B0000", (139,0,0)),
    ("蓝色""#0000FF", (0,0,255)),
    ("深蓝""#003366", (0,51,102)),
    ("浅蓝""#4A90D9", (74,144,217)),
    ("天蓝""#87CEEB", (135,206,235)),
    ("灰色""#808080", (128,128,128)),
    ("浅灰""#C0C0C0", (192,192,192)),
    ("黑色""#000000", (0,0,0)),
    ("绿色""#008000", (0,128,0)),
    ("浅绿""#90EE90", (144,238,144)),
    ("黄色""#FFFF00", (255,255,0)),
    ("橙色""#FFA500", (255,165,0)),
    ("粉色""#FFC0CB", (255,192,203)),
    ("紫色""#800080", (128,0,128)),
    ("棕色""#8B4513", (139,69,19)),
    ("米色""#F5F5DC", (245,245,220)),
    ("青色""#00FFFF", (0,255,255)),
    ("藏青""#000080", (0,0,128)),
]

classIDPhotoTool:
    PRIMARY = "#1677ff"
    BG_COLOR = "#f0f2f5"
    CARD_BG = "#ffffff"
    TEXT_COLOR = "#333333"
    TEXT2_COLOR = "#666666"
    BORDER_COLOR = "#e8e8e8"
    SUCCESS_COLOR = "#52c41a"

def__init__(self, root):
        self.root = root
        self.root.title("证件照批量处理工具")
        self.root.geometry("1100x700")
        self.root.configure(bg=self.BG_COLOR)
        self.root.minsize(1000650)
        self.images = []
        self.selected_size_idx = tk.IntVar(value=0)
        self.auto_crop = tk.BooleanVar(value=True)
        self.brightness = tk.DoubleVar(value=1.0)
        self.contrast = tk.DoubleVar(value=1.0)
        self.tolerance = tk.IntVar(value=30)
        self.selected_bg_idx = 0
        self.photo_count = tk.StringVar(value="已添加:0 张")
        self.current_bg_name = tk.StringVar(value="当前:纯白")
        self.status_text = tk.StringVar(value="就绪")
        self._build_ui()

def_build_ui(self):
        main = tk.Frame(self.root, bg=self.BG_COLOR)
        main.pack(fill=tk.BOTH, expand=True, padx=12, pady=12)
        self.left_panel = tk.Frame(main, bg=self.CARD_BG, width=320,
                                   highlightbackground=self.BORDER_COLOR, highlightthickness=1)
        self.left_panel.pack(side=tk.LEFT, fill=tk.Y, padx=(0,10))
        self.left_panel.pack_propagate(False)
        self.right_panel = tk.Frame(main, bg=self.CARD_BG,
                                    highlightbackground=self.BORDER_COLOR, highlightthickness=1)
        self.right_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self._build_left_panel()
        self._build_right_panel()

# ... (左侧面板、右侧预览区、处理逻辑等完整代码见 photo_gui.py 源文件)

if __name__ == "__main__":
    root = tk.Tk()
    app = IDPhotoTool(root)
    root.mainloop()

完整源码约386行,包含所有功能模块。详见同目录下 photo_gui.py 文件。


五、知识点总结

知识点
说明
Tkinter 布局管理
pack(side=LEFT/RIGHT, fill=BOTH, expand=True)

 实现左右分栏
自定义配色主题
通过类常量统一管理颜色,所有控件引用同一套色值
PIL/Pillow 图像处理
ImageEnhance

 调节亮度对比度,Image.resize + LANCZOS 高质量缩放
像素级操作
img.load()

 获取像素矩阵,逐像素判断并替换背景色
Canvas 滚动列表
Canvas

 + create_window + Scrollbar 实现可滚动的动态列表
ImageTk 引用保持
PhotoImage 必须保存引用防止GC回收,否则图片不显示
ttk.Combobox
下拉选择框,state="readonly" 防止用户手动输入
filedialog
askopenfilenames

 多选文件,askdirectory 选择保存目录
事件绑定
<<ComboboxSelected>>

 监听下拉框选择变化
批量文件操作
os.path.join

 拼接路径,Image.save(quality=95) 高质量保存

六、拓展场景与测试步骤

拓展方向

  1. 接入人脸检测:使用 OpenCV haarcascade_frontalface 或 dlib 实现自动人脸定位裁剪
  2. AI抠图:接入 rembg 库实现精准背景去除,替代简单的颜色容差算法
  3. 排版输出:将多张证件照排列到一张A4纸上(如8张一寸排版),直接打印
  4. 水印添加:在照片底部添加日期、用途等水印文字
  5. 美颜功能:磨皮、美白、瘦脸等基础美颜处理
  6. 打包为EXE:使用 pyinstaller -F photo_gui.py 打包为独立可执行文件分发

测试步骤

  1. 启动测试:运行 py photo_gui.py,确认窗口正常显示,左右分栏布局正确
  2. 添加图片:点击「添加图片」,选择3-5张不同尺寸的照片,确认右侧预览列表正确渲染
  3. 尺寸切换:下拉选择不同尺寸(一寸、二寸等),确认选择状态正确
  4. 背景色选择:点击不同色块,确认高亮状态切换,底部文字更新
  5. 参数调节:拖动亮度、对比度、容差滑块,确认无报错
  6. 刷新预览:点击「刷新所有预览」,确认右侧出现处理后图片和绿色对勾
  7. 批量保存:点击「批量保存」,选择目录,确认文件正确生成
  8. 清空测试:点击「清空全部」,确认列表清空、计数归零
  9. 边界测试:不添加图片直接点击保存/刷新,确认有友好提示
  10. 大批量测试:添加20+张图片,确认滚动条正常工作,处理不卡死

“纸上得来终觉浅,绝知此事要躬行。”  陆游《冬夜读书示子聿》

一个不到400行的Python脚本,就能替代照相馆里那台几万块的证件照处理软件。代码即生产力,这就是程序员的浪漫。

Python证件照批量处理工具 从此告别照相馆

“授人以鱼不如授人以渔,授人以渔不如给他写个脚本。” 某不愿透露姓名的程序员

你有没有算过,这辈子在证件照上花了多少冤枉钱?

学生时代考四六级、教资、普通话,毕业求职入职、办社保卡,成年后办身份证、护照、驾照,甚至报名各类职业考试,都离不开证件照。更让人头疼的是,不同场景的要求千差万别:一寸、二寸、小一寸、大一寸的尺寸区分,白底、蓝底、红底的背景要求,排列组合下来足足有十几种规格。每次都要跑照相馆,花30-80元,排队半小时、拍照一分钟,拿到的照片要么表情僵硬,要么尺寸不合规,返工更是家常便饭。

对于经常需要用到证件照的人来说,这笔重复开销积少成多,而且耗时又费力。作为程序员,我们最擅长用代码解决重复劳动——我花了一个下午,基于 Python + Tkinter + Pillow 打造了这款证件照批量处理工具。它无需复杂配置,打开即用,能一键完成图片导入、尺寸裁剪、背景替换、亮度对比度调节,批量导出合规证件照。

工具采用现代化GUI设计,操作逻辑极简,即使是不懂代码的新手也能轻松上手;同时代码结构清晰、注释完整,不到400行的源码,是学习Python桌面开发、图像处理的绝佳实战案例。接下来,我将全面拆解这款工具的设计、功能与使用方法,让你彻底告别照相馆,实现证件照自由。


一、开箱即用:环境准备与核心优势

在使用工具前,我们只需要完成最简单的环境配置,这也是新手入门的第一步:

  1. 确保安装Python 3.7及以上版本;
  2. 安装图像处理依赖库:打开命令提示符,执行 pip install pillow
  3. 将完整代码保存为 photo_gui.py,双击运行即可启动工具。

这款工具对比传统照相馆、在线证件照网站,拥有三大核心优势:

  • 零成本无广告:完全开源免费,无需充值、无水印,本地处理保护隐私;
  • 批量高效处理:一次导入几十张照片,统一设置参数,一键导出所有成品;
  • 自定义自由度高:20+种背景色、全规格尺寸、参数可调,满足所有官方证件照要求。

二、极简设计:左右分栏的专业界面架构

工具摒弃了复杂的布局,采用左右分栏+卡片式设计,兼顾美观与实用性,视觉风格对标商业软件,操作一目了然。

配色与布局规范

整体以品牌蓝#1677ff为主色调,搭配浅灰背景、白色卡片,清晰区分功能区与预览区,长时间使用也不会视觉疲劳。界面分为两大核心区域:

  • 左侧固定控制面板(320px):集中所有操作功能,分为6大模块,层级分明;
  • 右侧自适应预览区:实时展示原图与处理后的对比效果,支持滚动查看多张图片。

左侧功能模块详解

  1. 图片管理:支持多选图片导入、一键清空列表,底部实时显示已添加图片数量;
  2. 尺寸选择:内置小一寸、一寸、二寸等6种常用证件照尺寸,覆盖所有生活场景;
  3. 图像调节:开关自动裁剪,拖动滑块即可调整亮度、对比度,优化照片效果;
  4. 背景颜色:20种色块网格选择,纯白、红蓝底等官方要求色一键切换;
  5. 容差调节:针对背景不干净的照片,微调容差数值,精准替换背景不误伤人像;
  6. 底部操作:刷新预览实时查看效果,批量保存导出所有处理好的证件照。

三、核心技术:零基础也能懂的图像处理逻辑

工具的核心功能是背景替换+尺寸裁剪,算法简单高效,无需AI模型,对纯色背景的证件照效果拉满,我用通俗的语言拆解原理:

1. 智能背景替换

传统抠图需要复杂算法,而这款工具采用四角取色法:自动读取图片四个角落的像素颜色,计算平均背景色,再通过容差判断,将所有匹配背景色的像素替换为目标颜色。

  • 容差滑块是关键:背景有杂色就调大数值,人像被误抠就调小数值;
  • 支持纯白、红、蓝等所有常用背景,适配身份证、护照、简历等全场景。

2. 高清尺寸裁剪

内置标准证件照像素尺寸,采用 LANCZOS 高清缩放算法,保证裁剪后的照片不模糊、不变形,直接符合官方上传要求。

3. 图像美化调节

基于Pillow库的 ImageEnhance 模块,实时调整照片亮度与对比度,让人像更清晰、肤色更自然,无需后期修图。

核心处理代码仅几十行,却实现了专业软件的核心功能,充分体现了Python的简洁与强大。


四、可视化体验:动态预览与滚动列表

右侧预览区是工具的核心交互部分,采用 Canvas+Scrollbar 实现可滚动列表,完美解决多张图片的展示问题:

  • 每导入一张图片,自动渲染一行:序号+原图缩略图+箭头+处理后缩略图;
  • 处理完成后显示绿色对勾,直观区分未处理/已处理状态;
  • 解决Tkinter图片显示的经典坑:通过保存引用防止图片被垃圾回收,确保缩略图正常显示。

无论导入10张还是50张照片,都能流畅滚动查看、实时预览效果,操作反馈清晰,新手也不会迷茫。


五、完整源码:复制即用,无任何隐藏依赖

以下是工具的完整可运行代码,仅依赖Pillow库,保存后直接运行,即可拥有属于自己的证件照处理工具:

"""
证件照批量处理工具 - 专业桌面端GUI
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk, ImageEnhance
import os

# 标准证件照尺寸配置:名称、毫米、像素
PHOTO_SIZES = [
    ("小一寸 22x32mm"2232260378),
    ("一寸 25x35mm"2535295413),
    ("大一寸 33x48mm"3348390567),
    ("二寸 35x49mm"3549413579),
    ("小二寸 35x45mm"3545413531),
    ("五寸 89x127mm"8912710501500),
]



if __name__ == "__main__":
    root = tk.Tk()
    app = IDPhotoTool(root)
    root.mainloop()

六、进阶拓展:让工具更强大

基础版工具已经满足90%的日常需求,我们还可以轻松拓展功能:

  1. AI精准抠图:接入rembg库,告别纯色限制,复杂背景也能一键抠图;
  2. A4排版打印:自动将多张证件照排列到A4纸上,直接连接打印机打印;
  3. 打包EXE:用pyinstaller -F -w photo_gui.py打包成独立程序,发给家人朋友无需安装Python;
  4. 人脸自动裁剪:接入OpenCV人脸检测,自动居中人像,裁剪更标准。

七、新手必看:使用步骤与避坑指南

标准操作流程

  1. 运行工具,点击「添加图片」导入照片;
  2. 选择需要的证件照尺寸、背景色;
  3. 拖动容差滑块,调整背景替换效果;
  4. 点击「刷新预览」处理图片;
  5. 点击「批量保存」导出成品。

常见问题解决

  • 图片不显示:确保图片格式为JPG/PNG,路径无中文;
  • 背景替换不干净:调大容差数值;
  • 人像被抠掉:调小容差数值。

总结

这款不到400行的Python工具,用极简的代码实现了专业证件照处理软件的核心功能,彻底解决了我们日常使用证件照的痛点。它不仅是一个实用工具,更是Python GUI开发、图像处理的入门实战案例——让我们明白,代码不是冰冷的语法,而是解放双手、提升效率的利器。

无需花费金钱,无需浪费时间,自己动手打造专属工具,这就是编程带给我们的自由与浪漫。

完整代码
"""证件照处理工具 V5上栏(1/3):小尺寸原图+处理后预览下栏(2/3):紧凑拼接成一张完整图展示,可点击预览大图,可保存"""import tkinter as tkfrom tkinter import ttk, filedialog, messageboxfrom PIL import Image, ImageTk, ImageEnhanceimport os, numpy as npPHOTO_SIZES = [("小一寸 22x32mm"260378),("一寸 25x35mm"295413),("大一寸 33x48mm"390567),("二寸 35x49mm"413579),("小二寸 35x45mm"413531),]BG_COLORS = [("纯白","#FFFFFF",(255,255,255)),("红色","#FF0000",(255,0,0)),("深红","#8B0000",(139,0,0)),("蓝色","#0000FF",(0,0,255)),("深蓝","#003366",(0,51,102)),("浅蓝","#4A90D9",(74,144,217)),("灰色","#808080",(128,128,128)),("浅灰","#C0C0C0",(192,192,192)),("黑色","#000000",(0,0,0)),("绿色","#008000",(0,128,0)),("粉色","#FFC0CB",(255,192,203)),("紫色","#800080",(128,0,128)),("米色","#F5F5DC",(245,245,220)),("藏青","#000080",(0,0,128)),]LAYOUTS = ["2x2","3x3","4x4","2x3","3x4","4x5"]class App:C1="#1677ff";BG="#f0f2f5";CD="#ffffff";TX="#333";TX2="#666";BD="#e8e8e8";OK="#52c41a"def __init__(self, root):    self.root=root    self.root.title("证件照处理工具 V5")    self.root.geometry("1200x820")    self.root.configure(bg=self.BG)    self.images=[]    self.cur_idx=-1    self.selected_size=tk.IntVar(value=0)    self.brightness=tk.DoubleVar(value=1.0)    self.contrast=tk.DoubleVar(value=1.0)    self.tolerance=tk.IntVar(value=30)    self.bg_idx=0    self.count_var=tk.StringVar(value="0 张")    self.bg_name=tk.StringVar(value="纯白")    self.status=tk.StringVar(value="就绪")    self.layout_var=tk.StringVar(value="3x3")    self._refs=[]    self._layout_img=None  # 拼接后的完整PIL图    self._build()def _build(self):    m=tk.Frame(self.root,bg=self.BG)    m.pack(fill=tk.BOTH,expand=True,padx=8,pady=8)    self.lp=tk.Frame(m,bg=self.CD,width=250,highlightbackground=self.BD,highlightthickness=1)    self.lp.pack(side=tk.LEFT,fill=tk.Y,padx=(0,6))    self.lp.pack_propagate(False)    self.rp=tk.Frame(m,bg=self.BG)    self.rp.pack(side=tk.LEFT,fill=tk.BOTH,expand=True)    self._left()    self._right()def _left(self):    p=self.lp    tk.Label(p,text="证件照工具",font=("Microsoft YaHei UI",12,"bold"),fg=self.C1,bg=self.CD).pack(anchor="w",padx=12,pady=(10,6))    tk.Frame(p,height=1,bg=self.BD).pack(fill=tk.X,padx=12)    bf=tk.Frame(p,bg=self.CD);bf.pack(fill=tk.X,padx=12,pady=(6,2))    tk.Button(bf,text="添加",command=self.add_imgs,bg=self.C1,fg="white",font=("Microsoft YaHei UI",8,"bold"),relief="flat",padx=8,pady=2,cursor="hand2").pack(side=tk.LEFT)    tk.Button(bf,text="清空",command=self.clear_all,bg="#ddd",fg=self.TX,font=("Microsoft YaHei UI",8),relief="flat",padx=8,pady=2,cursor="hand2").pack(side=tk.LEFT,padx=4)    tk.Label(p,textvariable=self.count_var,font=("Microsoft YaHei UI",8),fg=self.TX2,bg=self.CD).pack(anchor="w",padx=12)    # 列表    lf=tk.Frame(p,bg=self.CD);lf.pack(fill=tk.BOTH,expand=True,padx=12,pady=4)    self.lb=tk.Listbox(lf,font=("Microsoft YaHei UI",8),bg="#fafafa",selectbackground=self.C1,selectforeground="white",relief="flat",highlightthickness=1,highlightbackground=self.BD)    self.lb.pack(fill=tk.BOTH,expand=True)    self.lb.bind("<<ListboxSelect>>",self._on_sel)    tk.Button(p,text="删除选中",command=self.del_sel,bg="#ff4d4f",fg="white",font=("Microsoft YaHei UI",8),relief="flat",padx=6,cursor="hand2").pack(anchor="w",padx=12,pady=2)    tk.Frame(p,height=1,bg=self.BD).pack(fill=tk.X,padx=12)    # 尺寸    self.sz_cb=ttk.Combobox(p,values=[s[0] for s in PHOTO_SIZES],state="readonly",width=20,font=("Microsoft YaHei UI",8))    self.sz_cb.current(0);self.sz_cb.pack(anchor="w",padx=12,pady=4)    self.sz_cb.bind("<<ComboboxSelected>>",lambda e:self.selected_size.set(self.sz_cb.current()))    # 亮度对比度    for lbl,var in [("亮度",self.brightness),("对比度",self.contrast)]:        f=tk.Frame(p,bg=self.CD);f.pack(fill=tk.X,padx=12)        tk.Label(f,text=lbl,font=("Microsoft YaHei UI",8),fg=self.TX2,bg=self.CD,width=4).pack(side=tk.LEFT)        tk.Scale(f,from_=0.5,to=2.0,resolution=0.1,orient=tk.HORIZONTAL,variable=var,length=130,bg=self.CD,highlightthickness=0,troughcolor=self.BD).pack(side=tk.LEFT)    # 背景色    cf=tk.Frame(p,bg=self.CD);cf.pack(fill=tk.X,padx=12,pady=4)    self.cbts=[]    for i,(nm,hx,rgb) in enumerate(BG_COLORS):        b=tk.Button(cf,bg=hx,width=2,height=1,relief="solid",bd=1,cursor="hand2",command=lambda x=i:self._sbg(x))        b.grid(row=i//7,column=i%7,padx=1,pady=1);self.cbts.append(b)    self.cbts[0].configure(relief="sunken",bd=3)    tk.Label(p,textvariable=self.bg_name,font=("Microsoft YaHei UI",8),fg=self.TX2,bg=self.CD).pack(anchor="w",padx=12)    tk.Scale(p,from_=5,to=100,orient=tk.HORIZONTAL,variable=self.tolerance,length=200,bg=self.CD,highlightthickness=0,troughcolor=self.BD,label="容差").pack(anchor="w",padx=12)    tk.Frame(p,height=1,bg=self.BD).pack(fill=tk.X,padx=12,pady=2)    tk.Button(p,text="处理全部",command=self.process_all,bg=self.C1,fg="white",font=("Microsoft YaHei UI",9,"bold"),relief="flat",padx=12,pady=3,cursor="hand2").pack(anchor="w",padx=12,pady=6)    tk.Label(p,textvariable=self.status,font=("Microsoft YaHei UI",8),fg=self.TX2,bg=self.CD).pack(anchor="w",padx=12)def _sbg(self,i):    for b in self.cbts:b.configure(relief="solid",bd=1)    self.cbts[i].configure(relief="sunken",bd=3)    self.bg_idx=i;self.bg_name.set(BG_COLORS[i][0])def _right(self):    # 上栏 1/3:小预览    self.top_f=tk.Frame(self.rp,bg=self.CD,highlightbackground=self.BD,highlightthickness=1)    self.top_f.place(relx=0,rely=0,relwidth=1,relheight=0.32)    tk.Label(self.top_f,text="原图 vs 处理后",font=("Microsoft YaHei UI",9,"bold"),fg=self.TX,bg=self.CD).pack(anchor="w",padx=8,pady=(4,2))    prev_row=tk.Frame(self.top_f,bg=self.CD)    prev_row.pack(fill=tk.BOTH,expand=True,padx=8,pady=(0,4))    self.orig_lbl=tk.Label(prev_row,text="原图",bg="#f5f5f5",fg="#aaa",font=("Microsoft YaHei UI",9),width=20,relief="groove",bd=1)    self.orig_lbl.pack(side=tk.LEFT,fill=tk.BOTH,expand=True,padx=(0,2))    self.proc_lbl=tk.Label(prev_row,text="处理后",bg="#f5f5f5",fg="#aaa",font=("Microsoft YaHei UI",9),width=20,relief="groove",bd=1)    self.proc_lbl.pack(side=tk.LEFT,fill=tk.BOTH,expand=True,padx=(2,0))    btn_row=tk.Frame(self.top_f,bg=self.CD)    btn_row.pack(fill=tk.X,padx=8,pady=(0,4))    tk.Button(btn_row,text="保存当前图",command=self.save_single,bg=self.C1,fg="white",font=("Microsoft YaHei UI",8),relief="flat",padx=6,cursor="hand2").pack(side=tk.LEFT)    # 下栏 2/3:排版拼接    self.bot_f=tk.Frame(self.rp,bg=self.CD,highlightbackground=self.BD,highlightthickness=1)    self.bot_f.place(relx=0,rely=0.34,relwidth=1,relheight=0.66)    ctrl=tk.Frame(self.bot_f,bg=self.CD)    ctrl.pack(fill=tk.X,padx=8,pady=(6,2))    tk.Label(ctrl,text="排版:",font=("Microsoft YaHei UI",9),fg=self.TX,bg=self.CD).pack(side=tk.LEFT)    for ly in LAYOUTS:        tk.Radiobutton(ctrl,text=ly,variable=self.layout_var,value=ly,bg=self.CD,font=("Microsoft YaHei UI",8),activebackground=self.CD,selectcolor=self.CD,command=self._show_layout).pack(side=tk.LEFT,padx=2)    tk.Button(ctrl,text="保存排版图",command=self.save_layout,bg=self.OK,fg="white",font=("Microsoft YaHei UI",8,"bold"),relief="flat",padx=8,cursor="hand2").pack(side=tk.RIGHT)    tk.Button(ctrl,text="预览大图",command=self._preview_layout_big,bg="#fa8c16",fg="white",font=("Microsoft YaHei UI",8,"bold"),relief="flat",padx=8,cursor="hand2").pack(side=tk.RIGHT,padx=4)    # 排版显示区    self.lay_lbl=tk.Label(self.bot_f,text="处理后自动生成排版图",bg="#fafafa",fg="#aaa",font=("Microsoft YaHei UI",10),relief="flat")    self.lay_lbl.pack(fill=tk.BOTH,expand=True,padx=8,pady=(2,8))# ===== 核心 =====def _rr(self,img,mw,mh):    w,h=img.size;r=min(mw/w,mh/h);return img.resize((max(1,int(w*r)),max(1,int(h*r))),Image.LANCZOS)def _rep_bg(self,img):    t=BG_COLORS[self.bg_idx][2];tol=self.tolerance.get()    if img.mode=="RGBA":        bg=Image.new("RGBA",img.size,t+(255,));return Image.alpha_composite(bg,img).convert("RGB")    arr=np.array(img,dtype=np.int16);h,w=arr.shape[:2]    pts=[(0,0),(w-1,0),(0,h-1),(w-1,h-1),(w//2,0),(w//2,h-1),(0,h//2),(w-1,h//2)]    avg=np.mean([arr[y,x] for x,y in pts if 0<=x<w and 0<=y<h],axis=0).astype(np.int16)    mask=np.all(np.abs(arr-avg)<tol,axis=2);arr[mask]=t    return Image.fromarray(arr.astype(np.uint8),"RGB")def _proc(self,item):    img=item["original"].copy()    if img.mode=="RGBA":        r,g,b,a=img.split();rgb=Image.merge("RGB",(r,g,b))        if self.brightness.get()!=1.0:rgb=ImageEnhance.Brightness(rgb).enhance(self.brightness.get())        if self.contrast.get()!=1.0:rgb=ImageEnhance.Contrast(rgb).enhance(self.contrast.get())        img=Image.merge("RGBA",(rgb.split()[0],rgb.split()[1],rgb.split()[2],a))    else:        if self.brightness.get()!=1.0:img=ImageEnhance.Brightness(img).enhance(self.brightness.get())        if self.contrast.get()!=1.0:img=ImageEnhance.Contrast(img).enhance(self.contrast.get())    img=self._rep_bg(img)    sz=PHOTO_SIZES[self.selected_size.get()];tw,th=sz[1],sz[2]    ow,oh=img.size;sc=max(tw/ow,th/oh)    img=img.resize((int(ow*sc),int(oh*sc)),Image.LANCZOS)    nw,nh=img.size;img=img.crop(((nw-tw)//2,(nh-th)//2,(nw-tw)//2+tw,(nh-th)//2+th))    item["processed"]=imgdef add_imgs(self):    fs=filedialog.askopenfilenames(filetypes=[("图片","*.jpg *.jpeg *.png *.bmp *.webp")])    if not fs:return    for f in fs:        try:            im=Image.open(f)            if im.mode not in("RGB","RGBA"):im=im.convert("RGBA")            self.images.append({"path":f,"original":im,"processed":None})        except:pass    self.count_var.set(f"{len(self.images)} 张");self._rlb()def clear_all(self):    self.images.clear();self.cur_idx=-1;self.count_var.set("0 张")    self.status.set("已清空");self._rlb()    self.orig_lbl.configure(image="",text="原图")    self.proc_lbl.configure(image="",text="处理后")    self.lay_lbl.configure(image="",text="处理后自动生成排版图")    self._layout_img=Nonedef del_sel(self):    s=self.lb.curselection()    if not s:return    self.images.pop(s[0]);self.cur_idx=-1    self.count_var.set(f"{len(self.images)} 张");self._rlb()def _rlb(self):    self.lb.delete(0,tk.END)    for i,it in enumerate(self.images):        n=os.path.basename(it["path"]);tag=" [OK]" if it.get("processed"else ""        self.lb.insert(tk.END,f"{i+1}. {n}{tag}")def _on_sel(self,e):    s=self.lb.curselection()    if not s:return    self.cur_idx=s[0];self._show_top()def _show_top(self):    if self.cur_idx<0 or self.cur_idx>=len(self.images):return    it=self.images[self.cur_idx]    self.root.update_idletasks()    pw=max(100,self.orig_lbl.winfo_width()-6)    ph=max(80,self.orig_lbl.winfo_height()-6)    # 原图    o=it["original"]    if o.mode=="RGBA":        bg=Image.new("RGBA",o.size,(240,240,240,255));o=Image.alpha_composite(bg,o).convert("RGB")    else:o=o.convert("RGB")    self._tko=ImageTk.PhotoImage(self._rr(o,pw,ph))    self.orig_lbl.configure(image=self._tko,text="")    # 处理后    if it.get("processed"):        self._tkp=ImageTk.PhotoImage(self._rr(it["processed"],pw,ph))        self.proc_lbl.configure(image=self._tkp,text="")    else:        self.proc_lbl.configure(image="",text="待处理")def process_all(self):    if not self.images:messagebox.showinfo("提示","请先添加");return    self.status.set("处理中...");self.root.update()    for it in self.images:self._proc(it)    self.status.set(f"完成 {len(self.images)} 张");self._rlb()    if self.cur_idx>=0:self._show_top()    self._show_layout()def _build_layout_image(self):    """生成紧凑拼接的完整图片(2px白色间隔)"""    processed=[it for it in self.images if it.get("processed")]    if not processed:return None    ly=self.layout_var.get()    rows,cols=[int(x) for x in ly.split("x")]    sz=PHOTO_SIZES[self.selected_size.get()];pw,ph=sz[1],sz[2]    gap=2    tw=cols*pw+(cols-1)*gap    th=rows*ph+(rows-1)*gap    canvas=Image.new("RGB",(tw,th),(255,255,255))    idx=0    for r in range(rows):        for c in range(cols):            if idx>=len(processed):idx=idx%len(processed)            canvas.paste(processed[idx]["processed"],(c*(pw+gap),r*(ph+gap)))            idx+=1    return canvasdef _show_layout(self):    self._layout_img=self._build_layout_image()    if not self._layout_img:        self.lay_lbl.configure(image="",text="请先处理");return    self.root.update_idletasks()    lw=max(200,self.lay_lbl.winfo_width()-10)    lh=max(150,self.lay_lbl.winfo_height()-10)    thumb=self._rr(self._layout_img,lw,lh)    self._tklay=ImageTk.PhotoImage(thumb)    self.lay_lbl.configure(image=self._tklay,text="")def _preview_layout_big(self):    if not self._layout_img:messagebox.showinfo("提示","请先处理");return    win=tk.Toplevel(self.root)    win.title("排版预览")    sw,sh=self.root.winfo_screenwidth(),self.root.winfo_screenheight()    thumb=self._rr(self._layout_img,sw-100,sh-100)    tk_img=ImageTk.PhotoImage(thumb)    lbl=tk.Label(win,image=tk_img,bg="white")    lbl.image=tk_img    lbl.pack()    win.geometry(f"{thumb.size[0]+20}x{thumb.size[1]+20}")def save_single(self):    if self.cur_idx<0 or not self.images[self.cur_idx].get("processed"):        messagebox.showinfo("提示","请选择并处理图片");return    p=filedialog.asksaveasfilename(defaultextension=".jpg",filetypes=[("JPEG","*.jpg"),("PNG","*.png")])    if p:self.images[self.cur_idx]["processed"].save(p,quality=95);self.status.set("已保存")def save_layout(self):    if not self._layout_img:messagebox.showinfo("提示","请先处理");return    ly=self.layout_var.get()    p=filedialog.asksaveasfilename(defaultextension=".jpg",filetypes=[("JPEG","*.jpg"),("PNG","*.png")],initialfile=f"layout_{ly}.jpg")    if p:self._layout_img.save(p,quality=95);self.status.set("排版已保存");messagebox.showinfo("完成",f"已保存:\n{p}")if name=="main":root=tk.Tk();App(root);root.mainloop()

通过网盘分享的文件:证件照排版生成器小助手.exe

链接: https://pan.baidu.com/s/1MudM4D-jFxQGHAnEKLpj1g?pwd=4444 提取码: 4444 复制这段内容后打开百度网盘手机App,操作更方便哦