在日常工作和生活中,我们经常需要将图片从一种格式转换为另一种格式。比如:
如果只有一两张图片,用画图工具"另存为"就行了。但如果是几十张、上百张呢?一个个手动转换显然不现实。
今天我们就用 Python + Tkinter 来打造一款批量图片格式转换工具,支持拖拽添加文件、自定义输出路径、实时进度显示,让图片格式转换变得轻松高效!

欢迎大家关注此公众号,后台留言"python书籍"可免费获取【Python办公自动化高清PDF】电子书一本
此外小庄推荐一本适合于新手\小白入手一本 Python基础书籍,欢迎大家订阅
工具界面采用左右分栏布局:
支持的格式互转:PNG、JPG、WebP、BMP
pip install Pillow可选(支持拖拽功能):
pip install tkinterdnd2
Pillow是 Python 最强大的图像处理库,tkinterdnd2则为 Tkinter 添加了原生拖拽支持。
import tkinter as tkfrom tkinter import ttk, filedialog, messageboxfrom PIL import Imageimport osimport threadingtkinter | |
ttk | |
filedialog | |
messagebox | |
PIL.Image | |
os | |
threading |
整个工具封装在 ImageConverterApp 类中,通过面向对象的方式组织代码,结构清晰。
classImageConverterApp:def__init__(self, root):self.root = rootself.root.title('图片格式转换工具')self.root.geometry('800x600')self.root.configure(bg='#f0f0f0')self.setup_styles()self.create_widgets()self.file_list = []setup_styles() 方法统一配置了界面主题:
defsetup_styles(self): style = ttk.Style() style.theme_use('clam') style.configure('Custom.TButton', font=('微软雅黑', 10), padding=(10, 5)) style.configure('Title.TLabel', font=('微软雅黑', 16, 'bold'), background='#f0f0f0', foreground='#333333') style.configure('Info.TLabel', font=('微软雅黑', 9), background='#f0f0f0', foreground='#666666')要点解读:
clam 主题,比默认主题更现代Custom.TButton、Title.TLabel)实现样式复用defcreate_widgets(self):# 主标题 title_frame = tk.Frame(self.root, bg='#f0f0f0') title_frame.pack(fill='x', padx=20, pady=(20, 10))# 主容器 - 左右分栏 main_frame = tk.Frame(self.root, bg='#f0f0f0') main_frame.pack(fill='both', expand=True, padx=20, pady=10)# 左侧:拖拽区域 + 文件列表 left_frame = tk.Frame(main_frame, bg='#f0f0f0') left_frame.pack(side='left', fill='both', expand=True, padx=(0, 10))# 右侧:控制面板 right_frame = tk.Frame(main_frame, bg='#ffffff', relief='raised', bd=1) right_frame.pack(side='right', fill='y', padx=(10, 0))布局逻辑:
┌──────────────────────────────────────────────┐│ 主标题 │├──────────────────────┬───────────────────────┤│ 拖拽区域 │ 转换设置 ││ │ 目标格式: [png ▼] ││ 📂选择目录 🗑️清空 │ 保存路径: [默认] ││ │ 转换进度: ████░ 3/5 ││ 待转换文件列表 │ ││ ├ image1.png │ [🚀 开始转换] ││ ├ image2.jpg │ ││ └ image3.bmp │ │└──────────────────────┴───────────────────────┘defcreate_drop_area(self, parent):self.drop_frame = tk.Frame(parent, bg='#e8f4fd', relief='solid', bd=2, height=120)self.drop_frame.pack(fill='x', pady=(0, 15))self.drop_frame.pack_propagate(False) drop_label = tk.Label(inner_frame, text='📁 拖拽图片文件到这里\n或点击下方按钮选择目录', bg='#e8f4fd', fg='#666666', font=('微软雅黑', 11), justify='center')pack_propagate(False) 阻止子组件改变父容器大小,保证拖拽区域高度固定为 120px#e8f4fd 让拖拽区域在视觉上更突出defcreate_file_list(self, parent): list_frame = tk.Frame(parent, bg='#ffffff', relief='sunken', bd=1) list_frame.pack(fill='both', expand=True) scrollbar = ttk.Scrollbar(list_frame) scrollbar.pack(side='right', fill='y')self.listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set, font=('Consolas', 9), selectbackground='#0078d4')self.listbox.pack(fill='both', expand=True) scrollbar.config(command=self.listbox.yview)技巧:Listbox 和 Scrollbar 通过 yscrollcommand 和 command 双向绑定,实现同步滚动。使用等宽字体 Consolas 显示文件路径,更加整齐。
# 格式下拉框self.format_combobox = ttk.Combobox(format_frame, textvariable=self.format_var, values=['jpg', 'png', 'webp', 'bmp'], state='readonly', font=('微软雅黑', 10))self.format_combobox.set('png')# 输出路径self.output_path = tk.StringVar()self.output_path.set('默认(原图片目录)')state='readonly' 防止用户手动输入非法格式defsetup_drag_drop(self):try:from tkinterdnd2 import DND_FILES, TkinterDnDself.drop_frame.drop_target_register(DND_FILES)self.drop_frame.dnd_bind('<<Drop>>', self.drop_files)except (ImportError, AttributeError):# 优雅降级:未安装拖拽库时显示提示 drop_label = tk.Label(self.drop_frame, text='⚠️ 拖拽功能暂不可用', bg='#e8f4fd', fg='#ff6b6b')设计亮点——优雅降级:
tkinterdnd2,拖拽功能正常启用拖拽事件处理函数支持文件和文件夹两种拖入方式:
defdrop_files(self, event): files = event.data.split(' ')for f in files: file_path = f.strip('{}')if os.path.isfile(file_path):# 单个文件:检查是否为图片格式if file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.webp')): new_files.append(file_path)elif os.path.isdir(file_path):# 文件夹:扫描其中的所有图片for filename in os.listdir(file_path):if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.webp')): new_files.append(os.path.join(file_path, filename))还做了去重处理,已存在的文件不会重复添加。
defstart_conversion(self): output_format = self.format_var.get()ifnot output_format ornotself.file_list:return# 在子线程中执行转换,避免界面卡死 thread = threading.Thread(target=self.convert_images, args=(output_format,)) thread.start()为什么要用多线程?
图片转换是 I/O 密集型操作,如果在主线程中执行,GUI 会完全卡住,用户无法看到进度更新,甚至会出现"程序未响应"。使用 threading 将转换任务放到后台线程,主线程保持响应。
转换逻辑的核心:
defconvert_images(self, output_format):for i, file_path inenumerate(self.file_list): img = Image.open(file_path)# 关键:JPG 不支持透明通道if output_format.lower() == 'jpg'and img.mode in ('RGBA', 'LA'): background = Image.new('RGB', img.size, (255, 255, 255)) background.paste(img, mask=img.split()[-1]) img = background# 确定输出路径ifself.output_path.get() == '默认(原图片目录)': output_file_path = os.path.splitext(file_path)[0] + f'.{output_format}'else: filename = os.path.splitext(os.path.basename(file_path))[0] + f'.{output_format}' output_file_path = os.path.join(self.output_path.get(), filename) img.save(output_file_path)RGBA → JPG 的处理细节:
这是一个非常容易踩的坑!PNG 和 WebP 支持透明通道(RGBA),但 JPG 不支持。如果直接将 RGBA 图片保存为 JPG,Pillow 会报错。
解决方案:创建一个白色背景的 RGB 图片,将原图粘贴上去,用白色填充透明区域。
# 转换过程中实时更新进度progress_value = (i + 1) / total * 100self.progress['value'] = progress_valueself.progress_label.config(text=f'{i+1}/{total}')self.root.update_idletasks()# 转换期间禁用按钮,防止重复点击self.convert_btn.config(state='disabled', text='转换中...')# 完成后恢复self.convert_btn.config(state='normal', text='🚀 开始转换')messagebox.showinfo('转换完成', f'成功转换 {success_count}/{total} 个文件')update_idletasks() 强制刷新界面,确保进度条实时更新if __name__ == '__main__':try:from tkinterdnd2 import TkinterDnD root = TkinterDnD.Tk() # 支持拖拽的窗口except ImportError: root = tk.Tk() # 普通窗口 app = ImageConverterApp(root) root.mainloop()同样是优雅降级的设计:有 tkinterdnd2 就用增强版窗口,没有就用标准窗口。
| ttk 主题化 | clam 主题 + 自定义样式,界面更现代 |
| 左右分栏布局 | pack(side='left/right') |
| 多线程转换 | threading.Thread |
| 优雅降级 | |
| RGBA 兼容 | |
| 进度反馈 | |
| 去重机制 | |
| 自定义输出路径 |
这个批量图片格式转换工具虽然代码不到 400 行,但涵盖了 Tkinter GUI 开发中很多实用技巧:
无论你是想学习 Tkinter 开发,还是真的需要一款批量图片转换工具,这个项目都是一个很好的起点。快动手试试吧!