跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python算法

Python 像素级图片编辑工具:支持高倍缩放与批量颜色替换

Python 基于 tkinter 开发的像素级图片编辑工具。支持图片加载、画笔绘制、颜色提取及批量替换功能。具备滚轮缩放(最高 500 倍)、网格显示、取色器预览等特性。可统计图片颜色分布,支持多选颜色替换或设为透明。适用于 Windows、macOS、Linux 系统,需 Python 3.7+ 环境。

虚拟内存发布于 2026/3/16更新于 2026/5/3129 浏览

效果预览

文章配图

功能说明

一个基于 Python 和 tkinter 的像素级图片编辑工具,支持图片加载、画笔绘制、颜色提取、批量替换等功能。

功能特性

基础功能
  • 加载图片 - 支持按钮选择或拖拽导入
  • 保存图片 - 支持 PNG、JPEG 格式导出
  • 缩放查看 - 滚轮缩放,最高支持 500 倍放大
  • 平移移动 - 左键拖动图片(默认)
  • 画笔绘制 - 像素级精确编辑
  • 网格显示 - 高倍缩放时显示像素网格
画笔工具
  • 画笔大小 - 1-50px 可调
  • 颜色选择 - 内置颜色选择器
  • 橡皮擦 - 快速擦除为白色
颜色管理
  • 颜色列表 - 统计图片中所有颜色及像素数量
  • 取色器 - 点击获取像素颜色
  • 复制代码 - 复制 HEX/RGB 颜色代码
  • 高亮显示 - 选中颜色的像素高亮显示(默认开启)
批量操作
  • 多选颜色 - Ctrl/点击多选
  • 全选/清空/反选 - 快捷选择操作
  • 批量替换 - 支持三种替换方式:
    • 选择颜色替换
    • 替换为取色器颜色
    • 设置为透明

安装

依赖
pip install -r requirements.txt 
requirements.txt
Pillow>=10.0.0 tkinterdnd2>=0.3.0 

使用方法

启动程序
python image_editor.py 
界面布局
┌─────────────────────────────────────────────────────────────┐
│ 📁 加载 💾 保存 │ 画笔控制 │ ✋移动 🖌️画笔 💉取色 │ 适应 100% │ 🔄重置 │
├─────────────────────────────────┬───────────────────────────┤
│                                 │                           │
│ 📊 颜色列表                     │                           │
│ ┌─────────────────────┐         │                           │
│ 图片显示区域            │         │ 颜色 1 (1200px)           │
│                         │         │ 颜色 2 (800px)            │
│                         │         │ ...                       │
│ └─────────────────────┘         │                           │
│                                 │                           │
│ 🔲 取色器预览                   │                           │
│ 全选 清空 反选                  │                           │
│ ☑ 高亮选中颜色                  │                           │
│ 🎨 选择颜色替换                 │                           │
│ 💉 替换为取色                   │                           │
│ 🔳 设置为透明                   │                           │
├─────────────────────────────────┴───────────────────────────┤
│ 状态栏:请加载图片 | 滚轮缩放 | 左键拖动 | 点击画笔按钮绘制   │
└─────────────────────────────────────────────────────────────┘
工具模式
工具光标功能
✋ 移动手型左键拖动图片(默认)
🖌️ 画笔十字左键绘制像素
💉 取色十字点击获取像素颜色
操作示例
像素级编辑
  1. 加载图片
  2. 点击 🖌️ 画笔 切换到绘制模式
  3. 调整画笔大小和颜色
  4. 在图片上拖动绘制
提取颜色
  1. 点击 💉 取色 切换到取色模式
  2. 点击图片上的任意像素
  3. 预览区显示颜色信息
  4. 点击 使用此颜色 或 📋 复制代码
批量替换颜色
  1. 在颜色列表中选择要替换的颜色(支持多选)
  2. 点击 💉 替换为取色 使用取色器颜色替换
  3. 或点击 🎨 选择颜色替换 弹窗选择新颜色
去除背景
  1. 在颜色列表中选中背景色
  2. 点击 🔳 设置为透明
  3. 保存为 PNG 格式
快捷操作
  • 右键拖动 - 任何模式下都可以平移图片
  • 滚轮 - 缩放图片
  • Ctrl+点击 - 多选颜色(颜色列表)

颜色代码格式

复制颜色代码后,剪贴板内容:

#7C7074 rgb(124, 112, 116) 

可用于:

  • CSS: background-color: #7C7074;
  • HTML: <span>
  • Python: (124, 112, 116)

文件结构

修图/
├── image_editor.py # 主程序
├── requirements.txt # 依赖列表
└── README.md # 说明文档

注意事项

  1. 透明度支持 - 保存为 PNG 格式可保留透明通道
  2. 大图片 - 高倍缩放时使用最近邻插值保持像素清晰
  3. 性能优化 - 编辑大图片时建议使用"适应"视图

系统要求

  • Python 3.7+
  • 支持 Windows、macOS、Linux

核心代码实现

""" 简易图片修图工具
功能:加载图片、选择画笔大小和颜色、鼠标拖动修改像素
"""
import tkinter as tk
from tkinter import filedialog, colorchooser
from tkinter import ttk
from PIL import Image, ImageTk, ImageDraw
import os

