用tkinter代替pyqt5重新生成image_converter


目录
  1. 1. 主要代码
  2. 2. 生成exe文件
  3. 3. 为什么 Tkinter 版更小

py打包之的exe之所以大,是因为pyqt5大,本文用tkinter重新生成。

主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
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 的复杂渲染引擎。
  • 功能精简
    虽然界面简单,但核心转换功能完全保留。