吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1426|回复: 75
收起左侧

[Python 原创] 写了个图片处理小工具,没想到工程量有点大

  [复制链接]
nobiyou 发表于 2025-4-11 15:18
本帖最后由 nobiyou 于 2025-4-14 00:52 编辑

图像工坊 - 一款多功能图像处理工具

开发背景

在日常工作中,经常需要处理大量图片,比如调整大小、添加水印、转换格式等。虽然市面上有很多图片处理软件,但要么功能太复杂,要么操作不够便捷。因此,我开发了这款轻量级的图片处理工具,专注于最常用的图片处理功能。

开发时间的实际不长,在错误的分析上有了AI的帮助,比自己排查快很多。

改名字了,基础功能图片预览,再才是图片编辑。

软件功能

1. 基础图片处理

  • 裁剪:支持自由裁剪和固定比例裁剪
  • 调整大小:支持按像素和按比例调整
  • 旋转翻转:90°/180°/270°旋转,水平/垂直翻转
  • 格式转换:支持主流格式(JPG、PNG、BMP、WEBP等)
  • 基础调整:亮度、对比度、饱和度、锐化等

2. 高级功能

  • 模板裁剪:支持自定义模板位置和大小
  • 添加水印:支持文字水印和图片水印
  • 防伪水印:支持全图平铺水印,适合版权保护

3. 批量处理

  • 批量调整:同时处理多张图片的尺寸
  • 批量转换:批量转换图片格式
  • 批量水印:批量添加水印

开发过程

1. 技术选型

  • 编程语言:Python 3
  • GUI框架:Tkinter
  • 图像处理:Pillow (PIL)
  • 打包工具:PyInstaller

2. 开发难点及解决方案

2.1 字体加载优化
  • 问题:程序启动时加载系统字体较慢
  • 解决:
    • 添加加载进度窗口
    • 显示实时加载状态
    • 优化字体搜索算法
2.2 图片预览性能
  • 问题:大图片预览卡顿
  • 解决:
    • 实现智能缩放算法
    • 添加缓存机制
    • 优化内存使用
2.3 水印功能实现
  • 问题:水印位置调整不直观
  • 解决:
    • 添加可视化预览
    • 支持拖拽调整位置
    • 实现网格对齐功能
2.4 批量处理
  • 问题:处理大量图片时程序无响应
  • 解决:
    • 实现多步骤向导界面
    • 添加进度显示
    • 优化文件处理逻辑
2.5 水印的定位和同步
  • 问题:位置不精准,现在也不,只是相对,因为字体的原因,定位有偏差
  • 解决:
    • 实现实时同步水印的参数
    • 水印的角度和大小
    • 之前想的是用到时候加载字体,实际是软件启动就加载字体最好
2.6 批量功能自动创建文件夹(4.12更新)

- 问题:解决批次处理图片混淆的问题

  • 解决:
    全新批次文件夹功能: 自动创建日期时间标记的文件夹,更好地组织输出

      - 批量调整大小: 使用"resize_yyyyMMdd_HHmmss"格式文件夹
      - 批量格式转换: 使用"convert_yyyyMMdd_HHmmss"格式文件夹
      - 批量添加水印: 使用"watermark_yyyyMMdd_HHmmss"格式文件夹
2.7 加载2000+文件(4.14更新)

- 问题:解决加载文件多性能问题及获取缩略图方法

  • 解决:
    图片预览功能简单,但2000+文件性能的问题想了很多办法,处理方法是每次加载100张图片缩略图

用户界面

1. 主界面设计

  • 左侧工具栏:常用功能快捷访问
  • 中央预览区:实时预览处理效果
  • 右侧参数区:调整处理参数

2. 交互优化

  • 支持拖拽操作
  • 实时预览效果
  • 简洁的参数调整界面
  • 清晰的操作提示

3. 简洁不美化

  • 实用就好,不想做的花里胡哨的

开发心得

  1. 用户体验至上

    • 注重操作流程的简洁性
    • 添加适当的视觉反馈
    • 优化各项功能的响应速度
  2. 性能优化

    • 合理使用缓存机制
    • 优化大文件处理逻辑
    • 注意内存资源管理
  3. 代码质量

    • 模块化设计
    • 清晰的代码结构
    • 完善的错误处理

未来计划

  1. 功能扩展

    • 添加更多图片滤镜
    • 支持更多图片格式
    • 添加图片编辑历史记录
  2. 性能提升

    • 引入多线程处理
    • 优化内存使用
    • 提升处理速度
  3. 界面优化

    • 支持自定义主题
    • 添加快捷键支持
    • 优化操作流程

下载使用

系统要求

  • Windows 7及以上系统
  • 不需要安装Python环境
  • 无需额外依赖

@hengzhenhui945 的建议,加了批量功能自动创建文件夹,同时修复了一下bug
@施施乐 的建议,基础功能是图片浏览,再才是图片编辑处理

新版下载地址