class ImageEditor:
    def __init__(self, root):
        self.root = root
        self.root.title("图片修图工具")
        self.root.geometry("1200x800")
        
        # 图片相关变量
        self.original_image = None
        self.display_image = None
        self.photo = None
        self.image_path = None
        self.zoom = 1.0
        self.offset_x = 0
        self.offset_y = 0
        
        # 画笔相关变量
        self.brush_size = 1  # 默认 1 像素,便于精细编辑
        self.brush_color = "#FF0000"
        self.is_drawing = False
        self.last_x = None
        self.last_y = None
        
        # 平移相关变量(右键拖动)
        self.is_panning = False
        self.pan_start_x = None
        self.pan_start_y = None
        self.pan_offset_x = None
        self.pan_offset_y = None
        
        # 工具模式:'pan' 移动 (默认), 'brush' 画笔,'picker' 取色器
        self.tool_mode = 'pan'
        
        # 取色器相关
        self.picked_color = None  # 最近取的颜色
        self.is_picking = False  # 是否正在取色
        
        # 像素网格显示
        self.show_grid = False
        
        # 颜色列表相关
        self.color_stats = {}  # 颜色统计 {颜色 RGB: 像素数量}
        self.selected_colors = []  # 当前选中的颜色列表
        self.highlight_colors = ["#00FF00", "#FF0000", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FF8800", "#8800FF"]
        self.show_highlight = True  # 是否显示高亮(默认开启)
        
        # 创建界面(必须在 setup_drag_drop 之前)
        self.create_ui()
        # 拖放支持(需要在 canvas 创建后)
        self.drag_drop_enabled = False
        self.setup_drag_drop()

    def setup_drag_drop(self):
        """设置拖放功能"""
        try:
            from tkinterdnd2 import DND_FILES
            # 注册画布为拖放目标
            self.canvas.drop_target_register(DND_FILES)
            self.canvas.dnd_bind('<<Drop>>', self.on_drop)
            self.canvas.dnd_bind('<<DragEnter>>', self.on_drag_enter)
            self.drag_drop_enabled = True
        except Exception as e:
            # 拖放不可用,程序仍可正常运行
            self.drag_drop_enabled = False
            print(f"拖放功能不可用:{e}")

    def create_ui(self):
        # 顶部控制面板
        control_frame = tk.Frame(self.root, bg="#f0f0f0", height=80)
        control_frame.pack(side=tk.TOP, fill=tk.X)
        control_frame.pack_propagate(False)
        
        # 左侧按钮区域
        btn_frame = tk.Frame(control_frame, bg="#f0f0f0")
        btn_frame.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 加载图片按钮
        load_btn = tk.Button(
            btn_frame, text="📁 加载图片", command=self.load_image, font=("Microsoft YaHei", 10), bg="#4a90d9", fg="white", width=12, height=2
        )
        load_btn.pack(side=tk.LEFT, padx=5)
        
        # 保存图片按钮
        save_btn = tk.Button(
            btn_frame, text="💾 保存图片", command=self.save_image, font=("Microsoft YaHei", 10), bg="#50c878", fg="white", width=12, height=2
        )
        save_btn.pack(side=tk.LEFT, padx=5)
        
        # 中间画笔控制区域
        brush_frame = tk.Frame(control_frame, bg="#f0f0f0")
        brush_frame.pack(side=tk.LEFT, padx=30, pady=10)
        
        # 画笔大小
        tk.Label(
            brush_frame, text="画笔大小:", bg="#f0f0f0", font=("Microsoft YaHei", 10)
        ).pack(side=tk.LEFT, padx=5)
        self.size_var = tk.IntVar(value=1)
        size_scale = tk.Scale(
            brush_frame, from_=1, to=50, orient=tk.HORIZONTAL, variable=self.size_var, command=self.update_brush_size, bg="#f0f0f0", length=150
        )
        size_scale.pack(side=tk.LEFT, padx=5)
        
        # 当前画笔大小显示
        self.size_label = tk.Label(
            brush_frame, text=f"{self.brush_size}px", bg="#f0f0f0", font=("Microsoft YaHei", 10), width=5
        )
        self.size_label.pack(side=tk.LEFT, padx=5)
        
        # 颜色选择
        tk.Label(
            brush_frame, text="画笔颜色:", bg="#f0f0f0", font=("Microsoft YaHei", 10)
        ).pack(side=tk.LEFT, padx=(20, 5))
        self.color_btn = tk.Button(
            brush_frame, bg=self.brush_color, command=self.choose_color, width=4, height=1, relief=tk.RAISED
        )
        self.color_btn.pack(side=tk.LEFT, padx=5)
        
        # 橡皮擦按钮
        eraser_btn = tk.Button(
            brush_frame, text="🧽 橡皮擦", command=self.use_eraser, font=("Microsoft YaHei", 10), bg="#ff9500", fg="white", width=10, height=1
        )
        eraser_btn.pack(side=tk.LEFT, padx=10)
        
        # 工具切换按钮
        tool_frame = tk.Frame(brush_frame, bg="#f0f0f0")
        tool_frame.pack(side=tk.LEFT, padx=10)
        self.pan_tool_btn = tk.Button(
            tool_frame, text="✋ 移动", command=lambda: self.set_tool('pan'), font=("Microsoft YaHei", 10), bg="#4CAF50", fg="white", width=8, height=1
        )
        self.pan_tool_btn.pack(side=tk.LEFT, padx=2)
        self.brush_tool_btn = tk.Button(
            tool_frame, text="🖌️ 画笔", command=lambda: self.set_tool('brush'), font=("Microsoft YaHei", 10), bg="#95a5a6", fg="white", width=8, height=1
        )
        self.brush_tool_btn.pack(side=tk.LEFT, padx=2)
        self.picker_tool_btn = tk.Button(
            tool_frame, text="💉 取色", command=lambda: self.set_tool('picker'), font=("Microsoft YaHei", 10), bg="#95a5a6", fg="white", width=8, height=1
        )
        self.picker_tool_btn.pack(side=tk.LEFT, padx=2)
        
        # 像素网格开关
        grid_btn = tk.Button(
            brush_frame, text="⚏ 网格", command=self.toggle_grid, font=("Microsoft YaHei", 10), bg="#9b59b6", fg="white", width=8, height=1
        )
        grid_btn.pack(side=tk.LEFT, padx=5)
        
        # 右侧工具区域
        right_tool_frame = tk.Frame(control_frame, bg="#f0f0f0")
        right_tool_frame.pack(side=tk.RIGHT, padx=10, pady=10)
        
        # 缩放控制
        zoom_frame = tk.Frame(right_tool_frame, bg="#f0f0f0")
        zoom_frame.pack(side=tk.LEFT, padx=10)
        tk.Label(
            zoom_frame, text="缩放:", bg="#f0f0f0", font=("Microsoft YaHei", 9)
        ).pack(side=tk.LEFT, padx=2)
        zoom_fit_btn = tk.Button(
            zoom_frame, text="适应", command=self.zoom_fit, font=("Microsoft YaHei", 8), bg="#3498db", fg="white", width=6, height=1
        )
        zoom_fit_btn.pack(side=tk.LEFT, padx=2)
        zoom_100_btn = tk.Button(
            zoom_frame, text="100%", command=self.zoom_100, font=("Microsoft YaHei", 8), bg="#3498db", fg="white", width=6, height=1
        )
        zoom_100_btn.pack(side=tk.LEFT, padx=2)
        
        # 清空按钮
        clear_btn = tk.Button(
            right_tool_frame, text="🔄 重置图片", command=self.reset_image, font=("Microsoft YaHei", 10), bg="#e74c3c", fg="white", width=10, height=2
        )
        clear_btn.pack(side=tk.LEFT, padx=5)
        
        # 主画布区域
        canvas_frame = tk.Frame(self.root, bg="#ffffff")
        canvas_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        
        # 画布
        self.canvas = tk.Canvas(
            canvas_frame, bg="#ffffff", highlightthickness=0, cursor="fleur"  # 默认移动工具,手型光标
        )
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # 右侧颜色列表面板
        color_panel = tk.Frame(canvas_frame, bg="#f5f5f5", width=250)
        color_panel.pack(side=tk.RIGHT, fill=tk.Y)
        color_panel.pack_propagate(False)
        
        # 颜色列表面板标题
        tk.Label(
            color_panel, text="📊 颜色列表", bg="#f5f5f5", font=("Microsoft YaHei", 12, "bold")
        ).pack(side=tk.TOP, pady=10)
        
        # 统计信息
        self.stats_label = tk.Label(
            color_panel, text="加载图片后显示", bg="#f5f5f5", font=("Microsoft YaHei", 9), justify=tk.LEFT
        )
        self.stats_label.pack(side=tk.TOP, pady=5, padx=10, anchor=tk.W)
        
        # 刷新颜色列表按钮
        refresh_btn = tk.Button(
            color_panel, text="🔄 刷新颜色", command=self.refresh_color_list, font=("Microsoft YaHei", 9), bg="#3498db", fg="white", width=15
        )
        refresh_btn.pack(side=tk.TOP, pady=5)
        
        # 颜色列表容器(带滚动条)
        list_frame = tk.Frame(color_panel, bg="#f5f5f5")
        list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 滚动条
        scrollbar = tk.Scrollbar(list_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 颜色列表(支持多选)
        self.color_listbox = tk.Listbox(
            list_frame, bg="white", font=("Microsoft YaHei", 9), yscrollcommand=scrollbar.set, height=20, selectmode=tk.MULTIPLE  # 支持多选
        )
        self.color_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=self.color_listbox.yview)
        
        # 绑定选择事件
        self.color_listbox.bind('<<ListboxSelect>>', self.on_color_select)
        
        # 取色器预览区域
        picker_frame = tk.Frame(color_panel, bg="#f5f5f5")
        picker_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5, padx=5)
        tk.Label(
            picker_frame, text="💉 取色器预览:", bg="#f5f5f5", font=("Microsoft YaHei", 9)
        ).pack(side=tk.TOP, anchor=tk.W)
        
        # 颜色预览容器
        preview_container = tk.Frame(picker_frame, bg="#ffffff", relief=tk.SUNKEN, borderwidth=1)
        preview_container.pack(side=tk.TOP, fill=tk.X, pady=3)
        
        # 颜色预览块
        self.picked_color_preview = tk.Label(
            preview_container, bg="#ffffff", width=5, height=2
        )
        self.picked_color_preview.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 颜色信息显示
        self.picked_color_info = tk.Label(
            preview_container, text="点击取色后\n点击图片获取颜色", bg="#ffffff", font=("Consolas", 9), justify=tk.LEFT, anchor=tk.W
        )
        self.picked_color_info.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
        
        # 按钮容器
        picker_btn_frame = tk.Frame(picker_frame, bg="#f5f5f5")
        picker_btn_frame.pack(side=tk.TOP, pady=3)
        
        # 使用取色按钮
        use_picked_btn = tk.Button(
            picker_btn_frame, text="使用此颜色", command=self.use_picked_color, font=("Microsoft YaHei", 8), bg="#3498db", fg="white", width=12
        )
        use_picked_btn.pack(side=tk.LEFT, padx=2)
        
        # 复制颜色代码按钮
        copy_color_btn = tk.Button(
            picker_btn_frame, text="📋 复制代码", command=self.copy_picked_color_code, font=("Microsoft YaHei", 8), bg="#27ae60", fg="white", width=10
        )
        copy_color_btn.pack(side=tk.LEFT, padx=2)
        
        # 操作按钮区域
        btn_frame = tk.Frame(color_panel, bg="#f5f5f5")
        btn_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=10, padx=5)
        
        # 选择控制按钮
        select_frame = tk.Frame(btn_frame, bg="#f5f5f5")
        select_frame.pack(side=tk.TOP, pady=2)
        tk.Button(
            select_frame, text="全选", command=self.select_all_colors, font=("Microsoft YaHei", 8), bg="#95a5a6", fg="white", width=5
        ).pack(side=tk.LEFT, padx=1)
        tk.Button(
            select_frame, text="清空", command=self.clear_color_selection, font=("Microsoft YaHei", 8), bg="#95a5a6", fg="white", width=5
        ).pack(side=tk.LEFT, padx=1)
        tk.Button(
            select_frame, text="反选", command=self.invert_color_selection, font=("Microsoft YaHei", 8), bg="#95a5a6", fg="white", width=5
        ).pack(side=tk.LEFT, padx=1)
        
        # 高亮开关
        self.highlight_var = tk.BooleanVar(value=True)  # 默认勾选
        highlight_check = tk.Checkbutton(
            btn_frame, text="高亮选中颜色", variable=self.highlight_var, command=self.toggle_highlight, bg="#f5f5f5", font=("Microsoft YaHei", 9)
        )
        highlight_check.pack(side=tk.TOP, pady=2)
        
        # 替换按钮组
        replace_group_frame = tk.Frame(btn_frame, bg="#f5f5f5")
        replace_group_frame.pack(side=tk.TOP, pady=5)
        
        # 替换颜色按钮
        replace_btn = tk.Button(
            replace_group_frame, text="🎨 选择颜色替换", command=self.replace_color, font=("Microsoft YaHei", 9), bg="#e67e22", fg="white", width=18
        )
        replace_btn.pack(side=tk.TOP, pady=2)
        
        # 替换为取色器颜色
        replace_picked_btn = tk.Button(
            replace_group_frame, text="💉 替换为取色", command=self.replace_with_picked_color, font=("Microsoft YaHei", 9), bg="#9b59b6", fg="white", width=18
        )
        replace_picked_btn.pack(side=tk.TOP, pady=2)
        
        # 设置为透明按钮
        transparent_btn = tk.Button(
            replace_group_frame, text="🔳 设置为透明", command=self.replace_with_transparent, font=("Microsoft YaHei", 9), bg="#34495e", fg="white", width=18
        )
        transparent_btn.pack(side=tk.TOP, pady=2)
        
        # 绑定鼠标事件
        self.canvas.bind("<Button-1>", self.on_mouse_down)
        self.canvas.bind("<B1-Motion>", self.on_mouse_drag)
        self.canvas.bind("<ButtonRelease-1>", self.on_mouse_up)
        
        # 中键/右键拖动平移(在任何工具模式下都可用)
        self.canvas.bind("<Button-2>", self.on_pan_start)
        self.canvas.bind("<Button-3>", self.on_pan_start)
        self.canvas.bind("<B2-Motion>", self.on_pan_drag)
        self.canvas.bind("<B3-Motion>", self.on_pan_drag)
        self.canvas.bind("<ButtonRelease-2>", self.on_pan_end)
        self.canvas.bind("<ButtonRelease-3>", self.on_pan_end)
        
        # 滚轮缩放
        self.canvas.bind("<MouseWheel>", self.on_zoom)
        self.canvas.bind("<Button-4>", self.on_zoom)
        self.canvas.bind("<Button-5>", self.on_zoom)
        
        # 底部状态栏
        status_frame = tk.Frame(self.root, bg="#e0e0e0", height=30)
        status_frame.pack(side=tk.BOTTOM, fill=tk.X)
        status_frame.pack_propagate(False)
        self.status_label = tk.Label(
            status_frame, text="请加载图片 | 滚轮缩放 | 左键拖动 | 点击画笔按钮绘制 | 右键也可拖动", bg="#e0e0e0", font=("Microsoft YaHei", 9), anchor=tk.W
        )
        self.status_label.pack(side=tk.LEFT, padx=10)
        
        # 图片尺寸显示
        self.size_info_label = tk.Label(
            status_frame, bg="#e0e0e0", font=("Microsoft YaHei", 9)
        )
        self.size_info_label.pack(side=tk.RIGHT, padx=10)
        
        # 初始提示
        self.show_placeholder()

    def show_placeholder(self):
        """显示占位提示"""
        self.canvas.delete("all")
        self.canvas.create_text(
            self.canvas.winfo_width() // 2 if self.canvas.winfo_width() > 1 else 400,
            self.canvas.winfo_height() // 2 if self.canvas.winfo_height() > 1 else 300,
            text="📁 点击上方按钮加载图片",
            fill="#666666",
            font=("Microsoft YaHei", 16),
            tags="placeholder"
        )

    def toggle_grid(self):
        """切换像素网格显示"""
        self.show_grid = not self.show_grid
        if self.display_image:
            self.display_image_on_canvas()
            self.status_label.config(text="像素网格已" + ("开启" if self.show_grid else "关闭"))

    def on_pan_start(self, event):
        """中键/右键平移开始"""
        if self.display_image is None:
            return
        self.is_panning = True
        self.pan_start_x = event.x
        self.pan_start_y = event.y
        self.pan_offset_x = self.offset_x
        self.pan_offset_y = self.offset_y
        self.canvas.config(cursor="fleur")

    def on_pan_drag(self, event):
        """中键/右键平移拖动"""
        if not self.is_panning or self.display_image is None:
            return
        dx = event.x - self.pan_start_x
        dy = event.y - self.pan_start_y
        self.offset_x = self.pan_offset_x + dx
        self.offset_y = self.pan_offset_y + dy
        self.display_image_on_canvas()

    def on_pan_end(self, event):
        """中键/右键平移结束"""
        self.is_panning = False
        self.canvas.config(cursor="crosshair")

    def zoom_fit(self):
        """适应窗口大小"""
        if self.display_image is None:
            return
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()
        if canvas_width > 1 and canvas_height > 1:
            img_width, img_height = self.display_image.size
            scale_x = (canvas_width - 40) / img_width
            scale_y = (canvas_height - 40) / img_height
            self.zoom = min(scale_x, scale_y, 1.0)
        else:
            self.zoom = 1.0
        self.offset_x = 0
        self.offset_y = 0
        self.display_image_on_canvas()
        self.size_info_label.config(
            text=f"{self.display_image.size[0]} x {self.display_image.size[1]} px | 缩放:{int(self.zoom * 100)}%"
        )

    def zoom_100(self):
        """缩放到 100%"""
        if self.display_image is None:
            return
        self.zoom = 1.0
        # 居中显示
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()
        display_width = self.display_image.width
        display_height = self.display_image.height
        if canvas_width > 1 and canvas_height > 1:
            self.offset_x = (canvas_width - display_width) // 2
            self.offset_y = (canvas_height - display_height) // 2
        else:
            self.offset_x = 10
            self.offset_y = 10
        self.display_image_on_canvas()
        self.size_info_label.config(
            text=f"{self.display_image.size[0]} x {self.display_image.size[1]} px | 缩放:100%"
        )

    def load_image(self):
        """加载图片"""
        file_path = filedialog.askopenfilename(
            title="选择图片",
            filetypes=[
                ("图片文件", "*.png *.jpg *.jpeg *.bmp *.gif *.tiff *.webp"),
                ("所有文件", "*.*")
            ]
        )
        if file_path:
            self.open_image(file_path)

    def open_image(self, file_path):
        """打开图片"""
        try:
            self.image_path = file_path
            img = Image.open(file_path)
            # 转换为 RGBA 以支持透明背景
            if img.mode != "RGBA":
                self.original_image = img.convert("RGBA")
            else:
                self.original_image = img.copy()
            # 如果有透明通道,在白色背景上合成
            if self.original_image.mode == "RGBA":
                # 创建白色背景
                white_bg = Image.new("RGB", self.original_image.size, (255, 255, 255))
                # 合成到白色背景上
                white_bg.paste(self.original_image, mask=self.original_image.split()[3])
                # 转换回 RGBA 以保持编辑能力
                self.display_image = white_bg.convert("RGBA")
            else:
                self.display_image = self.original_image.copy()
            
            # 自动适应窗口大小
            canvas_width = self.canvas.winfo_width()
            canvas_height = self.canvas.winfo_height()
            if canvas_width > 1 and canvas_height > 1:
                img_width, img_height = self.display_image.size
                scale_x = (canvas_width - 40) / img_width
                scale_y = (canvas_height - 40) / img_height
                self.zoom = min(scale_x, scale_y, 1.0)
            else:
                self.zoom = 1.0
            self.offset_x = 0
            self.offset_y = 0
            self.display_image_on_canvas()
            
            # 自动刷新颜色列表
            self.refresh_color_list()
            
            # 更新状态栏
            self.status_label.config(text=f"已加载:{os.path.basename(file_path)}")
            self.size_info_label.config(
                text=f"{self.display_image.size[0]} x {self.display_image.size[1]} px | 缩放:{int(self.zoom * 100)}%"
            )
        except Exception as e:
            self.status_label.config(text=f"加载失败:{str(e)}")

    def display_image_on_canvas(self):
        """在画布上显示图片"""
        if self.display_image is None:
            return
        self.canvas.delete("all")
        
        # 计算显示大小
        display_width = int(self.display_image.width * self.zoom)
        display_height = int(self.display_image.height * self.zoom)
        
        # 缩放图片用于显示
        if self.zoom != 1.0:
            # 当放大倍数很高时,使用最近邻插值以保持像素清晰
            if self.zoom >= 10.0:
                resized = self.display_image.resize((display_width, display_height), Image.Resampling.NEAREST)
            else:
                resized = self.display_image.resize((display_width, display_height), Image.Resampling.LANCZOS)
        else:
            resized = self.display_image
        
        # 确保显示在白色背景上(对于有透明通道的图片)
        if resized.mode == "RGBA":
            # 创建白色背景
            white_bg = Image.new("RGB", resized.size, (255, 255, 255))
            white_bg.paste(resized, mask=resized.split()[3])
            resized = white_bg
        
        self.photo = ImageTk.PhotoImage(resized)
        
        # 计算居中位置(如果偏移未设置)
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()
        if canvas_width > 1 and canvas_height > 1:
            # 只有在偏移为 0 时才居中
            if self.offset_x == 0 and self.offset_y == 0:
                self.offset_x = (canvas_width - display_width) // 2
                self.offset_y = (canvas_height - display_height) // 2
            else:
                if self.offset_x == 0 and self.offset_y == 0:
                    self.offset_x = 10
                    self.offset_y = 10
        
        # 显示图片
        self.canvas.create_image(
            self.offset_x, self.offset_y, anchor=tk.NW, image=self.photo, tags="image"
        )
        
        # 绘制像素网格
        if self.show_grid and self.zoom >= 2.0:
            self.draw_pixel_grid()
        
        # 绘制高亮(如果启用)- 在网格之上
        if self.highlight_var.get() and self.selected_colors:
            self.draw_color_highlight()

    def draw_color_highlight(self):
        """绘制选中颜色的高亮(支持多色)"""
        if self.display_image is None or not self.selected_colors:
            return
        width, height = self.display_image.size
        pixel_size = max(1, self.zoom)
        
        # 创建颜色到高亮色的映射
        color_to_highlight = {}
        for i, color in enumerate(self.selected_colors):
            highlight_color = self.highlight_colors[i % len(self.highlight_colors)]
            color_to_highlight[color] = highlight_color
        
        # 遍历所有像素,找到匹配的颜色并高亮
        for y in range(height):
            for x in range(width):
                pixel_color = self.display_image.getpixel((x, y))
                if isinstance(pixel_color, int):
                    pixel_color = (pixel_color, pixel_color, pixel_color)
                pixel_color = pixel_color[:3]
                if pixel_color in color_to_highlight:
                    # 计算画布坐标
                    canvas_x = self.offset_x + x * pixel_size
                    canvas_y = self.offset_y + y * pixel_size
                    # 获取该颜色对应的高亮色
                    highlight_color = color_to_highlight[pixel_color]
                    # 绘制高亮边框
                    self.canvas.create_rectangle(
                        canvas_x, canvas_y, canvas_x + pixel_size, canvas_y + pixel_size,
                        outline=highlight_color, width=max(1, int(pixel_size / 8)), tags="highlight"
                    )

    def draw_pixel_grid(self):
        """绘制像素网格"""
        if self.display_image is None:
            return
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()
        
        # 计算网格间距
        grid_spacing = self.zoom
        
        # 绘制垂直线
        for x in range(self.display_image.width + 1):
            canvas_x = self.offset_x + x * grid_spacing
            if canvas_x >= 0 and canvas_x <= canvas_width:
                self.canvas.create_line(
                    canvas_x, self.offset_y, canvas_x, self.offset_y + self.display_image.height * self.zoom,
                    fill="#cccccc", width=1, tags="grid"
                )
        
        # 绘制水平线
        for y in range(self.display_image.height + 1):
            canvas_y = self.offset_y + y * grid_spacing
            if canvas_y >= 0 and canvas_y <= canvas_height:
                self.canvas.create_line(
                    self.offset_x, canvas_y, self.offset_x + self.display_image.width * self.zoom, canvas_y,
                    fill="#cccccc", width=1, tags="grid"
                )

    def update_brush_size(self, value):
        """更新画笔大小"""
        self.brush_size = int(value)
        self.size_label.config(text=f"{self.brush_size}px")

    def choose_color(self):
        """选择颜色"""
        color = colorchooser.askcolor(self.brush_color)[1]
        if color:
            self.brush_color = color
            self.color_btn.config(bg=color)

    def use_eraser(self):
        """使用橡皮擦(白色)"""
        self.brush_color = "#FFFFFF"
        self.color_btn.config(bg="#FFFFFF")
        self.status_label.config(text="已切换到橡皮擦模式")

    def reset_image(self):
        """重置图片"""
        if self.original_image:
            self.display_image = self.original_image.copy()
            self.display_image_on_canvas()
            self.status_label.config(text="图片已重置")

    def pick_color_at(self, canvas_x, canvas_y):
        """在指定位置取色"""
        # 转换画布坐标到图片坐标
        img_x = int((canvas_x - self.offset_x) / self.zoom)
        img_y = int((canvas_y - self.offset_y) / self.zoom)
        
        # 检查边界
        if (img_x < 0 or img_x >= self.display_image.width or img_y < 0 or img_y >= self.display_image.height):
            return
        
        # 获取像素颜色
        color = self.display_image.getpixel((img_x, img_y))
        if isinstance(color, int):
            color = (color, color, color)
        color_rgb = color[:3]  # 只取 RGB
        
        # 保存取色结果
        self.picked_color = color_rgb
        
        # 转换为十六进制
        hex_color = f"#{color_rgb[0]:02X}{color_rgb[1]:02X}{color_rgb[2]:02X}"
        
        # 更新预览
        self.picked_color_preview.config(bg=hex_color)
        self.picked_color_info.config(
            text=f"RGB({color_rgb[0]}, {color_rgb[1]}, {color_rgb[2]})\nHEX: {hex_color}\n坐标:({img_x}, {img_y})",
            fg="black"
        )
        
        # 更新状态栏
        self.status_label.config(text=f"已取色:RGB({color_rgb[0]}, {color_rgb[1]}, {color_rgb[2]}) - 点击'使用此颜色'应用")

    def use_picked_color(self):
        """使用取色器获取的颜色作为画笔颜色"""
        if self.picked_color is None:
            self.status_label.config(text="请先使用取色器获取颜色")
            return
        
        # 转换为十六进制
        hex_color = f"#{self.picked_color[0]:02X}{self.picked_color[1]:02X}{self.picked_color[2]:02X}"
        
        # 设置为画笔颜色
        self.brush_color = hex_color
        self.color_btn.config(bg=hex_color)
        
        # 自动切换到画笔工具
        self.set_tool('brush')
        self.status_label.config(text=f"已应用取色:RGB({self.picked_color[0]}, {self.picked_color[1]}, {self.picked_color[2]})")

    def copy_picked_color_code(self):
        """复制取色器颜色代码到剪贴板"""
        if self.picked_color is None:
            self.status_label.config(text="请先使用取色器获取颜色")
            return
        
        # 准备颜色代码(简化格式,只保留一个 HEX 和一个 RGB)
        r, g, b = self.picked_color
        hex_color = f"#{r:02X}{g:02X}{b:02X}"
        rgb_format = f"rgb({r}, {g}, {b})"
        
        # 组合格式,用空格分隔
        color_codes = f"{hex_color} {rgb_format}"
        
        # 复制到剪贴板
        self.root.clipboard_clear()
        self.root.clipboard_append(color_codes)
        self.status_label.config(text=f"已复制:{hex_color} {rgb_format}")

    def set_tool(self, tool):
        """切换工具模式"""
        self.tool_mode = tool
        
        # 重置所有按钮为灰色
        self.pan_tool_btn.config(bg="#95a5a6")
        self.brush_tool_btn.config(bg="#95a5a6")
        if hasattr(self, 'picker_tool_btn'):
            self.picker_tool_btn.config(bg="#95a5a6")
        
        if tool == 'brush':
            self.brush_tool_btn.config(bg="#4CAF50")  # 绿色激活
            self.canvas.config(cursor="crosshair")
            self.status_label.config(text="已切换到画笔工具 - 点击拖动绘制")
        elif tool == 'pan':
            self.pan_tool_btn.config(bg="#4CAF50")  # 绿色激活
            self.canvas.config(cursor="fleur")
            self.status_label.config(text="已切换到移动工具 - 拖动移动图片")
        elif tool == 'picker':
            self.picker_tool_btn.config(bg="#4CAF50")  # 绿色激活
            self.canvas.config(cursor="crosshair")
            # 取色器也用十字光标
            self.status_label.config(text="已切换到取色器 - 点击图片获取颜色")

    def on_mouse_down(self, event):
        """左键按下 - 根据工具模式执行不同操作"""
        if self.display_image is None:
            return
        
        if self.tool_mode == 'pan':
            # 移动模式 - 开始平移
            self.is_panning = True
            self.pan_start_x = event.x
            self.pan_start_y = event.y
            self.pan_offset_x = self.offset_x
            self.pan_offset_y = self.offset_y
        elif self.tool_mode == 'picker':
            # 取色器模式 - 获取颜色
            self.pick_color_at(event.x, event.y)
        else:
            # 画笔模式 - 开始绘制
            self.is_drawing = True
            self.last_x = event.x
            self.last_y = event.y
            self.draw_at(event.x, event.y)

    def on_mouse_drag(self, event):
        """左键拖动 - 根据工具模式执行不同操作"""
        if self.display_image is None:
            return
        
        if self.tool_mode == 'pan' and self.is_panning:
            # 移动模式 - 平移图片
            dx = event.x - self.pan_start_x
            dy = event.y - self.pan_start_y
            self.offset_x = self.pan_offset_x + dx
            self.offset_y = self.pan_offset_y + dy
            self.display_image_on_canvas()
        elif self.tool_mode == 'brush' and self.is_drawing:
            # 画笔模式 - 绘制
            self.draw_line(self.last_x, self.last_y, event.x, event.y)
            self.last_x = event.x
            self.last_y = event.y

    def on_mouse_up(self, event):
        """左键释放 - 结束当前操作"""
        if self.tool_mode == 'pan':
            self.is_panning = False
        else:
            self.is_drawing = False
            self.last_x = None
            self.last_y = None
        
        # 绘制结束后,清理临时绘制标记并更新完整显示
        if self.display_image and self.zoom >= 5.0:
            self.canvas.delete("paint")
            self.display_image_on_canvas()

    def update_image_display_region(self, img_x, img_y, brush_size):
        """更新图片显示的特定区域(用于高倍放大时的实时反馈)"""
        # 对于高倍放大,直接重新显示整个图片以确保像素清晰
        self.canvas.delete("image")
        display_width = int(self.display_image.width * self.zoom)
        display_height = int(self.display_image.height * self.zoom)
        
        # 使用最近邻插值保持像素清晰
        resized = self.display_image.resize((display_width, display_height), Image.Resampling.NEAREST)
        
        # 确保显示在白色背景上(对于有透明通道的图片)
        if resized.mode == "RGBA":
            white_bg = Image.new("RGB", resized.size, (255, 255, 255))
            white_bg.paste(resized, mask=resized.split()[3])
            resized = white_bg
        
        self.photo = ImageTk.PhotoImage(resized)
        self.canvas.create_image(
            self.offset_x, self.offset_y, anchor=tk.NW, image=self.photo, tags="image"
        )
        
        # 重新绘制网格
        if self.show_grid:
            self.canvas.delete("grid")
            self.draw_pixel_grid()

    def canvas_to_image_coords(self, canvas_x, canvas_y):
        """画布坐标转图片坐标(精确到像素)"""
        img_x = int((canvas_x - self.offset_x) / self.zoom)
        img_y = int((canvas_y - self.offset_y) / self.zoom)
        return img_x, img_y

    def canvas_to_image_coords_exact(self, canvas_x, canvas_y):
        """画布坐标转图片坐标(精确位置,用于像素级编辑)"""
        # 当放大倍数足够高时,精确对齐到像素中心
        if self.zoom >= 10.0:
            pixel_size = self.zoom
            relative_x = canvas_x - self.offset_x
            relative_y = canvas_y - self.offset_y
            # 找到最接近的像素中心
            img_x = round((relative_x + pixel_size / 2) / pixel_size)
            img_y = round((relative_y + pixel_size / 2) / pixel_size)
        else:
            img_x = int((canvas_x - self.offset_x) / self.zoom)
            img_y = int((canvas_y - self.offset_y) / self.zoom)
        return img_x, img_y

    def draw_at(self, canvas_x, canvas_y):
        """在指定位置绘制"""
        # 使用精确坐标转换以获得更好的像素级编辑体验
        img_x, img_y = self.canvas_to_image_coords_exact(canvas_x, canvas_y)
        
        # 检查边界
        if (img_x < 0 or img_x >= self.display_image.width or img_y < 0 or img_y >= self.display_image.height):
            return
        
        # 计算实际画笔大小(考虑缩放)
        # 当放大倍数很高时,使用 1 像素进行精确编辑
        if self.zoom >= 10.0 and self.brush_size == 1:
            actual_brush_size = 1
        else:
            actual_brush_size = max(1, int(self.brush_size / self.zoom))
        
        # 解析颜色
        color_rgb = self.hex_to_rgb(self.brush_color)
        
        # 在画布上直接绘制显示
        display_brush_size = max(1, int(self.brush_size))
        self.canvas.create_oval(
            canvas_x - display_brush_size // 2,
            canvas_y - display_brush_size // 2,
            canvas_x + display_brush_size // 2,
            canvas_y + display_brush_size // 2,
            fill=self.brush_color, tags="paint"
        )
        
        # 在实际图片上修改像素
        draw = ImageDraw.Draw(self.display_image)
        if actual_brush_size == 1:
            # 单像素编辑,直接设置像素值
            if img_x >= 0 and img_x < self.display_image.width and img_y >= 0 and img_y < self.display_image.height:
                self.display_image.putpixel((img_x, img_y), color_rgb)
        else:
            draw.ellipse(
                [
                    img_x - actual_brush_size // 2,
                    img_y - actual_brush_size // 2,
                    img_x + actual_brush_size // 2,
                    img_y + actual_brush_size // 2
                ],
                fill=color_rgb
            )
        
        # 当放大倍数很高时,立即重新显示图片以获得清晰的像素效果
        if self.zoom >= 10.0:
            # 只更新受影响的区域
            self.update_image_display_region(img_x, img_y, actual_brush_size)
        elif self.zoom >= 5.0:
            # 中等放大时,偶尔更新完整显示
            if hasattr(self, '_update_counter'):
                self._update_counter += 1
            else:
                self._update_counter = 0
            if self._update_counter % 3 == 0:
                self.display_image_on_canvas()

    def draw_line(self, x1, y1, x2, y2):
        """在两点之间绘制连续线条"""
        distance = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
        steps = max(1, int(distance))
        for i in range(steps + 1):
            t = i / steps if steps > 0 else 0
            x = x1 + (x2 - x1) * t
            y = y1 + (y2 - y1) * t
            self.draw_at(x, y)

    def hex_to_rgb(self, hex_color):
        """十六进制颜色转 RGB 元组"""
        hex_color = hex_color.lstrip('#')
        return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

    def on_zoom(self, event):
        """滚轮缩放"""
        if self.display_image is None:
            return
        
        # 获取鼠标位置
        x = self.canvas.canvasx(event.x)
        y = self.canvas.canvasy(event.y)
        
        # 计算缩放前的图片坐标
        img_x = int((x - self.offset_x) / self.zoom)
        img_y = int((y - self.offset_y) / self.zoom)
        
        # 确定缩放方向 - 大幅提高最大缩放倍数以支持像素级编辑
        if event.num == 5 or event.delta < 0:
            new_zoom = max(0.1, self.zoom * 0.9)
        else:
            # 对于小图片,允许更大的放大倍数(最高 100 倍)
            max_zoom = 100.0
            if self.display_image:
                # 如果图片很小,可以放大更多倍,支持像素级编辑
                if self.display_image.width <= 112 and self.display_image.height <= 112:
                    max_zoom = 500.0  # 112 像素图片可以放大到 500 倍,每个像素约 5.5 厘米
                elif self.display_image.width < 200 and self.display_image.height < 200:
                    max_zoom = 300.0
            new_zoom = min(max_zoom, self.zoom * 1.1)
        
        self.zoom = new_zoom
        
        # 调整偏移以保持鼠标位置不变
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()
        display_width = int(self.display_image.width * self.zoom)
        display_height = int(self.display_image.height * self.zoom)
        self.offset_x = x - img_x * self.zoom
        self.offset_y = y - img_y * self.zoom
        
        # 限制偏移,防止图片完全移出画布
        max_offset_x = display_width - 50
        max_offset_y = display_height - 50
        self.offset_x = max(-max_offset_x, min(canvas_width - 50, self.offset_x))
        self.offset_y = max(-max_offset_y, min(canvas_height - 50, self.offset_y))
        
        self.display_image_on_canvas()
        self.size_info_label.config(
            text=f"{self.display_image.size[0]} x {self.display_image.size[1]} px | 缩放:{int(self.zoom * 100)}%"
        )

    def save_image(self):
        """保存图片"""
        if self.display_image is None:
            self.status_label.config(text="请先加载图片")
            return
        
        file_path = filedialog.asksaveasfilename(
            title="保存图片",
            defaultextension=".png",
            filetypes=[
                ("PNG", "*.png"),
                ("JPEG", "*.jpg"),
                ("所有文件", "*.*")
            ]
        )
        if file_path:
            try:
                # 转换为 RGB 模式(用于 JPEG)
                if file_path.lower().endswith(('.jpg', '.jpeg')):
                    rgb_image = Image.new("RGB", self.display_image.size, (255, 255, 255))
                    rgb_image.paste(self.display_image, mask=self.display_image.split()[3] if self.display_image.mode == "RGBA" else None)
                    rgb_image.save(file_path, quality=95)
                else:
                    self.display_image.save(file_path)
                self.status_label.config(text=f"已保存:{os.path.basename(file_path)}")
            except Exception as e:
                self.status_label.config(text=f"保存失败:{str(e)}")

    def on_drag_enter(self, event):
        """拖拽进入"""
        event.action = "copy"

    def on_drop(self, event):
        """拖拽放下"""
        files = event.data
        # 处理 Windows 路径格式 - tkinterdnd2 多个文件用 {} 分隔
        file_list = []
        if files.startswith('{') and files.endswith('}'):
            # 多个文件:{file1} {file2}
            import re
            # 提取所有花括号内的内容
            file_list = re.findall(r'\{([^{}]+)\}', files)
        elif '{' in files or '}' in files:
            # 混合格式,清理花括号
            files = files.replace('{', '').replace('}', '')
            file_list = [files]
        else:
            # 单个文件,直接使用(保留空格)
            file_list = [files]
        
        for file_path in file_list:
            # 去除首尾空白和引号
            file_path = file_path.strip().strip('"').strip("'")
            if os.path.isfile(file_path):
                ext = os.path.splitext(file_path)[1].lower()
                if ext in ['.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff', '.webp']:
                    self.open_image(file_path)
                    break

    def refresh_color_list(self):
        """刷新颜色列表 - 统计图片中所有颜色"""
        if self.display_image is None:
            return
        self.color_stats = {}
        self.color_listbox.delete(0, tk.END)
        
        # 统计颜色
        width, height = self.display_image.size
        for y in range(height):
            for x in range(width):
                color = self.display_image.getpixel((x, y))
                if isinstance(color, int):  # 灰度图
                    color = (color, color, color)
                color = color[:3]  # 只取 RGB,忽略 Alpha
                self.color_stats[color] = self.color_stats.get(color, 0) + 1
        
        # 按像素数量排序
        sorted_colors = sorted(self.color_stats.items(), key=lambda x: x[1], reverse=True)
        
        # 填充列表
        unique_colors = len(sorted_colors)
        total_pixels = width * height
        for i, (color_rgb, count) in enumerate(sorted_colors):
            hex_color = f"#{color_rgb[0]:02X}{color_rgb[1]:02X}{color_rgb[2]:02X}"
            percent = (count / total_pixels) * 100
            
            # 使用 Unicode 块字符显示颜色
            color_block = "■"
            display_text = f"{color_block} RGB({color_rgb[0]},{color_rgb[1]},{color_rgb[2]}) - {count}px ({percent:.1f}%)"
            self.color_listbox.insert(tk.END, display_text)
            
            # 计算亮度来决定文字颜色
            brightness = (color_rgb[0] * 299 + color_rgb[1] * 587 + color_rgb[2] * 114) / 1000
            
            # 设置背景色(用实际颜色)
            self.color_listbox.itemconfig(i, bg=hex_color)
            
            # 根据亮度决定文字颜色:亮背景用深色文字,暗背景用浅色文字
            if brightness > 128:
                self.color_listbox.itemconfig(i, fg="#000000")  # 黑色文字
            else:
                self.color_listbox.itemconfig(i, fg="#FFFFFF")  # 白色文字
        
        # 更新统计信息
        self.stats_label.config(
            text=f"总像素:{total_pixels}\n独特颜色:{unique_colors}"
        )

    def on_color_select(self, event):
        """颜色选择事件(支持多选)"""
        selection = self.color_listbox.curselection()
        sorted_colors = sorted(self.color_stats.items(), key=lambda x: x[1], reverse=True)
        
        # 清空之前的选择
        self.selected_colors = []
        
        # 获取所有选中的颜色
        for index in selection:
            if index < len(sorted_colors):
                color_rgb = sorted_colors[index][0]
                self.selected_colors.append(color_rgb)
        
        # 更新状态栏
        if len(self.selected_colors) == 0:
            self.status_label.config(text="未选择颜色")
        elif len(self.selected_colors) == 1:
            c = self.selected_colors[0]
            self.status_label.config(
                text=f"选中 1 个颜色:RGB({c[0]},{c[1]},{c[2]}) - {self.color_stats[c]} 像素"
            )
        else:
            total_pixels = sum(self.color_stats[c] for c in self.selected_colors)
            self.status_label.config(
                text=f"选中 {len(self.selected_colors)} 个颜色 - 共 {total_pixels} 像素"
            )
        
        # 如果开启了高亮,刷新显示
        if self.highlight_var.get():
            self.display_image_on_canvas()

    def toggle_highlight(self):
        """切换高亮显示"""
        self.show_highlight = self.highlight_var.get()
        if self.display_image:
            self.display_image_on_canvas()

    def select_all_colors(self):
        """全选所有颜色"""
        self.color_listbox.selection_set(0, tk.END)
        # 触发选择事件
        self.on_color_select(None)

    def clear_color_selection(self):
        """清空颜色选择"""
        self.color_listbox.selection_clear(0, tk.END)
        self.selected_colors = []
        self.status_label.config(text="已清空颜色选择")
        if self.highlight_var.get():
            self.display_image_on_canvas()

    def invert_color_selection(self):
        """反选颜色"""
        total = self.color_listbox.size()
        for i in range(total):
            if self.color_listbox.selection_includes(i):
                self.color_listbox.selection_clear(i)
            else:
                self.color_listbox.selection_set(i)
        # 触发选择事件
        self.on_color_select(None)

    def replace_color(self):
        """批量替换选中的颜色(支持多选)"""
        if self.display_image is None or not self.selected_colors:
            self.status_label.config(text="请先加载图片并选择颜色")
            return
        
        # 选择新颜色
        new_color = colorchooser.askcolor(
            title="选择替换后的颜色",
            initialcolor="#FF0000"
        )[1]
        if not new_color:
            return
        
        new_rgb = self.hex_to_rgb(new_color)
        old_colors = self.selected_colors.copy()
        
        # 替换所有匹配的像素
        width, height = self.display_image.size
        replaced_count = 0
        for y in range(height):
            for x in range(width):
                pixel_color = self.display_image.getpixel((x, y))
                if isinstance(pixel_color, int):
                    pixel_color = (pixel_color, pixel_color, pixel_color)
                pixel_color = pixel_color[:3]
                if pixel_color in old_colors:
                    self.display_image.putpixel((x, y), new_rgb)
                    replaced_count += 1
        
        # 更新显示
        self.display_image_on_canvas()
        self.status_label.config(
            text=f"已替换 {replaced_count} 像素 ({len(old_colors)} 种颜色) -> RGB({new_rgb[0]},{new_rgb[1]},{new_rgb[2]})"
        )
        
        # 刷新颜色列表
        self.refresh_color_list()

    def replace_with_picked_color(self):
        """使用取色器颜色替换选中的颜色"""
        if self.display_image is None or not self.selected_colors:
            self.status_label.config(text="请先加载图片并选择颜色")
            return
        
        if self.picked_color is None:
            self.status_label.config(text="请先使用取色器获取颜色")
            return
        
        new_rgb = self.picked_color
        old_colors = self.selected_colors.copy()
        
        # 替换所有匹配的像素
        width, height = self.display_image.size
        replaced_count = 0
        for y in range(height):
            for x in range(width):
                pixel_color = self.display_image.getpixel((x, y))
                if isinstance(pixel_color, int):
                    pixel_color = (pixel_color, pixel_color, pixel_color)
                pixel_color = pixel_color[:3]
                if pixel_color in old_colors:
                    self.display_image.putpixel((x, y), new_rgb)
                    replaced_count += 1
        
        # 更新显示
        self.display_image_on_canvas()
        self.status_label.config(
            text=f"已替换 {replaced_count} 像素 ({len(old_colors)} 种颜色) -> 取色器颜色 RGB({new_rgb[0]},{new_rgb[1]},{new_rgb[2]})"
        )
        
        # 刷新颜色列表
        self.refresh_color_list()

    def replace_with_transparent(self):
        """将选中的颜色设置为透明"""
        if self.display_image is None or not self.selected_colors:
            self.status_label.config(text="请先加载图片并选择颜色")
            return
        
        old_colors = self.selected_colors.copy()
        transparent_color = (0, 0, 0, 0)  # RGBA 全透明
        
        # 替换所有匹配的像素为透明
        width, height = self.display_image.size
        replaced_count = 0
        for y in range(height):
            for x in range(width):
                pixel_color = self.display_image.getpixel((x, y))
                if isinstance(pixel_color, int):
                    pixel_color = (pixel_color, pixel_color, pixel_color)
                pixel_color_rgb = pixel_color[:3]
                if pixel_color_rgb in old_colors:
                    self.display_image.putpixel((x, y), transparent_color)
                    replaced_count += 1
        
        # 更新显示
        self.display_image_on_canvas()
        self.status_label.config(
            text=f"已设置 {replaced_count} 像素 ({len(old_colors)} 种颜色) 为透明"
        )
        
        # 刷新颜色列表
        self.refresh_color_list()

