大漠FindColor易语言封装:模块化颜色查找原理与实战
说实话每次有人问我“大漠模块怎么做”我第一反应都是反问一句你是想直接调用还是想做成能拿出去复用、也让队友看得懂的正规模块如果你的回答是后者那用易语言封装大漠的查找颜色功能尤其是FindColor这个高频接口是一个非常值得认真做的入门项目。它不复杂但把参数设计、颜色原理、错误处理、性能意识全部过一遍之后你对“模块化封装”这件事的理解会和直接复制粘贴代码完全不一样。这篇文章我就用一次实际封装经历来讲全程围绕FindColor展开。从接口拆解、颜色计算原理到模块结构设计、代码实现再到坑点排查和性能优化尽量把能踩的坑和该有的思路一次说透。1. 先搞明白大漠插件的FindColor到底是什么1.1 接口原型与参数逐项拆解大漠插件在自动化领域用得非常多说它是很多易语言用户的“启蒙插件”也不过分。FindColor这个接口通俗讲就是在屏幕或者指定窗口区域内按你给出的颜色条件去搜索第一个符合要求的像素点然后把坐标告诉你。接口长这样FindColor(x1, y1, x2, y2, color, sim, dir, intX, intY)逐项拆开看x1, y1查找区域的左上角坐标。x2, y2查找区域的右下角坐标。color颜色描述字符串支持多种格式后面详细讲。sim相似度取值在0到1.0之间比如0.9表示允许和指定颜色有10%的偏差。dir查找方向0到7分别代表从左上到右下、从左下到右上等不同扫描顺序。intX, intY输出参数如果找到了颜色这里会返回像素坐标如果没找到根据版本不同会返回-1或0。我刚接触时最不理解的就是dir。为什么找个颜色还要分方向后来实际做界面自动化才明白当界面里多个按钮颜色相似时你需要确定“先找哪个”。比如左上角有个返回按钮右下角也有个关闭按钮都长一个色这时候查找方向直接决定你会拿到哪个坐标。方向选错了找到的坐标就不是你想要的。sim也是一个容易犯迷糊的点。很多人以为相似度是0-100的整数开心地填了个100结果搜索半天找不到颜色。大漠卧龙等插件的相似度是0-1.0的小数填1.0才是完全匹配填0.8左右偏向宽容匹配。颜色必然受系统主题、窗口阴影、字体渲染等因素影响完全不给自己留容差很容易翻车。1.2 颜色是怎么“算出来”的颜色值、容差与相似度要真正用好FindColor光记住参数不够还得理解它背后怎么判断“两个颜色像不像”。计算机里颜色通常用 RGB 三个分量表示每个分量是0-255在十六进制里就是两位。我们平时看到的RRGGBB比如FFFFFF是白色、000000是黑色、FF0000是纯红。但要注意一点很多底层图像接口在内存里是按 BGR 顺序存放的也就是蓝、绿、红。大漠的接口文档明确指出颜色字符串用的是RRGGBB格式也就是十六进制里从左到右是红、绿、蓝。你如果用抓色工具抓到的是 BGR 格式直接照抄进去就会发生“找一个永远找不到的颜色”的诡异情况。早年代码里大量出现这种问题排查半天发现不是代码错是颜色格式写反了。FindColor的color参数除了支持单个颜色还支持带容差的形式例如FF0000-101010这里的-101010表示每个颜色分量允许偏差0x10也就是十六进制的16。相当于在红色分量、绿色分量、蓝色分量上都允许 ±16 的浮动形成一个“颜色范围”。这比只靠sim调整更直观也更好控制。实际操作中我常常两个一起用color串里加上容差sim再给个0.9双保险。相似度的计算原理说白了就是把当前像素的 RGB 三个分量分别和目标颜色比较算出一个总偏差比例。这个比例落在你给的相似度范围内就算匹配成功。它和“容差”不是一回事容差是直接限定每个分量的偏移量相似度是计算整体偏离程度。理解这一点你才能在不同场景下选对调节工具个别分量容易受光影影响用容差整体颜色偏一点但三个分量一起漂用相似度。从底层原理往回看FindColor本质上是在做“遍历像素 颜色距离计算”效率瓶颈往往在查找区域的面积上。你给它画一个1920x1080的全屏区域它就要比对两百万个像素点。如果把区域缩小到200x80同样的逻辑速度快几十倍。这一点到后面优化部分还会展开。2. 为什么要封装成易语言模块2.1 直接写调用代码和封装成模块的差别有的朋友说我不就一行对象.方法 (FindColor, ...)的事吗干嘛非要封装一层这句话在只写一个小脚本、一个人用、跑完就扔的场景下成立。但只要脚本逻辑稍微变复杂或者说这个脚本以后要维护、要给别人用、要接入更多功能你不用模块化写法后面基本就是灾难。直接调用的痛点非常明显每个调用点都要重复写参数颜色串、相似度、方向一多代码里到处都是魔法数字。返回值处理不统一。有人写成返回真/假有人写成返回坐标队友接手时完全不知道函数到底会返回什么。出错时没有统一的日志。颜色没找到到底是因为区域没截对还是因为区域坐标算错了只能靠人肉猜。想加个重试机制必须去每个调用点改代码。而封装成模块之后外部调用者只需要关心三个东西传入查找区域、传入颜色条件、拿回坐标和结果状态。至于内部是调大漠还是调别的引擎、要不要做重试、失败后记不记日志全部由模块自己处理。我自己的经验是封装模块最大的收益不是省代码行数而是“降低调用方的认知负担”。写自动化脚本时人的注意力应该放在业务逻辑上比如“先找登录按钮找到之后点击它找不到就截图存档”而不是每次都纠结dir1和dir2到底哪个是右上角开始的顺序。2.2 一个合理模块应该具备的三个层次一个真正能拿出去用的模块至少要分三个层次第一层是“基础封装层”。把底层插件对象的方法调用包装成易语言子程序参数一一对应返回统一的结果结构。这是最机械的一层也是所有后续功能的地基。第二层是“便捷策略层”。比如自动拼接颜色容差、默认相似度、默认查找方向、超时重试、失败回调。这一层开始体现封装者对实际业务的理解。不是为了封而封而是为了让外部调用更顺手。第三层是“业务语义层”。举个例子你封装一个查找主界面开始按钮内部其实先查找文字区域附近的一种颜色再拿坐标去偏移点击。这种业务级函数直接对应你的脚本场景可读性极高测试也好写。我这里强调一点很多初学者只做第一层以为把一个方法包装成子程序就算完成模块了。这个不能说错但价值有限。真正让模块好用的是第二层和第三层的设计。下面我讲的实操会从第一层开始然后逐步带你加上第二层的东西。3. 从零开始制作大漠查找颜色模块3.1 环境准备注册组件与创建对象动手之前先确认环境。大漠插件本身是一个 COM 组件它的 DLL 文件需要先注册到系统里易语言里才能用“创建对象”的方式调用它的方法。注册方式通常是regsvr32 大漠.dll注册成功后在易语言里写两行代码测试一下能否正常创建对象.版本 2 .支持库 spec .子程序 测试创建对象 .局部变量 大漠对象, 对象 大漠对象.创建 (“dm.dmsoft”, ) 调试输出 (大漠对象.是否为空 ())如果返回不为空说明组件注册成功可以继续下一步。如果为空优先检查三件事注册路径是否包含中文或空格、是否用管理员权限运行、DLL 位数和易语言位数是否一致。32 位易语言配 32 位大漠 DLL这是最常见的基础配置。对象创建成功后我个人习惯把它保存在一个全局变量或者程序集变量里。FindColor这种高频函数每次调用都重新创建对象是很大的浪费因为创建对象的开销完全没必要承担。模块设计里我会设计一个“初始化模块”的公开子程序模块内部持有唯一的插件对象实例。3.2 编写FindColor封装子程序这一节是核心。我们直接写一个名叫查找颜色的公开子程序它的作用就是把大漠的FindColor调用包起来对外提供一个简单、稳定的接口。先看代码框架.版本 2 .支持库 spec .程序集 大漠模块集 .程序集变量 插件对象, 对象 .程序集变量 模块已初始化, 逻辑型 .子程序 初始化大漠, 逻辑型, 公开 .局部变量 创建结果, 逻辑型 如果真 (模块已初始化) 返回 (真) 如果真结束 插件对象.创建 (“dm.dmsoft”, ) 如果 (插件对象.是否为空 ()) 调试输出 (“插件创建失败”) 返回 (假) 否则 模块已初始化 真 返回 (真) 如果结束 .子程序 查找颜色, 逻辑型, 公开 .参数 查找区域左, 整数型 .参数 查找区域顶, 整数型 .参数 查找区域右, 整数型 .参数 查找区域底, 整数型 .参数 颜色描述, 文本型 .参数 颜色相似度, 双精度小数型 .参数 查找方向, 整数型 .参数 返回横坐标, 整数型, 参考 .参数 返回纵坐标, 整数型, 参考 .局部变量 调用结果, 整数型 如果真 (插件对象.是否为空 ()) 返回横坐标 -1 返回纵坐标 -1 返回 (假) 如果真结束 调用结果 插件对象.数值方法 (“FindColor”, 查找区域左, 查找区域顶, 查找区域右, 查找区域底, 颜色描述, 颜色相似度, 查找方向, 返回横坐标, 返回纵坐标) 如果 (调用结果 ≥ 1) 返回 (真) 否则 返回横坐标 -1 返回纵坐标 -1 返回 (假) 如果结束几个关键点说一下调用结果的判断。不同大漠版本对找到颜色的返回值定义不完全一致大部分情况返回1表示找到返回0表示没找到也有版本返回坐标序号。稳妥的办法是判断“是否大于 0”而不是判断“是否等于 1”。用参考参数返回坐标。易语言里参考类似于其他语言的out参数外部传入变量后子程序会直接修改外部变量的值。这样封装函数可以同时返回“是否找到”和“坐标在哪”比用一个文本拼接返回结果要清晰得多。调用前检查插件对象是否为空。这个检查看似简单实际上能挡住一大半“模块没初始化就开始调用”的低级错误。如果你愿意更进一步可以把参数的默认值也加上。比如颜色相似度默认填0.9查找方向默认填0。易语言支持参数默认值外部调用就可以偷懒只传必要参数。不过要注意默认值一旦设置后面的参数要么都省略要么都传值不能跳着传。3.3 加入超时重试、日志和默认参数第一层封装完成接下来把“便捷策略层”的东西补上。我常用的增强功能有三个超时重试、日志记录、颜色串预处理。超时重试的典型场景是界面还没完全加载出来按钮颜色还没渲染到位你第一次找自然找不到。这时候如果直接返回失败脚本就会走错误分支其实等 200 毫秒再找一次就能成功。封装一个带重试的函数逻辑会比较长但非常实用.子程序 查找颜色带重试, 逻辑型, 公开 .参数 查找区域左, 整数型 .参数 查找区域顶, 整数型 .参数 查找区域右, 整数型 .参数 查找区域底, 整数型 .参数 颜色描述, 文本型 .参数 最大重试次数, 整数型 .参数 重试间隔毫秒, 整数型 .参数 返回横坐标, 整数型, 参考 .参数 返回纵坐标, 整数型, 参考 .局部变量 当前次数, 整数型 当前次数 0 循环判断首 () 当前次数 当前次数 1 如果 (查找颜色 (查找区域左, 查找区域顶, 查找区域右, 查找区域底, 颜色描述, 0.9, 0, 返回横坐标, 返回纵坐标)) 返回 (真) 否则 程序_延时 (重试间隔毫秒) 如果结束 循环判断尾 (当前次数 最大重试次数) 返回 (假)日志记录也不要复杂化。最简单的做法是在函数内部加一个日志输出把查找区域、颜色串、结果都拼成一行调试文本。平时不用开一旦脚本跑飞了开着日志就能快速定位是哪个步骤出了问题。我自己习惯的做法是封装一个内部子程序写日志内部先判断一个“是否启用日志”的标志为真才输出避免高频调用时刷屏。颜色串预处理主要是处理用户输入格式。比如你希望外部调用时传“FF0000”就行内部自动补成“FF0000-101010”再配合默认相似度0.9。这样业务代码看起来非常清爽。但要注意不是所有场景都该给颜色加容差。如果你做的是像素级严格匹配比如找某个色值非常精确的状态指示灯统一加容差反而会误匹配。所以我建议把“是否自动加容差”做成一个模块级开关或者提供一个额外参数来控制。4. 实战扩展多颜色与横向变体4.1 多色查找与多点找色的区别FindColor学完之后你几乎一定会遇到需求升级的情况一个按钮上有高光、暗部、文字三块颜色你希望匹配的是“这几种颜色同时出现且相对位置固定”的目标。这时候有两个扩展方向。第一个方向是多颜色查找大漠里有FindColorEx这样的接口可以同时传入多个独立的颜色条件只要区域内存在其中任意一个颜色就算命中。它适合的场景是目标元素本身颜色不稳定可能有几种典型色调比如普通状态和悬停状态的外框色不一样。第二个方向是多点找色典型接口是FindMultiColor。它不再关心“任意颜色”而是先找一个主色再以主色坐标为基准验证相对偏移处的若干子色是否满足条件。这个能力非常强因为 UI 元素的颜色分布通常是稳定的。比如一个黄色警告框主色是黄色左上角有红色感叹号图标右上角有个小叉号三点同时成立才能确定找的就是这个警告框。从模块封装的角度说我建议你先做FindColor再做FindMultiColor不要急着直接上FindColorEx。因为多点找色涉及“颜色坐标偏移表”的概念理解它需要先对坐标系统有直觉而FindColor恰好能建立这种直觉你先知道一个点的精确坐标再在这个坐标基础上做加减偏移去验证下一个点。4.2 封装一个更灵活的查找函数封装多个查找函数时有一个很容易犯的设计错误把每个接口都封成独立子程序导致外部代码一会儿调“查找颜色”一会儿调“查找多点颜色”还得记着哪个函数支持方向、哪个不支持。我的做法是只在模块内部为不同接口写独立的基础封装对外只暴露一个“按配置查找”的入口。外部调用者传入一个“查找配置”类型的数据结构包含区域、颜色模式、颜色数据、相似度、方向、重试次数。模块内部根据模式自动路由到FindColor还是FindMultiColor。这个设计在易语言里可以这样实现定义一个自定义数据类型里面放区域坐标、颜色字符串、相似度、方向、模式标识。然后公开子程序接收这个数据类型变量。外部代码构造好配置调用一次拿结果。这样做的好处是什么好处是业务代码完全不需要关心底层是多点还是单点。今天你用的是单色定位明天改成多点定位只改颜色数据和模式标识调用逻辑一行都不用动。对于长期维护的脚本来说这种稳定接口的价值比“多写几个函数”的价值高得多。5. 常见错误和性能优化实录5.1 新手必踩的六个坑我整理了一下自己做模块和帮别人看代码时遇到的高频问题直接列成表方便对照排查。现象可能原因处理办法创建对象失败返回空对象大漠DLL未注册或注册失败用管理员权限重新执行 regsvr32找不到颜色但肉眼明明能看到颜色字符串用了BGR格式抓色后用转换工具确认RRGGBB顺序找到坐标但位置不对查找方向设置不合适打印坐标对照dir 0-7含义调整偶尔找不到时好时坏相似度太严或区域太大调低相似度到0.8-0.9缩小查找范围脚本运行一会就卡顿高频全屏查找无延时加入重试间隔避免空转同一颜色不同屏幕表现不同系统主题/缩放影响渲染使用多颜色或多点匹配提升稳定度这里重点说下“区域选大”的问题。很多新人图省事直接把整个屏幕作为查找区域。这样做不是找不到而是太慢、容易误报。屏幕上同一种颜色可能出现在多个无关位置区域一大FindColor只能返回按方向扫描到的第一个这个“第一个”往往不是你想要的。我的建议是区域先圈小圈到目标元素附近留出20-50像素的余量就好。比如要找一个宽200、高40的按钮按钮左上角屏幕坐标大约是(500, 300)查找区域就设(480, 280, 760, 380)。这样既快又不容易匹配到其他元素。5.2 查找速度优化的几点经验说到速度我给几个亲测有效的经验都是一些零碎但实用的点。第一优先缩小区域而不是优化算法。大漠本身已经做了不少优化但这个接口的本质还是逐个像素比对。查找区域面积减少 50%耗时基本也能减少接近 50%这个收益是肉眼可见的。真正写模块时我会要求所有调用方必须显式传入区域不允许提供“全屏查找”的便捷函数。不是不能做而是不想让人偷懒。第二颜色串尽量精简。如果你只用一种颜色不要一次性传一堆花里胡哨的格式。颜色串里每个字符都要参与解析解析开销在低端机器上会被放大。把需要匹配的颜色数量控制到最少必要时再加容差。第三循环查找必须加延时。有些场景必须不停地找直到颜色出现比如等待某个界面加载完成。这时候脚本如果写成一个死循环高频调用FindColorCPU 占用率会非常难看。正确做法是FindColor一次找到就返回没找到就延时200-500毫秒再试。这样既不会错过界面出现的时间点也不会把 CPU 跑满。这个“延时再试”的思路比“无脑搜索”要稳得多也更符合真实场景中界面加载的节奏。第四善用模块级对象复用。这是性能优化的隐藏项目。每次FindColor都创建并释放组件对象这个开销虽然单次不大但在高频循环里会被无限放大。保持模块内全局唯一的插件对象是所有自动化模块都应该遵循的规范。6. 一个完整应用示例界面按钮自动定位6.1 场景设计与代码整合前面讲了大量原理和封装细节最后用一个完整的场景把它们串起来。假设我们要写一个脚本自动打开某个桌面软件后点击“确认登录”按钮。按钮特征是浅蓝色背景RGB 值是(66, 133, 244)转成十六进制就是4285F4。先做模块初始化.子程序 自动登录流程, 逻辑型 .局部变量 是否找到, 逻辑型 .局部变量 目标X, 整数型 .局部变量 目标Y, 整数型 如果真 (初始化大漠 () 假) 信息框 (“大漠初始化失败”, 0, , ) 返回 (假) 如果真结束 假设软件窗口客户区左上角在屏幕 (200, 150) 按钮大约位于客户区中央偏下位置 计算得到按钮左上约为 (400, 330)按钮尺寸约为 180x40 查找区域设置在按钮周边留出余量 是否找到 查找颜色 (380, 310, 620, 390, “4285F4-101010”, 0.9, 0, 目标X, 目标Y) 如果 (是否找到) 找到按钮中心点击 鼠标_移动点击 (目标X 90, 目标Y 20) 返回 (真) 否则 写日志 (“登录按钮未找到”) 返回 (假) 如果结束注意我这里的坐标是经过“按钮左上角推算”的。实际按钮可能因为窗口位置变化而移动所以更稳妥的做法是先用大漠的窗口绑定功能绑定窗口然后在窗口坐标空间里做查找。窗口移动后窗口内部的相对坐标不会变这样脚本会稳定很多。我这里为了把FindColor的用法讲清楚简单化了窗口坐标转换。真实项目中你会在初始化后调用插件绑定窗口绑定成功后屏幕坐标和窗口客户区坐标的关系会由插件内部处理此时传查找区域直接用客户区相对坐标即可。6.2 运行效果与调整思路第一次运行这个流程大概率会遇到一种情况明明按钮就在那里但颜色找不到。这时候不要急着改代码先做一次“取色验证”。用大漠自带的取色工具或者系统放大镜把鼠标移到按钮上看看实际颜色是不是真的是4285F4。大概率你会发现按钮的渲染色比你抓到的深一点或浅一点因为系统主题或者显卡渲染会影响最终屏幕输出。这时候有两个调整方向。一是把容差扩大比如改成4285F4-202020每个分量允许 32 的浮动。二是改用FindMultiColor同时匹配按钮背景色和按钮文字颜色用组合特征定位。前者适合颜色整体偏移不大、背景干净的场景后者适合界面复杂、容易误匹配的场景。我个人经验是哪怕最开始只有一个按钮要定位也建议把多点找色的思路保留下来。原因很简单单点颜色在真实屏幕上“撞色”的概率比你想的高很多。两个按钮挨在一起主色一样只靠一个点定位鼠标点过去很可能点到的是第二个按钮。多点匹配相当于把“只认脸”升级成“认脸还要认身材和衣服”误判率会成倍下降。7. 最后说点模块维护的事模块写完不是终点。我见过好几个项目模块第一版写得挺好结果一两个月后没人敢动因为当初没留扩展位新需求一进来就得改底层。针对FindColor这个模块给你三条维护建议。第一所有公开子程序的参数顺序要保持“区域 → 颜色条件 → 控制参数 → 输出坐标”。这个顺序符合人的阅读习惯也方便后续加参数时不容易把位置搞混。加新参数时优先往“控制参数”后面追加而不是在中间插入否则所有调用点的参数都会错位。第二日志开关、默认相似度、默认方向这些配置值尽量集中放在模块顶部的常量区或者一个初始化子程序里。改一处全局生效。散落在各个子程序里的魔法数字是模块腐化的开始。第三每次调整底层实现之前跑一遍模块的“回归用例”。你可以写一个测试脚本里面定义几个已知颜色坐标验证模块是否能按预期找到。这个测试脚本不参与业务专门用来在改动后确认模块基本功能没坏。虽然易语言生态里没有特别规范的测试框架但手工维护一个小测试脚本对自己的长期开发效率帮助很大。做模块这件事本质上是在为自己的代码降低复杂度。FindColor只是一个很好的起点当你把单色查找、多点查找、超时重试、日志记录、配置管理都封装顺手之后再去封装截图、找字、ocr 这些功能就会觉得思路特别顺。希望这篇文章能帮你把第一块积木搭好。