import tkinter as tk
from tkinter import ttk, filedialog, messagebox, colorchooser
import random
import json
import os
from PIL import Image, ImageDraw
import time


class XiaobaWangPainter:
    def __init__(self, root):
        self.root = root
        self.root.title("小霸王风格像素画编辑器 - 增强版")
        self.root.geometry("1350x950")
        self.root.configure(bg='#2c3e50')

        # ========== 核心参数 ==========
        self.grid_width = 32
        self.grid_height = 32
        self.pixel_size = 20
        self.current_color = '1'
        self.current_tool = 'pen'
        self.current_frame = 0
        self.frames = []  # 多帧动画
        self.history = []
        self.history_index = -1
        self.is_playing = False
        self.anim_index = 0
        self.last_save_time = 0
        self.code_text_update_timer = None
        self.loop_animation = tk.BooleanVar(value=True)
        self.frame_delay = 200
        self.current_animation_id = None
        self._updating_code = False  # 防止循环更新

        # 调色板
        self.palette = {
            '0': '#000000', '1': '#FF0000', '2': '#00FF00', '3': '#FFFF00',
            '4': '#0000FF', '5': '#FF00FF', '6': '#00FFFF', '7': '#FFFFFF',
            '8': '#FF8800', '9': '#8800FF', 'A': '#888888', 'B': '#FF8888',
            'C': '#88FF88', 'D': '#8888FF', 'E': '#FFFF88', 'F': '#FF88FF',
        }

        self.tools = {
            'pen': '✏️ 画笔', 'fill': '🪣 填充', 'line': '📏 直线',
            'circle': '⚪ 圆形', 'rect': '⬛ 矩形', 'eraser': '🧽 橡皮',
            'picker': '🎨 取色'
        }

        # 初始化数据
        self.grid_data = [['7' for _ in range(self.grid_width)] for _ in range(self.grid_height)]
        self.save_to_history()

        self.setup_ui()
        self.draw_grid()
        self.load_demo_pattern()

        print("=" * 50)
        print("小霸王像素画编辑器已启动")
        print("使用说明:")
        print("1. 点击「➕ 添加帧」添加当前画布为第一帧")
        print("2. 在文本框中编辑颜色代码,会自动更新当前帧和画布")
        print("3. 点击「下一帧」切换到其他帧编辑")
        print("4. 点击「播放」观看动画")
        print("=" * 50)

    def setup_ui(self):
        toolbar = tk.Frame(self.root, bg='#34495e', height=40)
        toolbar.pack(fill=tk.X, padx=5, pady=5)

        file_frame = tk.Frame(toolbar, bg='#34495e')
        file_frame.pack(side=tk.LEFT, padx=10)

        tk.Button(file_frame, text="📁 新建", command=self.new_file,
                  bg='#3498db', fg='white', padx=10).pack(side=tk.LEFT, padx=2)
        tk.Button(file_frame, text="💾 保存", command=self.save_file,
                  bg='#2ecc71', fg='white', padx=10).pack(side=tk.LEFT, padx=2)
        tk.Button(file_frame, text="📂 打开", command=self.open_file,
                  bg='#3498db', fg='white', padx=10).pack(side=tk.LEFT, padx=2)
        tk.Button(file_frame, text="📸 导出图片", command=self.export_image,
                  bg='#e67e22', fg='white', padx=10).pack(side=tk.LEFT, padx=2)
        tk.Button(file_frame, text="🎬 导出GIF", command=self.export_gif,
                  bg='#e74c3c', fg='white', padx=10).pack(side=tk.LEFT, padx=2)

        edit_frame = tk.Frame(toolbar, bg='#34495e')
        edit_frame.pack(side=tk.LEFT, padx=20)

        tk.Button(edit_frame, text="↩️ 撤销", command=self.undo,
                  bg='#f39c12', fg='white', padx=8).pack(side=tk.LEFT, padx=2)
        tk.Button(edit_frame, text="🔄 重做", command=self.redo,
                  bg='#f39c12', fg='white', padx=8).pack(side=tk.LEFT, padx=2)
        tk.Button(edit_frame, text="🗑️ 清空", command=self.clear_grid,
                  bg='#e74c3c', fg='white', padx=8).pack(side=tk.LEFT, padx=2)

        tool_frame = tk.LabelFrame(self.root, text="绘图工具", bg='#ecf0f1', fg='#2c3e50')
        tool_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)

        self.tool_buttons = {}
        for tool_id, tool_name in self.tools.items():
            btn = tk.Button(tool_frame, text=tool_name, width=10,
                            command=lambda t=tool_id: self.set_tool(t),
                            relief=tk.RAISED, bg='#bdc3c7')
            btn.pack(padx=10, pady=5)
            self.tool_buttons[tool_id] = btn
        self.tool_buttons['pen'].config(relief=tk.SUNKEN, bg='#3498db', fg='white')

        self.palette_frame = tk.LabelFrame(self.root, text="调色板", bg='#ecf0f1', fg='#2c3e50')
        self.palette_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)

        colors = list(self.palette.items())
        for i, (code, color) in enumerate(colors):
            row = i // 4
            col = i % 4
            btn = tk.Button(self.palette_frame, bg=color, width=8, height=2,
                            command=lambda c=code: self.set_color(c))
            btn.grid(row=row, column=col, padx=2, pady=2)
            if code == '1':
                btn.config(relief=tk.SUNKEN, bd=3)
                self.current_color_btn = btn

        right_frame = tk.Frame(self.root, bg='#ecf0f1')
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        canvas_frame = tk.LabelFrame(right_frame, text="绘图画布", bg='#ecf0f1')
        canvas_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        self.canvas_container = tk.Canvas(canvas_frame, bg='#95a5a6')
        self.canvas_container.pack(fill=tk.BOTH, expand=True)

        self.canvas = tk.Canvas(self.canvas_container, bg='white',
                                width=self.grid_width * self.pixel_size,
                                height=self.grid_height * self.pixel_size)
        self.canvas.place(x=0, y=0)

        self.canvas.bind('<Button-1>', self.on_mouse_click)
        self.canvas.bind('<B1-Motion>', self.on_mouse_drag)
        self.canvas.bind('<Button-3>', self.on_right_click)

        h_scroll = tk.Scrollbar(canvas_frame, orient=tk.HORIZONTAL, command=self.canvas_container.xview)
        v_scroll = tk.Scrollbar(canvas_frame, orient=tk.VERTICAL, command=self.canvas_container.yview)
        self.canvas_container.configure(xscrollcommand=h_scroll.set, yscrollcommand=v_scroll.set)
        h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
        v_scroll.pack(side=tk.RIGHT, fill=tk.Y)

        anim_frame = tk.LabelFrame(right_frame, text="动画控制", bg='#ecf0f1')
        anim_frame.pack(fill=tk.X, pady=5)

        anim_controls = tk.Frame(anim_frame, bg='#ecf0f1')
        anim_controls.pack(pady=5)

        button_row1 = tk.Frame(anim_controls, bg='#ecf0f1')
        button_row1.pack()

        tk.Button(button_row1, text="◀ 上一帧", command=self.prev_frame,
                  bg='#3498db', fg='white', width=8).pack(side=tk.LEFT, padx=2)
        tk.Button(button_row1, text="▶ 播放", command=self.play_animation,
                  bg='#2ecc71', fg='white', width=8).pack(side=tk.LEFT, padx=2)
        tk.Button(button_row1, text="⏸ 暂停", command=self.pause_animation,
                  bg='#f39c12', fg='white', width=8).pack(side=tk.LEFT, padx=2)
        tk.Button(button_row1, text="⏹ 停止", command=self.stop_animation,
                  bg='#e74c3c', fg='white', width=8).pack(side=tk.LEFT, padx=2)
        tk.Button(button_row1, text="▶ 下一帧", command=self.next_frame,
                  bg='#3498db', fg='white', width=8).pack(side=tk.LEFT, padx=2)
        tk.Button(button_row1, text="➕ 添加帧", command=self.add_frame,
                  bg='#9b59b6', fg='white', width=8).pack(side=tk.LEFT, padx=2)
        tk.Button(button_row1, text="➖ 删除帧", command=self.delete_frame,
                  bg='#e74c3c', fg='white', width=8).pack(side=tk.LEFT, padx=2)

        button_row2 = tk.Frame(anim_controls, bg='#ecf0f1')
        button_row2.pack(pady=5)

        self.loop_checkbox = tk.Checkbutton(button_row2, text="🔁 循环播放",
                                            variable=self.loop_animation,
                                            bg='#ecf0f1', font=('Arial', 10))
        self.loop_checkbox.pack(side=tk.LEFT, padx=10)

        tk.Label(button_row2, text="播放速度:", bg='#ecf0f1', font=('Arial', 10)).pack(side=tk.LEFT, padx=5)

        self.speed_var = tk.IntVar(value=200)
        self.speed_scale = tk.Scale(button_row2, from_=0, to=5000, orient=tk.HORIZONTAL,
                                    variable=self.speed_var, length=200,
                                    command=self.on_speed_change,
                                    bg='#ecf0f1', highlightthickness=0)
        self.speed_scale.pack(side=tk.LEFT, padx=5)

        tk.Label(button_row2, text="毫秒/帧", bg='#ecf0f1', font=('Arial', 10)).pack(side=tk.LEFT)

        self.speed_label = tk.Label(button_row2, text="(200ms)", bg='#ecf0f1', font=('Arial', 9))
        self.speed_label.pack(side=tk.LEFT, padx=5)

        # 在动画控制面板添加帧跳转功能(button_row2 后面添加)
        frame_jump_frame = tk.Frame(anim_controls, bg='#ecf0f1')
        frame_jump_frame.pack(pady=2)

        tk.Label(frame_jump_frame, text="跳转到帧:", bg='#ecf0f1').pack(side=tk.LEFT, padx=5)
        self.jump_entry = tk.Entry(frame_jump_frame, width=6)
        self.jump_entry.pack(side=tk.LEFT, padx=5)
        tk.Button(frame_jump_frame, text="GO", command=self.jump_to_frame,
                  bg='#3498db', fg='white', width=4).pack(side=tk.LEFT)

        self.frame_label = tk.Label(anim_controls, text="当前帧: 0/0", bg='#ecf0f1', font=('Arial', 12, 'bold'))
        self.frame_label.pack(pady=2)

        effect_frame = tk.LabelFrame(right_frame, text="特效工具", bg='#ecf0f1')
        effect_frame.pack(fill=tk.X, pady=5)

        effects = [
            ('🔍 放大', self.zoom_in), ('🔍 缩小', self.zoom_out),
            ('🔄 水平翻转', self.flip_horizontal), ('🔄 垂直翻转', self.flip_vertical),
            ('🎲 随机填充', self.random_fill), ('🌈 渐变填充', self.gradient_fill),
        ]

        for text, cmd in effects:
            tk.Button(effect_frame, text=text, command=cmd,
                      bg='#95a5a6', fg='white', width=12).pack(side=tk.LEFT, padx=2, pady=2)

        code_frame = tk.LabelFrame(right_frame, text="数字序列编辑器 (编辑后自动更新当前帧)", bg='#ecf0f1')
        code_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        code_text_frame = tk.Frame(code_frame)
        code_text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.code_text = tk.Text(code_text_frame, font=('Courier', 10), wrap=tk.NONE, height=8)
        self.code_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        code_scrollbar = tk.Scrollbar(code_text_frame, orient=tk.VERTICAL, command=self.code_text.yview)
        code_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.code_text.config(yscrollcommand=code_scrollbar.set)

        code_h_scroll = tk.Scrollbar(code_frame, orient=tk.HORIZONTAL, command=self.code_text.xview)
        code_h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
        self.code_text.config(xscrollcommand=code_h_scroll.set)

        # 绑定文本框变化事件
        self.code_text.bind("<<Modified>>", self.on_code_text_edit)
        self.code_text.bind("<KeyRelease>", self.on_code_text_edit)

        code_btn_frame = tk.Frame(code_frame, bg='#ecf0f1')
        code_btn_frame.pack(fill=tk.X, pady=5)

        tk.Button(code_btn_frame, text="🔄 刷新显示", command=self.update_code_from_grid,
                  bg='#3498db', fg='white').pack(side=tk.LEFT, padx=2)
        tk.Button(code_btn_frame, text="💾 保存到当前帧", command=self.save_current_frame_from_code,
                  bg='#2ecc71', fg='white').pack(side=tk.LEFT, padx=2)

        self.status_bar = tk.Label(self.root, text="就绪 | 工具: 画笔 | 颜色: 红色",
                                   bg='#34495e', fg='white', anchor=tk.W)
        self.status_bar.pack(fill=tk.X, side=tk.BOTTOM)

    def jump_to_frame(self):
        try:
            frame_num = int(self.jump_entry.get()) - 1
            if 0 <= frame_num < len(self.frames):
                self.current_frame = frame_num
                self.grid_data = [row[:] for row in self.frames[self.current_frame]]
                self.draw_grid()
                self.update_code_from_grid()
                self.update_frame_indicator()
                self.status_bar.config(text=f"跳转到第 {frame_num + 1} 帧")
        except:
            pass


    def on_code_text_edit(self, event=None):
        """文本框编辑时自动更新当前帧和画布"""
        if event == '<<Modified>>':
            self.code_text.edit_modified(False)

        # 防抖处理
        if self.code_text_update_timer:
            self.root.after_cancel(self.code_text_update_timer)
        self.code_text_update_timer = self.root.after(300, self.update_current_frame_from_code)

    def update_current_frame_from_code(self):
        """从文本框内容更新当前帧和画布"""
        if self._updating_code:
            return

        self._updating_code = True
        try:
            text = self.code_text.get("1.0", tk.END).strip()

            # 解析文本框内容
            lines = []
            current_line = ""
            for char in text:
                if char == '\n':
                    if current_line:
                        lines.append(current_line)
                        current_line = ""
                elif char == ' ':
                    continue
                elif char == '/':
                    if current_line:
                        lines.append(current_line)
                        current_line = ""
                elif char.upper() in '0123456789ABCDEF':
                    current_line += char.upper()
            if current_line:
                lines.append(current_line)

            # 创建新网格
            new_grid = [['7' for _ in range(self.grid_width)] for _ in range(self.grid_height)]
            for y, line in enumerate(lines):
                if y >= self.grid_height:
                    break
                for x, char in enumerate(line):
                    if x >= self.grid_width:
                        break
                    if char in self.palette:
                        new_grid[y][x] = char

            # 更新当前网格
            self.grid_data = new_grid
            self.draw_grid()

            # 如果有帧列表,更新当前帧
            if self.frames and self.current_frame < len(self.frames):
                self.frames[self.current_frame] = [row[:] for row in self.grid_data]
                self.update_frame_indicator()
                self.status_bar.config(text=f"✅ 已更新当前帧 (第{self.current_frame + 1}帧)")
            else:
                self.status_bar.config(text="✅ 画布已更新 (尚未添加帧)")

            self.save_to_history()

        except Exception as e:
            print(f"更新出错: {e}")
        finally:
            self._updating_code = False

    def save_current_frame_from_code(self):
        """手动保存当前文本框内容到当前帧"""
        self.update_current_frame_from_code()
        messagebox.showinfo("成功", f"已保存到第 {self.current_frame + 1} 帧")

    def update_code_from_grid(self):
        """从当前网格更新文本框显示"""
        if self._updating_code:
            return

        self._updating_code = True
        try:
            code = []
            for y in range(self.grid_height):
                row = ''.join(str(self.grid_data[y][x]) for x in range(self.grid_width))
                code.append(row)
            self.code_text.delete("1.0", tk.END)
            self.code_text.insert(tk.END, '\n'.join(code))
        finally:
            self._updating_code = False

    def add_frame(self):
        """添加动画帧"""
        new_frame = [row[:] for row in self.grid_data]
        self.frames.append(new_frame)
        self.current_frame = len(self.frames) - 1
        self.update_frame_indicator()
        self.status_bar.config(text=f"✅ 已添加第 {len(self.frames)} 帧")
        print(f"添加帧: 当前共有 {len(self.frames)} 帧")

    def next_frame(self):
        """下一帧"""
        if not self.frames:
            self.status_bar.config(text="没有动画帧,请先添加帧")
            return

        if self.current_frame < len(self.frames) - 1:
            self.current_frame += 1
            # 加载帧数据到当前网格
            self.grid_data = [row[:] for row in self.frames[self.current_frame]]
            self.draw_grid()
            self.update_code_from_grid()  # 更新文本框显示
            self.status_bar.config(text=f"切换到第 {self.current_frame + 1} 帧")
            self.update_frame_indicator()

    def prev_frame(self):
        """上一帧"""
        if not self.frames:
            self.status_bar.config(text="没有动画帧,请先添加帧")
            return

        if self.current_frame > 0:
            self.current_frame -= 1
            # 加载帧数据到当前网格
            self.grid_data = [row[:] for row in self.frames[self.current_frame]]
            self.draw_grid()
            self.update_code_from_grid()  # 更新文本框显示
            self.status_bar.config(text=f"切换到第 {self.current_frame + 1} 帧")
            self.update_frame_indicator()

    def delete_frame(self):
        """删除当前帧"""
        if not self.frames:
            return

        if len(self.frames) == 1:
            self.frames = []
            self.current_frame = 0
            self.grid_data = [['7' for _ in range(self.grid_width)] for _ in range(self.grid_height)]
            self.draw_grid()
            self.update_code_from_grid()
            self.status_bar.config(text="已删除最后一帧")
            self.update_frame_indicator()
            return

        self.frames.pop(self.current_frame)
        if self.current_frame >= len(self.frames):
            self.current_frame = len(self.frames) - 1

        self.grid_data = [row[:] for row in self.frames[self.current_frame]]
        self.draw_grid()
        self.update_code_from_grid()
        self.status_bar.config(text=f"已删除帧,现在显示第 {self.current_frame + 1} 帧")
        self.update_frame_indicator()

    def play_animation(self):
        """播放动画"""
        if self.is_playing:
            return

        if not self.frames:
            self.status_bar.config(text="❌ 没有动画帧,请先添加帧")
            messagebox.showwarning("警告", "没有动画帧!请先点击「➕ 添加帧」")
            return

        if self.current_animation_id:
            self.root.after_cancel(self.current_animation_id)

        self.is_playing = True
        self.anim_index = 0
        speed_text = "最快" if self.frame_delay == 0 else f"{self.frame_delay}ms"
        self.status_bar.config(text=f"▶ 播放中 ({len(self.frames)}帧, {speed_text}/帧)")
        self.animate()

    def animate(self):
        """动画循环"""
        if not self.is_playing:
            return

        if not self.frames:
            self.is_playing = False
            return

        if self.anim_index < len(self.frames):
            # 显示当前帧
            frame_data = self.frames[self.anim_index]
            for y in range(self.grid_height):
                for x in range(self.grid_width):
                    self.grid_data[y][x] = frame_data[y][x]
            self.draw_grid()
            self.current_frame = self.anim_index
            self.update_frame_indicator()

            self.anim_index += 1
            delay = max(1, self.frame_delay)
            self.current_animation_id = self.root.after(delay, self.animate)
        else:
            if self.loop_animation.get():
                self.anim_index = 0
                delay = max(1, self.frame_delay)
                self.current_animation_id = self.root.after(delay, self.animate)
            else:
                self.is_playing = False
                self.current_animation_id = None
                self.status_bar.config(text="⏹ 播放完毕")

    def pause_animation(self):
        if self.is_playing:
            self.is_playing = False
            if self.current_animation_id:
                self.root.after_cancel(self.current_animation_id)
            self.status_bar.config(text="⏸ 已暂停")

    def stop_animation(self):
        self.is_playing = False
        if self.current_animation_id:
            self.root.after_cancel(self.current_animation_id)
            self.current_animation_id = None
        if self.frames and self.current_frame < len(self.frames):
            self.grid_data = [row[:] for row in self.frames[self.current_frame]]
            self.draw_grid()
            self.update_code_from_grid()
        self.status_bar.config(text="⏹ 已停止")

    def update_frame_indicator(self):
        if self.frames and len(self.frames) > 0:
            self.frame_label.config(text=f"当前帧: {self.current_frame + 1}/{len(self.frames)}", fg='black')
        else:
            self.frame_label.config(text="当前帧: 0/0 (点击➕添加帧)", fg='red')

    def on_speed_change(self, value):
        self.frame_delay = int(value)
        if self.frame_delay == 0:
            self.speed_label.config(text="(最快)")
        else:
            self.speed_label.config(text=f"({self.frame_delay}ms)")

    # ========== 基础绘图方法 ==========
    def set_tool(self, tool_id):
        self.current_tool = tool_id
        for tid, btn in self.tool_buttons.items():
            if tid == tool_id:
                btn.config(relief=tk.SUNKEN, bg='#3498db', fg='white')
            else:
                btn.config(relief=tk.RAISED, bg='#bdc3c7', fg='black')
        self.status_bar.config(text=f"工具: {self.tools[tool_id]} | 颜色: {self.get_color_name()}")

    def set_color(self, color_code):
        self.current_color = color_code
        if hasattr(self, 'current_color_btn'):
            self.current_color_btn.config(relief=tk.RAISED)
        for widget in self.palette_frame.winfo_children():
            if isinstance(widget, tk.Button) and widget.cget('bg') == self.palette.get(color_code, '#000000'):
                widget.config(relief=tk.SUNKEN, bd=3)
                self.current_color_btn = widget
                break
        self.status_bar.config(text=f"工具: {self.tools[self.current_tool]} | 颜色: {self.get_color_name()}")

    def get_color_name(self):
        color_names = {'0': '黑', '1': '红', '2': '绿', '3': '黄', '4': '蓝',
                       '5': '紫', '6': '青', '7': '白', '8': '橙', '9': '靛',
                       'A': '灰', 'B': '粉', 'C': '浅绿', 'D': '浅蓝', 'E': '浅黄', 'F': '浅紫'}
        return color_names.get(self.current_color, '未知')

    def draw_pixel(self, x, y, color_code):
        color = self.palette.get(color_code, '#FFFFFF')
        x1 = x * self.pixel_size
        y1 = y * self.pixel_size
        x2 = x1 + self.pixel_size
        y2 = y1 + self.pixel_size
        self.canvas.create_rectangle(x1, y1, x2, y2, fill=color, outline='#DDD', width=0.5)

    def draw_grid(self):
        self.canvas.delete('all')
        for y in range(self.grid_height):
            for x in range(self.grid_width):
                self.draw_pixel(x, y, self.grid_data[y][x])

    def on_mouse_click(self, event):
        x = event.x // self.pixel_size
        y = event.y // self.pixel_size
        if 0 <= x < self.grid_width and 0 <= y < self.grid_height:
            if self.current_tool == 'pen':
                self.set_pixel(x, y, self.current_color)
            elif self.current_tool == 'eraser':
                self.set_pixel(x, y, '7')
            elif self.current_tool == 'picker':
                self.current_color = self.grid_data[y][x]
                self.set_color(self.current_color)
            elif self.current_tool == 'fill':
                self.flood_fill(x, y, self.current_color)

    def on_mouse_drag(self, event):
        x = event.x // self.pixel_size
        y = event.y // self.pixel_size
        if 0 <= x < self.grid_width and 0 <= y < self.grid_height:
            if self.current_tool == 'pen':
                self.set_pixel(x, y, self.current_color)
            elif self.current_tool == 'eraser':
                self.set_pixel(x, y, '7')

    def on_right_click(self, event):
        x = event.x // self.pixel_size
        y = event.y // self.pixel_size
        if 0 <= x < self.grid_width and 0 <= y < self.grid_height:
            self.current_color = self.grid_data[y][x]
            self.set_color(self.current_color)

    def set_pixel(self, x, y, color):
        if self.grid_data[y][x] != color:
            self.grid_data[y][x] = color
            self.draw_pixel(x, y, color)
            self.save_to_history()
            # 同时更新当前帧
            if self.frames and self.current_frame < len(self.frames):
                self.frames[self.current_frame] = [row[:] for row in self.grid_data]
            self.update_code_from_grid()

    def flood_fill(self, x, y, new_color):
        old_color = self.grid_data[y][x]
        if old_color == new_color:
            return
        stack = [(x, y)]
        visited = set()
        while stack:
            cx, cy = stack.pop()
            if (cx, cy) in visited:
                continue
            if cx < 0 or cx >= self.grid_width or cy < 0 or cy >= self.grid_height:
                continue
            if self.grid_data[cy][cx] != old_color:
                continue
            visited.add((cx, cy))
            self.set_pixel(cx, cy, new_color)
            stack.extend([(cx + 1, cy), (cx - 1, cy), (cx, cy + 1), (cx, cy - 1)])
        self.draw_grid()

    def save_to_history(self):
        import copy
        self.history = self.history[:self.history_index + 1]
        self.history.append(copy.deepcopy(self.grid_data))
        if len(self.history) > 50:
            self.history.pop(0)
        self.history_index = len(self.history) - 1

    def undo(self):
        if self.history_index > 0:
            self.history_index -= 1
            self.grid_data = [row[:] for row in self.history[self.history_index]]
            self.draw_grid()
            self.update_code_from_grid()
            if self.frames and self.current_frame < len(self.frames):
                self.frames[self.current_frame] = [row[:] for row in self.grid_data]

    def redo(self):
        if self.history_index < len(self.history) - 1:
            self.history_index += 1
            self.grid_data = [row[:] for row in self.history[self.history_index]]
            self.draw_grid()
            self.update_code_from_grid()
            if self.frames and self.current_frame < len(self.frames):
                self.frames[self.current_frame] = [row[:] for row in self.grid_data]

    def clear_grid(self):
        for y in range(self.grid_height):
            for x in range(self.grid_width):
                self.grid_data[y][x] = '7'
        self.draw_grid()
        self.save_to_history()
        self.update_code_from_grid()
        if self.frames and self.current_frame < len(self.frames):
            self.frames[self.current_frame] = [row[:] for row in self.grid_data]

    def random_fill(self):
        colors = list(self.palette.keys())
        for y in range(self.grid_height):
            for x in range(self.grid_width):
                self.grid_data[y][x] = random.choice(colors)
        self.draw_grid()
        self.save_to_history()
        self.update_code_from_grid()
        if self.frames and self.current_frame < len(self.frames):
            self.frames[self.current_frame] = [row[:] for row in self.grid_data]

    def gradient_fill(self):
        colors = list(self.palette.keys())
        for y in range(self.grid_height):
            for x in range(self.grid_width):
                idx = (x + y) % len(colors)
                self.grid_data[y][x] = colors[idx]
        self.draw_grid()
        self.save_to_history()
        self.update_code_from_grid()
        if self.frames and self.current_frame < len(self.frames):
            self.frames[self.current_frame] = [row[:] for row in self.grid_data]

    def flip_horizontal(self):
        for y in range(self.grid_height):
            self.grid_data[y].reverse()
        self.draw_grid()
        self.save_to_history()
        self.update_code_from_grid()
        if self.frames and self.current_frame < len(self.frames):
            self.frames[self.current_frame] = [row[:] for row in self.grid_data]

    def flip_vertical(self):
        self.grid_data.reverse()
        self.draw_grid()
        self.save_to_history()
        self.update_code_from_grid()
        if self.frames and self.current_frame < len(self.frames):
            self.frames[self.current_frame] = [row[:] for row in self.grid_data]

    def zoom_in(self):
        if self.pixel_size < 40:
            self.pixel_size += 5
            self.canvas.config(width=self.grid_width * self.pixel_size,
                               height=self.grid_height * self.pixel_size)
            self.draw_grid()

    def zoom_out(self):
        if self.pixel_size > 10:
            self.pixel_size -= 5
            self.canvas.config(width=self.grid_width * self.pixel_size,
                               height=self.grid_height * self.pixel_size)
            self.draw_grid()

    def new_file(self):
        self.grid_data = [['7' for _ in range(self.grid_width)] for _ in range(self.grid_height)]
        self.draw_grid()
        self.save_to_history()
        self.update_code_from_grid()
        self.frames = []
        self.current_frame = 0
        self.update_frame_indicator()

    def save_file(self):
        filename = filedialog.asksaveasfilename(defaultextension=".draw",
                                                filetypes=[("画谱文件", "*.draw")])
        if filename:
            data = {
                'grid_data': self.grid_data,
                'frames': self.frames,
                'frame_delay': self.frame_delay,
                'grid_width': self.grid_width,
                'grid_height': self.grid_height
            }
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2)
            messagebox.showinfo("成功", f"已保存到 {filename}")

    def open_file(self):
        filename = filedialog.askopenfilename(filetypes=[("画谱文件", "*.draw")])
        if filename:
            with open(filename, 'r', encoding='utf-8') as f:
                data = json.load(f)
            self.grid_data = data['grid_data']
            self.frames = data.get('frames', [])
            self.frame_delay = data.get('frame_delay', 200)
            self.speed_var.set(self.frame_delay)
            self.on_speed_change(self.frame_delay)
            self.draw_grid()
            self.save_to_history()
            self.update_code_from_grid()
            if self.frames:
                self.current_frame = 0
            self.update_frame_indicator()

    def export_image(self):
        filename = filedialog.asksaveasfilename(defaultextension=".png",
                                                filetypes=[("PNG图片", "*.png")])
        if filename:
            img = Image.new('RGB', (self.grid_width * 10, self.grid_height * 10), (255, 255, 255))
            draw = ImageDraw.Draw(img)
            for y in range(self.grid_height):
                for x in range(self.grid_width):
                    color = self.palette.get(self.grid_data[y][x], '#FFFFFF')
                    r = int(color[1:3], 16)
                    g = int(color[3:5], 16)
                    b = int(color[5:7], 16)
                    draw.rectangle([x * 10, y * 10, (x + 1) * 10, (y + 1) * 10], fill=(r, g, b))
            img.save(filename)
            messagebox.showinfo("成功", "图片已保存")

    def export_gif(self):
        if not self.frames:
            messagebox.showwarning("警告", "没有动画帧可导出!")
            return
        filename = filedialog.asksaveasfilename(defaultextension=".gif",
                                                filetypes=[("GIF动画", "*.gif")])
        if filename:
            images = []
            for frame_data in self.frames:
                img = Image.new('RGB', (self.grid_width * 10, self.grid_height * 10), (255, 255, 255))
                draw = ImageDraw.Draw(img)
                for y in range(self.grid_height):
                    for x in range(self.grid_width):
                        color = self.palette.get(frame_data[y][x], '#FFFFFF')
                        r = int(color[1:3], 16)
                        g = int(color[3:5], 16)
                        b = int(color[5:7], 16)
                        draw.rectangle([x * 10, y * 10, (x + 1) * 10, (y + 1) * 10], fill=(r, g, b))
                images.append(img)
            gif_delay = max(20, self.frame_delay) if self.frame_delay > 0 else 20
            images[0].save(filename, save_all=True, append_images=images[1:], duration=gif_delay, loop=0)
            messagebox.showinfo("成功", f"GIF已保存")

    def custom_color(self):
        color = colorchooser.askcolor(title="选择颜色")
        if color:
            new_code = str(len(self.palette))
            if new_code in '0123456789ABCDEF':
                new_code = chr(ord('A') + len(self.palette) - 10)
            self.palette[new_code] = color[1]

    def load_demo_pattern(self):
        demo = [
            "00000000000000000000000000000000",
            "0FFFFFFFFFFFFFFFFFFFFFFFFFFFFF0",
            "0F111111111111111111111111111F0",
            "0F1FFFFFFFFFFFFFFFFFFFFFFFF1F0",
            "0F1F0000000000000000000000F1F0",
            "0F1F0FFFFFFFFFFFFFFFFFF0F1F0",
            "0F1F0F1111111111111111F0F1F0",
            "0F1F0F1FFFFFFFFFFFF1F0F1F0",
            "0F1F0F1F0000000000F1F0F1F0",
            "0F1F0F1F0FFFFFFF0F1F0F1F0",
            "0F1F0F1F0F111111F0F1F0F1F0",
            "0F1F0F1F0F1FFFF1F0F1F0F1F0",
            "0F1F0F1F0F1F111F1F0F1F0F1F0",
            "0F1F0F1F0F1F111F1F0F1F0F1F0",
            "0F1F0F1F0F1F111F1F0F1F0F1F0",
            "0F1F0F1F0F1FFFF1F0F1F0F1F0",
            "0F1F0F1F0F111111F0F1F0F1F0",
            "0F1F0F1F0FFFFFFF0F1F0F1F0",
            "0F1F0F1F0000000000F1F0F1F0",
            "0F1F0F1FFFFFFFFFFFF1F0F1F0",
            "0F1F0F1111111111111111F0F1F0",
            "0F1F0FFFFFFFFFFFFFFFFFF0F1F0",
            "0F1F00000000000000000000F1F0",
            "0F1FFFFFFFFFFFFFFFFFFFFFFFF1F0",
            "0F111111111111111111111111111F0",
            "0FFFFFFFFFFFFFFFFFFFFFFFFFFFFF0",
            "00000000000000000000000000000000",
        ]
        for y, line in enumerate(demo):
            if y >= self.grid_height:
                break
            for x, char in enumerate(line):
                if x >= self.grid_width:
                    break
                if char in self.palette:
                    self.grid_data[y][x] = char
        self.draw_grid()
        self.save_to_history()
        self.update_code_from_grid()


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

更多推荐