virusmaster 发表于 2026-8-7 10:19:50

图片反向源码

新版块?麦子搞得,不得不支持一下了。{:4_101:}


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

麦青儿 发表于 2026-8-7 11:53:02

还能这样{:4_92:}
谢谢支持{:4_97:}
页: [1]
查看完整版本: 图片反向源码