|
|
新版块?麦子搞得,不得不支持一下了。
以下是AI生成的图片反向工具源码。例如黑底白字的图片,转成白底黑字。
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinterdnd2 import TkinterDnD, DND_FILES
import os
from datetime import datetime
import threading
from PIL import Image
import tempfile
import shutil
class JPGInverter(TkinterDnD.Tk):
def __init__(self):
super().__init__()
self.title("JPG照片反相处理工具")
self.geometry("800x600")
self.resizable(False, False)
self.configure(bg='#f0f0f0')
# 设置字体
self.font_title = ("微软雅黑", 16, "bold")
self.font_normal = ("微软雅黑", 10)
self.font_small = ("微软雅黑", 9)
self.file_list = []
self.is_processing = False
self.overwrite_mode = tk.BooleanVar(value=False) # 覆盖模式变量
self.auto_clear = tk.BooleanVar(value=True) # 自动清空列表选项
self.create_widgets()
def create_widgets(self):
# 主标题
title_label = tk.Label(self, text="JPG照片反相处理工具",
font=self.font_title, fg="#2c3e50", bg='#f0f0f0')
title_label.pack(pady=15)
# 说明
desc_label = tk.Label(self,
text="将照片中黑色变为白色,白色变为黑色\n支持JPG/JPEG格式文件",
font=self.font_normal, fg="#666666", justify=tk.CENTER, bg='#f0f0f0')
desc_label.pack(pady=5)
# 文件列表框架
list_frame = tk.LabelFrame(self, text=" 已选择的文件 ",
font=self.font_normal, padx=15, pady=15, bg='#f0f0f0')
list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 创建标题栏框架,包含标题和选项
title_bar_frame = tk.Frame(list_frame, bg='#f0f0f0')
title_bar_frame.pack(fill=tk.X, pady=(0, 10))
# 标题
title_label = tk.Label(title_bar_frame, text="文件列表",
font=self.font_normal, fg="#2c3e50", bg='#f0f0f0')
title_label.pack(side=tk.LEFT)
# 选项框 - 放在右上角
options_frame = tk.Frame(title_bar_frame, bg='#f0f0f0')
options_frame.pack(side=tk.RIGHT)
# 自动清空选项
self.auto_clear_check = tk.Checkbutton(
options_frame,
text="完成后自动清空",
variable=self.auto_clear,
font=self.font_small,
bg='#f0f0f0',
command=self.on_auto_clear_change
)
self.auto_clear_check.pack(side=tk.RIGHT, padx=(10, 0))
# 覆盖选项框
self.overwrite_check = tk.Checkbutton(
options_frame,
text="覆盖原文件",
variable=self.overwrite_mode,
font=self.font_small,
bg='#f0f0f0',
command=self.on_overwrite_change
)
self.overwrite_check.pack(side=tk.RIGHT)
# 文件列表框
listbox_frame = tk.Frame(list_frame, bg='#f0f0f0')
listbox_frame.pack(fill=tk.BOTH, expand=True)
self.listbox = tk.Listbox(listbox_frame, selectmode=tk.EXTENDED,
font=self.font_small, height=12, bg='white',
relief='solid', bd=1, highlightthickness=0)
scrollbar = tk.Scrollbar(listbox_frame)
self.listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.listbox.yview)
self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 设置拖放功能
self.setup_drag_drop()
# 按钮框架
button_frame = tk.Frame(self, bg='#f0f0f0')
button_frame.pack(fill=tk.X, padx=20, pady=15)
# 创建彩色按钮 - 并排排列
self.create_colored_buttons(button_frame)
# 进度条
progress_frame = tk.Frame(self, bg='#f0f0f0')
progress_frame.pack(fill=tk.X, padx=20, pady=10)
self.progress_text = tk.StringVar()
self.progress_text.set("等待处理...")
progress_label = tk.Label(progress_frame, textvariable=self.progress_text,
font=self.font_normal, fg="#2196F3", bg='#f0f0f0')
progress_label.pack()
self.progress = ttk.Progressbar(progress_frame, mode='determinate')
self.progress.pack(fill=tk.X, pady=5)
# 状态标签
self.status_var = tk.StringVar()
self.status_var.set("就绪 - 请选择JPG文件或拖放文件到列表")
status_label = tk.Label(self, textvariable=self.status_var,
font=self.font_small, fg="#666666", bg='#f0f0f0')
status_label.pack(pady=5)
# 初始提示
self.listbox.insert(tk.END, "请点击按钮选择文件或直接将文件拖放到此区域")
self.listbox.itemconfig(0, fg='gray')
def on_overwrite_change(self):
"""覆盖选项改变时的回调函数"""
if self.overwrite_mode.get():
self.status_var.set("已启用覆盖模式 - 处理后将直接覆盖原文件")
else:
self.status_var.set("已禁用覆盖模式 - 处理后将创建新文件")
def on_auto_clear_change(self):
"""自动清空选项改变时的回调函数"""
if self.auto_clear.get():
self.status_var.set("已启用自动清空 - 处理完成后将自动清空文件列表")
else:
self.status_var.set("已禁用自动清空 - 处理完成后保留文件列表")
def create_colored_buttons(self, parent):
"""创建彩色按钮并添加动画效果 - 并排排列"""
# 按钮配置
buttons_config = [
{"text": "选择文件", "command": self.select_files, "color": "#4CAF50", "hover": "#45a049"},
{"text": "选择文件夹", "command": self.select_folder, "color": "#2196F3", "hover": "#0b7dda"},
{"text": "进行处理", "command": self.start_processing, "color": "#FF9800", "hover": "#e68900"},
{"text": "清除列表", "command": self.clear_list, "color": "#f44336", "hover": "#da190b"}
]
# 创建按钮容器,使按钮等距排列
button_container = tk.Frame(parent, bg='#f0f0f0')
button_container.pack(expand=True)
# 创建按钮
for i, config in enumerate(buttons_config):
btn = tk.Button(
button_container,
text=config["text"],
font=self.font_normal,
width=15,
height=2,
bg=config["color"],
fg="white",
relief="flat",
bd=0,
cursor="hand2",
command=config["command"]
)
# 使用grid布局使按钮等距排列
btn.grid(row=0, column=i, padx=10, pady=5)
# 绑定鼠标事件实现动画效果
btn.bind("<Enter>", lambda e, b=btn, h=config["hover"]: self.on_enter(e, b, h))
btn.bind("<Leave>", lambda e, b=btn, c=config["color"]: self.on_leave(e, b, c))
btn.bind("<ButtonPress-1>", lambda e, b=btn: self.on_press(e, b))
btn.bind("<ButtonRelease-1>", lambda e, b=btn, c=config["color"]: self.on_release(e, b, c))
# 保存处理按钮引用以便禁用/启用
if config["text"] == "进行处理":
self.process_btn = btn
# 配置grid权重,使按钮居中
for i in range(len(buttons_config)):
button_container.grid_columnconfigure(i, weight=1)
def on_enter(self, event, button, hover_color):
"""鼠标进入按钮效果"""
button.config(bg=hover_color)
def on_leave(self, event, button, normal_color):
"""鼠标离开按钮效果"""
button.config(bg=normal_color)
def on_press(self, event, button):
"""鼠标按下效果"""
button.config(relief="sunken")
def on_release(self, event, button, normal_color):
"""鼠标释放效果"""
button.config(relief="flat", bg=normal_color)
def setup_drag_drop(self):
"""使用tkinterdnd2设置拖放功能"""
# 为列表框注册拖放功能
self.listbox.drop_target_register(DND_FILES)
self.listbox.dnd_bind('<<Drop>>', self.on_drop)
# 为整个窗口也注册拖放功能,增加拖放区域
self.drop_target_register(DND_FILES)
self.dnd_bind('<<Drop>>', self.on_drop_window)
def on_drop(self, event):
"""处理列表框的拖放事件"""
try:
# 获取拖放的文件路径
files = self.tk.splitlist(event.data)
self.process_dropped_files(files)
except Exception as e:
print(f"拖放处理错误: {e}")
messagebox.showerror("错误", f"拖放文件时出错: {str(e)}")
def on_drop_window(self, event):
"""处理窗口的拖放事件"""
try:
# 获取拖放的文件路径
files = self.tk.splitlist(event.data)
self.process_dropped_files(files)
except Exception as e:
print(f"窗口拖放处理错误: {e}")
messagebox.showerror("错误", f"拖放文件时出错: {str(e)}")
def process_dropped_files(self, files):
"""处理拖放的文件"""
valid_files = []
for file_path in files:
# 清理文件路径(去除花括号等)
clean_path = file_path.strip('{}')
if clean_path.lower().endswith(('.jpg', '.jpeg')):
if os.path.exists(clean_path):
valid_files.append(clean_path)
if valid_files:
self.add_files(valid_files)
self.status_var.set(f"通过拖放添加了 {len(valid_files)} 个文件,共 {len(self.file_list)} 个文件")
else:
messagebox.showwarning("警告", "拖放的文件中没有找到有效的JPG格式文件")
def select_files(self):
"""选择文件"""
files = filedialog.askopenfilenames(
title="选择JPG图像文件",
filetypes=[
("JPG/JPEG图像", "*.jpg *.jpeg"),
("所有文件", "*.*")
]
)
if files:
self.add_files(files)
def select_folder(self):
"""选择文件夹"""
folder = filedialog.askdirectory(title="选择包含JPG文件的文件夹")
if folder:
jpg_files = []
for file in os.listdir(folder):
if file.lower().endswith(('.jpg', '.jpeg')):
full_path = os.path.join(folder, file)
if os.path.isfile(full_path):
jpg_files.append(full_path)
if jpg_files:
self.add_files(jpg_files)
self.status_var.set(f"已从文件夹添加 {len(jpg_files)} 个文件,共 {len(self.file_list)} 个文件")
else:
messagebox.showinfo("提示", "该文件夹中没有找到JPG文件")
def add_files(self, files):
"""添加文件到列表"""
new_files = []
for f in files:
if f not in self.file_list:
if f.lower().endswith(('.jpg', '.jpeg')):
if os.path.exists(f):
new_files.append(f)
if new_files:
self.file_list.extend(new_files)
self.update_listbox()
self.status_var.set(f"已添加 {len(new_files)} 个文件,共 {len(self.file_list)} 个文件")
def update_listbox(self):
"""更新文件列表显示"""
self.listbox.delete(0, tk.END)
if self.file_list:
for file_path in self.file_list:
filename = os.path.basename(file_path)
try:
file_size = os.path.getsize(file_path) // 1024
display_text = f"{filename} ({file_size} KB)"
except:
display_text = f"{filename}"
self.listbox.insert(tk.END, display_text)
else:
self.listbox.insert(tk.END, "请点击按钮选择文件或直接将文件拖放到此区域")
self.listbox.itemconfig(0, fg='gray')
def clear_list(self):
"""清除文件列表"""
if self.file_list:
self.file_list = []
self.update_listbox()
self.status_var.set("文件列表已清空")
self.progress['value'] = 0
self.progress_text.set("等待处理...")
def start_processing(self):
"""开始处理文件"""
if not self.file_list:
messagebox.showwarning("警告", "请先选择要处理的JPG文件")
return
if self.is_processing:
messagebox.showwarning("警告", "正在处理中,请稍候...")
return
# 根据覆盖模式设置确认消息
if self.overwrite_mode.get():
confirm_msg = (f"即将处理 {len(self.file_list)} 个JPG文件。\n"
f"⚠️ 警告:已启用覆盖模式,原文件将被直接覆盖!\n\n"
f"是否继续?")
else:
confirm_msg = (f"即将处理 {len(self.file_list)} 个JPG文件。\n"
f"处理后的文件将保存在原文件同一目录下。\n\n"
f"是否继续?")
result = messagebox.askyesno("确认", confirm_msg)
if not result:
return
self.is_processing = True
self.process_btn.config(state=tk.DISABLED, bg='#cccccc')
thread = threading.Thread(target=self.process_images)
thread.daemon = True
thread.start()
def process_images(self):
"""处理图像文件"""
total = len(self.file_list)
success_count = 0
failed_files = []
self.progress['maximum'] = total
self.progress['value'] = 0
for i, file_path in enumerate(self.file_list):
try:
filename = os.path.basename(file_path)
mode_text = "覆盖" if self.overwrite_mode.get() else "创建新文件"
self.progress_text.set(f"处理中: {filename} ({i+1}/{total})")
self.status_var.set(f"正在反相处理: {filename} [{mode_text}]")
# 处理图像
new_file_path = self.invert_image(file_path)
success_count += 1
except Exception as e:
error_msg = str(e)
failed_files.append(f"{filename}: {error_msg}")
finally:
self.progress['value'] = i + 1
progress_percent = int((i + 1) / total * 100)
self.progress_text.set(f"{progress_percent}%")
self.update_idletasks()
self.is_processing = False
self.process_btn.config(state=tk.NORMAL, bg='#FF9800')
self.show_result(success_count, failed_files)
def invert_image(self, file_path):
"""图像反相处理"""
# 检查是否启用覆盖模式
if self.overwrite_mode.get():
# 覆盖模式:直接使用原文件名
new_filepath = file_path
# 使用临时文件确保安全
with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file:
temp_path = temp_file.name
try:
# 处理图像到临时文件
with Image.open(file_path) as img:
# 转换为RGB模式
if img.mode != 'RGB':
img = img.convert('RGB')
# 反相处理
inverted_img = Image.eval(img, lambda x: 255 - x)
# 保存到临时文件
inverted_img.save(temp_path, quality=95)
# 用临时文件替换原文件
shutil.copy2(temp_path, new_filepath)
finally:
# 删除临时文件
if os.path.exists(temp_path):
os.unlink(temp_path)
else:
# 非覆盖模式:生成新文件名
current_date = datetime.now().strftime("%Y%m%d")
dir_name = os.path.dirname(file_path)
file_name, ext = os.path.splitext(os.path.basename(file_path))
new_filename = f"{file_name}_{current_date}{ext}"
new_filepath = os.path.join(dir_name, new_filename)
# 使用Pillow处理图像
with Image.open(file_path) as img:
# 转换为RGB模式
if img.mode != 'RGB':
img = img.convert('RGB')
# 反相处理
inverted_img = Image.eval(img, lambda x: 255 - x)
# 保存图像
inverted_img.save(new_filepath, quality=95)
return new_filepath
def show_result(self, success, failed):
"""显示处理结果"""
def show():
mode_text = "覆盖" if self.overwrite_mode.get() else "创建新文件"
# 检查是否需要自动清空列表
auto_clear_enabled = self.auto_clear.get()
if failed:
failed_list = "\n".join(failed[:5])
if len(failed) > 5:
failed_list += f"\n... 还有 {len(failed) - 5} 个文件失败"
messagebox.showwarning("处理完成",
f"✅ 成功{mode_text}: {success} 个文件\n"
f"❌ 失败: {len(failed)} 个文件\n\n"
f"失败文件:\n{failed_list}")
# 如果有失败文件,不清空列表(即使启用了自动清空)
# 这样用户可以重新处理失败的文件
if auto_clear_enabled and success > 0:
# 只保留失败的文件在列表中
self.keep_only_failed_files(failed)
else:
if self.overwrite_mode.get():
messagebox.showinfo("处理完成",
f"✅ 成功覆盖了 {success} 个JPG文件!\n\n"
f"原文件已被反相处理后的版本替换。")
else:
messagebox.showinfo("处理完成",
f"✅ 成功处理了 {success} 个JPG文件!\n\n"
f"反相后的文件已保存在原文件目录下。")
# 全部成功,如果启用了自动清空,则清空列表
if auto_clear_enabled:
self.clear_list()
# 更新状态
if auto_clear_enabled and not failed:
self.status_var.set("处理完成,文件列表已自动清空")
else:
self.status_var.set(f"处理完成: 成功 {success}, 失败 {len(failed)}")
self.progress_text.set("处理完成!")
self.after(0, show)
def keep_only_failed_files(self, failed):
"""只保留失败的文件在列表中"""
# 提取失败文件的原始路径
failed_paths = []
for failed_item in failed:
# 失败项目的格式是 "文件名: 错误信息"
filename = failed_item.split(":")[0].strip()
# 在文件列表中查找匹配的文件路径
for file_path in self.file_list:
if os.path.basename(file_path) == filename:
failed_paths.append(file_path)
break
# 更新文件列表,只保留失败的文件
self.file_list = failed_paths
self.update_listbox()
if self.file_list:
self.status_var.set(f"已自动移除成功处理的文件,保留 {len(self.file_list)} 个失败文件")
else:
self.status_var.set("所有文件处理完成")
def main():
try:
# 窗口居中
app = JPGInverter()
app.update_idletasks()
screen_width = app.winfo_screenwidth()
screen_height = app.winfo_screenheight()
x = (screen_width - 800) // 2
y = (screen_height - 600) // 2
app.geometry(f"800x600+{x}+{y}")
app.mainloop()
except Exception as e:
messagebox.showerror("错误", f"程序启动失败: {str(e)}")
if __name__ == "__main__":
main()
|
|