在做桌面自动化项目时,很多人觉得“全自动绘制一张像素画”就是把鼠标点到每个格子上去。实际动手后才发现,真正难的是三件事:第一,如何把任意图片变成可绘制的像素模板;第二,如何让程序准确找到画布位置,而不是靠肉眼拖拽鼠标;第三,如何判断一次点击是否真的画上了,画错之后怎么补。这篇文章以《明日方舟》桌面客户端作为演示目标,讲解一套基于 Python、OpenCV 与 PyAutoGUI 的像素画全自动绘制方案。方案不修改游戏文件、不读取内存、不发送任何网络数据包,只做截图识别、颜色匹配和模拟鼠标操作,属于桌面自动化控制层面的常规工程实现。
为了让方案具备可维护性,这里的核心实现会按照 V1.2 版本的思路来组织。也就是说,不满足于“能跑通一次”,而是把 DPI 缩放、窗口遮挡、绘制后校验、失败重试、安全停止这些问题一起考虑进去。下面先拆解全流程,再逐步给出可运行的代码和参数说明。
1. 先拆解全自动绘制像素画的完整链路
1.1 为什么不能直接“模拟鼠标一顿乱点”
如果只看最终效果,自动绘制像素画就是在画布上不断用不同颜色填充格子。但程序没有人类的视觉,它不知道“画布左上角在哪里”“一个格子多宽多高”“调色板里的某个颜色对应哪个坐标”。如果直接把整个绘制过程写成一串固定坐标的鼠标点击,那么一旦窗口移动、分辨率变化、任务栏遮挡,所有坐标全部失效。
所以一个可靠的自动化方案,不能只写鼠标事件,必须先把下面这条链路跑通:
图片输入 -> 像素模板生成 -> 窗口定位 -> 画布ROI识别 -> 坐标映射 -> 颜色选择 -> 格子点击 -> 截图校验 -> 失败重试。
其中任何一环脱节,最后的绘制效果都会出现错位、漏色、重复点色等问题。尤其是坐标映射,如果映射公式错误,程序会非常稳定地把每个格子点偏,而且肉眼很难发现。
1.2 从图片到画布需要经过哪些模块
可以把整个项目拆成四个独立模块:
- 模板模块:读取图片,缩放到目标网格尺寸,量化成有限数量的颜色,导出 JSON 模板。
- 定位模块:通过窗口标题或类名找到目标窗口,拿到窗口在屏幕上的位置和尺寸。
- 映射模块:把模板里的格子坐标转换成屏幕上的点击坐标。
- 执行模块:按模板顺序,模拟鼠标在调色板和画布之间切换,完成绘制并校验结果。
模块之间通过配置文件解耦。比如画布ROI和调色板位置变化时,不需要改代码,只需要改配置文件。这是工程化项目和平时代码脚本最明显的区别。
1.3 V1.2 版本需要额外考虑的问题
基础版本只要解决“能画出来”的问题,V1.2 版本要解决的是“画得准、画得稳”的问题。在实际迭代中,通常会加入以下功能:
- DPI 感知:Windows 系统如果开启了 125% 或 150% 缩放,
win32gui拿到的窗口坐标可能和实际鼠标坐标不一致,需要在程序启动时先声明 DPI 感知。 - 画布区域校验:不再假设画布永远在固定位置,而是截图后用 OpenCV 或手动配置的 ROI 做二次确认。
- 点击后校验:每次点击之后,截图目标附近区域,计算期望颜色和实际颜色的误差,误差超过阈值时自动重试。
- 安全停止:绘制过程中支持通过热键中止,防止误操作导致无法退出。
这些能力正是“演示脚本”和“可维护工具”之间的分水岭。
2. 环境准备与依赖安装
2.1 推荐开发环境
自动绘制方案主要依赖窗口截屏和鼠标模拟,所以优先选择 Windows 环境。macOS 和 Linux 也能实现,但在窗口句柄、权限控制方面差异较大,下面的代码示例以 Windows 10/11 为主。
建议环境如下:
| 项目 | 推荐版本 | 说明 |
|---|---|---|
| 操作系统 | Windows 10/11 | 窗口 API 和 DPI 控制比较成熟 |
| Python | 3.10 或 3.11 | 兼容 OpenCV、PyAutoGUI、pynput |
| 目标程序 | 《明日方舟》桌面客户端 | 也可用其他可定点绘制的桌面程序替代 |
| 显示器 | 1080P 或以上 | 低分辨率下画布格子过小,校验截图困难 |
| 权限 | 普通管理员权限 | 部分系统需要管理员权限才能模拟鼠标 |
如果你只是学习自动化流程,不一定要用游戏客户端。可以用 Windows 自带的“画图”程序替代,因为“画图”里同样有画布、调色板和填充功能,流程完全一致,而且更可控。
2.2 安装 Python 依赖
项目只需要五个核心库:
- Pillow:图片读取、缩放、颜色量化。
- opencv-python:图像处理,画布区域识别、颜色误差计算。
- pywin32:Windows 窗口句柄获取、窗口信息读取。
- pyautogui:鼠标移动、点击、屏幕尺寸读取。
- pynput:全局热键监听,用于安全停止。
在项目目录下创建requirements.txt:
Pillow==10.4.0 opencv-python==4.10.0.84 pyautogui==0.9.54 pywin32==306 pynput==1.7.7安装命令:
pip install -r requirements.txt如果安装过程中遇到编译问题,优先换 Python 小版本,不要用 Python 3.13 等过新版本,某些 Windows 轮子可能还没有适配完全。
2.3 先验证截图和控制链路是否可用
在执行主流程之前,最好先用一个 30 秒小脚本验证三件事:能不能截到窗口图像,能不能移动鼠标,能不能点击。这样可以尽快把环境问题暴露出来,而不是等到绘制到一半才发现权限不足。
import win32gui import win32con import pyautogui import time # 声明 DPI 感知,防止坐标偏移 try: win32con.PROCESS_DPI_UNAWARE = 0 win32gui.SetProcessDPIAware() except Exception as e: print("DPI awareness failed:", e) title_fragment = "明日方舟" hwnd = None def enum_callback(h, _): global hwnd if win32gui.IsWindowVisible(h): title = win32gui.GetWindowText(h) if title_fragment in title: hwnd = h print("found window:", hwnd, title) win32gui.EnumWindows(enum_callback, None) if hwnd: rect = win32gui.GetWindowRect(hwnd) print("window rect:", rect) else: print("window not found")这段代码的作用是定位目标窗口。如果打印出的窗口 rect 和实际视觉位置明显不一致,通常是因为没有设置 DPI 感知,或者鼠标位置包含多个屏幕。之后再做鼠标模拟时,坐标会整体偏移。
3. 项目结构与启动流程
3.1 目录结构设计
不建议把所有逻辑写在一个文件里。随着功能增多,代码会迅速膨胀。推荐目录结构如下:
pixel_drawer/ ├── main.py # 主流程控制 ├── config.yaml # 窗口、ROI、调色板配置 ├── template_generator.py # 图片转像素模板 ├── window_locator.py # 窗口定位与截图 ├── coordinate_mapper.py # 坐标映射 ├── draw_engine.py # 鼠标绘制与校验 ├── templates/ │ └── example.json # 生成的像素模板 ├── screenshots/ │ └── result.png # 最终截图 └── requirements.txt这样拆分的目的是让每个模块可以直接独立测试。比如template_generator.py可以不用鼠标,只处理图片;window_locator.py可以只打印窗口坐标,不点击鼠标。
3.2 配置文件:窗口标题、画布ROI与调色板位置
使用 YAML 配置文件,可以把和环境强相关的参数从代码中剥离出来。常见配置文件结构如下:
window: title_fragment: "明日方舟" client_area: true canvas_roi: x: 220 y: 160 width: 640 height: 640 grid: width: 16 height: 16 palette_positions: "#000000": [80, 120] "#FFFFFF": [120, 120] "#FF0000": [160, 120] "#00FF00": [200, 120] "#0000FF": [240, 120] draw: cell_padding: 2 click_interval: 0.3 palette_click_interval: 0.2 max_retry: 3 color_threshold: 30 hotkey: stop: "esc"其中canvas_roi是画布区域在窗口客户区内的相对坐标。palette_positions是每个颜色对应的调色板色块坐标。这里的坐标都是屏幕绝对坐标,只用于示例。实际项目需要根据目标画布界面重新标注。
3.3 主程序流程
主程序按照以下顺序执行:
- 读取配置文件。
- 启动全局热键监听。
- 定位目标窗口并截图。
- 从模板文件加载像素数据。
- 根据模板数据、ROI、窗口坐标计算映射关系。
- 执行绘制循环。
- 绘制完成后截图保存,输出统计信息。
伪代码如下:
def main(): config = load_config("config.yaml") start_hotkey_listener(config["hotkey"]["stop"]) hwnd = find_window(config["window"]["title_fragment"]) window_rect = get_window_rect(hwnd) screenshot = capture_window(hwnd) template = load_template("templates/example.json") mapper = CoordinateMapper(window_rect, config["canvas_roi"], template["width"], template["height"]) engine = DrawEngine(config, mapper) result = engine.run(template) print(result.stats())这里需要注意,capture_window截取的图像可能和用户视觉看到的窗口尺寸不同,原因是 DPI 缩放。所以一定要在入口处统一调用SetProcessDPIAware(),让窗口坐标和图像截取坐标保持一致。
4. 图片转像素模板:先解决“画什么”
4.1 缩放与网格化
像素画模板并不是把图片原样缩小,而是把图片划分成固定网格,每个格子里取一个代表色。比如要在 16x16 的画布上绘制,就需要把图片缩放到 16x16 像素,再放大每个像素为“一格”。
使用 Pillow 处理:
from PIL import Image def create_template_from_image(image_path, grid_width, grid_height): image = Image.open(image_path).convert("RGB") image = image.resize((grid_width, grid_height), Image.Resampling.LANCZOS) return image这里选择 LANCZOS 是因为它对图片缩放后的颜色保留更好。缩得太小后,原图的细节会丢失,这是像素画的正常效果,不一定需要额外增强。
4.2 颜色量化与调色板
画布通常只能显示有限数量的颜色。为了减少调色板切换次数,需要把缩放后的颜色合并成少量颜色。调用 Pillow 的quantize方法可以将图片量化到指定颜色数:
def quantize_image(image, color_count=16): q_image = image.quantize(colors=color_count, method=Image.MEDIANCUT) palette = q_image.getpalette()[: color_count * 3] palette_colors = [ "#{:02X}{:02X}{:02X}".format(*palette[i * 3 : i * 3 + 3]) for i in range(color_count) ] return q_image, palette_colors需要注意的是,quantize返回的调色板顺序不一定是按颜色出现频率排列,所以最终绘制时,不能通过“调色板第几个色块”去推断颜色。
4.3 模板JSON导出格式
为了让绘制引擎和模板生成互相独立,把像素数据导出为标准 JSON:
{ "width": 16, "height": 16, "palette": [ "#000000", "#FFFFFF", "#FF0000", "#00FF00" ], "pixels": [ [0, 0, 0], [1, 0, 1], [2, 0, 1], [3, 0, 2] ] }其中pixels每行表示一个格子,前两个数字是格子坐标(x, y),第三个数字是调色板索引。这里使用调色板索引而不是直接写颜色,是为了节省 JSON 体积,也方便绘制引擎按索引查调色板坐标。
生成像素列表的代码:
def export_pixels(q_image, palette_colors): color_to_index = {color: i for i, color in enumerate(palette_colors)} pixels = [] width, height = q_image.size for y in range(height): for x in range(width): r, g, b = q_image.convert("RGB").getpixel((x, y)) color = "#{:02X}{:02X}{:02X}".format(r, g, b) pixels.append([x, y, color_to_index[color]]) return pixels5. 窗口定位与画布识别:解决“画在哪”
5.1 按窗口标题获取进程窗口
Windows 下定位窗口最常用的方式是枚举所有可见窗口,再根据标题片段匹配。前面章节已经给出了窗口枚举代码。这里需要补充的是获取窗口客户区矩形。
窗口矩形分为窗口矩形和客户区矩形。前者包含标题栏和边框,后者才是内容区域。对于绘制画布来说,应该使用客户区矩形。
import win32gui import win32con def get_client_rect(hwnd): left, top, right, bottom = win32gui.GetClientRect(hwnd) left, top = win32gui.ClientToScreen(hwnd, (left, top)) right, bottom = win32gui.ClientToScreen(hwnd, (right, bottom)) return (left, top, right, bottom)这样获取到的坐标是屏幕绝对坐标,可以直接用于 PyAutoGUI 的鼠标操作。
5.2 截取窗口画面并裁剪画布区域
截取窗口使用 Pillow 的ImageGrab。如果窗口被遮挡,截图可能会取到遮挡内容。V1.2 版本建议在绘制前先置顶窗口。
from PIL import ImageGrab def capture_client_area(hwnd): rect = get_client_rect(hwnd) return ImageGrab.grab(bbox=rect)然后根据配置中的canvas_roi裁剪出画布区域。ROI 是相对客户区左上角的偏移,所以绘制坐标需要转成屏幕绝对坐标:
client_left, client_top, _, _ = get_client_rect(hwnd) roi_x = client_left + config["canvas_roi"]["x"] roi_y = client_top + config["canvas_roi"]["y"]5.3 坐标映射公式是核心
假设模板尺寸是grid_width x grid_height,画布ROI的宽度是roi_w,高度是roi_h,那么每个格子的宽度是:
cell_w = roi_w / grid_width cell_h = roi_h / grid_height格子中心点的屏幕坐标是:
center_x = roi_x + (tx + 0.5) * cell_w center_y = roi_y + (ty + 0.5) * cell_h如果画布格子和模板不是一一对应,中间还有留白或边距,可以增加cell_padding参数来收缩点击中心。例如让点击位置稍微偏向格子内部,避免点到边框:
center_x = roi_x + (tx + 0.5) * cell_w + config["draw"]["cell_padding"] center_y = roi_y + (ty + 0.5) * cell_h + config["draw"]["cell_padding"]这里需要说明,cell_padding的单位是像素,正常情况下设为 0 或 1 即可。如果画布边框较粗,再适当调大。
5.4 ROI配置:自动化不是百分百万能
有些人希望程序自动识别画布边界,也就是完全不需要配置 ROI。这种想法可以理解,但实现成本很高。没有 OpenCV 的模板特征时,程序无法区分“画布”和“背景面板”。比较稳妥的做法是:
- 在第一次运行时,手动截取窗口图片。
- 用画图工具查看画布区域的坐标。
- 把坐标写入配置文件。
这样只要窗口尺寸不变,后续运行都不需要再调整。如果用户的窗口分辨率变了,ROI 等比例变化时,可以先用client_rect宽高计算缩放比,再修正ROI。对于 V1.2 版本,直接支持配置“ROI 比例”比“ROI 绝对像素”更稳定:
canvas_roi: x: 0.1 y: 0.15 width: 0.5 height: 0.5这里的值表示相对于窗口客户区宽高的比例。在代码中先乘客户区宽高再取整。这样做的好处是分辨率变化后,画布位置仍然能保持相对正确。
6. 绘制执行引擎:把模板变成鼠标点击
6.1 调色板点击与格子点击的配合
绘制引擎的核心难点不是“点击一次”,而是“颜色切换”。如果每次画一个格子都先点调色板再点画布,颜色切换次数会非常频繁。更高效的做法是:
- 遍历模板像素,按颜色索引分组。
- 先点击一个颜色对应的调色板区域。
- 然后连续点击该颜色对应的所有格子。
- 再切换到下一个颜色。
这能减少鼠标在调色板和画布之间的移动次数。模板颜色通常只有几种到十几种,按颜色分组后,绘制速度会明显提高。
代码如下:
def build_color_groups(template): groups = {} for x, y, palette_index in template["pixels"]: groups.setdefault(palette_index, []).append((x, y)) return groups6.2 绘制循环与颜色切换策略
在绘制循环里,每个颜色一次切换,之后对该颜色所有格子逐个点击:
import pyautogui import time class DrawEngine: def __init__(self, config, mapper): self.config = config self.mapper = mapper def run(self, template): groups = build_color_groups(template) stats = {"total": 0, "success": 0, "failed": 0} for palette_index, cells in groups.items(): self.click_palette_color(palette_index) for cell in cells: ok = self.paint_cell(cell) stats["total"] += 1 if ok: stats["success"] += 1 else: stats["failed"] += 1 return stats def click_palette_color(self, palette_index): pos = self.config["palette_positions"][palette_index] pyautogui.moveTo(*pos, duration=0.1) pyautogui.click() time.sleep(self.config["draw"]["palette_click_interval"]) def paint_cell(self, cell): pos = self.mapper.get_click_point(cell) pyautogui.moveTo(*pos, duration=0.05) pyautogui.click() time.sleep(self.config["draw"]["click_interval"]) return True这里每个格子点击后都认为成功。如果需要校验,就不能这么简单,而是要在点击后截图分析。
6.3 点击后的结果校验与超时重试
V1.2 版本最值得加入的功能就是“点击后校验”。在每次点击画布后,截取该格子附近的一小片区域,计算平均颜色和期望颜色的欧氏距离是否小于阈值。
颜色误差计算:
import numpy as np def calculate_expected_rgb(hex_color): hex_color = hex_color.lstrip("#") return np.array([int(hex_color[i:i+2], 16) for i in (0, 2, 4)]) def color_distance(c1, c2): return float(np.linalg.norm(c1 - c2))点击后截取小区域可以这样做:
def capture_cell_color(center_x, center_y, radius=3): bbox = (center_x - radius, center_y - radius, center_x + radius, center_y + radius) img = ImageGrab.grab(bbox=bbox) img = img.convert("RGB") pixels = list(img.getdata()) avg = np.mean(pixels, axis=0) return avg然后重试逻辑:
def paint_cell_with_retry(self, cell, expected_hex): expected_rgb = calculate_expected_rgb(expected_hex) for attempt in range(self.config["draw"]["max_retry"] + 1): pos = self.mapper.get_click_point(cell) pyautogui.click(*pos) time.sleep(self.config["draw"]["click_interval"] * 2) actual_rgb = capture_cell_color(pos[0], pos[1]) if color_distance(actual_rgb, expected_rgb) < self.config["draw"]["color_threshold"]: return True return False这里需要注意的是,capture_cell_color的radius不能设得太大,否则会采集到周围格子的颜色。如果画布格子很小,radius最好设置为格子边长的四分之一以内。
6.4 安全停止与异常兜底
自动绘制过程可能持续几分钟,用户很可能临时想终止。建议使用pynput监听全局热键,一旦按下指定键,就停止循环。
from pynput import keyboard stop_flag = False def on_press(key): global stop_flag if key == keyboard.Key.esc: stop_flag = True listener = keyboard.Listener(on_press=on_press) listener.start()在绘制循环中,每个格子绘制前检查标志位:
if stop_flag: print("stop requested, break loop") break同时还要在顶层捕获异常,避免鼠标操作报错导致程序直接崩溃:
def safe_run(engine, template): try: return engine.run(template) except Exception as e: print("draw failed:", e) return None异常被捕获后,至少要把窗口恢复到正常状态,不能把鼠标停在某个无法点击的位置。
7. 运行验证与性能分析
7.1 运行日志长什么样
为了便于排查,建议每次运行都输出结构化日志,而不是只打印“开始”和“结束”。下面是一个示例:
[10:00:01] INFO window found: 明日方舟, hwnd=458952, rect=(100, 100, 900, 720) [10:00:01] INFO client rect=(100, 140, 900, 700), roi=(220, 160, 860, 800) [10:00:02] INFO template loaded: 16x16, colors=12, pixels=256 [10:00:03] INFO color group 0: #000000, cells=20 [10:00:08] INFO color group 0 done, success=20, failed=0 [10:00:30] INFO color group 1: #FFFFFF, cells=18 ... [10:02:10] INFO draw finished, success=256, failed=0通过日志可以快速定位是在哪个颜色、哪个格子出现问题。生产环境还可以把日志写入文件,方便复盘。
7.2 用覆盖率评价绘制结果
绘制结束后,应该截取整个画布区域,和模板图像做对比。统计每个格子是否是目标颜色,计算覆盖率:
def calculate_coverage(actual_image, template): ...这个函数需要把截图缩放到和模板相同的 16x16,再逐格比较颜色误差。如果覆盖率低于 95%,说明绘制过程中存在大量点偏或漏点的情况。如果覆盖率在 99% 以上,说明绘制基本正确。
对于校验失败而不能重试成功的格子,建议在结果图上用红色边框标出来。这样用户可以直接看到哪些位置有问题,不用对着原图一个个数格子。
7.3 可能出现的失败模式
运行验证阶段常见失败模式包括:
| 失败模式 | 表现 | 常见原因 |
|---|---|---|
| 整体偏色 | 绘制颜色与模板明显不同 | 目标程序有滤镜或亮度调整 |
| 局部漏画 | 某些格子颜色为空 | 点击过快,颜色切换未生效 |
| 坐标偏移 | 绘制图案整体位移 | DPI 感知未设置或窗口移动 |
| 随机点偏 | 只有部分格子错位 | 画布格子尺寸计算精度不足 |
| 程序卡死 | 鼠标停在画布上不动 | 目标程序失去响应或热键监听失效 |
每种失败模式都要回到对应的模块去排查。不是所有问题都要靠“增加睡眠时间”解决。
8. 常见问题排查
8.1 找不到窗口或截图为黑屏
如果程序运行后一直提示“window not found”,首先确认目标窗口标题是否包含配置中的关键词。如果窗口被最小化,IsWindowVisible可能返回 false。解决办法是在运行前手动恢复窗口,或者代码中调用ShowWindow(hwnd, SW_RESTORE)。
如果窗口能找到但截图为黑屏,通常是目标程序使用了 GPU 渲染,普通ImageGrab无法直接捕获。解决办法有两个方向:
- 关闭硬件加速,让目标程序使用软件渲染。
- 改用 DXGI 或窗口捕获专用库。
在《明日方舟》这类游戏客户端中,如果遇到黑屏,先尝试把窗口从全屏切换到窗口化模式。
8.2 鼠标点击位置整体偏移
整体偏移是最常见的问题,原因大多是 DPI 缩放不一致。Windows 会把每个进程的 DPI 感知级别分开,如果不调用SetProcessDPIAware(),get_window_rect返回的坐标可能是虚拟坐标,而 PyAutoGUI 使用的是物理坐标。
排查步骤如下:
- 打印窗口矩形坐标。
- 手动把鼠标移动到窗口左上角,观察屏幕坐标。
- 对比两者是否一致。
如果不一致,就在程序入口增加 DPI 感知,并重启终端。另外,多显示器场景下,鼠标可能跨屏显示,需要确认目标窗口所在显示器在哪里。
8.3 颜色匹配总是偏差
点击后校验失败比例很高时,不要急着调阈值,先检查目标程序是否对画面应用了滤镜或透明度。例如游戏中常见的“雾气”“描边”“阴影”效果都会改变格子中心颜色。
处理方式有:
- 扩大采样半径,采集整个格子面积的平均色。
- 缩小色差阈值,但不要直接改成 100,否则会放过真实错误。
- 在模板量化之前,对原图做一次色域压缩,使模板颜色更接近目标画布的可选颜色。
如果调色板颜色本身就有多个相似色,建议在配置文件里手动重命名模板颜色,使量化后的色板和画布调色板一致。
8.4 绘制到一半程序失去响应
一种常见原因是连续点击过快,目标程序来不及响应鼠标事件。PyAutoGUI 的点击虽然会休眠,但默认时间很短。建议在每次点击后至少等待 100 到 300 毫秒。
另一种原因是程序在后台被系统弹窗或安全软件拦截。排查时先打开任务管理器,确认目标进程是否无响应。如果是,则需要降低发送速率,而不是继续重试。
8.5 系统安全软件拦截鼠标操作
某些安全软件会把高频鼠标操作识别成异常行为,从而拦截 PyAutoGUI 的点击。遇到这种情况,可以降低点击频率,或者将脚本目录加入白名单。但不要用“更隐蔽”的方式绕过安全机制,那会带来额外风险。从工程实践角度,高频点击本身对目标程序也不友好。
9. 生产级优化与可复用清单
9.1 学习环境、测试环境、生产环境的分层建议
如果你只是学习自动化流程,可以用 Windows 自带画图程序,准备一张 16x16 像素的图片,先跑通模板生成、窗口定位和坐标映射。测试环境需要加入结果校验和重试机制,模拟窗口被遮挡、分辨率变化等异常场景。生产环境则要考虑更多问题:
- 配置外置化:把窗口标题、ROI、调色板位置放到单独的 YAML 配置文件中,避免每次发布都改代码。
- 日志与监控:绘制开始、结束、失败率、失败坐标都要记录。
- 权限控制:只运行在受控机器上,避免用户手动修改配置导致程序点错位置。
- 回滚方案:如果模板生成和画布实际尺寸不一致,要能快速终止并恢复窗口位置。
- 版本管理:V1.2 版本的代码必须带上参数说明和升级记录。
9.2 绘制前检查清单
每次运行自动绘制前,建议按以下清单检查:
| 检查项 | 检查内容 | 通过标准 |
|---|---|---|
| 窗口是否启动 | 目标窗口标题是否匹配 | 能找到唯一窗口 |
| 窗口是否置顶 | 绘制过程不能被遮挡 | 窗口在前台 |
| DPI 是否感知 | 窗口坐标和鼠标坐标一致 | 手动测试左上角 |
| 画布ROI是否正确 | 配置的 ROI 是否覆盖画布 | 截图裁剪后边界符合预期 |
| 调色板位置是否正确 | 每个颜色坐标可点击到 | 手动验证不同色块 |
| 模板尺寸和画布尺寸 | 模板格子和画布格子比例匹配 | 先画 4x4 测试图 |
| 安全停止热键 | ESC 能随时退出 | 按下后立刻停止 |
这个清单建议放到项目的README.md中,每次运行前过一遍。尤其是第一次在新电脑上运行时,可以省掉很多“图完全画飞”的尴尬。
9.3 扩展方向:自定义画笔、中途暂停、多画布并发
把基础流程跑通之后,可以在 V1.2 基础上继续扩展。
- 自定义画笔:有些绘图画布支持不同画笔尺寸,可以按模板中的色块大小自动切换画笔。
- 中途暂停:在绘制过程中允许用户随时暂停和继续,避免中断后只能从头开始。
- 多画布并发:如果机器有多台显示器,可以同时控制多个画布窗口。但这需要更复杂的线程模型和互斥控制,不建议在 V1.2 中直接引入。
- 模板预览:在绘制前生成一个本地预览图,让用户提前看到最终效果,减少无效绘制时间。
这些扩展方向都有一个共同点:在保证坐标映射准确的前提下,先增加“可观察性”,再增加“自动化程度”。顺序反了,功能越多,出错越难查。对于像素画自动绘制这个项目而言,最重要的能力不是点击速度,而是“错了以后能发现、能重试、能停止”。把这个原则落地,V1.2 版本才算真正稳定可用了。