[https://wwyp.lanzoul.com/ilTgM2tj9n8b]

使用说明

  1. 下载并解压
  2. 运行图片处理小工具.exe
  3. 开始使用

反馈建议

如果您在使用过程中遇到任何问题,或有任何建议,欢迎反馈!

界面部分截图

新界面截图


[tr][td] [td] [td]

[tr][td] 微信截图_20250414001621.png [td] 微信截图_20250414001642.png [td]
[tr][td] [td] [td]

旧界面截图


[tr][td] 微信截图_20250411145614.png [td] 微信截图_20250411145635.png [td] 微信截图_20250411145700.png
[tr][td] 微信截图_20250411145714.png [td] 微信截图_20250411145745.png [td] 微信截图_20250411145834.png
[tr][td] 微信截图_20250411145904.png [td] 微信截图_20250411150029.png [td] 微信截图_20250411150107.png

主体代码

import tkinter as tk
from tkinter import ttk, filedialog, messagebox, colorchooser
from PIL import Image, ImageTk, ImageFont, ImageEnhance, ImageDraw
import os
import sys
import glob
import subprocess
import time
import io
import math

from image_utils import *
from batch_processor import BatchProcessor

class ImageProcessingApp:
    def __init__(self, root):
        self.root = root
        self.root.title("图片处理小工具")
        self.root.geometry("960x640")

        # 创建加载窗口
        loading_window = tk.Toplevel(self.root)
        loading_window.title("正在加载")
        loading_window.geometry("300x120")
        loading_window.transient(self.root)
        loading_window.grab_set()
        self.center_window(loading_window, 300, 120)
        loading_window.resizable(False, False)

        # 添加加载提示
        ttk.Label(loading_window, text="正在加载系统字体...", font=("Arial", 10)).pack(pady=10)

        # 添加进度条
        progress = ttk.Progressbar(loading_window, mode='indeterminate')
        progress.pack(fill='x', padx=20)
        progress.start(10)

        # 添加详细信息标签
        info_label = ttk.Label(loading_window, text="初始化中...", font=("Arial", 9))
        info_label.pack(pady=5)

        # 添加提示标签
        hint_label = ttk.Label(loading_window, text="首次加载可能需要较长时间,请耐心等待", font=("Arial", 8), foreground="gray")
        hint_label.pack(pady=5)

        # 更新界面
        loading_window.update()

        # 预加载系统字体
        try:
            self.get_system_fonts(info_label)
        except Exception as e:
            messagebox.showerror("错误", f"加载字体时出错:{str(e)}")
        finally:
            loading_window.destroy()

        # 当前加载的图片
        self.current_image = None
        self.display_image = None
        self.original_image = None

        # 添加历史记录用于撤销和重做
        self.history = []
        self.history_index = -1
        self.max_history = 20  # 最多保存20步历史记录

        # 初始化批处理器
        self.batch_processor = BatchProcessor(self.root, self)

        # 设置UI
        self.setup_ui()

        # 创建应用图标
        self.create_app_icon()

    def center_window(self, window, width, height):
        """将窗口居中显示"""
        screen_width = window.winfo_screenwidth()
        screen_height = window.winfo_screenheight()
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2
        window.geometry(f"+{x}+{y}")

    def setup_ui(self):
        """设置界面元素"""
        # 创建并设置应用图标
        self.create_app_icon()

        # 创建菜单
        self.create_menu()

        # 创建主框架
        self.main_frame = ttk.Frame(self.root)
        self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 左侧工具栏
        self.tools_frame = ttk.LabelFrame(self.main_frame, text="工具", style="Toolbar.TLabelframe")
        self.tools_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)

        # 设置工具栏样式
        style = ttk.Style()
        style.configure("Toolbar.TLabelframe", background="#f5f5f5", borderwidth=1, relief="flat")
        style.configure("Toolbar.TLabelframe.Label", font=("Segoe UI", 9, "bold"), foreground="#555555")
        style.configure("Toolbar.TButton", font=("Segoe UI", 9), padding=4, background="#ffffff", foreground="#333333")
        style.map("Toolbar.TButton",
            background=[("active", "#e6f3ff")],
            foreground=[("active", "#0078d7")]
        )

        # 1. 文件操作区
        file_frame = ttk.Frame(self.tools_frame)
        file_frame.pack(fill=tk.X, padx=5, pady=(5, 0))

        # 按钮 - 打开图片
        ttk.Button(file_frame, text="打开图片", command=self.open_image, style="Toolbar.TButton").pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
        # 按钮 - 保存图片
        ttk.Button(file_frame, text="保存图片", command=self.save_image, style="Toolbar.TButton").pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)

        # 2. 基础图片处理区域
        self.basic_frame = ttk.LabelFrame(self.tools_frame, text="基础处理", style="Toolbar.TLabelframe")
        self.basic_frame.pack(fill=tk.X, padx=5, pady=(20, 0))

        # 创建两列布局
        basic_left_frame = ttk.Frame(self.basic_frame)
        basic_left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)

        basic_right_frame = ttk.Frame(self.basic_frame)
        basic_right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)

        # 左列按钮 - 使用grid确保顶端对齐
        ttk.Button(basic_left_frame, text="裁剪", command=self.crop_image).grid(row=0, column=0, sticky="ew", pady=2)
        ttk.Button(basic_left_frame, text="调整大小", command=self.resize_image).grid(row=1, column=0, sticky="ew", pady=2)
        ttk.Button(basic_left_frame, text="旋转翻转", command=self.rotate_image).grid(row=2, column=0, sticky="ew", pady=2)

        # 配置列宽度
        basic_left_frame.grid_columnconfigure(0, weight=1)

        # 右列按钮 - 使用grid确保顶端对齐
        ttk.Button(basic_right_frame, text="基础调整", command=self.adjust_image).grid(row=0, column=0, sticky="ew", pady=2)
        ttk.Button(basic_right_frame, text="格式转换", command=self.convert_format).grid(row=1, column=0, sticky="ew", pady=2)
        ttk.Button(basic_right_frame, text="应用滤镜", command=self.apply_image_filter).grid(row=2, column=0, sticky="ew", pady=2)

        # 配置列宽度
        basic_right_frame.grid_columnconfigure(0, weight=1)

        # 3. 高级功能区域
        advanced_frame = ttk.LabelFrame(self.tools_frame, text="高级功能", style="Toolbar.TLabelframe")
        advanced_frame.pack(fill=tk.X, padx=5, pady=(25, 0))

        # 创建两列布局
        advanced_left_frame = ttk.Frame(advanced_frame)
        advanced_left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)

        advanced_right_frame = ttk.Frame(advanced_frame)
        advanced_right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)

        # 左列按钮 - 使用grid确保顶端对齐
        ttk.Button(advanced_left_frame, text="模板裁剪", command=self.template_crop).grid(row=0, column=0, sticky="ew", pady=2)
        ttk.Button(advanced_left_frame, text="添加水印", command=self.add_watermark).grid(row=1, column=0, sticky="ew", pady=2)

        # 配置列宽度
        advanced_left_frame.grid_columnconfigure(0, weight=1)

        # 右列按钮 - 使用grid确保顶端对齐
        ttk.Button(advanced_right_frame, text="防伪水印", command=self.add_tiled_watermark).grid(row=0, column=0, sticky="ew", pady=2)

        # 配置列宽度
        advanced_right_frame.grid_columnconfigure(0, weight=1)

        # 4. 批量处理区域
        batch_frame = ttk.LabelFrame(self.tools_frame, text="批量处理", style="Toolbar.TLabelframe")
        batch_frame.pack(fill=tk.X, padx=5, pady=(25, 5))

        # 创建两列布局
        batch_left_frame = ttk.Frame(batch_frame)
        batch_left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)

        batch_right_frame = ttk.Frame(batch_frame)
        batch_right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2)

        # 左列按钮 - 使用grid确保顶端对齐
        ttk.Button(batch_left_frame, text="批量调整", command=self.batch_processor.show_batch_resize_dialog).grid(row=0, column=0, sticky="ew", pady=2)
        ttk.Button(batch_left_frame, text="批量转换", command=self.batch_processor.show_batch_convert_dialog).grid(row=1, column=0, sticky="ew", pady=2)

        # 配置列宽度
        batch_left_frame.grid_columnconfigure(0, weight=1)

        # 右列按钮 - 使用grid确保顶端对齐
        ttk.Button(batch_right_frame, text="批量水印", command=self.batch_processor.show_batch_watermark_dialog).grid(row=0, column=0, sticky="ew", pady=2)

        # 配置列宽度
        batch_right_frame.grid_columnconfigure(0, weight=1)

        # 图片显示区域
        self.image_frame = ttk.LabelFrame(self.main_frame, text="图片预览")
        self.image_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.canvas = tk.Canvas(self.image_frame, bg="lightgray")
        self.canvas.pack(fill=tk.BOTH, expand=True)

        # 状态栏
        self.status_bar = ttk.Label(self.root, text="就绪", relief=tk.SUNKEN, anchor=tk.W)
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    def open_image(self):
        """打开图片文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[
                ("Image files", "*.jpg *.jpeg *.png *.bmp *.gif *.webp"),
                ("All files", "*.*")
            ]
        )

        if file_path:
            try:
                self.original_image = Image.open(file_path)
                self.current_image = self.original_image.copy()
                self.display_image_on_canvas()
                self.status_bar.config(text=f"已加载图片: {os.path.basename(file_path)}")
            except Exception as e:
                messagebox.showerror("错误", f"无法打开图片: {str(e)}")

    def display_image_on_canvas(self):
        """在画布上显示当前图片"""
        if self.current_image:
            # 获取画布尺寸
            canvas_width = self.canvas.winfo_width()
            canvas_height = self.canvas.winfo_height()

            # 如果画布尺寸为0(可能未完全初始化),使用默认尺寸
            if canvas_width <= 1 or canvas_height <= 1:
                canvas_width = 800
                canvas_height = 600

            # 调整图片大小以适应画布
            img_width, img_height = self.current_image.size
            ratio = min(canvas_width/img_width, canvas_height/img_height)
            new_width = int(img_width * ratio)
            new_height = int(img_height * ratio)

            # 显示调整后的图片
            resized_img = self.current_image.resize((new_width, new_height), Image.LANCZOS)
            self.display_image = ImageTk.PhotoImage(resized_img)

            # 清除画布并显示新图片
            self.canvas.delete("all")
            self.canvas.create_image(
                canvas_width//2, canvas_height//2,
                image=self.display_image, anchor=tk.CENTER
            )

            # 更新状态栏显示图片尺寸
            self.status_bar.config(text=f"图片尺寸: {img_width} x {img_height} 像素")

    def save_image(self):
        """保存当前图片"""
        if self.current_image:
            # 获取当前图像格式,确定默认扩展名
            default_ext = ".png"  # 默认使用PNG
            default_format = "PNG files"

            if hasattr(self.current_image, 'format') and self.current_image.format:
                format_map = {
                    "JPEG": (".jpg", "JPEG files"),
                    "PNG": (".png", "PNG files"),
                    "WEBP": (".webp", "WEBP files"),
                    "BMP": (".bmp", "BMP files"),
                    "GIF": (".gif", "GIF files"),
                    "TIFF": (".tif", "TIFF files")
                }
                if self.current_image.format in format_map:
                    default_ext, default_format = format_map[self.current_image.format]

            file_path = filedialog.asksaveasfilename(
                defaultextension=default_ext,
                initialfile=f"image{default_ext}",
                filetypes=[
                    (default_format, f"*{default_ext}"),  # 当前格式放在首位
                    ("PNG files", "*.png"),
                    ("JPEG files", "*.jpg *.jpeg"),
                    ("WEBP files", "*.webp"),
                    ("BMP files", "*.bmp"),
                    ("GIF files", "*.gif"),
                    ("TIFF files", "*.tif"),
                    ("All files", "*.*")
                ]
            )

            if file_path:
                try:
                    # 检查文件扩展名是否与图像格式匹配
                    file_ext = os.path.splitext(file_path)[1].lower()
                    save_format = None

                    # 根据扩展名确定保存格式
                    ext_to_format = {
                        '.jpg': 'JPEG', '.jpeg': 'JPEG', 
                        '.png': 'PNG', '.webp': 'WEBP', 
                        '.bmp': 'BMP', '.gif': 'GIF', 
                        '.tif': 'TIFF', '.tiff': 'TIFF'
                    }

                    if file_ext in ext_to_format:
                        save_format = ext_to_format[file_ext]

                    # 保存图片,指定格式
                    save_options = {}
                    if save_format == 'JPEG':
                        save_options['quality'] = 95

                    if save_format:
                        self.current_image.save(file_path, format=save_format, **save_options)
                    else:
                        self.current_image.save(file_path)

                    self.status_bar.config(text=f"图片已保存: {os.path.basename(file_path)}")
                    messagebox.showinfo("成功", "图片保存成功!")
                except Exception as e:
                    messagebox.showerror("错误", f"保存图片时出错: {str(e)}")
        else:
            messagebox.showwarning("警告", "没有可保存的图片!")

    # ===== 基础图片处理功能 =====
    def crop_image(self):
        """裁剪图片"""
        if not self.current_image:
            messagebox.showwarning("警告", "请先打开一张图片!")
            return

        # 创建裁剪对话框
        crop_window = tk.Toplevel(self.root)
        crop_window.title("裁剪图片")
        crop_window.geometry("300x200")
        crop_window.resizable(False, False)
        crop_window.transient(self.root)
        crop_window.grab_set()
        self.center_window(crop_window, 300, 200)
        crop_window.focus_set()

        # 裁剪模式选择
        ttk.Label(crop_window, text="裁剪模式:").grid(row=0, column=0, padx=10, pady=10, sticky=tk.W)

        crop_mode = tk.StringVar(value="free")
        ttk.Radiobutton(crop_window, text="自由裁剪", variable=crop_mode, value="free").grid(
            row=0, column=1, padx=5, pady=5, sticky=tk.W)
        ttk.Radiobutton(crop_window, text="固定比例", variable=crop_mode, value="fixed").grid(
            row=1, column=1, padx=5, pady=5, sticky=tk.W)

        # 固定比例选项
        ratio_frame = ttk.Frame(crop_window)
        ratio_frame.grid(row=2, column=0, columnspan=2, padx=10, pady=5, sticky=tk.W)

        ttk.Label(ratio_frame, text="选择比例:").pack(side=tk.LEFT, padx=5)

        ratio_var = tk.StringVar(value="1:1")
        ratio_combo = ttk.Combobox(ratio_frame, textvariable=ratio_var, width=10, state="readonly")
        ratio_combo['values'] = ("1:1", "4:3", "16:9", "3:2", "2:3", "9:16")
        ratio_combo.pack(side=tk.LEFT, padx=5)

        # 控制固定比例选项是否可用
        def update_ratio_state(*args):
            if crop_mode.get() == "fixed":
                ratio_combo.config(state="readonly")
            else:
                ratio_combo.config(state="disabled")

        crop_mode.trace_add("write", update_ratio_state)
        update_ratio_state()  # 初始状态

        # 开始裁剪按钮
        def start_crop():
            crop_window.destroy()
            # 开始裁剪过程
            self.start_crop_mode(crop_mode.get(), ratio_var.get())

        button_frame = ttk.Frame(crop_window)
        button_frame.grid(row=3, column=0, columnspan=2, padx=10, pady=20)

        ttk.Button(button_frame, text="开始裁剪", command=start_crop).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="取消", command=crop_window.destroy).pack(side=tk.LEFT, padx=10)

    def start_crop_mode(self, mode, ratio="1:1"):
        """开始裁剪模式,允许用户在图像上选择区域"""
        if not self.current_image:
            return

        # 储存原始图像大小和显示比例
        self.orig_image_size = self.current_image.size
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()
        img_width, img_height = self.current_image.size
        self.display_ratio = min(canvas_width/img_width, canvas_height/img_height)

        # 计算图像在画布上的位置和大小
        new_width = int(img_width * self.display_ratio)
        new_height = int(img_height * self.display_ratio)
        x_offset = (canvas_width - new_width) // 2
        y_offset = (canvas_height - new_height) // 2
        self.image_bbox = (x_offset, y_offset, x_offset + new_width, y_offset + new_height)

        # 裁剪相关变量
        self.crop_mode = mode
        self.crop_ratio = ratio
        self.crop_rect = None  # 裁剪区域矩形ID
        self.crop_start_x = 0  # 裁剪开始位置X
        self.crop_start_y = 0  # 裁剪开始位置Y
        self.crop_current = None  # 当前裁剪区域

        # 显示裁剪指示信息
        self.status_bar.config(text="请在图片上拖动鼠标选择裁剪区域,按ESC键取消")

        # 在画布上方显示指示和确认/取消按钮
        self.crop_indicator = ttk.Label(
            self.canvas, 
            text="裁剪模式: " + ("自由裁剪" if mode == "free" else f"固定比例 ({ratio})"),
            background="#ffffff",
            padding=5
        )
        self.crop_indicator.place(x=10, y=10)

        self.crop_confirm_btn = ttk.Button(
            self.canvas, 
            text="确认裁剪", 
            command=self.apply_crop
        )
        self.crop_confirm_btn.place(x=10, y=50)

        self.crop_cancel_btn = ttk.Button(
            self.canvas, 
            text="取消", 
            command=self.cancel_crop
        )
        self.crop_cancel_btn.place(x=100, y=50)

        # 绑定裁剪事件
        self.canvas.bind("<ButtonPress-1>", self.crop_start)
        self.canvas.bind("<B1-Motion>", self.crop_drag)
        self.canvas.bind("<ButtonRelease-1>", self.crop_end)
        self.root.bind("<Escape>", lambda e: self.cancel_crop())

    def crop_start(self, event):
        """开始裁剪操作"""
        # 检查点击位置是否在图像内
        if not (self.image_bbox[0] <= event.x <= self.image_bbox[2] and 
                self.image_bbox[1] <= event.y <= self.image_bbox[3]):
            return

        # 删除之前的裁剪矩形
        if self.crop_rect:
            self.canvas.delete(self.crop_rect)

        # 记录起始点
        self.crop_start_x, self.crop_start_y = event.x, event.y

        # 创建新矩形
        self.crop_rect = self.canvas.create_rectangle(
            event.x, event.y, event.x, event.y,
            outline="red", width=2
        )

    def crop_drag(self, event):
        """拖动过程中更新裁剪区域"""
        if not self.crop_rect:
            return

        # 确保鼠标位置不超出图片边界
        x = max(self.image_bbox[0], min(event.x, self.image_bbox[2]))
        y = max(self.image_bbox[1], min(event.y, self.image_bbox[3]))

        if self.crop_mode == "free":
            # 自由裁剪模式
            self.canvas.coords(self.crop_rect, self.crop_start_x, self.crop_start_y, x, y)
        else:
            # 固定比例模式
            width, height = self.parse_ratio(self.crop_ratio)

            # 计算边界
            dx = x - self.crop_start_x
            dy = y - self.crop_start_y

            # 确定方向
            sign_x = 1 if dx >= 0 else -1
            sign_y = 1 if dy >= 0 else -1

            # 按比例计算宽高
            if abs(dx) / width > abs(dy) / height:
                # 以高度为基准
                new_height = abs(dy)
                new_width = new_height * width / height
            else:
                # 以宽度为基准
                new_width = abs(dx)
                new_height = new_width * height / width

            # 更新矩形坐标
            x2 = self.crop_start_x + sign_x * new_width
            y2 = self.crop_start_y + sign_y * new_height

            # 确保不超出图片边界
            if x2 < self.image_bbox[0]:
                x2 = self.image_bbox[0]
                # 重新计算y2保持比例
                new_width = abs(x2 - self.crop_start_x)
                new_height = new_width * height / width
                y2 = self.crop_start_y + sign_y * new_height
            elif x2 > self.image_bbox[2]:
                x2 = self.image_bbox[2]
                new_width = abs(x2 - self.crop_start_x)
                new_height = new_width * height / width
                y2 = self.crop_start_y + sign_y * new_height

            if y2 < self.image_bbox[1]:
                y2 = self.image_bbox[1]
                new_height = abs(y2 - self.crop_start_y)
                new_width = new_height * width / height
                x2 = self.crop_start_x + sign_x * new_width
            elif y2 > self.image_bbox[3]:
                y2 = self.image_bbox[3]
                new_height = abs(y2 - self.crop_start_y)
                new_width = new_height * width / height
                x2 = self.crop_start_x + sign_x * new_width

            self.canvas.coords(self.crop_rect, self.crop_start_x, self.crop_start_y, x2, y2)

        # 更新状态栏显示裁剪区域大小
        x1, y1, x2, y2 = self.canvas.coords(self.crop_rect)
        crop_width = abs(x2 - x1) / self.display_ratio
        crop_height = abs(y2 - y1) / self.display_ratio
        self.status_bar.config(text=f"裁剪区域: {int(crop_width)} x {int(crop_height)} 像素")

    def crop_end(self, event):
        """结束裁剪选择"""
        if not self.crop_rect:
            return

        # 获取矩形位置
        x1, y1, x2, y2 = self.canvas.coords(self.crop_rect)

        # 确保x1 < x2 且 y1 < y2
        x1, x2 = min(x1, x2), max(x1, x2)
        y1, y2 = min(y1, y2), max(y1, y2)

        # 检查裁剪区域是否有效
        if x2 - x1 < 10 or y2 - y1 < 10:
            self.canvas.delete(self.crop_rect)
            self.crop_rect = None
            self.status_bar.config(text="裁剪区域太小,请重新选择")
            return

        # 更新矩形位置
        self.canvas.coords(self.crop_rect, x1, y1, x2, y2)

        # 保存裁剪区域
        self.crop_current = (x1, y1, x2, y2)

    def parse_ratio(self, ratio_str):
        """解析比例字符串,返回宽高值"""
        try:
            w, h = ratio_str.split(":")
            return int(w), int(h)
        except:
            return 1, 1

    def apply_crop(self):
        """应用裁剪"""
        if not self.crop_current:
            self.status_bar.config(text="请先选择裁剪区域")
            return

        # 从显示坐标转换回原始图像坐标
        x1, y1, x2, y2 = self.crop_current

        # 调整为相对于图像的坐标
        x1 = (x1 - self.image_bbox[0]) / self.display_ratio
        y1 = (y1 - self.image_bbox[1]) / self.display_ratio
        x2 = (x2 - self.image_bbox[0]) / self.display_ratio
        y2 = (y2 - self.image_bbox[1]) / self.display_ratio

        # 确保坐标在图像范围内
        x1 = max(0, int(x1))
        y1 = max(0, int(y1))
        x2 = min(self.orig_image_size[0], int(x2))
        y2 = min(self.orig_image_size[1], int(y2))

        # 裁剪图像
        self.current_image = self.current_image.crop((x1, y1, x2, y2))

        # 结束裁剪模式
        self.end_crop_mode()

        # 显示裁剪后的图像
        self.display_image_on_canvas()
        self.status_bar.config(text=f"图片已裁剪为 {x2-x1} x {y2-y1} 像素")

    def cancel_crop(self):
        """取消裁剪操作"""
        self.end_crop_mode()
        self.display_image_on_canvas()
        self.status_bar.config(text="裁剪已取消")

    def end_crop_mode(self):
        """结束裁剪模式,清理UI和事件绑定"""
        # 解绑事件
        self.canvas.unbind("<ButtonPress-1>")
        self.canvas.unbind("<B1-Motion>")
        self.canvas.unbind("<ButtonRelease-1>")
        self.root.unbind("<Escape>")

        # 移除临时UI元素
        if hasattr(self, 'crop_indicator') and self.crop_indicator:
            self.crop_indicator.destroy()
            self.crop_indicator = None

        if hasattr(self, 'crop_confirm_btn') and self.crop_confirm_btn:
            self.crop_confirm_btn.destroy()
            self.crop_confirm_btn = None

        if hasattr(self, 'crop_cancel_btn') and self.crop_cancel_btn:
            self.crop_cancel_btn.destroy()
            self.crop_cancel_btn = None

        # 删除裁剪矩形
        if self.crop_rect:
            self.canvas.delete(self.crop_rect)
            self.crop_rect = None

    def resize_image(self):
        """调整图片大小"""
        if not self.current_image:
            messagebox.showwarning("警告", "请先打开一张图片!")
            return

        # 创建调整大小对话框
        resize_window = tk.Toplevel(self.root)
        resize_window.title("调整图片大小")
        resize_window.geometry("350x260")
        resize_window.resizable(False, False)
        self.center_window(resize_window, 350, 260)
        resize_window.transient(self.root)  # 设置为主窗口的子窗口
        resize_window.grab_set()  # 获取所有的输入焦点
        resize_window.focus_set()  # 设置焦点

        # 获取原图尺寸
        original_width, original_height = self.current_image.size

        # 创建模式选择框架
        mode_frame = ttk.Frame(resize_window)
        mode_frame.pack(fill=tk.X, padx=10, pady=5)

        resize_mode = tk.StringVar(value="pixel")
        ttk.Radiobutton(mode_frame, text="按像素调整", variable=resize_mode, value="pixel").pack(side=tk.LEFT, padx=10)
        ttk.Radiobutton(mode_frame, text="按百分比调整", variable=resize_mode, value="percent").pack(side=tk.LEFT, padx=10)

        # 像素调整框架
        pixel_frame = ttk.LabelFrame(resize_window, text="像素设置")
        pixel_frame.pack(fill=tk.X, padx=10, pady=5)

        ttk.Label(pixel_frame, text="原始尺寸:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        ttk.Label(pixel_frame, text=f"{original_width} x {original_height} 像素").grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)

        ttk.Label(pixel_frame, text="新宽度:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        width_var = tk.StringVar(value=str(original_width))
        width_entry = ttk.Entry(pixel_frame, textvariable=width_var, width=10)
        width_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
        ttk.Label(pixel_frame, text="像素").grid(row=1, column=2, padx=0, pady=5, sticky=tk.W)

        ttk.Label(pixel_frame, text="新高度:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
        height_var = tk.StringVar(value=str(original_height))
        height_entry = ttk.Entry(pixel_frame, textvariable=height_var, width=10)
        height_entry.grid(row=2, column=1, padx=5, pady=5, sticky=tk.W)
        ttk.Label(pixel_frame, text="像素").grid(row=2, column=2, padx=0, pady=5, sticky=tk.W)

        # 百分比调整框架
        percent_frame = ttk.LabelFrame(resize_window, text="百分比设置")
        percent_frame.pack(fill=tk.X, padx=10, pady=5)

        ttk.Label(percent_frame, text="调整百分比:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        percent_var = tk.StringVar(value="100")
        percent_entry = ttk.Entry(percent_frame, textvariable=percent_var, width=10)
        percent_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
        ttk.Label(percent_frame, text="%").grid(row=0, column=2, padx=0, pady=5, sticky=tk.W)

        # 新尺寸预览
        preview_label = ttk.Label(percent_frame, text=f"预览: {original_width} x {original_height} 像素")
        preview_label.grid(row=1, column=0, columnspan=3, padx=5, pady=5, sticky=tk.W)

        # 保持比例选项
        maintain_ratio = tk.BooleanVar(value=True)
        ratio_check = ttk.Checkbutton(resize_window, text="保持宽高比", variable=maintain_ratio)
        ratio_check.pack(padx=10, pady=5, anchor=tk.W)

        # 根据模式显示/隐藏不同的框架
        def update_frames(*args):
            if resize_mode.get() == "pixel":
                pixel_frame.pack(fill=tk.X, padx=10, pady=5)
                percent_frame.pack_forget()
            else:
                pixel_frame.pack_forget()
                percent_frame.pack(fill=tk.X, padx=10, pady=5)

        # 更新百分比预览
        def update_percent_preview(*args):
            try:
                percent = float(percent_var.get())
                if percent <= 0:
                    percent = 1
                    percent_var.set("1")

                new_width = int(original_width * percent / 100)
                new_height = int(original_height * percent / 100)
                preview_label.config(text=f"预览: {new_width} x {new_height} 像素")
            except ValueError:
                preview_label.config(text="请输入有效的百分比")

        # 确定和取消按钮
        def apply_resize():
            try:
                if resize_mode.get() == "pixel":
                    new_width = int(width_var.get())
                    new_height = int(height_var.get())

                    if new_width <= 0 or new_height <= 0:
                        messagebox.showerror("错误", "宽度和高度必须大于0!")
                        return
                else:  # 百分比模式
                    percent = float(percent_var.get())
                    if percent <= 0:
                        messagebox.showerror("错误", "百分比必须大于0!")
                        return

                    new_width = int(original_width * percent / 100)
                    new_height = int(original_height * percent / 100)

                # 调整图片大小
                self.current_image = resize_image(self.current_image, new_width, new_height, False)
                self.display_image_on_canvas()
                self.status_bar.config(text=f"图片已调整为 {new_width} x {new_height} 像素")
                resize_window.destroy()
            except ValueError:
                messagebox.showerror("错误", "请输入有效的数字!")

        def update_height(*args):
            if maintain_ratio.get() and resize_mode.get() == "pixel":
                try:
                    new_width = int(width_var.get())
                    # 计算对应的高度
                    ratio = original_height / original_width
                    new_height = int(new_width * ratio)
                    height_var.set(str(new_height))
                except ValueError:
                    pass

        def update_width(*args):
            if maintain_ratio.get() and resize_mode.get() == "pixel":
                try:
                    new_height = int(height_var.get())
                    # 计算对应的宽度
                    ratio = original_width / original_height
                    new_width = int(new_height * ratio)
                    width_var.set(str(new_width))
                except ValueError:
                    pass

        # 绑定事件
        resize_mode.trace_add("write", update_frames)
        width_var.trace_add("write", update_height)
        height_var.trace_add("write", update_width)
        percent_var.trace_add("write", update_percent_preview)

        button_frame = ttk.Frame(resize_window)
        button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=10)

        ttk.Button(button_frame, text="确定", command=apply_resize).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="取消", command=resize_window.destroy).pack(side=tk.LEFT, padx=10)

        # 初始化UI状态
        update_frames()
        update_percent_preview()

    def rotate_image(self):
        """旋转图片"""
        if not self.current_image:
            messagebox.showwarning("警告", "请先打开一张图片!")
            return

        # 创建旋转对话框
        rotate_window = tk.Toplevel(self.root)
        rotate_window.title("旋转与翻转")
        rotate_window.geometry("300x300")
        rotate_window.resizable(False, False)
        self.center_window(rotate_window, 300, 300)
        rotate_window.transient(self.root)  # 设置为主窗口的子窗口
        rotate_window.grab_set()  # 获取所有的输入焦点
        rotate_window.focus_set()  # 设置焦点

        # 分组框架 - 旋转选项
        rotate_frame = ttk.LabelFrame(rotate_window, text="旋转")
        rotate_frame.pack(fill=tk.X, padx=20, pady=5)

        # 旋转角度选择
        ttk.Button(rotate_frame, text="向左旋转90°", 
                   command=lambda: self.apply_rotation(-90)).pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(rotate_frame, text="向右旋转90°", 
                   command=lambda: self.apply_rotation(90)).pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(rotate_frame, text="旋转180°", 
                   command=lambda: self.apply_rotation(180)).pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(rotate_frame, text="旋转270°", 
                   command=lambda: self.apply_rotation(270)).pack(fill=tk.X, padx=10, pady=5)

        # 分组框架 - 翻转选项
        flip_frame = ttk.LabelFrame(rotate_window, text="翻转")
        flip_frame.pack(fill=tk.X, padx=20, pady=5)

        ttk.Button(flip_frame, text="水平翻转", 
                   command=lambda: self.apply_flip("horizontal")).pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(flip_frame, text="垂直翻转", 
                   command=lambda: self.apply_flip("vertical")).pack(fill=tk.X, padx=10, pady=5)

        # 关闭按钮
        ttk.Button(rotate_window, text="关闭", 
                   command=rotate_window.destroy).pack(fill=tk.X, padx=20, pady=10)

    def apply_rotation(self, angle):
        """应用旋转"""
        self.current_image = rotate_image(self.current_image, angle)
        self.display_image_on_canvas()
        self.status_bar.config(text=f"图片已旋转 {abs(angle)}°")

    def apply_flip(self, direction):
        """应用翻转"""
        if direction == "horizontal":
            self.current_image = self.current_image.transpose(Image.FLIP_LEFT_RIGHT)
            self.display_image_on_canvas()
            self.status_bar.config(text="图片已水平翻转")
        elif direction == "vertical":
            self.current_image = self.current_image.transpose(Image.FLIP_TOP_BOTTOM)
            self.display_image_on_canvas()
            self.status_bar.config(text="图片已垂直翻转")

    def flip_image(self):
        """翻转图片 - 调用旋转界面,因为旋转界面已经包含了翻转功能"""
        self.rotate_image()

    # ===== 新媒体处理功能 =====
    def template_crop(self):
        if not self.current_image:
            messagebox.showwarning("警告", "请先打开一张图片!")
            return

        # 创建模板选择对话框
        template_window = tk.Toplevel(self.root)
        template_window.title("模板裁切")
        template_window.geometry("900x580")  # 调整窗口大小以适应左右布局
        template_window.transient(self.root)  # 设置为主窗口的子窗口
        template_window.grab_set()  # 获取所有的输入焦点
        template_window.focus_set()  # 设置焦点
        self.center_window(template_window, 900, 580)

        # 创建左右分栏布局
        main_frame = ttk.Frame(template_window)
        main_frame.pack(fill="both", expand=True, padx=10, pady=10)

        # 左侧设置面板
        settings_frame = ttk.Frame(main_frame)
        settings_frame.pack(side="left", fill="both", padx=5, pady=5)

        ttk.Label(settings_frame, text="请选择裁切模板:").pack(anchor=tk.W, padx=5, pady=5)

        # 模板选项
        templates = [
            ("微信公众号封面 (900×383)", (900, 383)),
            ("微信朋友圈 (1080×1080)", (1080, 1080)),
            ("小红书 (1000×1000)", (1000, 1000)),
            ("抖音视频封面 (720×1280)", (720, 1280)),
            ("微博配图 (1200×900)", (1200, 900))
        ]

        # 创建单选按钮框架
        radio_frame = ttk.Frame(settings_frame)
        radio_frame.pack(fill=tk.X, padx=5, pady=5)

        template_var = tk.StringVar()

        # 将模板选项排列为两列
        for i, (name, size) in enumerate(templates):
            col = i % 2
            row = i // 2
            ttk.Radiobutton(radio_frame, text=name, variable=template_var, value=i).grid(row=row, column=col, sticky=tk.W, padx=5, pady=5)

        # 添加自定义分辨率的单选按钮
        custom_size_radio = ttk.Radiobutton(radio_frame, text="自定义分辨率", variable=template_var, value=len(templates))
        custom_size_radio.grid(row=(len(templates)+1)//2, column=0, sticky=tk.W, padx=5, pady=5)

        # 自定义分辨率选项
        custom_size_frame = ttk.LabelFrame(settings_frame, text="自定义分辨率")
        custom_size_frame.pack(fill=tk.X, padx=5, pady=10)

        # 宽度和高度输入
        size_entry_frame = ttk.Frame(custom_size_frame)
        size_entry_frame.pack(fill=tk.X, padx=5, pady=5)

        # 宽度输入
        ttk.Label(size_entry_frame, text="宽度:").grid(row=0, column=0, padx=5, pady=5)
        custom_width_var = tk.StringVar(value="800")
        custom_width_entry = ttk.Entry(size_entry_frame, textvariable=custom_width_var, width=6)
        custom_width_entry.grid(row=0, column=1, padx=5, pady=5)

        # 高度输入
        ttk.Label(size_entry_frame, text="高度:").grid(row=0, column=2, padx=5, pady=5)
        custom_height_var = tk.StringVar(value="600")
        custom_height_entry = ttk.Entry(size_entry_frame, textvariable=custom_height_var, width=6)
        custom_height_entry.grid(row=0, column=3, padx=5, pady=5)

        # 按钮
        custom_size_button = ttk.Button(size_entry_frame, text="应用自定义尺寸", 
                                   command=lambda: template_var.set(str(len(templates))))
        custom_size_button.grid(row=0, column=4, padx=5, pady=5)

        # 裁切位置调整
        position_frame = ttk.LabelFrame(settings_frame, text="裁切位置")
        position_frame.pack(fill=tk.X, padx=5, pady=10)

        position_inner_frame = ttk.Frame(position_frame)
        position_inner_frame.pack(fill=tk.X, padx=5, pady=5)

        ttk.Label(position_inner_frame, text="位置:").grid(row=0, column=0, sticky="w", padx=5, pady=5)

        position_var = tk.StringVar(value="center")
        positions = [
            ("左上", "top-left"),
            ("居中", "center"),
            ("右上", "top-right"),
            ("左下", "bottom-left"),
            ("右下", "bottom-right"),
            ("自定义(拖动)", "custom")  # 添加自定义选项
        ]

        position_combo = ttk.Combobox(position_inner_frame, textvariable=position_var, values=[p[0] for p in positions], state="readonly", width=10)
        position_combo.grid(row=0, column=1, padx=5, pady=5)
        position_combo.current(1)  # 默认选择"居中"

        # 添加拖动提示标签
        drag_hint_label = ttk.Label(position_inner_frame, text="选择'自定义'后可拖动红框", foreground="blue")
        drag_hint_label.grid(row=0, column=2, padx=5, pady=5)

        # 添加等比例缩小模板选项
        scale_frame = ttk.LabelFrame(settings_frame, text="尺寸设置")
        scale_frame.pack(fill=tk.X, padx=5, pady=10)

        scale_template_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(scale_frame, text="当图片小于模板尺寸时,等比例缩小模板", 
                       variable=scale_template_var).pack(anchor="w", padx=5, pady=5)

        # 状态标签
        status_frame = ttk.Frame(settings_frame)
        status_frame.pack(fill=tk.X, padx=5, pady=5)

        preview_status_label = ttk.Label(status_frame, text="", foreground="red")
        preview_status_label.pack(fill=tk.X, padx=5, pady=5)

        # 按钮框架
        button_frame = ttk.Frame(settings_frame)
        button_frame.pack(padx=5, pady=15, fill=tk.X)

        ttk.Button(button_frame, text="应用裁切", command=lambda: apply_template()).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="取消", command=template_window.destroy).pack(side=tk.RIGHT, padx=10)

        # 右侧预览面板
        preview_frame = ttk.LabelFrame(main_frame, text="预览")
        preview_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)

        preview_canvas = tk.Canvas(preview_frame, bg="gray90")
        preview_canvas.pack(fill="both", expand=True, padx=5, pady=5)

        # 创建预览图像
        preview_image = None
        preview_crop_rect = None
        preview_photo = None

        # 当前模板尺寸
        current_template_size = None

        # 跟踪拖动状态的变量
        drag_data = {
            "item": None,
            "x": 0,
            "y": 0,
            "start_x": 0,
            "start_y": 0,
            "crop_x": 0,
            "crop_y": 0,
            "crop_width": 0,
            "crop_height": 0,
            "img_x": 0,
            "img_y": 0,
            "img_width": 0,
            "img_height": 0,
            "dragging": False
        }

        # 鼠标事件处理函数
        def on_drag_start(event):
            # 只有当选择了自定义位置时才能拖动
            if position_var.get() != "自定义(拖动)" or preview_crop_rect is None:
                return

            # 获取当前鼠标位置
            drag_data["start_x"] = event.x
            drag_data["start_y"] = event.y

            # 检查鼠标是否在裁切框上
            x1, y1, x2, y2 = preview_canvas.coords(preview_crop_rect)
            if x1 <= event.x <= x2 and y1 <= event.y <= y2:
                drag_data["item"] = preview_crop_rect
                drag_data["dragging"] = True
                # 改变鼠标光标
                preview_canvas.config(cursor="fleur")

                # 保存当前裁切框位置和大小
                drag_data["crop_x"] = x1
                drag_data["crop_y"] = y1
                drag_data["crop_width"] = x2 - x1
                drag_data["crop_height"] = y2 - y1

        def on_drag_motion(event):
            if not drag_data.get("dragging", False) or drag_data["item"] is None:
                return

            # 计算移动距离
            dx = event.x - drag_data["start_x"]
            dy = event.y - drag_data["start_y"]

            # 更新拖动起点
            drag_data["start_x"] = event.x
            drag_data["start_y"] = event.y

            # 计算新位置
            new_x = drag_data["crop_x"] + dx
            new_y = drag_data["crop_y"] + dy

            # 确保裁切框不超出图像边界
            img_x = drag_data["img_x"]
            img_y = drag_data["img_y"]
            img_width = drag_data["img_width"]
            img_height = drag_data["img_height"]
            crop_width = drag_data["crop_width"]
            crop_height = drag_data["crop_height"]

            new_x = max(img_x, min(new_x, img_x + img_width - crop_width))
            new_y = max(img_y, min(new_y, img_y + img_height - crop_height))

            # 更新裁切框位置
            preview_canvas.coords(preview_crop_rect, new_x, new_y, new_x + crop_width, new_y + crop_height)

            # 更新存储的位置
            drag_data["crop_x"] = new_x
            drag_data["crop_y"] = new_y

            # 更新状态标签
            relative_x = new_x - img_x
            relative_y = new_y - img_y
            preview_status_label.config(text=f"裁切框位置: X={relative_x}, Y={relative_y} (预览坐标)")

        def on_drag_end(event):
            if drag_data.get("dragging", False):
                drag_data["dragging"] = False
                preview_canvas.config(cursor="")  # 恢复默认光标

        # 绑定鼠标事件
        preview_canvas.bind("<ButtonPress-1>", on_drag_start)
        preview_canvas.bind("<B1-Motion>", on_drag_motion)
        preview_canvas.bind("<ButtonRelease-1>", on_drag_end)

        # 更新预览
        def update_preview(*args):
            nonlocal preview_image, preview_crop_rect, preview_photo, current_template_size

            # 清除画布
            preview_canvas.delete("all")

            try:
                # 获取模板大小
                if template_var.get() == str(len(templates)):  # 自定义分辨率
                    try:
                        width = int(custom_width_var.get())
                        height = int(custom_height_var.get())
                        if width <= 0 or height <= 0:
                            raise ValueError("尺寸必须为正数")
                        size = (width, height)
                        current_template_size = size
                    except ValueError as e:
                        preview_status_label.config(text=f"无效的尺寸: {str(e)}")
                        return
                else:
                    index = int(template_var.get())
                    _, size = templates[index]
                    current_template_size = size

                # 计算预览画布的大小
                canvas_width = preview_canvas.winfo_width()
                canvas_height = preview_canvas.winfo_height()

                if canvas_width <= 1 or canvas_height <= 1:
                    # 画布尚未准备好,延迟更新
                    template_window.after(100, update_preview)
                    return

                # 获取图像宽高比
                img_ratio = self.current_image.width / self.current_image.height
                template_ratio = size[0] / size[1]

                # 调整图像大小以适应预览窗口
                preview_scale = min(canvas_width / self.current_image.width, 
                                   canvas_height / self.current_image.height)

                preview_width = int(self.current_image.width * preview_scale)
                preview_height = int(self.current_image.height * preview_scale)

                # 创建预览图像
                preview_img = self.current_image.copy()
                preview_img.thumbnail((preview_width, preview_height), Image.LANCZOS)
                preview_image = ImageTk.PhotoImage(preview_img)

                # 显示图像
                img_x = (canvas_width - preview_width) // 2
                img_y = (canvas_height - preview_height) // 2
                preview_canvas.create_image(img_x, img_y, anchor=tk.NW, image=preview_image)

                # 更新拖动数据中的图像信息
                drag_data["img_x"] = img_x
                drag_data["img_y"] = img_y
                drag_data["img_width"] = preview_width
                drag_data["img_height"] = preview_height

                # 计算裁切框大小
                # 检查图像是否小于模板尺寸
                img_too_small = self.current_image.width < size[0] or self.current_image.height < size[1]

                # 如果要根据缩放设置调整预览裁切框
                scale_crop_preview = scale_template_var.get()

                # 计算裁切框在预览中的尺寸
                if img_too_small and scale_crop_preview:
                    # 计算等比例缩小的尺寸
                    scale_ratio = min(self.current_image.width / size[0], 
                                      self.current_image.height / size[1])
                    crop_width = int(size[0] * scale_ratio * preview_scale)
                    crop_height = int(size[1] * scale_ratio * preview_scale)

                    # 更新状态消息
                    status_text = f"模板已等比缩小 ({int(size[0] * scale_ratio)}×{int(size[1] * scale_ratio)})"
                    preview_status_label.config(text=status_text)
                else:
                    crop_width = int(size[0] * preview_scale)
                    crop_height = int(size[1] * preview_scale)

                    if img_too_small:
                        preview_status_label.config(text="警告: 图片尺寸小于模板尺寸")
                    else:
                        if template_var.get() == str(len(templates)):  # 自定义分辨率
                            preview_status_label.config(text=f"使用自定义分辨率: {size[0]}×{size[1]}")
                        else:
                            preview_status_label.config(text="")

                # 根据选择的位置确定裁切框位置
                pos_value = position_var.get()
                pos_dict = {p[0]: p[1] for p in positions}
                position = pos_dict.get(pos_value, "center")

                if position == "custom" and preview_crop_rect is not None and drag_data["crop_x"] != 0:
                    # 如果是拖动模式且已有裁切框,保持当前位置
                    crop_x = drag_data["crop_x"]
                    crop_y = drag_data["crop_y"]

                    # 确保裁切框不超出图像边界
                    crop_x = max(img_x, min(crop_x, img_x + preview_width - crop_width))
                    crop_y = max(img_y, min(crop_y, img_y + preview_height - crop_height))

                    # 更新拖动数据
                    drag_data["crop_width"] = crop_width
                    drag_data["crop_height"] = crop_height
                    drag_data["crop_x"] = crop_x
                    drag_data["crop_y"] = crop_y
                elif position == "center":
                    crop_x = img_x + (preview_width - crop_width) // 2
                    crop_y = img_y + (preview_height - crop_height) // 2
                elif position == "top-left":
                    crop_x = img_x
                    crop_y = img_y
                elif position == "top-right":
                    crop_x = img_x + preview_width - crop_width
                    crop_y = img_y
                elif position == "bottom-left":
                    crop_x = img_x
                    crop_y = img_y + preview_height - crop_height
                elif position == "bottom-right":
                    crop_x = img_x + preview_width - crop_width
                    crop_y = img_y + preview_height - crop_height
                elif position == "custom":
                    # 初始自定义位置设为中心
                    crop_x = img_x + (preview_width - crop_width) // 2
                    crop_y = img_y + (preview_height - crop_height) // 2

                    # 更新拖动数据
                    drag_data["crop_width"] = crop_width
                    drag_data["crop_height"] = crop_height
                    drag_data["crop_x"] = crop_x
                    drag_data["crop_y"] = crop_y

                # 确保裁切框不超出图像边界
                crop_x = max(img_x, min(crop_x, img_x + preview_width - crop_width))
                crop_y = max(img_y, min(crop_y, img_y + preview_height - crop_height))

                # 绘制裁切框
                preview_crop_rect = preview_canvas.create_rectangle(
                    crop_x, crop_y, crop_x + crop_width, crop_y + crop_height,
                    outline="red", width=2
                )

                # 显示提示文本
                preview_canvas.create_text(
                    canvas_width // 2, 
                    img_y - 10 if img_y > 20 else img_y + preview_height + 20,
                    text=f"红色框表示裁切区域 ({size[0]}×{size[1]})",
                    fill="blue"
                )

                # 如果是自定义位置,显示拖动提示
                if position == "custom":
                    preview_canvas.create_text(
                        canvas_width // 2,
                        img_y - 30 if img_y > 40 else img_y + preview_height + 40,
                        text="点击红框并拖动可调整位置",
                        fill="red"
                    )

            except (ValueError, IndexError) as e:
                preview_status_label.config(text=f"预览错误: {str(e)}")
                import traceback
                traceback.print_exc()

        # 为输入框变量添加跟踪
        custom_width_var.trace_add("write", lambda *args: template_var.get() == str(len(templates)) and update_preview())
        custom_height_var.trace_add("write", lambda *args: template_var.get() == str(len(templates)) and update_preview())

        # 为模板变量和位置变量添加跟踪
        template_var.trace_add("write", update_preview)
        position_var.trace_add("write", update_preview)
        scale_template_var.trace_add("write", update_preview)  # 在定义update_preview后添加跟踪

        # 窗口调整大小时更新预览
        def on_resize(event):
            update_preview()

        preview_canvas.bind("<Configure>", on_resize)

        # 应用裁切模板
        def apply_template():
            nonlocal current_template_size, preview_crop_rect

            if current_template_size is None:
                messagebox.showerror("错误", "请选择一个模板!")
                return

            try:
                # 获取所选模板尺寸
                target_width, target_height = current_template_size

                # 计算原始图像上的裁切位置
                img_width, img_height = self.current_image.width, self.current_image.height

                # 获取所选位置
                pos_value = position_var.get()
                pos_dict = {p[0]: p[1] for p in positions}
                position = pos_dict.get(pos_value, "center")

                # 检查图像是否小于模板尺寸
                img_too_small = img_width < target_width or img_height < target_height
                scale_template = scale_template_var.get()

                if img_too_small:
                    if scale_template:
                        # 等比例缩小模板尺寸以适应图像
                        scale_ratio = min(img_width / target_width, img_height / target_height)
                        target_width = int(target_width * scale_ratio)
                        target_height = int(target_height * scale_ratio)

                        # 更新状态消息
                        status_msg = f"模板已等比缩小至 {target_width}×{target_height}"
                    else:
                        # 放大图像以适应模板尺寸
                        messagebox.showwarning("警告", 
                                             f"图像尺寸({img_width}×{img_height})小于模板尺寸({target_width}×{target_height}),\n"
                                             "将自动调整大小以适应模板。这可能导致图像质量下降。")
                        # 如果图像小于模板尺寸,先放大图像
                        scale = max(target_width / img_width, target_height / img_height)
                        new_width = int(img_width * scale)
                        new_height = int(img_height * scale)
                        self.current_image = self.current_image.resize((new_width, new_height), Image.LANCZOS)
                        img_width, img_height = new_width, new_height

                        status_msg = f"图像已放大至 {img_width}×{img_height} 以适应模板"
                else:
                    status_msg = "已裁切为模板尺寸"

                # 根据位置计算裁切区域
                if position == "custom" and preview_crop_rect is not None:
                    # 对于自定义位置,基于预览中的相对位置计算实际裁切位置
                    if drag_data["img_width"] > 0 and drag_data["img_height"] > 0:
                        # 获取预览中裁切框的相对位置
                        crop_coords = preview_canvas.coords(preview_crop_rect)
                        preview_x = crop_coords[0] - drag_data["img_x"]
                        preview_y = crop_coords[1] - drag_data["img_y"]

                        # 计算比例
                        x_ratio = preview_x / drag_data["img_width"]
                        y_ratio = preview_y / drag_data["img_height"]

                        # 应用到实际图像
                        left = int(x_ratio * img_width)
                        top = int(y_ratio * img_height)

                        status_msg += f" (自定义位置 X:{left}, Y:{top})"
                    else:
                        # 如果拖动数据无效,使用居中位置
                        left = (img_width - target_width) // 2
                        top = (img_height - target_height) // 2
                elif position == "center":
                    left = (img_width - target_width) // 2
                    top = (img_height - target_height) // 2
                elif position == "top-left":
                    left = 0
                    top = 0
                elif position == "top-right":
                    left = img_width - target_width
                    top = 0
                elif position == "bottom-left":
                    left = 0
                    top = img_height - target_height
                elif position == "bottom-right":
                    left = img_width - target_width
                    top = img_height - target_height

                # 确保裁切区域不会超出图像边界
                left = max(0, min(left, img_width - target_width))
                top = max(0, min(top, img_height - target_height))
                right = left + target_width
                bottom = top + target_height

                # 执行裁切
                self.current_image = self.current_image.crop((left, top, right, bottom))

                # 更新显示
                self.display_image_on_canvas()

                # 获取模板名称
                template_info = ""
                if template_var.get() == str(len(templates)):  # 自定义分辨率
                    template_info = f"自定义分辨率 ({target_width}×{target_height})"
                else:
                    template_index = int(template_var.get())
                    template_info = templates[template_index][0]

                self.status_bar.config(text=f"{status_msg}: {template_info}")
                template_window.destroy()

            except Exception as e:
                messagebox.showerror("错误", f"裁切出错: {str(e)}")
                import traceback
                traceback.print_exc()

        # 初始选择第一个模板
        template_var.set("0")

        # 初始化预览
        template_window.update_idletasks()
        template_window.after(100, update_preview)

    def add_watermark(self):

        if not self.current_image:
            messagebox.showwarning("警告", "请先打开一张图片!")
            return

        # 添加字体调试功能
        def debug_fonts():
            try:
                print("\n===== 字体调试信息 =====")
                # 检查self.font_paths是否存在
                if hasattr(self, 'font_paths'):
                    print(f"字体路径字典包含 {len(self.font_paths)} 个字体")

                    # 列出所有字体路径
                    for name, path in self.font_paths.items():
                        print(f"字体 '{name}' -> '{path}'")

                    # 测试几个字体
                    test_fonts = list(self.font_paths.items())[:3]  # 测试前3个字体

                    from PIL import Image, ImageDraw, ImageFont
                    for name, path in test_fonts:
                        print(f"\n测试字体: {name} -> {path}")
                        try:
                            # 尝试加载字体
                            font = ImageFont.truetype(path, 24)
                            print(f"字体加载成功: {font}")

                            # 尝试渲染
                            img = Image.new('RGB', (200, 50), color=(255, 255, 255))
                            d = ImageDraw.Draw(img)
                            d.text((10, 10), "测试文字ABC", font=font, fill=(0, 0, 0))
                            print("渲染成功!")

                            # 保存测试图像到临时目录
                            temp_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp")
                            os.makedirs(temp_dir, exist_ok=True)
                            img_path = os.path.join(temp_dir, f"font_test_{name}.png")
                            img.save(img_path)
                            print(f"测试图像已保存到: {img_path}")
                        except Exception as e:
                            print(f"字体测试失败: {e}")
                else:
                    print("self.font_paths属性不存在")

                # 尝试直接用字体名加载常见字体
                common_fonts = ["微软雅黑", "宋体", "黑体", "楷体", "Arial"]
                for font_name in common_fonts:
                    try:
                        font = ImageFont.truetype(font_name, 24)
                        print(f"直接加载字体名称成功: {font_name}")
                    except Exception as e:
                        print(f"直接加载字体名称失败: {font_name}, 错误: {e}")

                print("===== 字体调试结束 =====\n")
            except Exception as e:
                print(f"字体调试过程出错: {e}")

        # 执行字体调试
        debug_fonts()

        # 创建水印窗口
        watermark_window = tk.Toplevel(self.root)
        watermark_window.title("添加水印")
        watermark_window.geometry("800x600")
        watermark_window.grab_set()  # 设置为模态窗口
        self.center_window(watermark_window, 800, 600)

        # 创建左右布局
        main_frame = ttk.Frame(watermark_window)
        main_frame.pack(fill="both", expand=True, padx=10, pady=10)

        # 左侧设置面板
        settings_frame = ttk.Frame(main_frame)
        settings_frame.pack(side="left", fill="both", expand=True, padx=5, pady=5)

        # 右侧预览面板
        preview_frame = ttk.LabelFrame(main_frame, text="预览")
        preview_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)

        # 预览控制区
        preview_controls = ttk.Frame(preview_frame)
        preview_controls.pack(fill="x", padx=5, pady=5)

        # 缩放控制
        ttk.Label(preview_controls, text="缩放:").pack(side="left", padx=5)
        zoom_var = tk.DoubleVar(value=1.0)
        zoom_scale = ttk.Scale(preview_controls, from_=0.5, to=3.0, orient="horizontal", 
                             variable=zoom_var, length=120)
        zoom_scale.pack(side="left", padx=5)

        # 显示缩放百分比
        zoom_label = ttk.Label(preview_controls, text="100%")
        zoom_label.pack(side="left", padx=5)

        # 初始化拖动数据
        drag_data = {
            "start_x": 0,
            "start_y": 0,
            "offset_x": 0,
            "offset_y": 0,
            "dragging": False
        }

        # 拖动事件处理标志
        keep_offset = False

        def update_zoom_label(*args):
            zoom_label.config(text=f"{int(zoom_var.get() * 100)}%")
            update_preview()

        zoom_var.trace_add("write", update_zoom_label)

        # 网格线开关
        grid_var = tk.BooleanVar(value=True)
        grid_check = ttk.Checkbutton(preview_controls, text="显示网格", variable=grid_var, 
                               command=lambda: update_preview())
        grid_check.pack(side="right", padx=10)

        # 创建预览图像容器
        preview_container = ttk.Frame(preview_frame, style="Preview.TFrame")
        preview_container.pack(fill="both", expand=True, padx=10, pady=10)

        # 创建样式
        style = ttk.Style()
        style.configure("Preview.TFrame", background="#f0f0f0", relief="sunken", borderwidth=1)

        # 创建预览画布
        preview_canvas = tk.Canvas(preview_container, bg="#f0f0f0", highlightthickness=1, 
                               highlightbackground="#cccccc")
        preview_canvas.pack(fill="both", expand=True)

        # 添加鼠标事件处理
        def on_drag_start(event):
            # 只有在放大状态下才启用拖动
            if zoom_var.get() <= 1.0:
                return

            # 记录开始拖动的位置
            drag_data["start_x"] = event.x
            drag_data["start_y"] = event.y
            drag_data["dragging"] = True
            preview_canvas.config(cursor="fleur")  # 更改鼠标光标为移动模式

        def on_drag_motion(event):
            if not drag_data.get("dragging", False):
                return

            # 计算移动距离
            delta_x = event.x - drag_data["start_x"]
            delta_y = event.y - drag_data["start_y"]

            # 更新偏移量
            drag_data["offset_x"] += delta_x
            drag_data["offset_y"] += delta_y

            # 更新拖动起始点
            drag_data["start_x"] = event.x
            drag_data["start_y"] = event.y

            # 设置保持偏移标志
            nonlocal keep_offset
            keep_offset = True

            # 更新预览
            update_preview()

            # 重置标志
            keep_offset = False

        def on_drag_end(event):
            # 结束拖动
            drag_data["dragging"] = False
            preview_canvas.config(cursor="")  # 恢复默认光标

        # 绑定鼠标事件
        preview_canvas.bind("<ButtonPress-1>", on_drag_start)
        preview_canvas.bind("<B1-Motion>", on_drag_motion)
        preview_canvas.bind("<ButtonRelease-1>", on_drag_end)

        # 状态标签
        status_var = tk.StringVar(value="准备添加水印")
        status_label = ttk.Label(preview_frame, textvariable=status_var, anchor="center")
        status_label.pack(fill="x", padx=10, pady=5)

        # 上部容器 - 选择水印类型
        type_frame = ttk.LabelFrame(settings_frame, text="水印类型", padding=5)
        type_frame.pack(fill="x", padx=10, pady=5)

        watermark_type = tk.StringVar(value="text")

        text_radio = ttk.Radiobutton(type_frame, text="文字水印", variable=watermark_type, value="text")
        image_radio = ttk.Radiobutton(type_frame, text="图片水印", variable=watermark_type, value="image")

        text_radio.grid(row=0, column=0, padx=5, pady=5)
        image_radio.grid(row=0, column=1, padx=5, pady=5)

        # 添加位置模式变量
        position_mode_var = tk.StringVar(value="preset")

        # 位置设置框架
        position_frame = ttk.LabelFrame(settings_frame, text="位置设置", padding=5)
        position_frame.pack(fill="x", padx=10, pady=5)

        # 位置模式选择
        mode_frame = ttk.Frame(position_frame)
        mode_frame.pack(fill="x", padx=5, pady=5)

        ttk.Radiobutton(mode_frame, text="预设位置", variable=position_mode_var, value="preset").pack(side="left", padx=5)
        ttk.Radiobutton(mode_frame, text="自定义坐标", variable=position_mode_var, value="custom").pack(side="left", padx=20)

        # 预设位置框架
        preset_frame = ttk.Frame(position_frame)
        preset_frame.pack(fill="x", padx=5, pady=5)

        ttk.Label(preset_frame, text="预设位置:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        position_var = tk.StringVar(value="右下")
        position_combo = ttk.Combobox(preset_frame, textvariable=position_var, 
                                   values=["左上", "右上", "左下", "右下", "中心", "顶部居中", "底部居中", "左侧居中", "右侧居中"])
        position_combo.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        position_combo.current(3)  # 默认选择右下角

        ttk.Label(preset_frame, text="边距:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        margin_var = tk.IntVar(value=10)
        margin_spinbox = ttk.Spinbox(preset_frame, from_=0, to=100, textvariable=margin_var, width=10)
        margin_spinbox.grid(row=1, column=1, sticky="w", padx=5, pady=5)

        # 自定义坐标框架
        custom_frame = ttk.Frame(position_frame)

        ttk.Label(custom_frame, text="X坐标:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        x_position_var = tk.IntVar(value=30)
        x_spinbox = ttk.Spinbox(custom_frame, from_=0, to=9999, textvariable=x_position_var, width=10)
        x_spinbox.grid(row=0, column=1, sticky="w", padx=5, pady=5)

        ttk.Label(custom_frame, text="Y坐标:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        y_position_var = tk.IntVar(value=30)
        y_spinbox = ttk.Spinbox(custom_frame, from_=0, to=9999, textvariable=y_position_var, width=10)
        y_spinbox.grid(row=1, column=1, sticky="w", padx=5, pady=5)

        # 根据选择模式显示/隐藏对应的设置界面
        def update_position_ui(*args):
            if position_mode_var.get() == "preset":
                preset_frame.pack(fill=tk.X)
                custom_frame.pack_forget()
                for child in preset_frame.winfo_children():
                    child.configure(state="normal")
                for child in custom_frame.winfo_children():
                    child.configure(state="disabled")
            else:
                preset_frame.pack_forget()
                custom_frame.pack(fill=tk.X)
                for child in preset_frame.winfo_children():
                    child.configure(state="disabled")
                for child in custom_frame.winfo_children():
                    child.configure(state="normal")

        # 绑定位置模式变化事件
        position_mode_var.trace_add("write", update_position_ui)
        position_var.trace_add("write", lambda *args: update_preview())
        margin_var.trace_add("write", lambda *args: update_preview())
        x_position_var.trace_add("write", lambda *args: update_preview())
        y_position_var.trace_add("write", lambda *args: update_preview())

        # 创建文字水印设置框架
        text_frame = ttk.LabelFrame(settings_frame, text="文字水印设置", padding=5)
        text_frame.pack(fill="x", padx=10, pady=5)

        ttk.Label(text_frame, text="水印文字:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        watermark_text = tk.StringVar(value="版权所有")
        text_entry = ttk.Entry(text_frame, textvariable=watermark_text, width=30)
        text_entry.grid(row=0, column=1, columnspan=2, sticky="ew", padx=5, pady=5)

        ttk.Label(text_frame, text="字体:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        system_fonts, font_paths = self.get_system_fonts()
        self.font_paths = font_paths  # 保存字体路径映射
        font_var = tk.StringVar()

        if system_fonts:
            font_combo = ttk.Combobox(text_frame, textvariable=font_var, values=system_fonts)
            font_combo.grid(row=1, column=1, columnspan=2, sticky="ew", padx=5, pady=5)
            if len(system_fonts) > 0:
                font_combo.current(0)
        else:
            font_entry = ttk.Entry(text_frame, textvariable=font_var)
            font_entry.grid(row=1, column=1, columnspan=2, sticky="ew", padx=5, pady=5)

        ttk.Label(text_frame, text="字号:").grid(row=2, column=0, sticky="w", padx=5, pady=5)
        font_size_var = tk.IntVar(value=36)
        font_size_spinbox = ttk.Spinbox(text_frame, from_=8, to=120, textvariable=font_size_var, width=10)
        font_size_spinbox.grid(row=2, column=1, sticky="w", padx=5, pady=5)

        # 字体间距控制
        ttk.Label(text_frame, text="字间距:").grid(row=2, column=2, sticky="w", padx=5, pady=5)
        letter_spacing_var = tk.IntVar(value=0)
        letter_spacing_spinbox = ttk.Spinbox(text_frame, from_=-10, to=50, textvariable=letter_spacing_var, width=10)
        letter_spacing_spinbox.grid(row=2, column=3, sticky="w", padx=5, pady=5)

        # 颜色选择器
        ttk.Label(text_frame, text="颜色:").grid(row=3, column=0, sticky="w", padx=5, pady=5)
        color_var = tk.StringVar(value="#000000")
        color_preview = tk.Canvas(text_frame, width=30, height=20, bg=color_var.get())
        color_preview.grid(row=3, column=1, sticky="w", padx=5, pady=5)

        def choose_color():
            color = colorchooser.askcolor(color_var.get())
            if color[1]:
                color_var.set(color[1])
                color_preview.config(bg=color[1])
                update_preview()

        color_button = ttk.Button(text_frame, text="选择颜色", command=choose_color)
        color_button.grid(row=3, column=2, sticky="w", padx=5, pady=5)

        # 添加粗体和斜体选项
        bold_var = tk.BooleanVar(value=False)
        italic_var = tk.BooleanVar(value=False)

        ttk.Label(text_frame, text="文字样式:").grid(row=4, column=0, sticky="w", padx=5, pady=5)
        style_frame = ttk.Frame(text_frame)
        style_frame.grid(row=4, column=1, columnspan=2, sticky="w", padx=5, pady=5)

        bold_check = ttk.Checkbutton(style_frame, text="粗体", variable=bold_var)
        bold_check.pack(side="left", padx=5)
        italic_check = ttk.Checkbutton(style_frame, text="斜体", variable=italic_var)
        italic_check.pack(side="left", padx=20)

        # 绑定变更事件到预览更新
        bold_var.trace_add("write", lambda *args: update_preview())
        italic_var.trace_add("write", lambda *args: update_preview())

        # 创建图片水印设置框架
        image_frame = ttk.LabelFrame(settings_frame, text="图片水印设置", padding=10)
        image_frame.pack(fill="x", padx=10, pady=5)

        watermark_image_path = tk.StringVar()
        watermark_img_obj = [None]  # 使用列表存储对象,以便在函数间共享

        ttk.Label(image_frame, text="图片路径:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        path_entry = ttk.Entry(image_frame, textvariable=watermark_image_path, state="readonly", width=30)
        path_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")

        def select_watermark_image():
            filename = filedialog.askopenfilename(
                title="选择水印图片",
                filetypes=(
                    ("PNG图片(透明背景)", "*.png"),
                    ("图片文件", "*.jpg;*.jpeg;*.bmp;*.gif"),
                    ("所有文件", "*.*")
                )
            )
            if filename:
                try:
                    # 加载图片并保存引用
                    from PIL import Image
                    img = Image.open(filename)
                    watermark_img_obj[0] = img

                    # 更新图片信息
                    watermark_image_path.set(filename)
                    img_width, img_height = img.size
                    img_info_var.set(f"原始大小: {img_width}×{img_height}像素")

                    # 初始化水印尺寸为100%
                    wm_size_percent_var.set(100)

                    # 更新界面和预览
                    update_ui()
                    update_preview()
                except Exception as e:
                    messagebox.showerror("错误", f"无法加载图片: {str(e)}")

        browse_button = ttk.Button(image_frame, text="浏览...", command=select_watermark_image)
        browse_button.grid(row=0, column=2, padx=5, pady=5)

        # 图片信息和大小调整
        img_info_var = tk.StringVar(value="未选择图片")
        ttk.Label(image_frame, textvariable=img_info_var).grid(row=1, column=0, columnspan=3, sticky="w", padx=5, pady=5)

        # 添加图片大小调整
        size_frame = ttk.Frame(image_frame)
        size_frame.grid(row=2, column=0, columnspan=3, sticky="ew", padx=5, pady=5)

        ttk.Label(size_frame, text="水印大小:").pack(side="left", padx=5)
        wm_size_percent_var = tk.IntVar(value=100)
        size_scale = ttk.Scale(size_frame, from_=5, to=200, orient="horizontal", 
                             variable=wm_size_percent_var, length=200)
        size_scale.pack(side="left", fill="x", expand=True, padx=5)

        # 显示百分比
        ttk.Label(size_frame, textvariable=tk.StringVar(value="%")).pack(side="right", padx=2)
        size_spinbox = ttk.Spinbox(size_frame, from_=5, to=200, textvariable=wm_size_percent_var, width=5)
        size_spinbox.pack(side="right", padx=2)

        wm_size_percent_var.trace_add("write", lambda *args: update_preview())

        # 透明度设置框架
        opacity_frame = ttk.LabelFrame(settings_frame, text="透明度设置", padding=5)
        opacity_frame.pack(fill="x", padx=10, pady=5)

        opacity_var = tk.DoubleVar(value=0.5)
        opacity_label_var = tk.StringVar(value="50%")

        def update_opacity_label(*args):
            opacity_label_var.set(f"{int(opacity_var.get() * 100)}%")
            update_preview()

        opacity_var.trace_add("write", update_opacity_label)

        ttk.Label(opacity_frame, text="不透明度:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        opacity_scale = ttk.Scale(opacity_frame, from_=0.0, to=1.0, orient="horizontal", 
                              variable=opacity_var, length=200)
        opacity_scale.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        ttk.Label(opacity_frame, textvariable=opacity_label_var).grid(row=0, column=2, padx=5, pady=5, sticky="w")

        # 预览图像
        preview_image = None
        preview_photo = None

        def update_preview():
            """更新预览图像"""
            nonlocal preview_image, preview_photo

            if not self.current_image:
                status_var.set("请先打开一张图片")
                return

            try:
                # 获取当前设置
                position = None
                if position_mode_var.get() == "preset":
                    position = position_var.get()
                    margin = margin_var.get()
                else:
                    position = (x_position_var.get(), y_position_var.get())
                    margin = 0

                # 创建预览图像的副本
                # 调整大小以适合预览窗口,保持宽高比
                canvas_width = preview_canvas.winfo_width() - 20
                canvas_height = preview_canvas.winfo_height() - 20

                if canvas_width <= 1 or canvas_height <= 1:
                    # 窗口尚未完全创建,延迟执行
                    preview_canvas.after(100, update_preview)
                    return

                # 创建原图的副本并缩放以适合预览窗口
                img_copy = self.current_image.copy()
                img_width, img_height = img_copy.size

                # 计算缩放比例(考虑用户缩放因子)
                try:
                    zoom_factor = zoom_var.get()
                except (NameError, AttributeError):
                    zoom_factor = 1.0  # 如果zoom_var未定义,使用默认值

                base_scale = min(canvas_width / img_width, canvas_height / img_height)
                scale = base_scale * zoom_factor

                new_width = int(img_width * scale)
                new_height = int(img_height * scale)

                # 如果缩放因子小于等于1且不是强制保持偏移,重置偏移量
                if zoom_factor <= 1.0 and not keep_offset:
                    drag_data["offset_x"] = 0
                    drag_data["offset_y"] = 0

                # 缩放原图以适合预览窗口
                preview_base = img_copy.resize((new_width, new_height), Image.LANCZOS)

                # 调整预览时的边距,确保边距比例与原图一致
                # 对于预设位置,边距应该按照预览图与原图的比例进行缩放
                preview_margin = int(margin * scale)

                # 应用水印
                if watermark_type.get() == "text":
                    # 文字水印
                    text = watermark_text.get()
                    font = font_var.get()
                    size = max(int(font_size_var.get() * scale), 8)  # 确保字体大小适合预览
                    letter_spacing = int(letter_spacing_var.get() * scale)

                    # 获取字体实际路径
                    font_path = None
                    if hasattr(self, 'font_paths'):
                        print(f"字体路径字典包含 {len(self.font_paths)} 个条目")
                        if font in self.font_paths:
                            font_path = self.font_paths[font]
                            print(f"找到字体路径: {font_path} (对应字体: {font})")
                        else:
                            print(f"字体 '{font}' 不在字体路径字典中")
                            print(f"可用字体: {list(self.font_paths.keys())}")
                    else:
                        print("font_paths 属性不存在")

                    # 调整预览的字体大小和间距
                    try:
                        from PIL import ImageColor
                        color = ImageColor.getcolor(color_var.get(), "RGB")
                    except:
                        color = (0, 0, 0)  # 默认黑色

                    # 确保位置参数基于图片分辨率而非画布
                    preview_position = position
                    if position_mode_var.get() == "custom":
                        # 自定义坐标模式,坐标为绝对像素值
                        # 无需调整预设位置,因为add_watermark内部会基于图片尺寸计算
                        # 只需记录当前位置用于标记指示器
                        custom_x, custom_y = position
                        preview_position = (custom_x, custom_y)  # 无需调整,add_watermark会处理

                    # 调用图像处理模块添加水印
                    from image_utils import add_watermark
                    preview_image = add_watermark(
                        preview_base, 
                        watermark_text=text,
                        position=preview_position,
                        opacity=opacity_var.get(),
                        font_name=font_path if font_path else font,
                        font_size=size,
                        font_color=color,
                        letter_spacing=letter_spacing,
                        margin=preview_margin,  # 使用缩放后的边距值
                        bold=bold_var.get(),     # 添加粗体参数
                        italic=italic_var.get()  # 添加斜体参数
                    )
                    if position_mode_var.get() == "preset":
                        status_var.set(f"预览文字水印: {text} (边距: {margin}px)")
                    else:
                        status_var.set(f"预览文字水印: {text} (坐标: {position})")

                elif watermark_type.get() == "image" and watermark_img_obj[0] is not None:
                    # 图片水印
                    wm_img = watermark_img_obj[0].copy()
                    if wm_img:
                        wm_w, wm_h = wm_img.size

                        # 根据百分比调整水印大小
                        percent = wm_size_percent_var.get() / 100.0
                        # 应用缩放比例(针对预览)和用户设置的百分比
                        wm_new_width = int(wm_w * scale * percent)
                        wm_new_height = int(wm_h * scale * percent)

                        # 确保缩放后的尺寸至少为1像素
                        wm_new_width = max(1, wm_new_width)
                        wm_new_height = max(1, wm_new_height)

                        wm_img = wm_img.resize((wm_new_width, wm_new_height), Image.LANCZOS)

                        # 确保位置参数基于图片分辨率而非画布
                        preview_position = position
                        if position_mode_var.get() == "custom":
                            # 自定义坐标模式,坐标为绝对像素值
                            # 无需调整预设位置,因为add_watermark内部会基于图片尺寸计算
                            # 只需记录当前位置用于标记指示器
                            custom_x, custom_y = position
                            preview_position = (custom_x, custom_y)  # 无需调整,add_watermark会处理

                        # 调用图像处理模块添加水印
                        from image_utils import add_watermark
                        preview_image = add_watermark(
                            preview_base,
                            watermark_image=wm_img,
                            position=preview_position,
                            opacity=opacity_var.get(),
                            margin=preview_margin,  # 使用缩放后的边距值
                            bold=False,  # 添加默认值
                            italic=False  # 添加默认值
                        )

                        if position_mode_var.get() == "preset":
                            status_var.set(f"预览图片水印: {os.path.basename(watermark_image_path.get())} ({wm_size_percent_var.get()}%) (边距: {margin}px)")
                        else:
                            status_var.set(f"预览图片水印: {os.path.basename(watermark_image_path.get())} ({wm_size_percent_var.get()}%) (坐标: {position})")
                    else:
                        preview_image = preview_base
                        status_var.set("水印图片加载失败")
                else:
                    preview_image = preview_base
                    if watermark_type.get() == "image":
                        status_var.set("请选择水印图片")
                    else:
                        status_var.set("请配置水印选项")

                # 更新画布上的图像
                preview_photo = ImageTk.PhotoImage(preview_image)

                # 计算居中位置,加上偏移量
                canvas_width = preview_canvas.winfo_width()
                canvas_height = preview_canvas.winfo_height()
                x_pos = max(0, (canvas_width - new_width) // 2) + drag_data["offset_x"]
                y_pos = max(0, (canvas_height - new_height) // 2) + drag_data["offset_y"]

                # 限制拖动边界,防止图片完全拖出视图
                if x_pos > canvas_width - 50:
                    x_pos = canvas_width - 50
                    drag_data["offset_x"] = x_pos - max(0, (canvas_width - new_width) // 2)

                if y_pos > canvas_height - 50:
                    y_pos = canvas_height - 50
                    drag_data["offset_y"] = y_pos - max(0, (canvas_height - new_height) // 2)

                if x_pos + new_width < 50:
                    x_pos = 50 - new_width
                    drag_data["offset_x"] = x_pos - max(0, (canvas_width - new_width) // 2)

                if y_pos + new_height < 50:
                    y_pos = 50 - new_height
                    drag_data["offset_y"] = y_pos - max(0, (canvas_height - new_height) // 2)

                # 清除画布并重绘
                preview_canvas.delete("all")

                # 绘制网格线
                try:
                    show_grid = grid_var.get()
                except (NameError, AttributeError):
                    show_grid = False  # 如果grid_var未定义,默认不显示网格

                if show_grid:
                    # 水平网格线
                    for y in range(0, canvas_height, 20):
                        preview_canvas.create_line(0, y, canvas_width, y, fill="#dddddd", dash=(2, 4))

                    # 垂直网格线
                    for x in range(0, canvas_width, 20):
                        preview_canvas.create_line(x, 0, x, canvas_height, fill="#dddddd", dash=(2, 4))

                    # 中心十字线
                    center_x = canvas_width // 2
                    center_y = canvas_height // 2
                    preview_canvas.create_line(center_x, 0, center_x, canvas_height, fill="#999999", dash=(4, 4))
                    preview_canvas.create_line(0, center_y, canvas_width, center_y, fill="#999999", dash=(4, 4))

                # 绘制图像边框
                border_color = "#3399ff"
                preview_canvas.create_rectangle(
                    x_pos - 1, y_pos - 1, 
                    x_pos + new_width + 1, y_pos + new_height + 1, 
                    outline=border_color, width=2
                )

                # 绘制图像
                img_item = preview_canvas.create_image(x_pos, y_pos, image=preview_photo, anchor="nw")

                # 将图像置于顶层
                preview_canvas.tag_raise(img_item)

                # 添加坐标标签
                if position_mode_var.get() == "custom":
                    # 显示自定义坐标点
                    custom_x, custom_y = position
                    # 缩放到预览尺寸并调整位置
                    scaled_x = int(custom_x * scale) + x_pos
                    scaled_y = int(custom_y * scale) + y_pos
                    # 在预览中标出位置点
                    if 0 <= scaled_x < canvas_width and 0 <= scaled_y < canvas_height:
                        # 绘制十字标记而不是圆形,使定位更精确
                        mark_size = 5
                        preview_canvas.create_line(
                            scaled_x - mark_size, scaled_y,
                            scaled_x + mark_size, scaled_y,
                            fill="red", width=2
                        )
                        preview_canvas.create_line(
                            scaled_x, scaled_y - mark_size,
                            scaled_x, scaled_y + mark_size,
                            fill="red", width=2
                        )
                        # 显示坐标信息
                        preview_canvas.create_text(
                            scaled_x, scaled_y + mark_size + 10,
                            text=f"({custom_x}, {custom_y})",
                            fill="red", font=("Arial", 8)
                        )

                # 如果放大,显示当前视图状态
                try:
                    if zoom_var.get() > 1.0:
                        help_text = "鼠标拖动可平移图像"
                        if drag_data.get("dragging", False):
                            help_text = "正在平移..."
                        preview_canvas.create_text(
                            canvas_width - 10, canvas_height - 10,
                            text=help_text,
                            anchor="se", fill="#666666", font=("Arial", 8)
                        )
                except (NameError, AttributeError):
                    pass  # 如果zoom_var未定义,忽略

            except Exception as e:
                status_var.set(f"预览出错: {str(e)}")
                import traceback
                traceback.print_exc()

        def update_ui():
            # 根据水印类型更新界面
            if watermark_type.get() == "text":
                text_frame.pack(fill="x", padx=10, pady=5)
                image_frame.pack_forget()
            else:
                text_frame.pack_forget()
                image_frame.pack(fill="x", padx=10, pady=5)
            update_preview()

        # 跟踪UI变化,更新预览
        watermark_type.trace_add("write", lambda *args: update_ui())
        watermark_text.trace_add("write", lambda *args: update_preview())
        font_var.trace_add("write", lambda *args: update_preview())
        font_size_var.trace_add("write", lambda *args: update_preview())
        letter_spacing_var.trace_add("write", lambda *args: update_preview())

        # 窗口大小变化时更新预览
        def on_resize(event):
            if event.widget == preview_canvas:
                update_preview()

        preview_canvas.bind("<Configure>", on_resize)

        update_ui()
        update_position_ui()

        # 创建底部按钮栏
        button_frame = ttk.Frame(watermark_window)
        button_frame.pack(side="bottom", fill="x", padx=10, pady=10)

        def apply_watermark():
            if not self.current_image:
                messagebox.showerror("错误", "请先打开一张图片")
                return

            try:
                img_copy = self.current_image.copy()

                # 获取位置设置
                position = None
                if position_mode_var.get() == "preset":
                    position = position_var.get()
                    margin = margin_var.get()
                else:
                    position = (x_position_var.get(), y_position_var.get())
                    margin = 0

                if watermark_type.get() == "text":
                    # 文字水印
                    text = watermark_text.get()
                    font = font_var.get()

                    # 获取字体实际路径
                    font_path = None
                    if hasattr(self, 'font_paths'):
                        print(f"字体路径字典包含 {len(self.font_paths)} 个条目")
                        if font in self.font_paths:
                            font_path = self.font_paths[font]
                            print(f"找到字体路径: {font_path} (对应字体: {font})")
                        else:
                            print(f"字体 '{font}' 不在字体路径字典中")
                            print(f"可用字体: {list(self.font_paths.keys())}")
                    else:
                        print("font_paths 属性不存在")

                    # 调整字体大小和间距
                    try:
                        from PIL import ImageColor
                        color = ImageColor.getcolor(color_var.get(), "RGB")
                    except:
                        color = (0, 0, 0)  # 默认黑色

                    # 调用图像处理模块添加水印
                    from image_utils import add_watermark
                    self.current_image = add_watermark(
                        img_copy,
                        watermark_text=text,
                        position=position,
                        opacity=opacity_var.get(),
                        font_name=font_path if font_path else font,
                        font_size=font_size_var.get(),
                        font_color=color,
                        letter_spacing=letter_spacing_var.get(),
                        margin=margin,
                        bold=bold_var.get(),     # 添加粗体参数
                        italic=italic_var.get()  # 添加斜体参数
                    )
                    self.display_image_on_canvas()
                    watermark_window.destroy()
                    self.status_bar.config(text=f"已添加文字水印: {text}")

                else:
                    # 图片水印
                    if watermark_img_obj[0] is None:
                        messagebox.showerror("错误", "请选择水印图片")
                        return

                    # 获取水印图片并应用大小调整
                    watermark_img = watermark_img_obj[0].copy()

                    # 根据百分比调整大小
                    if wm_size_percent_var.get() != 100:
                        wm_w, wm_h = watermark_img.size
                        percent = wm_size_percent_var.get() / 100.0
                        new_width = int(wm_w * percent)
                        new_height = int(wm_h * percent)
                        # 确保尺寸至少为1像素
                        new_width = max(1, new_width)
                        new_height = max(1, new_height)
                        watermark_img = watermark_img.resize((new_width, new_height), Image.LANCZOS)

                    opacity = opacity_var.get()

                    # 调用图像处理模块添加水印
                    from image_utils import add_watermark
                    self.current_image = add_watermark(
                        img_copy,
                        watermark_image=watermark_img,
                        position=position,
                        opacity=opacity,
                        margin=margin
                    )
                    self.display_image_on_canvas()
                    watermark_window.destroy()

                    size_info = ""
                    if wm_size_percent_var.get() != 100:
                        size_info = f" ({wm_size_percent_var.get()}%)"

                    self.status_bar.config(text=f"已添加图片水印: {os.path.basename(watermark_image_path.get())}{size_info}")

            except Exception as e:
                messagebox.showerror("错误", f"添加水印时出错: {str(e)}")
                import traceback
                traceback.print_exc()

        # 创建分隔线
        separator = ttk.Separator(button_frame, orient="horizontal")
        separator.pack(fill="x", pady=5)

        # 创建按钮
        cancel_button = ttk.Button(button_frame, text="取消", command=watermark_window.destroy)
        cancel_button.pack(side="right", padx=5)

        apply_button = ttk.Button(button_frame, text="应用水印", command=apply_watermark)
        apply_button.pack(side="right", padx=5)

        # 延迟执行一次预览更新,确保窗口大小已经调整
        watermark_window.after(200, update_preview)

    def adjust_image(self):
        """基础调整:亮度、对比度、饱和度、色调"""
        if not self.current_image:
            messagebox.showwarning("警告", "请先打开一张图片!")
            return

        # 保存原始图像供预览使用
        original_image = self.current_image.copy()

        # 创建调整对话框
        adjust_window = tk.Toplevel(self.root)
        adjust_window.title("图像基础调整")
        adjust_window.geometry("400x250")
        adjust_window.resizable(False, False)
        adjust_window.transient(self.root)
        adjust_window.grab_set()
        adjust_window.focus_set()
        self.center_window(adjust_window, 400, 250)

        # 定义参数及其范围
        adjustments = [
            ("亮度", 0.0, 2.0, 1.0),
            ("对比度", 0.0, 2.0, 1.0),
            ("饱和度", 0.0, 2.0, 1.0),
            ("色调", 0, 360, 0)
        ]

        # 存储滑块变量
        sliders_vars = []

        # 创建UI
        for i, (name, min_val, max_val, default) in enumerate(adjustments):
            frame = ttk.Frame(adjust_window)
            frame.pack(fill=tk.X, padx=20, pady=5)

            ttk.Label(frame, text=f"{name}:").pack(side=tk.LEFT, padx=5)

            # 值显示标签
            value_var = tk.StringVar(value=str(default))
            ttk.Label(frame, textvariable=value_var, width=4).pack(side=tk.RIGHT, padx=5)

            # 滑块
            slider_var = tk.DoubleVar(value=default)
            slider = ttk.Scale(
                frame, 
                from_=min_val, 
                to=max_val, 
                variable=slider_var, 
                orient=tk.HORIZONTAL,
                length=300
            )
            slider.pack(fill=tk.X, padx=5, expand=True)

            # 将变量存储起来
            sliders_vars.append((slider_var, value_var))

            # 绑定滑块值变化事件
            def update_value(var, value_var, _):
                value_var.set(f"{var.get():.1f}")

            slider_var.trace_add("write", lambda *args, var=slider_var, value_var=value_var: update_value(var, value_var, args))

        # 预览刷新周期(毫秒)
        preview_delay = 300
        last_update_time = [0]  # 使用列表以便在闭包中修改

        # 预览函数
        def update_preview():
            current_time = int(time.time() * 1000)
            if current_time - last_update_time[0] < preview_delay:
                # 如果距离上次更新不足预览延迟时间,则安排下一次更新
                adjust_window.after(preview_delay - (current_time - last_update_time[0]), update_preview)
                return

            last_update_time[0] = current_time

            try:
                # 从原始图像开始调整
                img = original_image.copy()

                # 应用亮度调整
                brightness_factor = sliders_vars[0][0].get()
                if brightness_factor != 1.0:
                    enhancer = ImageEnhance.Brightness(img)
                    img = enhancer.enhance(brightness_factor)

                # 应用对比度调整
                contrast_factor = sliders_vars[1][0].get()
                if contrast_factor != 1.0:
                    enhancer = ImageEnhance.Contrast(img)
                    img = enhancer.enhance(contrast_factor)

                # 应用饱和度调整
                saturation_factor = sliders_vars[2][0].get()
                if saturation_factor != 1.0:
                    img = img.convert("HSV")
                    h, s, v = img.split()
                    s = ImageEnhance.Brightness(s).enhance(saturation_factor)
                    img = Image.merge("HSV", (h, s, v)).convert("RGB")

                # 应用色调调整
                hue_shift = int(sliders_vars[3][0].get())
                if hue_shift != 0:
                    img = img.convert("HSV")
                    h, s, v = img.split()
                    h_data = list(h.getdata())
                    h_data = [(x + hue_shift) % 360 for x in h_data]
                    h.putdata(h_data)
                    img = Image.merge("HSV", (h, s, v)).convert("RGB")

                # 更新预览图像
                self.current_image = img
                self.display_image_on_canvas()
            except Exception as e:
                print(f"预览更新错误: {str(e)}")

        # 滑块值变化时更新预览
        def on_slider_change(*args):
            update_preview()

        # 绑定所有滑块的值变化事件
        for slider_var, _ in sliders_vars:
            slider_var.trace_add("write", on_slider_change)

        # 按钮区域
        button_frame = ttk.Frame(adjust_window)
        button_frame.pack(padx=20, pady=10)

        def apply_adjustments():
            # 确认当前调整,关闭窗口
            adjust_window.destroy()

        def reset_sliders():
            # 重置所有滑块到默认值
            for i, (slider_var, _) in enumerate(sliders_vars):
                slider_var.set(adjustments[i][3])  # 设为默认值

        def cancel_adjustments():
            # 撤销调整,恢复原图
            self.current_image = original_image
            self.display_image_on_canvas()
            adjust_window.destroy()

        ttk.Button(button_frame, text="应用", command=apply_adjustments).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="重置", command=reset_sliders).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="取消", command=cancel_adjustments).pack(side=tk.LEFT, padx=10)

        # 确保窗口关闭时恢复原图(如果未应用)
        def on_window_close():
            cancel_adjustments()

        adjust_window.protocol("WM_DELETE_WINDOW", on_window_close)

    def convert_format(self):
        """转换图片格式"""
        if not self.current_image:
            messagebox.showwarning("警告", "请先打开一张图片!")
            return

        # 创建格式转换对话框
        convert_window = tk.Toplevel(self.root)
        convert_window.title("转换图片格式")
        convert_window.geometry("400x480")  # 增加高度以容纳新选项
        convert_window.resizable(False, False)
        convert_window.transient(self.root)
        convert_window.grab_set()
        convert_window.focus_set()
        self.center_window(convert_window, 400, 480)

        # 使用普通框架代替滚动框架
        main_frame = ttk.Frame(convert_window)
        main_frame.pack(fill=tk.BOTH, expand=True)

        ttk.Label(main_frame, text="请选择目标格式:").pack(anchor=tk.W, padx=15, pady=10)

        # 格式选项
        formats = [
            ("JPEG/JPG - 常见照片格式,不支持透明", "JPEG", ".jpg"),
            ("PNG - 支持透明,适合图形和截图", "PNG", ".png"),
            ("WEBP - 谷歌开发的高压缩比格式", "WEBP", ".webp"),
            ("BMP - 无损位图格式", "BMP", ".bmp"),
            ("GIF - 支持动画的格式", "GIF", ".gif"),
            ("TIFF - 专业图像格式", "TIFF", ".tif")
        ]

        format_var = tk.StringVar()
        for i, (desc, _, _) in enumerate(formats):
            ttk.Radiobutton(main_frame, text=desc, variable=format_var, value=i).pack(anchor=tk.W, padx=20, pady=3)

        # 获取当前图片格式和大小
        current_format = "未知格式"
        if hasattr(self.current_image, 'format') and self.current_image.format:
            current_format = self.current_image.format

        # 估算当前图像大小
        img_byte_arr = io.BytesIO()
        self.current_image.save(img_byte_arr, format=current_format if current_format != "未知格式" else "PNG")
        current_size = len(img_byte_arr.getvalue()) / 1024  # KB

        info_frame = ttk.Frame(main_frame)
        info_frame.pack(fill=tk.X, padx=15, pady=5)
        ttk.Label(info_frame, text=f"当前格式: {current_format}").pack(side=tk.LEFT, padx=5)
        ttk.Label(info_frame, text=f"当前大小: {current_size:.1f} KB").pack(side=tk.LEFT, padx=15)

        # JPEG质量选项框架
        jpg_frame = ttk.LabelFrame(main_frame, text="JPEG设置")

        ttk.Label(jpg_frame, text="质量:").pack(side=tk.LEFT, padx=5, pady=5)
        quality_var = tk.IntVar(value=90)
        quality_scale = ttk.Scale(jpg_frame, from_=1, to=100, variable=quality_var, orient=tk.HORIZONTAL)
        quality_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5)

        quality_label = ttk.Label(jpg_frame, text="90")
        quality_label.pack(side=tk.LEFT, padx=5, pady=5)

        def update_quality_label(*args):
            quality_label.config(text=str(quality_var.get()))

        quality_var.trace_add("write", update_quality_label)

        # PNG透明度选项框架
        png_frame = ttk.LabelFrame(main_frame, text="PNG设置")

        transparency_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(png_frame, text="保留透明度", variable=transparency_var).pack(anchor=tk.W, padx=5, pady=5)

        # WebP设置框架
        webp_frame = ttk.LabelFrame(main_frame, text="WebP设置")

        webp_quality_var = tk.IntVar(value=80)
        ttk.Label(webp_frame, text="质量:").pack(side=tk.LEFT, padx=5, pady=5)
        webp_quality_scale = ttk.Scale(webp_frame, from_=1, to=100, variable=webp_quality_var, orient=tk.HORIZONTAL)
        webp_quality_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5)

        webp_quality_label = ttk.Label(webp_frame, text="80")
        webp_quality_label.pack(side=tk.LEFT, padx=5, pady=5)

        webp_quality_var.trace_add("write", lambda *args: webp_quality_label.config(text=str(webp_quality_var.get())))

        # 压缩到指定大小选项
        compress_frame = ttk.LabelFrame(main_frame, text="压缩到指定大小")

        # 是否启用压缩到指定大小
        compress_enable_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(compress_frame, text="启用压缩到指定大小", 
                       variable=compress_enable_var).pack(anchor=tk.W, padx=5, pady=5)

        # 压缩尺寸选择框架
        size_frame = ttk.Frame(compress_frame)
        size_frame.pack(fill=tk.X, padx=5, pady=2)

        ttk.Label(size_frame, text="目标大小:").pack(side=tk.LEFT, padx=5)
        target_size_var = tk.DoubleVar(value=100)
        target_size_entry = ttk.Entry(size_frame, textvariable=target_size_var, width=10)
        target_size_entry.pack(side=tk.LEFT, padx=5)

        # 单位选择
        size_unit_var = tk.StringVar(value="KB")
        unit_combo = ttk.Combobox(size_frame, textvariable=size_unit_var, values=["KB", "MB"], width=5, state="readonly")
        unit_combo.pack(side=tk.LEFT, padx=5)

        # 控制大小输入框的启用/禁用
        def update_compress_ui(*args):
            state = "normal" if compress_enable_var.get() else "disabled"
            combo_state = "readonly" if compress_enable_var.get() else "disabled"

            # 安全地设置state属性
            try:
                target_size_entry.config(state=state)
            except:
                pass

            try:
                unit_combo.config(state=combo_state)
            except:
                pass

        compress_enable_var.trace_add("write", update_compress_ui)
        update_compress_ui()  # 初始状态

        # 用于记录窗口关闭前显示的最后一个设置框架
        last_settings_frame = None

        # 根据选择的格式显示/隐藏不同的设置框架
        def update_format_frames(*args):
            nonlocal last_settings_frame
            try:
                index = int(format_var.get())
                _, fmt, _ = formats[index]

                # 隐藏所有格式专用设置框架
                for frame in [jpg_frame, png_frame, webp_frame, compress_frame]:
                    frame.pack_forget()

                # 显示当前格式的设置框架
                if fmt == "JPEG":
                    jpg_frame.pack(fill=tk.X, padx=15, pady=5)
                    last_settings_frame = jpg_frame
                elif fmt == "PNG":
                    png_frame.pack(fill=tk.X, padx=15, pady=5)
                    last_settings_frame = png_frame
                elif fmt == "WEBP":
                    webp_frame.pack(fill=tk.X, padx=15, pady=5)
                    last_settings_frame = webp_frame
                else:
                    last_settings_frame = None

                # 始终显示压缩大小选项,确保它是最后显示的
                compress_frame.pack(fill=tk.X, padx=15, pady=5)

                # 若选择BMP等不支持压缩的格式,禁用压缩选项
                if fmt in ["BMP"]:
                    compress_enable_var.set(False)
                    # 仅对支持state属性的组件设置state
                    for widget in compress_frame.winfo_children():
                        if isinstance(widget, (ttk.Entry, ttk.Combobox, ttk.Checkbutton, ttk.Button)):
                            widget.config(state="disabled")
                    # 特别处理
                    target_size_entry.config(state="disabled")
                    unit_combo.config(state="disabled")
                else:
                    # 仅对支持state属性的组件设置state
                    for widget in compress_frame.winfo_children():
                        if isinstance(widget, (ttk.Entry, ttk.Combobox, ttk.Button)):
                            widget.config(state="normal")
                        elif isinstance(widget, ttk.Checkbutton):
                            widget.config(state="normal")
                    update_compress_ui()
            except (ValueError, IndexError):
                for frame in [jpg_frame, png_frame, webp_frame, compress_frame]:
                    frame.pack_forget()

        format_var.trace_add("write", update_format_frames)

        # 初始选择JPEG格式
        format_var.set("0")

        # 确认按钮 - 使用固定的底部框架
        button_frame = ttk.Frame(convert_window)
        button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=15, pady=15)

        def compress_to_target_size(img, fmt, target_kb, save_options=None, min_quality=5):
            """压缩图像到指定大小"""
            if save_options is None:
                save_options = {}

            # 创建内存文件对象
            img_byte_arr = io.BytesIO()

            # 如果不需要压缩或格式不支持压缩,直接返回
            if fmt not in ["JPEG", "WEBP", "PNG"]:
                img.save(img_byte_arr, format=fmt)
                return img, len(img_byte_arr.getvalue()) / 1024

            # 确保格式兼容的图像模式
            processed_img = img.copy()
            if fmt == "JPEG" and processed_img.mode in ['P', 'RGBA', 'LA']:
                processed_img = processed_img.convert('RGB')
            elif fmt == "PNG" and processed_img.mode not in ['RGB', 'RGBA', 'P']:
                processed_img = processed_img.convert('RGBA')
            elif fmt == "WEBP" and processed_img.mode in ['P']:
                processed_img = processed_img.convert('RGBA')

            # 保存当前图像质量,逐步降低质量直到达到目标大小
            quality = 95  # 开始时使用高质量
            max_quality = 100

            if "quality" in save_options:
                quality = save_options["quality"]
                max_quality = quality

            # 先用当前质量尝试一次
            save_opts = save_options.copy()
            if fmt in ["JPEG", "WEBP"]:
                save_opts["quality"] = quality

            processed_img.save(img_byte_arr, format=fmt, **save_opts)
            current_size = len(img_byte_arr.getvalue()) / 1024

            # 如果已经小于目标大小,考虑增加质量
            if current_size <= target_kb:
                # 尝试提高质量但不超过目标大小
                low_q = quality
                high_q = max_quality
                best_q = quality
                best_size = current_size

                while low_q <= high_q:
                    mid_q = (low_q + high_q) // 2
                    img_byte_arr = io.BytesIO()
                    save_opts = save_options.copy()
                    if fmt in ["JPEG", "WEBP"]:
                        save_opts["quality"] = mid_q

                    processed_img.save(img_byte_arr, format=fmt, **save_opts)
                    mid_size = len(img_byte_arr.getvalue()) / 1024

                    if mid_size <= target_kb:
                        best_q = mid_q
                        best_size = mid_size
                        low_q = mid_q + 1
                    else:
                        high_q = mid_q - 1

                # 使用找到的最佳质量重新保存
                img_byte_arr = io.BytesIO()
                save_opts = save_options.copy()
                if fmt in ["JPEG", "WEBP"]:
                    save_opts["quality"] = best_q

                processed_img.save(img_byte_arr, format=fmt, **save_opts)
                img_byte_arr.seek(0)
                result_img = Image.open(img_byte_arr)
                return result_img, best_size

            # 如果大于目标大小,需要降低质量
            iterations = 0
            max_iterations = 20  # 防止无限循环

            # 先尝试通过降低质量来达到目标大小
            while current_size > target_kb and quality >= min_quality and iterations < max_iterations:
                quality -= 5  # 每次降低5%质量
                img_byte_arr = io.BytesIO()
                save_opts = save_options.copy()

                if fmt in ["JPEG", "WEBP"]:
                    save_opts["quality"] = quality
                elif fmt == "PNG" and quality < 50:
                    # 如果是PNG且质量要求低,转换为8位颜色减少文件大小
                    processed_img = processed_img.quantize(colors=256)

                # 确保JPEG兼容性
                if fmt == "JPEG" and processed_img.mode != 'RGB':
                    processed_img = processed_img.convert('RGB')

                processed_img.save(img_byte_arr, format=fmt, **save_opts)
                current_size = len(img_byte_arr.getvalue()) / 1024
                iterations += 1

            # 如果仍然太大,进行尺寸调整
            scale_factor = 1.0

            while current_size > target_kb and scale_factor > 0.1:
                # 计算新的缩放因子,确保每次至少减小5%
                prev_scale = scale_factor
                scale_factor = min(prev_scale * 0.95, math.sqrt(target_kb / current_size))

                # 如果缩放变化太小,则使用更激进的缩放
                if abs(scale_factor - prev_scale) < 0.01:
                    scale_factor = prev_scale * 0.9

                new_width = max(100, int(img.width * scale_factor))
                new_height = max(100, int(img.height * scale_factor))

                # 调整图像尺寸
                processed_img = img.resize((new_width, new_height), Image.LANCZOS)

                # 确保格式兼容
                if fmt == "JPEG" and processed_img.mode != 'RGB':
                    processed_img = processed_img.convert('RGB')

                # 保存并检查大小
                img_byte_arr = io.BytesIO()
                save_opts = save_options.copy()
                if fmt in ["JPEG", "WEBP"]:
                    save_opts["quality"] = quality

                processed_img.save(img_byte_arr, format=fmt, **save_opts)
                current_size = len(img_byte_arr.getvalue()) / 1024

            # 如果还是太大,尝试更极端的压缩方法
            if current_size > target_kb:
                # 对于PNG,尝试最小颜色数量
                if fmt == "PNG":
                    processed_img = processed_img.quantize(colors=64)  # 使用极少的颜色

                    img_byte_arr = io.BytesIO()
                    processed_img.save(img_byte_arr, format=fmt, optimize=True)
                    current_size = len(img_byte_arr.getvalue()) / 1024

                # 对于JPEG,尝试最低质量和更高压缩比
                elif fmt == "JPEG":
                    # 确保是RGB模式
                    if processed_img.mode != 'RGB':
                        processed_img = processed_img.convert('RGB')

                    img_byte_arr = io.BytesIO()
                    save_opts = {"quality": 1, "optimize": True, "subsampling": 2}
                    processed_img.save(img_byte_arr, format=fmt, **save_opts)
                    current_size = len(img_byte_arr.getvalue()) / 1024

                # 对于WEBP,尝试最低质量和有损压缩
                elif fmt == "WEBP":
                    img_byte_arr = io.BytesIO()
                    save_opts = {"quality": 1, "method": 6, "lossless": False}
                    processed_img.save(img_byte_arr, format=fmt, **save_opts)
                    current_size = len(img_byte_arr.getvalue()) / 1024

            # 返回处理后的图像和实际大小
            img_byte_arr.seek(0)
            result_img = Image.open(img_byte_arr)
            return result_img, current_size

        def apply_conversion():
            try:
                index = int(format_var.get())
                _, fmt, ext = formats[index]

                # 构建保存选项
                save_options = {}

                if fmt == "JPEG":
                    save_options["quality"] = quality_var.get()
                    save_options["optimize"] = True
                elif fmt == "PNG":
                    if transparency_var.get() and self.current_image.mode in ('RGBA', 'LA'):
                        # 保持透明度
                        save_options["format"] = "PNG"
                    else:
                        # 转换为RGB模式(无透明度)
                        self.current_image = self.current_image.convert('RGB')
                elif fmt == "WEBP":
                    save_options["quality"] = webp_quality_var.get()
                    save_options["method"] = 6  # 最高压缩质量

                # 处理图像
                processed_image = self.current_image.copy()

                # 如果启用了压缩到指定大小
                compressed_size = None
                if compress_enable_var.get():
                    target_size = target_size_var.get()

                    # 转换单位 (MB -> KB)
                    if size_unit_var.get() == "MB":
                        target_size *= 1024

                    # 压缩图像到目标大小
                    processed_image, compressed_size = compress_to_target_size(
                        processed_image, fmt, target_size, save_options
                    )

                    # 确认压缩后的大小
                    if compressed_size > target_size * 1.05:  # 允许5%的误差
                        messagebox.showwarning(
                            "压缩警告", 
                            f"无法将图像压缩到指定大小: {target_size:.1f} KB。\n"
                            f"实际大小: {compressed_size:.1f} KB。\n"
                            f"这可能是因为图像内容太复杂或格式限制。"
                        )

                # 临时文件路径
                temp_path = f"temp_converted{ext}"

                # 保存为目标格式
                if compressed_size is None:
                    processed_image.save(temp_path, format=fmt, **save_options)
                else:
                    # 已经压缩好了,直接保存
                    processed_image.save(temp_path, format=fmt)

                # 验证最终文件大小
                actual_size = os.path.getsize(temp_path) / 1024

                # 重新加载以更新格式
                converted_image = Image.open(temp_path)
                self.current_image = converted_image.copy()

                # 显式设置格式属性,确保格式信息不丢失
                self.current_image.format = fmt

                # 清理临时文件
                try:
                    os.remove(temp_path)
                except:
                    pass

                # 更新状态显示
                status_text = f"图片格式已转换为 {fmt}"
                if compressed_size:
                    unit = "KB" if actual_size < 1024 else "MB"
                    disp_size = actual_size if unit == "KB" else actual_size/1024
                    status_text += f",大小为 {disp_size:.1f} {unit}"

                self.display_image_on_canvas()
                self.status_bar.config(text=status_text)
                convert_window.destroy()

                # 建议保存
                if messagebox.askyesno("保存提示", f"图片已转换为{fmt}格式。是否现在保存?"):
                    self.save_image()

            except Exception as e:
                messagebox.showerror("错误", f"转换格式时出错: {str(e)}")
                import traceback
                traceback.print_exc()

        ttk.Button(button_frame, text="转换", command=apply_conversion).pack(side=tk.LEFT, padx=10)
        ttk.Button(button_frame, text="取消", command=convert_window.destroy).pack(side=tk.LEFT, padx=10)

    def create_menu(self):
        """创建菜单"""
        menu_bar = tk.Menu(self.root)

        # 文件菜单
        file_menu = tk.Menu(menu_bar, tearoff=0)
        file_menu.add_command(label="打开", command=self.open_image, accelerator="Ctrl+O")
        file_menu.add_command(label="保存", command=self.save_image, accelerator="Ctrl+S")
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.root.quit)
        menu_bar.add_cascade(label="文件", menu=file_menu)

        # 编辑菜单
        edit_menu = tk.Menu(menu_bar, tearoff=0)
        edit_menu.add_command(label="撤销", command=self.undo, accelerator="Ctrl+Z")
        edit_menu.add_command(label="重做", command=self.redo, accelerator="Ctrl+Y")
        edit_menu.add_separator()
        edit_menu.add_command(label="裁剪", command=self.crop_image)
        edit_menu.add_command(label="调整大小", command=self.resize_image)
        edit_menu.add_command(label="旋转", command=self.rotate_image)
        edit_menu.add_command(label="翻转", command=self.flip_image)
        menu_bar.add_cascade(label="编辑", menu=edit_menu)

        # 效果菜单
        effect_menu = tk.Menu(menu_bar, tearoff=0)
        effect_menu.add_command(label="调整亮度/对比度/饱和度", command=self.adjust_image)
        effect_menu.add_command(label="应用滤镜", command=self.apply_image_filter)
        menu_bar.add_cascade(label="效果", menu=effect_menu)

        # 工具菜单
        tools_menu = tk.Menu(menu_bar, tearoff=0)
        tools_menu.add_command(label="模板裁切", command=self.template_crop)
        tools_menu.add_command(label="格式转换", command=self.convert_format)
        tools_menu.add_command(label="添加水印", command=self.add_watermark)
        tools_menu.add_command(label="添加防伪水印", command=self.add_tiled_watermark)
        tools_menu.add_separator()
        tools_menu.add_command(label="批量调整大小", command=self.batch_processor.show_batch_resize_dialog)
        tools_menu.add_command(label="批量格式转换", command=self.batch_processor.show_batch_convert_dialog)
        tools_menu.add_command(label="批量添加水印", command=self.batch_processor.show_batch_watermark_dialog)
        menu_bar.add_cascade(label="工具", menu=tools_menu)

        # 帮助菜单
        help_menu = tk.Menu(menu_bar, tearoff=0)
        help_menu.add_command(label="关于", command=self.show_about)
        help_menu.add_command(label="使用帮助", command=self.show_help)
        menu_bar.add_cascade(label="帮助", menu=help_menu)

        self.root.config(menu=menu_bar)

    def get_system_fonts(self, info_label=None):
        """获取系统中所有可用的字体,以中文名称优先显示"""
        available_fonts = []
        font_paths = {}  # 用于存储字体名称到路径的映射

        # 中文字体映射表 - 将英文文件名映射到中文显示名称
        font_name_mapping = {
            # Windows常见字体 - 完整映射
            "msyh": "微软雅黑",
            "msyhbd": "微软雅黑 粗体",
            "msyhl": "微软雅黑 细体",
            "simhei": "黑体",
            "simsun": "宋体",
            "simkai": "楷体",
            "simfang": "仿宋",
            "simyou": "幼圆",
            "stkaiti": "华文楷体",
            "stfangsong": "华文仿宋",
            "stsong": "华文宋体",
            "stzhongsong": "华文中宋",
            "sthupo": "华文琥珀",
            "stxinwei": "华文新魏",
            "stliti": "华文隶书",
            "dengxian": "等线",
            "dengxianlight": "等线 细体",
            "dengxianmedium": "等线 中等",
            "dengxianbold": "等线 粗体",
            "yahei": "微软雅黑",
            "microsoft yahei": "微软雅黑",
            "microsoftyahei": "微软雅黑",
            "arial": "Arial",

            # macOS常见字体 - 完整映射
            "pingfang": "苹方",
            "pingfangsc": "苹方",
            ".pingfang": "苹方",
            "pingfang-sc": "苹方",
            "pingfanghk": "苹方港版",
            "pingfangtc": "苹方繁体",
            "songti": "宋体",
            "songti sc": "宋体",
            "songtitc": "宋体繁体",
            "heiti": "黑体",
            "heiti sc": "黑体",
            "heitisc": "黑体",
            "heititc": "黑体繁体",
            "kaiti": "楷体",
            "kaiti sc": "楷体",
            "kaitisc": "楷体",
            "kaititc": "楷体繁体",
            "yuanti": "圆体",
            "yuanti sc": "圆体",
            "yuantisc": "圆体",
            "yuantitc": "圆体繁体",
            "liukai": "柳体",
            "weibei": "魏碑",
            "weibeisc": "魏碑",
            "weibeitc": "魏碑繁体",
            "xingkai": "行楷",
            "xingkaisc": "行楷",
            "baoli": "报隶",
            "baolisc": "报隶",
            "baolitc": "报隶繁体",

            # Linux常见字体 - 完整映射
            "wqy-microhei": "文泉驿微米黑",
            "wqy-zenhei": "文泉驿正黑",
            "wqymicrohei": "文泉驿微米黑",
            "wqyzenhei": "文泉驿正黑",
            "notosanscjk": "思源黑体",
            "notosanscjksc": "思源黑体",
            "notosanscjktc": "思源黑体繁体",
            "notosanscjkhk": "思源黑体港版",
            "notosansc": "思源黑体",
            "notoserif": "思源宋体",
            "notoserifcjk": "思源宋体",
            "notoserifcjksc": "思源宋体",
            "notoserifcjktc": "思源宋体繁体",
            "notoserifcjkhk": "思源宋体港版",

            # 通用映射
            "regular": "常规",
            "light": "细体",
            "medium": "中等",
            "bold": "粗体",
            "heavy": "特粗",
            "thin": "极细",
            "-regular": "常规",
            "-light": "细体",
            "-medium": "中等",
            "-bold": "粗体",
            "-heavy": "特粗",
            "-thin": "极细"
        }

        # 测试字体是否可用于渲染
        def test_font(font_path, size=12):
            try:
                from PIL import Image, ImageDraw, ImageFont
                # 尝试加载字体
                font = None
                try:
                    # 首先尝试直接加载
                    font = ImageFont.truetype(font_path, size)
                except:
                    try:
                        # 如果失败,尝试使用索引0加载
                        font = ImageFont.truetype(font_path, size, index=0)
                    except:
                        return False

                if not font:
                    return False

                # 创建测试图像
                img = Image.new('RGB', (30, 30), color=(255, 255, 255))
                d = ImageDraw.Draw(img)
                # 尝试绘制中文字符
                d.text((5, 5), "测试", font=font, fill=(0, 0, 0))
                return True
            except Exception as e:
                if info_label:
                    info_label.config(text=f"字体测试失败: {os.path.basename(font_path)}")
                    info_label.update()
                return False

        if info_label:
            info_label.config(text="正在搜索系统字体...")
            info_label.update()

        # 系统字体目录列表
        font_dirs = []

        if os.name == 'nt':  # Windows
            # Windows字体目录
            if 'WINDIR' in os.environ:
                font_dirs.append(os.path.join(os.environ['WINDIR'], 'Fonts'))

            # Windows用户字体目录
            if 'LOCALAPPDATA' in os.environ:
                font_dirs.append(os.path.join(os.environ['LOCALAPPDATA'], 'Microsoft', 'Windows', 'Fonts'))

        elif sys.platform == 'darwin':  # macOS
            font_dirs.extend([
                '/Library/Fonts',
                '/System/Library/Fonts',
                os.path.expanduser('~/Library/Fonts')
            ])

        else:  # Linux/Unix
            font_dirs.extend([
                '/usr/share/fonts',
                '/usr/local/share/fonts',
                os.path.expanduser('~/.fonts'),
                os.path.expanduser('~/.local/share/fonts')
            ])

        total_fonts = 0
        processed_fonts = 0

        # 首先计算总字体文件数
        for font_dir in font_dirs:
            if os.path.exists(font_dir):
                for root, _, files in os.walk(font_dir):
                    total_fonts += len([f for f in files if f.lower().endswith(('.ttf', '.ttc', '.otf'))])

        # 遍历所有字体目录
        for font_dir in font_dirs:
            if not os.path.exists(font_dir):
                if info_label:
                    info_label.config(text=f"目录不存在: {font_dir}")
                    info_label.update()
                continue

            if info_label:
                info_label.config(text=f"搜索字体目录: {os.path.basename(font_dir)}")
                info_label.update()

            # 递归遍历目录
            for root, _, files in os.walk(font_dir):
                for filename in files:
                    if filename.lower().endswith(('.ttf', '.ttc', '.otf')):
                        processed_fonts += 1
                        if info_label:
                            info_label.config(text=f"正在加载字体 ({processed_fonts}/{total_fonts}): {filename}")
                            info_label.update()

                        font_path = os.path.join(root, filename)
                        name_without_ext = os.path.splitext(filename)[0].lower()

                        # 尝试将文件名映射到中文名称
                        display_name = None
                        for key, value in font_name_mapping.items():
                            if key.lower() in name_without_ext:
                                display_name = value
                                break

                        # 如果没有映射,使用原文件名
                        if not display_name:
                            display_name = name_without_ext

                        # 测试字体是否可用
                        if test_font(font_path):
                            available_fonts.append(display_name)
                            font_paths[display_name] = font_path
                            if info_label:
                                info_label.config(text=f"已加载字体 ({processed_fonts}/{total_fonts}): {display_name}")
                                info_label.update()

        # 如果没有找到系统字体,添加几个常见的Windows中文字体
        if len(available_fonts) == 0 and os.name == 'nt' and 'WINDIR' in os.environ:
            if info_label:
                info_label.config(text="未找到可用字体,添加常见Windows中文字体...")
                info_label.update()

            # Windows字体目录
            font_dir = os.path.join(os.environ['WINDIR'], 'Fonts')

            # 常见Windows中文字体文件
            common_fonts = [
                ("微软雅黑", "msyh.ttc"),
                ("宋体", "simsun.ttc"),
                ("黑体", "simhei.ttf"),
                ("楷体", "simkai.ttf"),
                ("仿宋", "simfang.ttf")
            ]

            for display_name, filename in common_fonts:
                font_path = os.path.join(font_dir, filename)
                if os.path.exists(font_path) and test_font(font_path):
                    available_fonts.append(display_name)
                    font_paths[display_name] = font_path
                    if info_label:
                        info_label.config(text=f"已加载字体: {display_name}")
                        info_label.update()

        if info_label:
            info_label.config(text=f"字体加载完成,共加载 {len(available_fonts)} 个字体")
            info_label.update()

        return available_fonts, font_paths

    def apply_image_filter(self):
        """应用图像滤镜效果"""
        if not self.current_image:
            messagebox.showwarning("警告", "请先打开一张图片!")
            return

        # 创建滤镜选择对话框
        filter_window = tk.Toplevel(self.root)
        filter_window.title("应用滤镜")
        filter_window.geometry("600x400")
        filter_window.resizable(False, False)

        # 设置为模态窗口
        filter_window.transient(self.root)
        filter_window.grab_set()
        self.center_window(filter_window, 600, 400)

        # 滤镜列表
        filters = [
            ("模糊", "BLUR"),
            ("轮廓", "CONTOUR"),
            ("锐化", "SHARPEN"),
            ("边缘增强", "EDGE_ENHANCE"),
            ("浮雕", "EMBOSS"),
            ("平滑", "SMOOTH"),
            ("灰度", "GRAYSCALE"),
            ("棕褐色", "SEPIA")
        ]

        # 创建主框架
        main_frame = ttk.Frame(filter_window)
        main_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

        # 创建滤镜选择框 - 左侧
        filter_frame = ttk.LabelFrame(main_frame, text="选择滤镜")
        filter_frame.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.Y)

        selected_filter = tk.StringVar()

        # 预览图像(缩小的当前图像)
        preview_image = self.current_image.copy()
        canvas_width, canvas_height = 320, 280
        preview_image.thumbnail((canvas_width, canvas_height))
        preview_photo = ImageTk.PhotoImage(preview_image)

        # 创建滤镜预览的Canvas - 右侧
        preview_frame = ttk.LabelFrame(main_frame, text="预览")
        preview_frame.pack(side=tk.RIGHT, padx=5, pady=5, fill=tk.BOTH, expand=True)

        preview_canvas = tk.Canvas(preview_frame, width=canvas_width, height=canvas_height, bg="white")
        preview_canvas.pack(padx=5, pady=5)

        # 在Canvas上显示预览图像
        preview_image_id = preview_canvas.create_image(
            canvas_width // 2, canvas_height // 2, 
            image=preview_photo
        )

        # 预览滤镜效果的函数
        def preview_filter():
            filter_name = selected_filter.get()
            if filter_name:
                # 应用滤镜到预览图像
                filtered_image = apply_filter(preview_image.copy(), filter_name)
                nonlocal preview_photo
                preview_photo = ImageTk.PhotoImage(filtered_image)
                preview_canvas.itemconfig(preview_image_id, image=preview_photo)

        # 应用滤镜的函数
        def apply_selected_filter():
            filter_name = selected_filter.get()
            if filter_name:
                # 保存当前状态用于撤销
                self.save_current_state()

                # 应用滤镜
                filtered_image = apply_filter(self.current_image, filter_name)
                self.current_image = filtered_image

                # 更新显示
                self.display_image_on_canvas()

                # 查找对应的显示名称
                filter_display_name = None
                for display_name, code in filters:
                    if code == filter_name:
                        filter_display_name = display_name
                        break

                filter_window.destroy()
                self.show_status(f"已应用{filter_display_name}滤镜")

        # 创建滤镜选择的单选按钮
        for filter_text, filter_name in filters:
            rb = ttk.Radiobutton(
                filter_frame, 
                text=filter_text, 
                value=filter_name, 
                variable=selected_filter,
                command=preview_filter
            )
            rb.pack(anchor=tk.W, padx=10, pady=5)

        # 设置默认选中的滤镜
        selected_filter.set("BLUR")
        preview_filter()  # 显示初始预览

        # 按钮框架
        button_frame = ttk.Frame(filter_window)
        button_frame.pack(pady=10)

        # 应用按钮
        apply_button = ttk.Button(button_frame, text="应用", command=apply_selected_filter)
        apply_button.pack(side=tk.LEFT, padx=5)

        # 取消按钮
        cancel_button = ttk.Button(button_frame, text="取消", command=filter_window.destroy)
        cancel_button.pack(side=tk.LEFT, padx=5)

    def save_current_state(self):
        """保存当前图像状态用于撤销/重做"""
        if self.current_image is None:
            return

        # 如果当前不在历史记录的最新状态,删除当前索引之后的所有记录
        if self.history_index < len(self.history) - 1:
            self.history = self.history[:self.history_index + 1]

        # 保存当前图像的副本
        self.history.append(self.current_image.copy())
        self.history_index = len(self.history) - 1

        # 限制历史记录长度
        if len(self.history) > self.max_history:
            self.history.pop(0)
            self.history_index -= 1

    def undo(self, event=None):
        """撤销上一步操作"""
        if self.history_index > 0:
            self.history_index -= 1
            self.current_image = self.history[self.history_index].copy()
            self.display_image_on_canvas()
            self.show_status("已撤销")
        else:
            self.show_status("无法撤销")

    def redo(self, event=None):
        """重做操作"""
        if self.history_index < len(self.history) - 1:
            self.history_index += 1
            self.current_image = self.history[self.history_index].copy()
            self.display_image_on_canvas()
            self.show_status("已重做")
        else:
            self.show_status("无法重做")

    def show_status(self, message):
        """在状态栏显示消息"""
        # 如果没有状态栏,直接打印消息
        print(message)

    def show_about(self):
        """显示关于对话框"""
        about_window = tk.Toplevel(self.root)
        about_window.title("关于")
        about_window.geometry("500x400")  # 增加窗口尺寸
        about_window.resizable(False, False)
        about_window.transient(self.root)
        about_window.grab_set()
        self.center_window(about_window, 500, 400)

        # 标题
        ttk.Label(about_window, text="图片处理小工具", font=("Arial", 18, "bold")).pack(pady=15)

        # 版本信息
        ttk.Label(about_window, text="版本: 2.0", font=("Arial", 10)).pack(pady=5)

        # 创建滚动文本区域显示软件描述
        frame = ttk.Frame(about_window)
        frame.pack(fill="both", expand=True, padx=20, pady=10)

        scrollbar = ttk.Scrollbar(frame)
        scrollbar.pack(side="right", fill="y")

        description_text = tk.Text(frame, wrap="word", height=10, width=50, 
                                 yscrollcommand=scrollbar.set, borderwidth=0,
                                 font=("Arial", 9))
        description_text.pack(side="left", fill="both", expand=True)
        scrollbar.config(command=description_text.yview)

        # 详细描述
        description = """图片处理小工具是一款功能全面的图像编辑软件,专为日常图片处理需求设计。

