[Python] 纯文本查看 复制代码
import os
import threading
from tkinter import (
Tk, Label, Entry, Button, StringVar, IntVar, Checkbutton, Text,
filedialog, Frame, ttk, DISABLED, NORMAL
)
from PIL import Image
class ImageResizerApp:
def __init__(self, root):
self.root = root
self.root.title("图片批量无损放大工具")
self.center_window(858, 600) # 居中窗口
self.stop_flag = False
self.supported_exts = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff']
self.selected_exts = []
self.processed_count = 0
self.skipped_count = 0
self.failed_count = 0
self.build_gui()
def center_window(self, width, height):
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
x = int((screen_width - width) / 2)
y = int((screen_height - height) / 2)
self.root.geometry(f"{width}x{height}+{x}+{y}")
def build_gui(self):
# 输入路径
Label(self.root, text="输入文件夹:").grid(row=0, column=0, sticky='e')
self.input_entry = Entry(self.root, width=60)
self.input_entry.grid(row=0, column=1, padx=5)
Button(self.root, text="浏览", command=self.select_input).grid(row=0, column=2)
# 覆盖原图(独立一行)
self.overwrite_var = IntVar()
Checkbutton(self.root, text="覆盖原图", variable=self.overwrite_var, command=self.toggle_output).grid(
row=1, column=0, columnspan=3, sticky='w', padx=20, pady=2)
# 输出路径(带标签)
Label(self.root, text="输出文件夹:").grid(row=2, column=0, sticky='e')
self.output_entry = Entry(self.root, width=60)
self.output_entry.grid(row=2, column=1, padx=5)
Button(self.root, text="浏览", command=self.select_output).grid(row=2, column=2)
# 放大倍数
Label(self.root, text="放大倍数:").grid(row=3, column=0, sticky='e')
self.scale_var = StringVar(value="3")
Entry(self.root, textvariable=self.scale_var, width=10).grid(row=3, column=1, sticky='w')
# 文件大小限制
Label(self.root, text="图片体积大于 KB自动跳过:").grid(row=3, column=1, sticky='e')
self.size_limit_var = StringVar(value="50")
Entry(self.root, textvariable=self.size_limit_var, width=10).grid(row=3, column=2, sticky='w')
# 图片格式选择
Label(self.root, text="图片格式:").grid(row=4, column=0, sticky='ne')
self.ext_vars = {}
format_frame = Frame(self.root)
format_frame.grid(row=4, column=1, sticky='w')
for ext in self.supported_exts:
var = IntVar(value=1)
Checkbutton(format_frame, text=ext, variable=var).pack(side='left')
self.ext_vars[ext] = var
# 自定义扩展名
Label(self.root, text="追加自定义\n图片类型扩展名 (逗号分隔):").grid(row=5, column=0, sticky='e')
self.custom_ext_entry = Entry(self.root, width=60)
self.custom_ext_entry.grid(row=5, column=1, padx=5, pady=2)
# 按钮 + 进度条
button_frame = Frame(self.root)
button_frame.grid(row=6, column=0, columnspan=3, pady=10)
self.start_btn = Button(button_frame, text="开始处理", command=self.start_processing)
self.start_btn.pack(side='left', padx=5)
self.stop_btn = Button(button_frame, text="停止", command=self.stop_processing, state=DISABLED)
self.stop_btn.pack(side='left', padx=5)
self.progress = ttk.Progressbar(self.root, length=500)
self.progress.grid(row=7, column=0, columnspan=3, pady=10)
# 日志区
Label(self.root, text="日志:").grid(row=8, column=0, sticky='ne')
self.log_text = Text(self.root, width=95, height=20)
self.log_text.grid(row=8, column=1, columnspan=2)
def select_input(self):
folder = filedialog.askdirectory()
if folder:
self.input_entry.delete(0, 'end')
self.input_entry.insert(0, folder)
def select_output(self):
folder = filedialog.askdirectory()
if folder:
self.output_entry.delete(0, 'end')
self.output_entry.insert(0, folder)
def toggle_output(self):
state = DISABLED if self.overwrite_var.get() else NORMAL
self.output_entry.config(state=state)
def log(self, message):
self.log_text.insert('end', message + '\n')
self.log_text.see('end')
def start_processing(self):
self.stop_flag = False
self.start_btn.config(state=DISABLED)
self.stop_btn.config(state=NORMAL)
self.log_text.delete('1.0', 'end')
threading.Thread(target=self.process_images).start()
def stop_processing(self):
self.stop_flag = True
self.log("正在停止...")
def process_images(self):
input_dir = self.input_entry.get().strip()
output_dir = self.output_entry.get().strip()
scale = int(self.scale_var.get())
size_limit_kb = int(self.size_limit_var.get())
self.selected_exts = [ext for ext, var in self.ext_vars.items() if var.get()]
custom_exts = [e.strip().lower() if e.startswith('.') else f".{e.strip().lower()}"
for e in self.custom_ext_entry.get().replace(',', ',').split(',') if e.strip()]
self.selected_exts.extend(custom_exts)
if not os.path.isdir(input_dir):
self.log("❌ 输入文件夹无效!")
return
all_files = []
for foldername, _, filenames in os.walk(input_dir):
for filename in filenames:
ext = os.path.splitext(filename)[1].lower()
if ext in self.selected_exts:
all_files.append(os.path.join(foldername, filename))
self.progress["maximum"] = len(all_files)
self.progress["value"] = 0
self.processed_count = self.skipped_count = self.failed_count = 0
for i, file_path in enumerate(all_files):
if self.stop_flag:
self.log("⏹ 已停止处理。")
break
try:
if os.path.getsize(file_path) >= size_limit_kb * 1024:
self.log(f"跳过(文件过大): {file_path}")
self.skipped_count += 1
continue
with Image.open(file_path) as img:
new_size = (img.width * scale, img.height * scale)
resized_img = img.resize(new_size, Image.Resampling.LANCZOS)
if self.overwrite_var.get():
save_path = file_path
else:
rel_path = os.path.relpath(file_path, input_dir)
save_path = os.path.join(output_dir, rel_path)
os.makedirs(os.path.dirname(save_path), exist_ok=True)
file_ext = os.path.splitext(save_path)[1].lower()
temp_path = save_path + ".tmp"
save_args = {'format': img.format, 'quality': 95} if file_ext in ('.jpg', '.jpeg') \
else {'format': img.format, 'optimize': True} if file_ext == '.png' \
else {'format': img.format}
resized_img.save(temp_path, **save_args)
os.replace(temp_path, save_path)
self.log(f"✅ 已处理: {file_path}")
self.processed_count += 1
except Exception as e:
self.log(f"❌ 失败: {file_path} - 错误: {str(e)}")
self.failed_count += 1
self.progress["value"] = i + 1
self.log(f"🎉 完成!成功处理: {self.processed_count} 张,跳过: {self.skipped_count} 张,失败: {self.failed_count} 张")
self.start_btn.config(state=NORMAL)
self.stop_btn.config(state=DISABLED)
if __name__ == "__main__":
root = Tk()
app = ImageResizerApp(root)
root.mainloop()