用tkinter代替pyqt5重新生成image_converter
py打包的exe之所以大,是因为pyqt5大,本文用tkinter重新生成。
主要代码
import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image
import threading
import sys
class ImageConverterApp:
def __init__(self, root):
self.root = root
# 初始化所有变量
self.src_dir = tk.StringVar()
self.dest_dir = tk.StringVar()
self.output_format = tk.StringVar(value="webp")
self.running = False
# 设置窗口
self.setup_window()
# 创建控件
self.create_widgets()
def setup_window(self):
"""初始化窗口设置"""
self.root.title("图片格式转换工具")
# 设置窗口图标
try:
icon_path = os.path.abspath("icon.ico")
if os.path.exists(icon_path):
self.root.iconbitmap(default=icon_path)
except Exception as e:
print(f"图标加载失败: {e}")
# Windows系统启用DPI感知
if os.name == 'nt':
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
# 设置全局字体
self.set_system_font()
def set_system_font(self):
"""设置系统最佳中文字体"""
system_fonts = {
'win32': ('Microsoft YaHei', 12),
'darwin': ('PingFang SC', 14),
'linux': ('Noto Sans CJK SC', 12)
}
default_font = system_fonts.get(sys.platform, ('TkDefaultFont', 12))
self.root.option_add("*Font", default_font)
style = ttk.Style()
style.configure(".", font=default_font)
def create_widgets(self):
"""创建界面控件"""
main_frame = ttk.Frame(self.root, padding=15)
main_frame.pack(fill=tk.BOTH, expand=True)
# 源目录选择
src_frame = ttk.Frame(main_frame)
src_frame.pack(fill=tk.X, pady=5)
ttk.Label(src_frame, text="源 目 录 :").pack(side=tk.LEFT)
ttk.Entry(src_frame, textvariable=self.src_dir, width=40).pack(side=tk.LEFT, padx=5, expand=True, fill=tk.X)
ttk.Button(src_frame, text="浏览...", command=self.browse_src_dir).pack(side=tk.LEFT)
# 目标目录选择
dest_frame = ttk.Frame(main_frame)
dest_frame.pack(fill=tk.X, pady=5)
ttk.Label(dest_frame, text="目标目录:").pack(side=tk.LEFT)
ttk.Entry(dest_frame, textvariable=self.dest_dir, width=40).pack(side=tk.LEFT, padx=5, expand=True, fill=tk.X)
ttk.Button(dest_frame, text="浏览...", command=self.browse_dest_dir).pack(side=tk.LEFT)
# 输出格式选择
format_frame = ttk.Frame(main_frame)
format_frame.pack(fill=tk.X, pady=5)
ttk.Label(format_frame, text="输出格式:").pack(side=tk.LEFT)
formats = ["webp", "jpg", "png", "bmp", "gif", "tiff"]
ttk.Combobox(format_frame, textvariable=self.output_format, values=formats, width=8, state="readonly").pack(side=tk.LEFT, padx=5)
# 进度条
self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=450, mode='determinate')
self.progress.pack(pady=15, fill=tk.X)
# 操作按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(pady=10)
self.convert_btn = ttk.Button(btn_frame, text="开始转换", command=self.start_conversion)
self.convert_btn.pack(side=tk.LEFT, padx=5)
self.cancel_btn = ttk.Button(btn_frame, text="取消", command=self.cancel_conversion, state=tk.DISABLED)
self.cancel_btn.pack(side=tk.LEFT, padx=5)
# 状态栏
self.status_var = tk.StringVar(value="准备就绪")
ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W, padding=(5, 5)).pack(fill=tk.X, pady=(10, 0))
def browse_src_dir(self):
"""选择源目录"""
dir_path = filedialog.askdirectory()
if dir_path:
self.src_dir.set(dir_path)
if not self.dest_dir.get():
self.dest_dir.set(os.path.join(dir_path, "converted"))
def browse_dest_dir(self):
"""选择目标目录"""
dir_path = filedialog.askdirectory()
if dir_path:
self.dest_dir.set(dir_path)
def start_conversion(self):
"""开始转换图片"""
src_dir = self.src_dir.get()
dest_dir = self.dest_dir.get()
output_format = self.output_format.get().lower()
if not src_dir or not dest_dir:
messagebox.showwarning("警告", "请选择源目录和目标目录!")
return
if src_dir == dest_dir:
messagebox.showwarning("警告", "源目录和目标目录不能相同!")
return
self.running = True
self.convert_btn.config(state=tk.DISABLED)
self.cancel_btn.config(state=tk.NORMAL)
self.progress["value"] = 0
self.status_var.set("正在转换...")
threading.Thread(
target=self.convert_images,
args=(src_dir, dest_dir, output_format),
daemon=True
).start()
def convert_images(self, src_dir, dest_dir, output_format):
"""执行图片转换"""
try:
if not os.path.exists(src_dir):
self.show_error(f"源目录不存在: {src_dir}")
return
os.makedirs(dest_dir, exist_ok=True)
image_files = [
f for f in os.listdir(src_dir)
if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff', '.webp'))
]
total_files = len(image_files)
if total_files == 0:
self.show_error("未找到支持的图片文件")
return
for i, filename in enumerate(image_files):
if not self.running:
break
try:
img_path = os.path.join(src_dir, filename)
output_path = os.path.join(
dest_dir,
f"{os.path.splitext(filename)[0]}.{output_format}"
)
with Image.open(img_path) as img:
if img.mode in ('RGBA', 'LA') and output_format in ('jpg', 'jpeg'):
background = Image.new('RGB', img.size, (255, 255, 255))
background.paste(img, mask=img.split()[-1])
img = background
save_params = {}
if output_format == 'webp':
save_params = {'quality': 100, 'method': 6}
elif output_format in ('jpg', 'jpeg'):
save_params = {'quality': 95}
img.save(output_path, **save_params)
progress = (i + 1) / total_files * 100
self.update_progress(progress)
except Exception as e:
self.show_error(f"处理文件 {filename} 失败: {str(e)}")
continue
if self.running:
self.conversion_complete(f"转换完成!共处理 {total_files} 个文件")
except Exception as e:
self.show_error(f"转换过程中出错: {str(e)}")
def update_progress(self, value):
"""更新进度条"""
self.root.after(0, lambda: self.progress.configure(value=value))
def conversion_complete(self, message):
"""转换完成处理"""
self.root.after(0, lambda: [
self.status_var.set(message),
self.convert_btn.config(state=tk.NORMAL),
self.cancel_btn.config(state=tk.DISABLED),
messagebox.showinfo("完成", message)
])
def show_error(self, error_message):
"""显示错误信息"""
self.root.after(0, lambda: [
self.status_var.set(f"错误: {error_message}"),
self.convert_btn.config(state=tk.NORMAL),
self.cancel_btn.config(state=tk.DISABLED),
messagebox.showerror("错误", error_message)
])
def cancel_conversion(self):
"""取消转换"""
self.running = False
self.status_var.set("转换已取消")
self.convert_btn.config(state=tk.NORMAL)
self.cancel_btn.config(state=tk.DISABLED)
def main():
root = tk.Tk()
window_width = 550
window_height = 320
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
root.geometry(f"{window_width}x{window_height}+{x}+{y}")
app = ImageConverterApp(root)
root.mainloop()
if __name__ == "__main__":
main()
保存为image_converter.py。
有个小问题,运行的py程序图标无法显示为指定的图标,怎么改都不行,不管了。
界面字体用tk默认字体会比较模糊,这里指定使用操作系统自带的字体。
生成exe文件
pyinstaller --onefile --windowed --icon=icon.ico --add-data "icon.ico;." image_converter.py
dist目录下生成的exe文件大小为11.4M,比含pyqt5的32M小多了。

为什么 Tkinter 版更小
- 无额外依赖
Tkinter 是 Python 标准库,PyQt5 需要单独安装。 - 更简单的运行时
不需要 Qt 的复杂渲染引擎。 - 功能精简
虽然界面简单,但核心转换功能完全保留。