找回密码 立即注册
查看: 5|回复: 1

[工具分享] 图片反向源码

[复制链接]

199

主题

966

回帖

3317

积分

社区嘉宾

积分
3317
发表于 3 小时前 | 显示全部楼层 |阅读模式
新版块?麦子搞得,不得不支持一下了。


以下是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()

      

44

主题

1390

回帖

4063

积分

管理员

积分
4063
发表于 1 小时前 | 显示全部楼层
还能这样
谢谢支持
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


快速回复 返回顶部 返回列表