主要功能:
• 基础图像编辑: 裁剪、调整大小、旋转和翻转
• 图像增强: 调整亮度、对比度、饱和度和色温
• 多种滤镜效果: 模糊、锐化、灰度、复古等
• 水印功能: 支持文字和图片水印,可调整位置、透明度等
• 批量处理: 批量调整大小、格式转换和添加水印
• 特色功能: 模板裁切、平铺式防伪水印
• 格式支持: JPG、PNG、WEBP、GIF、BMP等多种格式

最新更新:
• 改进的批量水印功能,支持自动预览
• 优化的用户界面,操作更加直观
• 增强的图像处理算法,提高处理质量和速度
• 新增平铺式防伪水印功能
• 性能优化,减少资源占用

您可以通过"帮助"菜单查看完整的使用指南。"""

        description_text.insert("1.0", description)
        description_text.config(state="disabled")  # 设为只读

        # 技术信息
        tech_frame = ttk.Frame(about_window)
        tech_frame.pack(fill="x", padx=20, pady=5)

        ttk.Label(tech_frame, text="技术支持: ", font=("Arial", 9, "bold")).pack(side="left")
        ttk.Label(tech_frame, text="Python + Tkinter + Pillow", font=("Arial", 9)).pack(side="left")

        # 版权信息
        ttk.Label(about_window, text="Copyright © 2023-2025  nobiyou", 
                 font=("Arial", 9)).pack(pady=5)

        # 关闭按钮
        ttk.Button(about_window, text="确定", command=about_window.destroy, width=15).pack(pady=15)

    def show_help(self):
        """显示使用帮助对话框"""
        help_window = tk.Toplevel(self.root)
        help_window.title("使用帮助")
        help_window.geometry("600x480")  # 增加窗口尺寸以容纳更多内容
        help_window.transient(self.root)
        help_window.grab_set()
        self.center_window(help_window, 600, 480)

        # 创建选项卡
        notebook = ttk.Notebook(help_window)
        notebook.pack(fill="both", expand=True, padx=10, pady=10)

        # 基础操作选项卡
        basic_tab = ttk.Frame(notebook)
        notebook.add(basic_tab, text="基础操作")

        basic_text = """
        基础操作:
        - 打开图片: 点击"文件"菜单 -> "打开",或使用快捷键Ctrl+O
        - 保存图片: 点击"文件"菜单 -> "保存",或使用快捷键Ctrl+S
        - 退出程序: 点击"文件"菜单 -> "退出",或使用快捷键Alt+F4

        图像浏览:
        - 缩放图像: 使用鼠标滚轮放大或缩小图像
        - 平移图像: 按住鼠标左键拖动图像
        - 重置视图: 点击"视图"菜单 -> "重置视图"
        - 适应窗口: 点击"视图"菜单 -> "适应窗口"

        编辑功能:
        - 裁剪: 点击"编辑"菜单 -> "裁剪",可通过拖动选择裁剪区域
        - 调整大小: 点击"编辑"菜单 -> "调整大小",可设定具体尺寸或百分比
        - 旋转: 点击"编辑"菜单 -> "旋转",支持90°旋转或自定义角度
        - 翻转: 点击"编辑"菜单 -> "翻转",可水平或垂直翻转图像

        撤销与重做:
        - 撤销: 点击"编辑"菜单 -> "撤销",或使用快捷键Ctrl+Z
        - 重做: 点击"编辑"菜单 -> "重做",或使用快捷键Ctrl+Y
        - 每个操作都会被记录,最多支持10步的撤销/重做操作
        """

        # 为基础操作选项卡添加滚动条
        basic_scroll = ttk.Scrollbar(basic_tab, orient="vertical")
        basic_scroll.pack(side="right", fill="y")

        basic_text_widget = tk.Text(basic_tab, yscrollcommand=basic_scroll.set, wrap="word", 
                                  height=15, width=65, borderwidth=0, font=("", 9))
        basic_text_widget.insert("1.0", basic_text)
        basic_text_widget.config(state="disabled")  # 使文本不可编辑
        basic_text_widget.pack(side="left", fill="both", expand=True, padx=20, pady=20)

        basic_scroll.config(command=basic_text_widget.yview)

        # 效果选项卡
        effect_tab = ttk.Frame(notebook)
        notebook.add(effect_tab, text="效果功能")

        effect_text = """
        图像调整:
        - 亮度/对比度: 点击"效果"菜单 -> "调整亮度/对比度"
          拖动滑块可实时预览效果,点击"应用"确认修改

        - 饱和度/色温: 点击"效果"菜单 -> "调整饱和度/色温"
          调整图像的色彩饱和度和冷暖色调

        - 色阶调整: 点击"效果"菜单 -> "色阶调整"
          调整图像的阴影、中间调和高光,提高图像质量

        滤镜效果:
        - 应用滤镜: 点击"效果"菜单 -> "应用滤镜"
          提供多种滤镜效果,包括:
          • 模糊(Blur): 使图像柔和,减少细节和噪点
          • 锐化(Sharpen): 增强图像边缘,使图像更加清晰
          • 灰度(Grayscale): 将彩色图像转换为黑白图像
          • 复古(Sepia): 添加棕褐色色调,创造复古效果
          • 浮雕(Emboss): 创造浮雕效果,突出图像轮廓
          • 边缘检测(Edge Enhance): 突出图像边缘

        - 自定义滤镜: 点击"效果"菜单 -> "自定义滤镜"
          可组合多种滤镜效果,调整参数创建独特效果

        颜色调整:
        - 自动增强: 点击"效果"菜单 -> "自动增强"
          自动优化图像的对比度和颜色平衡

        - 颜色平衡: 点击"效果"菜单 -> "颜色平衡"
          调整红、绿、蓝三个通道的平衡
        """

        # 为效果选项卡添加滚动条
        effect_scroll = ttk.Scrollbar(effect_tab, orient="vertical")
        effect_scroll.pack(side="right", fill="y")

        effect_text_widget = tk.Text(effect_tab, yscrollcommand=effect_scroll.set, wrap="word", 
                                    height=15, width=65, borderwidth=0, font=("", 9))
        effect_text_widget.insert("1.0", effect_text)
        effect_text_widget.config(state="disabled")  # 使文本不可编辑
        effect_text_widget.pack(side="left", fill="both", expand=True, padx=20, pady=20)

        effect_scroll.config(command=effect_text_widget.yview)

        # 工具选项卡
        tools_tab = ttk.Frame(notebook)
        notebook.add(tools_tab, text="工具")

        tools_text = """
        常用工具:
        - 模板裁切: 点击"工具"菜单 -> "模板裁切"
          支持多种社交媒体和设备的常用尺寸:
          • 微信公众号封面(900x383)
          • 朋友圈正方形(1080x1080)
          • 小红书(1000x1000)
          • 微博(440x245)
          • 可自定义尺寸

        - 格式转换: 点击"工具"菜单 -> "格式转换"
          支持多种图片格式之间的转换:
          • JPG(JPEG): 压缩率高,适合照片
          • PNG: 支持透明背景,适合图标和标志
          • WEBP: 谷歌开发的格式,比JPG更小
          • GIF: 支持动画效果
          • BMP: 无损格式,文件较大
          可设置压缩质量,优化图像大小和质量平衡

        - 添加水印: 点击"工具"菜单 -> "添加水印"
          支持两种水印模式:
          • 单个水印: 在图像的指定位置添加一个水印
          • 平铺水印: 在整张图像上重复添加水印,防伪效果强
          支持文字水印和图片水印,可调整透明度、大小、旋转角度等

        批量处理:
        - 批量调整大小: 点击"工具"菜单 -> "批量调整大小"
          可一次性处理多张图片,调整为统一尺寸或按比例缩放
          支持保留原文件名或使用序号重命名

        - 批量格式转换: 点击"工具"菜单 -> "批量格式转换"
          可一次性转换多张图片的格式,支持调整质量和压缩率
          可保持原始分辨率或在转换时调整尺寸

        - 批量添加水印: 点击"工具"菜单 -> "批量添加水印"
          可一次性为多张图片添加统一的水印
          批量操作时会自动加载第一张图片作为预览,实时调整效果
          支持单水印和平铺水印两种模式

        高级功能:
        - 图像修复: 点击"工具"菜单 -> "图像修复"
          去除图像中的小瑕疵、划痕或不需要的物体

        - 防伪水印: 点击"工具"菜单 -> "添加防伪水印"
          创建难以去除的平铺式水印,可用于版权保护
        """

        # 添加滚动容器
        tools_scroll = ttk.Scrollbar(tools_tab, orient="vertical")
        tools_scroll.pack(side="right", fill="y")

        tools_text_widget = tk.Text(tools_tab, yscrollcommand=tools_scroll.set, wrap="word", 
                                   height=15, width=65, borderwidth=0, font=("", 9))
        tools_text_widget.insert("1.0", tools_text)
        tools_text_widget.config(state="disabled")  # 使文本不可编辑
        tools_text_widget.pack(side="left", fill="both", expand=True, padx=20, pady=20)

        tools_scroll.config(command=tools_text_widget.yview)

        # 快捷键选项卡
        shortcuts_tab = ttk.Frame(notebook)
        notebook.add(shortcuts_tab, text="快捷键")

        shortcuts_text = """
        文件操作:
        - Ctrl+O: 打开图片
        - Ctrl+S: 保存当前图片
        - Ctrl+Shift+S: 另存为
        - Ctrl+P: 打印图片
        - Alt+F4: 退出程序

        编辑操作:
        - Ctrl+Z: 撤销上一步操作
        - Ctrl+Y: 重做上一步操作
        - Ctrl+X: 剪切
        - Ctrl+C: 复制
        - Ctrl+V: 粘贴
        - Delete: 删除选中内容

        视图操作:
        - Ctrl+数字键盘 +: 放大图像
        - Ctrl+数字键盘 -: 缩小图像
        - Ctrl+0: 重置视图到100%
        - F11: 全屏显示

        工具快捷键:
        - Ctrl+R: 调整大小
        - Ctrl+T: 裁剪图片
        - Ctrl+Shift+R: 旋转图片
        - Ctrl+W: 添加水印
        - Ctrl+B: 批量处理

        使用技巧:
        - 按住空格键并拖动可快速平移图像
        - 双击图像可快速恢复到100%显示
        - 右键点击图像可显示上下文菜单
        """

        # 为快捷键选项卡添加滚动条
        shortcuts_scroll = ttk.Scrollbar(shortcuts_tab, orient="vertical")
        shortcuts_scroll.pack(side="right", fill="y")

        shortcuts_text_widget = tk.Text(shortcuts_tab, yscrollcommand=shortcuts_scroll.set, wrap="word", 
                                       height=15, width=65, borderwidth=0, font=("", 9))
        shortcuts_text_widget.insert("1.0", shortcuts_text)
        shortcuts_text_widget.config(state="disabled")  # 使文本不可编辑
        shortcuts_text_widget.pack(side="left", fill="both", expand=True, padx=20, pady=20)

        shortcuts_scroll.config(command=shortcuts_text_widget.yview)

        # 关闭按钮
        ttk.Button(help_window, text="关闭", command=help_window.destroy).pack(pady=10)

    def add_tiled_watermark(self):
        """添加防伪水印(平铺式水印覆盖整张图片)"""
        if not self.current_image:
            messagebox.showerror("错误", "请先打开一张图片")
            return

        # 创建一个顶层窗口
        tiled_watermark_window = tk.Toplevel(self.root)
        tiled_watermark_window.title("添加防伪水印")
        tiled_watermark_window.geometry("800x600")
        tiled_watermark_window.resizable(True, True)
        tiled_watermark_window.transient(self.root)
        tiled_watermark_window.grab_set()
        self.center_window(tiled_watermark_window, 800, 600)

        # 创建主框架
        main_frame = ttk.Frame(tiled_watermark_window, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 创建内容框架 (包含所有控件,但不包括底部按钮)
        content_frame = ttk.Frame(main_frame)
        content_frame.pack(fill=tk.BOTH, expand=True)

        # 创建左右两栏
        left_frame = ttk.Frame(content_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))

        right_frame = ttk.Frame(content_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))

        # 水印类型
        type_frame = ttk.LabelFrame(left_frame, text="水印类型")
        type_frame.pack(fill=tk.X, padx=5, pady=5)

        watermark_type = tk.StringVar(value="text")
        ttk.Radiobutton(type_frame, text="文字水印", variable=watermark_type, 
                       value="text").grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
        ttk.Radiobutton(type_frame, text="图片水印", variable=watermark_type, 
                       value="image").grid(row=0, column=1, padx=10, pady=5, sticky=tk.W)

        # 文字水印设置
        text_frame = ttk.LabelFrame(left_frame, text="文字水印设置")
        text_frame.pack(fill=tk.X, padx=5, pady=5)

        ttk.Label(text_frame, text="水印文字:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)

        watermark_text = tk.StringVar(value="防伪水印")
        ttk.Entry(text_frame, textvariable=watermark_text, width=25).grid(
            row=0, column=1, padx=5, pady=5, sticky=tk.W)

        ttk.Label(text_frame, text="字体:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)

        # 获取系统字体
        font_var = tk.StringVar()
        fonts, font_paths = self.get_system_fonts()
        self.font_paths = font_paths  # 保存字体路径映射
        font_combo = ttk.Combobox(text_frame, textvariable=font_var, values=fonts, width=20)
        font_combo.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
        if fonts:
            font_combo.current(0)

        # 为下拉框选择事件添加绑定
        def on_font_selected(event):
            # 延迟执行更新预览,以确保变量已更新
            tiled_watermark_window.after(10, update_preview)

        font_combo.bind("<<ComboboxSelected>>", on_font_selected)

        # 字体颜色
        ttk.Label(text_frame, text="字体颜色:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)

        color_var = tk.StringVar(value="#000000")
        color_frame = ttk.Frame(text_frame)
        color_frame.grid(row=2, column=1, padx=5, pady=5, sticky=tk.W)

        color_preview = tk.Label(color_frame, bg=color_var.get(), width=3, height=1, relief="ridge")
        color_preview.pack(side=tk.LEFT, padx=2)

        # 颜色选择函数
        def choose_color():
            color = colorchooser.askcolor(color_var.get())[1]
            if color:
                color_var.set(color)
                color_preview.config(bg=color)
                # 确保颜色更改后立即更新预览
                tiled_watermark_window.after(10, update_preview)

        ttk.Button(color_frame, text="选择颜色", command=choose_color).pack(side=tk.LEFT, padx=5)

        # 字体样式
        style_frame = ttk.Frame(text_frame)
        style_frame.grid(row=3, column=0, columnspan=2, padx=5, pady=5, sticky=tk.W)

        bold_var = tk.BooleanVar(value=False)
        italic_var = tk.BooleanVar(value=False)

        # 添加command参数确保立即更新预览
        def update_style():
            tiled_watermark_window.after(10, update_preview)

        ttk.Checkbutton(style_frame, text="粗体", variable=bold_var, command=update_style).pack(side=tk.LEFT, padx=5)
        ttk.Checkbutton(style_frame, text="斜体", variable=italic_var, command=update_style).pack(side=tk.LEFT, padx=5)

        # 图片水印设置
        image_frame = ttk.LabelFrame(left_frame, text="图片水印设置")

        ttk.Label(image_frame, text="水印图片:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)

        watermark_image_path = tk.StringVar()
        ttk.Entry(image_frame, textvariable=watermark_image_path, width=25).grid(
            row=0, column=1, padx=5, pady=5, sticky=tk.W)

        # 保存加载的水印图片
        watermark_img_obj = [None]  # 使用列表存储图片对象以便在函数间共享

        # 选择图片函数
        def select_watermark_image():
            file_path = filedialog.askopenfilename(
                filetypes=[
                    ("图片文件", "*.png *.jpg *.jpeg *.gif *.bmp"),
                    ("PNG文件", "*.png"),
                    ("所有文件", "*.*")
                ]
            )
            if file_path:
                try:
                    # 打开并保存图片
                    from PIL import Image
                    img = Image.open(file_path)
                    watermark_img_obj[0] = img
                    watermark_image_path.set(file_path)

                    # 更新预览
                    update_preview()
                except Exception as e:
                    messagebox.showerror("错误", f"无法打开图片: {str(e)}")

        ttk.Button(image_frame, text="浏览...", command=select_watermark_image).grid(
            row=0, column=2, padx=5, pady=5)

        # 平铺水印设置(共享设置区域)
        tiled_frame = ttk.LabelFrame(left_frame, text="平铺水印设置")
        tiled_frame.pack(fill=tk.X, padx=5, pady=5)

        # 不透明度
        ttk.Label(tiled_frame, text="不透明度:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)

        opacity_var = tk.DoubleVar(value=0.2)
        opacity_scale = ttk.Scale(tiled_frame, from_=0.05, to=1, variable=opacity_var, length=200)
        opacity_scale.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
        opacity_scale.bind("<B1-Motion>", lambda e: update_preview())
        opacity_scale.bind("<ButtonRelease-1>", lambda e: update_preview())

        opacity_label = ttk.Label(tiled_frame, text=f"{opacity_var.get():.2f}")
        opacity_label.grid(row=0, column=2, padx=5, pady=5)

        def update_opacity_label(*args):
            opacity_label.config(text=f"{opacity_var.get():.2f}")
            # 在标签更新时同时更新预览
            tiled_watermark_window.after(10, update_preview)

        opacity_var.trace_add("write", update_opacity_label)

        # 旋转角度
        ttk.Label(tiled_frame, text="旋转角度:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)

        rotation_var = tk.IntVar(value=30)
        rotation_scale = ttk.Scale(tiled_frame, from_=0, to=90, variable=rotation_var, length=200)
        rotation_scale.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
        rotation_scale.bind("<B1-Motion>", lambda e: update_preview())
        rotation_scale.bind("<ButtonRelease-1>", lambda e: update_preview())

        rotation_label = ttk.Label(tiled_frame, text=f"{rotation_var.get()}°")
        rotation_label.grid(row=1, column=2, padx=5, pady=5)

        def update_rotation_label(*args):
            rotation_label.config(text=f"{rotation_var.get()}°")
            # 在标签更新时同时更新预览
            tiled_watermark_window.after(10, update_preview)

        rotation_var.trace_add("write", update_rotation_label)

        # 水印大小
        ttk.Label(tiled_frame, text="水印大小:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)

        tile_scale_var = tk.DoubleVar(value=0.15)
        tile_scale_scale = ttk.Scale(tiled_frame, from_=0.05, to=1, variable=tile_scale_var, length=200)
        tile_scale_scale.grid(row=2, column=1, padx=5, pady=5, sticky=tk.EW)
        tile_scale_scale.bind("<B1-Motion>", lambda e: update_preview())
        tile_scale_scale.bind("<ButtonRelease-1>", lambda e: update_preview())

        tile_scale_label = ttk.Label(tiled_frame, text=f"{int(tile_scale_var.get()*100)}%")
        tile_scale_label.grid(row=2, column=2, padx=5, pady=5)

        def update_tile_scale_label(*args):
            tile_scale_label.config(text=f"{int(tile_scale_var.get()*100)}%")
            # 在标签更新时同时更新预览
            tiled_watermark_window.after(10, update_preview)

        tile_scale_var.trace_add("write", update_tile_scale_label)

        # 水印间距
        ttk.Label(tiled_frame, text="间距系数:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)

        spacing_var = tk.DoubleVar(value=1.5)
        spacing_scale = ttk.Scale(tiled_frame, from_=0.1, to=3.0, variable=spacing_var, length=200)
        spacing_scale.grid(row=3, column=1, padx=5, pady=5, sticky=tk.EW)
        spacing_scale.bind("<B1-Motion>", lambda e: update_preview())
        spacing_scale.bind("<ButtonRelease-1>", lambda e: update_preview())

        spacing_label = ttk.Label(tiled_frame, text=f"{spacing_var.get():.1f}x")
        spacing_label.grid(row=3, column=2, padx=5, pady=5)

        def update_spacing_label(*args):
            spacing_label.config(text=f"{spacing_var.get():.1f}x")
            # 在标签更新时同时更新预览
            tiled_watermark_window.after(10, update_preview)

        spacing_var.trace_add("write", update_spacing_label)

        # 预览区域
        preview_frame = ttk.LabelFrame(right_frame, text="预览")
        preview_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 预览控制区域 - 添加缩放和网格控制
        preview_control_frame = ttk.Frame(preview_frame)
        preview_control_frame.pack(fill=tk.X, padx=5, pady=2)

        # 缩放控制
        ttk.Label(preview_control_frame, text="缩放:").pack(side=tk.LEFT, padx=2)
        zoom_var = tk.DoubleVar(value=1.0)
        zoom_scale = ttk.Scale(preview_control_frame, from_=0.5, to=3.0, variable=zoom_var, length=150)
        zoom_scale.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
        zoom_scale.bind("<B1-Motion>", lambda e: tiled_watermark_window.after(10, update_preview))
        zoom_scale.bind("<ButtonRelease-1>", lambda e: update_preview())

        zoom_label = ttk.Label(preview_control_frame, text="100%", width=5)
        zoom_label.pack(side=tk.LEFT, padx=2)

        def update_zoom_label(*args):
            zoom_label.config(text=f"{int(zoom_var.get() * 100)}%")

        zoom_var.trace_add("write", update_zoom_label)

        # 网格显示控制
        grid_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(preview_control_frame, text="显示网格", variable=grid_var, 
                       command=lambda: update_preview()).pack(side=tk.LEFT, padx=10)

        # 重置按钮
        reset_zoom_btn = ttk.Button(preview_control_frame, text="重置缩放", 
                                  command=lambda: (zoom_var.set(1.0), update_preview()))
        reset_zoom_btn.pack(side=tk.RIGHT, padx=5)

        # 创建画布用于预览
        preview_canvas = tk.Canvas(preview_frame, bg="white")
        preview_canvas.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 添加拖动支持
        drag_data = {"start_x": 0, "start_y": 0, "offset_x": 0, "offset_y": 0, "dragging": False}

        def on_drag_start(event):
            # 只有在放大状态下才启用拖动
            if zoom_var.get() > 1.0:
                drag_data["start_x"] = event.x
                drag_data["start_y"] = event.y
                drag_data["dragging"] = True
                preview_canvas.config(cursor="fleur")  # 更改鼠标光标为移动状态

        def on_drag_motion(event):
            # 只有在放大状态下才处理拖动
            if zoom_var.get() > 1.0:
                # 计算位移
                dx = event.x - drag_data["start_x"]
                dy = event.y - drag_data["start_y"]

                # 更新起始点
                drag_data["start_x"] = event.x
                drag_data["start_y"] = event.y

                # 更新偏移
                drag_data["offset_x"] += dx
                drag_data["offset_y"] += dy

                # 更新预览
                update_preview(keep_offset=True)

        def on_drag_end(event):
            # 结束拖动
            drag_data["dragging"] = False
            preview_canvas.config(cursor="")  # 恢复默认光标

        # 绑定拖动事件
        preview_canvas.bind("<ButtonPress-1>", on_drag_start)
        preview_canvas.bind("<B1-Motion>", on_drag_motion)
        preview_canvas.bind("<ButtonRelease-1>", on_drag_end)

        # 状态信息显示
        status_var = tk.StringVar(value="就绪")
        preview_status = ttk.Label(preview_frame, textvariable=status_var, anchor=tk.W)
        preview_status.pack(fill=tk.X, padx=5, pady=2)

        # 预览图像引用,防止垃圾回收
        preview_img_ref = [None]

        # 更新预览函数
        def update_preview(keep_offset=False):
            if not self.current_image:
                status_var.set("请先打开一张图片")
                return

            # 备份原图像
            img_copy = self.current_image.copy()

            try:
                # 获取画布尺寸
                canvas_width = preview_canvas.winfo_width()
                canvas_height = preview_canvas.winfo_height()

                if canvas_width <= 1 or canvas_height <= 1:
                    # 窗口尚未完全创建,延迟执行
                    preview_canvas.after(100, update_preview)
                    return

                # 计算缩放比例(考虑用户缩放因子)
                img_width, img_height = img_copy.size
                base_scale = min(canvas_width / img_width, canvas_height / img_height) * 0.9
                scale = base_scale * zoom_var.get()

                # 缩放图像以适应预览区域
                new_width = int(img_width * scale)
                new_height = int(img_height * scale)

                # 如果缩放因子小于等于1且不是强制保持偏移,重置偏移量
                if zoom_var.get() <= 1.0 and not keep_offset:
                    drag_data["offset_x"] = 0
                    drag_data["offset_y"] = 0

                # 创建适合预览的缩略图
                img_thumb = img_copy.resize((new_width, new_height), Image.LANCZOS)

                # 根据水印类型应用不同的水印
                if watermark_type.get() == "text":
                    # 文字水印
                    text = watermark_text.get()
                    font = font_var.get()

                    print(f"\n预览更新 - 水印文本: '{text}', 选择字体: '{font}'")

                    # 获取字体实际路径
                    font_path = None
                    if hasattr(self, 'font_paths'):
                        print(f"预览更新 - 字体路径字典包含 {len(self.font_paths)} 个条目")
                        if font in self.font_paths:
                            font_path = self.font_paths[font]
                            print(f"预览更新 - 找到字体路径: {font_path}")
                        else:
                            print(f"预览更新 - 字体 '{font}' 不在字体路径字典中")
                            if self.font_paths:
                                fonts_list = list(self.font_paths.keys())
                                print(f"预览更新 - 可用字体(前5个): {fonts_list[:min(5, len(fonts_list))]}")
                                # 尝试不区分大小写匹配
                                lower_font = font.lower()
                                for f in fonts_list:
                                    if f.lower() == lower_font:
                                        font_path = self.font_paths[f]
                                        print(f"预览更新 - 找到不区分大小写的匹配: {f} -> {font_path}")
                                        break
                    else:
                        print("预览更新 - font_paths 属性不存在")

                    # 获取颜色
                    try:
                        from PIL import ImageColor
                        color = ImageColor.getcolor(color_var.get(), "RGB")
                        print(f"预览更新 - 颜色值: {color}")
                    except Exception as e:
                        print(f"预览更新 - 颜色解析错误: {e}")
                        color = (0, 0, 0)  # 默认黑色

                    # 其他水印参数
                    opacity = opacity_var.get()
                    tile_scale = tile_scale_var.get()
                    rotation = rotation_var.get()
                    spacing = spacing_var.get()
                    is_bold = bold_var.get()
                    is_italic = italic_var.get()

                    print(f"预览更新 - 参数: 不透明度={opacity}, 缩放={tile_scale}, 旋转={rotation}, 间距={spacing}")
                    print(f"预览更新 - 文本样式: 粗体={is_bold}, 斜体={is_italic}")

                    # 应用平铺水印效果
                    from image_utils import add_tiled_watermark
                    try:
                        # 确保使用正确的字体路径
                        final_font = font_path if font_path else font
                        print(f"预览更新 - 最终使用的字体: {final_font}")

                        # 根据tile_scale计算适合的字体大小
                        # 基础字体大小设定为24,但会随着tile_scale缩放
                        base_font_size = 24
                        # 当缩放比例为0.15时使用基础字体大小,其他比例按比例缩放
                        scaled_font_size = int(base_font_size * (tile_scale / 0.15))
                        # 最小字体大小不小于8,否则可能看不清
                        scaled_font_size = max(8, scaled_font_size)
                        print(f"预览更新 - 水印缩放比例: {tile_scale}, 计算的字体大小: {scaled_font_size}")

                        preview_img = add_tiled_watermark(
                            img_thumb,
                            watermark_text=text,
                            opacity=opacity,
                            tile_scale=tile_scale,
                            rotation=rotation,
                            spacing=spacing,
                            font_name=final_font,
                            font_size=scaled_font_size,  # 使用根据缩放计算的字体大小
                            font_color=color,
                            bold=is_bold,
                            italic=is_italic,
                            letter_spacing=0  # 固定字间距以确保预览正常
                        )
                        status_var.set(f"文字防伪水印预览: {text}")
                    except Exception as e:
                        print(f"预览更新 - 水印应用错误: {e}")
                        import traceback
                        traceback.print_exc()
                        preview_img = img_thumb  # 失败时使用原图
                        status_var.set(f"水印应用错误: {str(e)}")
                else:
                    # 图片水印
                    if watermark_img_obj[0] is None:
                        status_var.set("请选择水印图片")
                        preview_img = img_thumb
                    else:
                        # 应用平铺水印效果
                        from image_utils import add_tiled_watermark
                        try:
                            preview_img = add_tiled_watermark(
                                img_thumb,
                                watermark_image=watermark_img_obj[0],
                                opacity=opacity_var.get(),
                                tile_scale=tile_scale_var.get(),
                                rotation=rotation_var.get(),
                                spacing=spacing_var.get()
                            )
                            img_name = os.path.basename(watermark_image_path.get()) if watermark_image_path.get() else "未命名"
                            status_var.set(f"图片防伪水印预览: {img_name}")
                        except Exception as e:
                            print(f"图片水印应用错误: {e}")
                            preview_img = img_thumb  # 失败时使用原图
                            status_var.set(f"水印应用错误: {str(e)}")

                # 创建预览图像
                preview_img_tk = ImageTk.PhotoImage(preview_img)
                preview_img_ref[0] = preview_img_tk  # 存储引用以防止垃圾回收

                # 计算居中位置,加上偏移量
                x_pos = max(0, (canvas_width - new_width) // 2) + drag_data["offset_x"]
                y_pos = max(0, (canvas_height - new_height) // 2) + drag_data["offset_y"]

                # 限制拖动边界,防止图片完全拖出视图
                if x_pos > canvas_width - 50:
                    x_pos = canvas_width - 50
                    drag_data["offset_x"] = x_pos - max(0, (canvas_width - new_width) // 2)

                if y_pos > canvas_height - 50:
                    y_pos = canvas_height - 50
                    drag_data["offset_y"] = y_pos - max(0, (canvas_height - new_height) // 2)

                if x_pos + new_width < 50:
                    x_pos = 50 - new_width
                    drag_data["offset_x"] = x_pos - max(0, (canvas_width - new_width) // 2)

                if y_pos + new_height < 50:
                    y_pos = 50 - new_height
                    drag_data["offset_y"] = y_pos - max(0, (canvas_height - new_height) // 2)

                # 清除画布并重绘
                preview_canvas.delete("all")

                # 绘制网格线
                if grid_var.get():
                    # 水平网格线
                    for y in range(0, canvas_height, 20):
                        preview_canvas.create_line(0, y, canvas_width, y, fill="#dddddd", dash=(2, 4))

                    # 垂直网格线
                    for x in range(0, canvas_width, 20):
                        preview_canvas.create_line(x, 0, x, canvas_height, fill="#dddddd", dash=(2, 4))

                    # 中心十字线
                    center_x = canvas_width // 2
                    center_y = canvas_height // 2
                    preview_canvas.create_line(center_x, 0, center_x, canvas_height, fill="#999999", dash=(4, 4))
                    preview_canvas.create_line(0, center_y, canvas_width, center_y, fill="#999999", dash=(4, 4))

                # 绘制图像边框
                border_color = "#3399ff"
                preview_canvas.create_rectangle(
                    x_pos - 1, y_pos - 1, 
                    x_pos + new_width + 1, y_pos + new_height + 1, 
                    outline=border_color, width=2
                )

                # 绘制图像
                img_item = preview_canvas.create_image(x_pos, y_pos, image=preview_img_tk, anchor="nw")

                # 将图像置于顶层
                preview_canvas.tag_raise(img_item)

                # 添加坐标标签
                # if position_mode_var.get() == "custom":
                #     # 显示自定义坐标点
                #     custom_x, custom_y = position
                #     # 缩放到预览尺寸并调整位置
                #     scaled_x = int(custom_x * scale) + x_pos
                #     scaled_y = int(custom_y * scale) + y_pos
                    # # 在预览中标出位置点
                    # if 0 <= scaled_x < canvas_width and 0 <= scaled_y < canvas_height:
                    #     # 绘制十字标记而不是圆形,使定位更精确
                    #     mark_size = 5
                    #     preview_canvas.create_line(
                    #         scaled_x - mark_size, scaled_y,
                    #         scaled_x + mark_size, scaled_y,
                    #         fill="red", width=2
                    #     )
                    #     preview_canvas.create_line(
                    #         scaled_x, scaled_y - mark_size,
                    #         scaled_x, scaled_y + mark_size,
                    #         fill="red", width=2
                    #     )
                    #     # 显示坐标信息
                    #     preview_canvas.create_text(
                    #         scaled_x, scaled_y + mark_size + 10,
                    #         text=f"({custom_x}, {custom_y})",
                    #         fill="red", font=("Arial", 8)
                    #     )

                # 如果放大,显示当前视图状态
                try:
                    if zoom_var.get() > 1.0:
                        help_text = "鼠标拖动可平移图像"
                        if drag_data.get("dragging", False):
                            help_text = "正在平移..."
                        preview_canvas.create_text(
                            canvas_width - 10, canvas_height - 10,
                            text=help_text,
                            anchor="se", fill="#666666", font=("Arial", 8)
                        )
                except (NameError, AttributeError):
                    pass  # 如果zoom_var未定义,忽略

            except Exception as e:
                print(f"预览错误: {str(e)}")
                import traceback
                traceback.print_exc()
                status_var.set(f"预览错误: {str(e)}")

        def update_type_ui(*args):
            # 根据选择显示/隐藏对应的设置面板
            if watermark_type.get() == "text":
                text_frame.pack(fill=tk.X, padx=5, pady=5, after=type_frame)
                # 隐藏图片水印设置
                if image_frame.winfo_manager():
                    image_frame.pack_forget()
            else:
                # 隐藏文字水印设置,显示图片水印设置
                if text_frame.winfo_manager():
                    text_frame.pack_forget()
                image_frame.pack(fill=tk.X, padx=5, pady=5, after=type_frame)

            update_preview()

        # 绑定变量变化到预览更新
        watermark_type.trace_add("write", update_type_ui)

        # 文字水印相关变量的直接延迟调用确保更新能及时触发
        def delayed_update(*args):
            tiled_watermark_window.after(50, update_preview)

        # 文字水印相关变量
        watermark_text.trace_add("write", delayed_update)
        font_var.trace_add("write", delayed_update)
        color_var.trace_add("write", delayed_update)
        bold_var.trace_add("write", delayed_update)
        italic_var.trace_add("write", delayed_update)

        # 通用平铺水印相关变量
        opacity_var.trace_add("write", update_preview)
        rotation_var.trace_add("write", update_preview)
        tile_scale_var.trace_add("write", update_preview)
        spacing_var.trace_add("write", update_preview)

        # 重置缩放和拖动时同时重置偏移
        def reset_zoom_and_offset():
            zoom_var.set(1.0)
            drag_data["offset_x"] = 0
            drag_data["offset_y"] = 0
            update_preview()

        reset_zoom_btn.configure(command=reset_zoom_and_offset)

        # 创建底部按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=10)

        # 添加分隔线
        separator = ttk.Separator(button_frame, orient="horizontal")
        separator.pack(fill="x", pady=5)

        def apply_tiled_watermark():
            if not self.current_image:
                messagebox.showerror("错误", "请先打开一张图片")
                return

            try:
                img_copy = self.current_image.copy()

                if watermark_type.get() == "text":
                    # 文字水印
                    text = watermark_text.get()
                    font = font_var.get()

                    # 获取字体实际路径
                    font_path = None
                    if hasattr(self, 'font_paths'):
                        print(f"应用水印 - 字体路径字典包含 {len(self.font_paths)} 个条目")
                        if font in self.font_paths:
                            font_path = self.font_paths[font]
                            print(f"应用水印 - 找到字体路径: {font_path}")
                        else:
                            print(f"应用水印 - 字体 '{font}' 不在字体路径字典中")
                            if self.font_paths:
                                fonts_list = list(self.font_paths.keys())
                                print(f"应用水印 - 可用字体(前5个): {fonts_list[:min(5, len(fonts_list))]}")
                                # 尝试不区分大小写匹配
                                lower_font = font.lower()
                                for f in fonts_list:
                                    if f.lower() == lower_font:
                                        font_path = self.font_paths[f]
                                        print(f"应用水印 - 找到不区分大小写的匹配: {f} -> {font_path}")
                                        break
                    else:
                        print("应用水印 - font_paths 属性不存在")

                    # 获取颜色
                    try:
                        from PIL import ImageColor
                        color = ImageColor.getcolor(color_var.get(), "RGB")
                        print(f"应用水印 - 颜色值: {color}")
                    except Exception as e:
                        print(f"应用水印 - 颜色解析错误: {e}")
                        color = (0, 0, 0)  # 默认黑色

                    # 其他水印参数
                    opacity = opacity_var.get()
                    tile_scale = tile_scale_var.get()
                    rotation = rotation_var.get()
                    spacing = spacing_var.get()
                    is_bold = bold_var.get()
                    is_italic = italic_var.get()

                    print(f"应用水印 - 参数: 不透明度={opacity}, 缩放={tile_scale}, 旋转={rotation}, 间距={spacing}")
                    print(f"应用水印 - 文本样式: 粗体={is_bold}, 斜体={is_italic}")

                    # 确保使用正确的字体路径
                    final_font = font_path if font_path else font
                    print(f"应用水印 - 最终使用的字体: {final_font}")

                    # 计算字体大小(根据预览中使用的24号字体和原图与预览图的比例)
                    # 获取原图大小
                    orig_width, orig_height = img_copy.size
                    print(f"应用水印 - 原图尺寸: {orig_width}x{orig_height}")

                    # 获取预览画布大小
                    canvas_width = preview_canvas.winfo_width()
                    canvas_height = preview_canvas.winfo_height()
                    print(f"应用水印 - 预览画布尺寸: {canvas_width}x{canvas_height}")

                    # 计算预览图的基础缩放比例
                    base_scale = min(canvas_width / orig_width, canvas_height / orig_height) * 0.9
                    preview_scale = base_scale * zoom_var.get()
                    print(f"应用水印 - 预览基础缩放比例: {base_scale}, 用户缩放因子: {zoom_var.get()}")

                    # 预览图大小
                    preview_width = int(orig_width * preview_scale)
                    preview_height = int(orig_height * preview_scale)
                    print(f"应用水印 - 预览图大小: {preview_width}x{preview_height}")

                    # 计算从预览到原图的比例因子
                    scale_factor = orig_width / preview_width if preview_width > 0 else 1
                    print(f"应用水印 - 预览到原图的比例因子: {scale_factor}")

                    # 计算实际应用时的字体大小
                    preview_font_size = 24  # 预览中使用的固定字体大小
                    actual_font_size = round(preview_font_size * scale_factor)
                    print(f"应用水印 - 预览字体大小: {preview_font_size}, 实际应用字体大小: {actual_font_size}")

                    # 调用平铺水印函数处理图像
                    from image_utils import add_tiled_watermark
                    self.current_image = add_tiled_watermark(
                        img_copy,
                        watermark_text=text,
                        opacity=opacity,
                        tile_scale=tile_scale,
                        rotation=rotation,
                        spacing=spacing,
                        font_name=final_font,
                        font_size=actual_font_size,  # 使用计算出的字体大小
                        font_color=color,
                        bold=is_bold,
                        italic=is_italic,
                        letter_spacing=0  # 保持与预览一致
                    )
                    self.display_image_on_canvas()
                    tiled_watermark_window.destroy()
                    self.status_bar.config(text=f"已添加防伪文字水印: {text}")
                    self.save_current_state()  # 保存历史记录

                else:
                    # 图片水印处理保持不变
                    if watermark_img_obj[0] is None:
                        messagebox.showerror("错误", "请选择水印图片")
                        return

                    from image_utils import add_tiled_watermark
                    self.current_image = add_tiled_watermark(
                        img_copy,
                        watermark_image=watermark_img_obj[0],
                        opacity=opacity_var.get(),
                        tile_scale=tile_scale_var.get(),
                        rotation=rotation_var.get(),
                        spacing=spacing_var.get()
                    )
                    self.display_image_on_canvas()
                    tiled_watermark_window.destroy()
                    self.status_bar.config(text=f"已添加防伪图片水印: {os.path.basename(watermark_image_path.get())}")
                    self.save_current_state()  # 保存历史记录

            except Exception as e:
                import traceback
                traceback.print_exc()
                messagebox.showerror("错误", f"添加水印失败: {str(e)}")

        ttk.Button(button_frame, text="取消", command=tiled_watermark_window.destroy).pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="应用水印", command=apply_tiled_watermark).pack(side=tk.RIGHT, padx=5)

        # 初始化界面
        update_type_ui()

        # 延迟执行预览,等待画布大小确定
        tiled_watermark_window.after(200, update_preview)

    def create_app_icon(self):
        """创建并设置应用图标"""
        try:
            from PIL import Image, ImageTk

            # 加载图标文件
            icon_image = Image.open('icon.png')

            # 转换为PhotoImage
            photo_image = ImageTk.PhotoImage(icon_image)

            # 设置为窗口图标
            self.root.iconphoto(True, photo_image)

            # 保存引用以防止垃圾回收
            self.icon_image = photo_image

        except Exception as e:
            print(f"创建图标时出错: {e}")
            # 如果创建图标失败,程序仍然可以继续运行

if __name__ == "__main__":
    root = tk.Tk()
    app = ImageProcessingApp(root)

    # 绑定快捷键
    root.bind("<Control-z>", app.undo)
    root.bind("<Control-y>", app.redo)
    root.bind("<Control-o>", lambda e: app.open_image())
    root.bind("<Control-s>", lambda e: app.save_image())

    # 响应窗口大小变化,重新调整图片显示
    def on_resize(event):
        if event.widget == root:
            app.display_image_on_canvas()

    root.bind("<Configure>", on_resize)
    root.mainloop()

希望这款工具能帮助到需要批量处理图片的朋友们!

微信截图_20250414002103.png

免费评分

参与人数 17威望 +1 吾爱币 +37 热心值 +16 收起 理由
caoyisheng + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
wdpjplc + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
hfol85 + 2 + 1 用心讨论,共获提升!
苏紫方璇 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
习惯大神 + 1 + 1 范围水印能不能加一个随机位置水印,满屏水印+随机位置。但是偏差不会太大 ...
huyufeng + 1 + 1 谢谢@Thanks!
施施乐 + 1 + 1 谢谢@Thanks!
yanglinman + 1 谢谢@Thanks!
YouEue + 1 + 1 学习一下
creazycar + 1 + 1 谢谢@Thanks!
lpwhd + 1 + 1 谢谢@Thanks!
yjn866y + 1 + 1 谢谢@Thanks!
愚无尽 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
walykyy + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zhuqiwen000 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
pyjiujiu + 1 用心讨论,共获提升!
深爱我的女孩 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

施施乐 发表于 2025-4-11 20:16
本帖最后由 施施乐 于 2025-4-11 20:46 编辑

挺好的工具,如果集成图片查看器就好了,以图片查看器作为主体,此工具作为查看器的附加功能(参考2345看图王),这样的话就完美了,工具就不会是下载来备用,而是取代其他的图片查看器,作为日常使用的工具了,希望作者可以考虑一下,谢谢分享。
建议增加:
1、裁剪模板建议增加常用的证件照尺寸
2、希望打开图片方式支持拖拽
3、图片压缩在格式转换内,不太直观,希望增加一个单项
4、调整大小希望增加图片压缩功能,很多网站上传的图片有像素限制,也有体积限制
5、支持格式希望继续增加,比如:svg
6、希望增加图片拼接,切割
7、希望增加简单编辑标注功能,比如:截图后的标注功能
目前就这些了,希望作者后面慢慢增加,慢慢完善,再次谢谢分享
施施乐 发表于 2025-4-14 12:33
nobiyou 发表于 2025-4-14 00:49
加了,基础就是查看图片的软件,再加上了图片的编辑处理功能。

谢谢,下载试了一下,你应该是理解错,或者是你没有使用过2345看图王,你可以试用一下2345的,把此工具格式关联,双击图片就可以查看,操作简单易用,这样用户就可以安装在电脑内,成为默认图片查看器(这是所有用户常用的功能,就避免用户下载来当库存备用了),需要编辑图片的时候,随时都能用,不用再满电脑找工具,甚至忘记有这么一个好工具。你现在加载的浏览模式,反而把编辑模式拖累了,不够直观,不够便捷。
我提建议是希望这个好工具让大家随手就可以使用,做大做强,也谢谢作者你采纳建议。
tjc1005 发表于 2025-4-11 15:22
深爱我的女孩 发表于 2025-4-11 15:22
强大处理图片软件,便于工作中处理繁琐的细节,感谢楼主提供,吾爱有你更精彩!
Whiteface 发表于 2025-4-11 15:22
给这个工程量赞一个
lyjm 发表于 2025-4-11 15:28
有用,大力支持
lyj0877 发表于 2025-4-11 15:29
支持!!!!!!!!!!!!
Lcp1027 发表于 2025-4-11 15:31
感谢UP主的分享
DaSheng520 发表于 2025-4-11 15:39

强大处理图片软件,便于工作中处理繁琐的细节,感谢楼主提供,吾爱有你更精彩!
cn52zc 发表于 2025-4-11 15:41
有些图片处理软件确实用得觉得复杂
清淡如风 发表于 2025-4-11 15:44
4000多行的代码,挺不容易的,支持一下!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-23 08:21

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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