def main():
    # 尝试使用支持拖放的 Tk
    try:
        from tkinterdnd2 import TkinterDnD
        root = TkinterDnD.Tk()
    except ImportError:
        root = tk.Tk()
    app = ImageEditor(root)
    root.mainloop()

if __name__ == "__main__":
    main()

目录

  1. 效果预览
  2. 功能说明
  3. 功能特性
  4. 基础功能
  5. 画笔工具
  6. 颜色管理
  7. 批量操作
  8. 安装
  9. 依赖
  10. requirements.txt
  11. 使用方法
  12. 启动程序
  13. 界面布局
  14. 工具模式
  15. 操作示例
  16. 像素级编辑
  17. 提取颜色
  18. 批量替换颜色
  19. 去除背景
  20. 快捷操作
  21. 颜色代码格式
  22. 文件结构
  23. 注意事项
  24. 系统要求
  25. 核心代码实现
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 大疆 MSDK 实现无人机视觉引导自适应降落
  • 英伟达开源 DreamDojo 世界模型:4.4 万小时数据破解机器人物理鸿沟
  • 主流大模型横评:GPT、Claude、Gemini、Llama 及国产模型选型指南
  • C++ 仿函数详解:让对象像函数一样调用
  • 基于 AI 的 Java 代码生成与优化实践指南
  • OpenClaw 安装与飞书接入指南
  • 基于 Vue3 的大文件分片加密上传与断点续传方案
  • C++ 手写 HTTP 服务器:从请求解析到响应构建
  • OpenClaw 框架更新:支持 GPT-5.4、记忆热插拔与插件化上下文引擎
  • 基于指数预定义时间控制的固定翼无人机轨迹跟踪控制研究
  • 基于 Coze 的 AI 应用开发:从智能体到 Web 部署
  • Elasticsearch + Kibana 实战指南:从安装部署到 C++ 客户端封装
  • Python 基础入门:you-get 与 turtle 库实战指南
  • 用 C 语言从零实现 Linux Shell:原理与实践
  • AI 绘画模型对比:Stable Diffusion 与 Z-Image-Turbo 快速部署方案
  • 扩散模型(Diffusion Model)原理与图像生成实战
  • Gitee+PicGo 搭建 Markdown 笔记图床指南
  • 基于 Flask 的校园失物招领系统设计与实现
  • 5 款免费 AIGC 检测工具推荐与降重方法
  • macOS 双开与多开微信 WeChat 教程(支持 4.X 及以上版本)

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online