/** * fun 标签:img-center-point-location * 图像匹配:识别模板图片在截图中的位置,返回中心点坐标 */ const path = require('path') const fs = require('fs') const os = require('os') const { spawnSync } = require('child_process') const configPath = process.env.STATIC_ROOT ? path.join(path.dirname(process.env.STATIC_ROOT), 'configs', 'config.js') : path.join(__dirname, '..', '..', '..', 'configs', 'config.js') const projectRoot = path.dirname(path.dirname(path.resolve(configPath))) const config = fs.existsSync(configPath) ? require(configPath) : {} const imageMatchScriptPath = path.join(projectRoot, 'python', 'scripts', 'image-match.py') const tagName = 'img-center-point-location' const schema = { description: '在屏幕截图中查找模板图片的位置并返回中心点坐标(可用于定位/点击)。', inputs: { template: '模板图片路径(相对于工作流目录)', variable: '输出变量名(保存中心点坐标)' }, outputs: { variable: '中心点坐标(JSON 字符串格式,如:{"x":123,"y":456})' }, } /** 解析 Python 可执行路径(与 config 中 pythonPath / pythonVenvPath 一致) */ function getPythonPath() { const base = config.pythonPath?.path || config.pythonVenvPath || path.join(projectRoot, 'python', process.arch === 'arm64' ? 'arm64' : 'x64') const envPy = path.join(base, 'env', 'Scripts', 'python.exe') const scriptsPy = path.join(base, 'Scripts', 'python.exe') const pyEmbedded = path.join(base, 'py', 'python.exe') if (fs.existsSync(envPy)) return envPy if (fs.existsSync(scriptsPy)) return scriptsPy if (fs.existsSync(pyEmbedded)) return pyEmbedded return 'python' } /** 在设备截图中匹配模板,返回坐标与中心点 */ function matchImageAndGetCoordinate(device, imagePath) { if (!imagePath || typeof imagePath !== 'string') return { success: false, error: '模板路径为空' } const templatePath = path.isAbsolute(imagePath) ? imagePath : path.resolve(projectRoot, imagePath) const ts = Date.now() const screenshotPath = path.join(os.tmpdir(), `ef-screenshot-${ts}.png`) const templateCopyPath = path.join(os.tmpdir(), `ef-template-${ts}.png`) fs.copyFileSync(templatePath, templateCopyPath) const pythonPath = getPythonPath() const adbPath = config.adbPath?.path ? (path.isAbsolute(config.adbPath.path) ? config.adbPath.path : path.resolve(projectRoot, config.adbPath.path)) : path.join(projectRoot, 'lib', 'scrcpy-adb', process.platform === 'win32' ? 'adb.exe' : 'adb') const r = spawnSync(pythonPath, [imageMatchScriptPath, '--adb', adbPath, '--device', device, '--screenshot', screenshotPath.replace(/\\/g, '/'), '--template', templateCopyPath.replace(/\\/g, '/'), '--method', 'feature'], { encoding: 'utf-8', timeout: 20000, env: { ...process.env, PYTHONIOENCODING: 'utf-8' }, cwd: projectRoot }) try { fs.unlinkSync(screenshotPath) } catch (_) {} try { fs.unlinkSync(templateCopyPath) } catch (_) {} if (r.status !== 0) return { success: false, error: (r.stderr || r.stdout || '').trim() || '图像匹配失败' } const out = JSON.parse(r.stdout.trim()) if (!out.success) return { success: false, error: out.error || '未找到图片' } return { success: true, coordinate: { x: out.x, y: out.y, width: out.width, height: out.height }, clickPosition: { x: out.center_x, y: out.center_y } } } async function executeImgCenterPointLocation({ device, template, folderPath }) { if (!device) return { success: false, error: '缺少设备 ID,无法自动获取截图' } if (!template || typeof template !== 'string') return { success: false, error: '缺少模板图片路径' } const baseDir = folderPath && typeof folderPath === 'string' ? folderPath : projectRoot const templatePath = template.startsWith('/') || template.includes(':') ? template : path.join(baseDir, 'resources', template) const result = matchImageAndGetCoordinate(device, templatePath) if (!result.success) return { success: false, error: result.error } const center = result.clickPosition || { x: result.coordinate.x + result.coordinate.width / 2, y: result.coordinate.y + result.coordinate.height / 2 } return { success: true, center, coordinate: result.coordinate } } module.exports = { tagName, schema, executeImgCenterPointLocation, matchImageAndGetCoordinate }