img-center-point-location.js 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. /**
  2. * fun 标签:img-center-point-location
  3. * 图像匹配:识别模板图片在截图中的位置,返回中心点坐标
  4. */
  5. const path = require('path')
  6. const fs = require('fs')
  7. const os = require('os')
  8. const { spawnSync } = require('child_process')
  9. const projectRoot = path.resolve(__dirname, '..', '..', '..')
  10. const config = require(path.join(projectRoot, 'configs', 'config.js'))
  11. const imageMatchScriptPath = path.join(projectRoot, 'python', 'scripts', 'image-match.py')
  12. const tagName = 'img-center-point-location'
  13. const schema = {
  14. description: '在屏幕截图中查找模板图片的位置并返回中心点坐标(可用于定位/点击)。',
  15. inputs: { template: '模板图片路径(相对于工作流目录)', variable: '输出变量名(保存中心点坐标)' },
  16. outputs: { variable: '中心点坐标(JSON 字符串格式,如:{"x":123,"y":456})' },
  17. }
  18. /** 解析 Python 可执行路径(与 config 中 pythonPath / pythonVenvPath 一致) */
  19. function getPythonPath() {
  20. const base = config.pythonPath?.path || config.pythonVenvPath || path.join(projectRoot, 'python', process.arch === 'arm64' ? 'arm64' : 'x64')
  21. const envPy = path.join(base, 'env', 'Scripts', 'python.exe')
  22. const scriptsPy = path.join(base, 'Scripts', 'python.exe')
  23. const pyEmbedded = path.join(base, 'py', 'python.exe')
  24. if (fs.existsSync(envPy)) return envPy
  25. if (fs.existsSync(scriptsPy)) return scriptsPy
  26. if (fs.existsSync(pyEmbedded)) return pyEmbedded
  27. return 'python'
  28. }
  29. /** 在设备截图中匹配模板,返回坐标与中心点 */
  30. function matchImageAndGetCoordinate(device, imagePath) {
  31. if (!imagePath || typeof imagePath !== 'string') return { success: false, error: '模板路径为空' }
  32. const templatePath = path.isAbsolute(imagePath) ? imagePath : path.resolve(projectRoot, imagePath)
  33. const ts = Date.now()
  34. const screenshotPath = path.join(os.tmpdir(), `ef-screenshot-${ts}.png`)
  35. const templateCopyPath = path.join(os.tmpdir(), `ef-template-${ts}.png`)
  36. fs.copyFileSync(templatePath, templateCopyPath)
  37. const pythonPath = getPythonPath()
  38. const adbPath = path.resolve(projectRoot, config.adbPath?.path || 'lib/scrcpy-adb/adb.exe')
  39. const r = spawnSync(pythonPath, [imageMatchScriptPath, '--adb', adbPath, '--device', device, '--screenshot', screenshotPath.replace(/\\/g, '/'), '--template', templateCopyPath.replace(/\\/g, '/')], {
  40. encoding: 'utf-8',
  41. timeout: 20000,
  42. env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
  43. cwd: projectRoot
  44. })
  45. try { fs.unlinkSync(screenshotPath) } catch (_) {}
  46. try { fs.unlinkSync(templateCopyPath) } catch (_) {}
  47. if (r.status !== 0) return { success: false, error: (r.stderr || r.stdout || '').trim() || '图像匹配失败' }
  48. const out = JSON.parse(r.stdout.trim())
  49. if (!out.success) return { success: false, error: out.error || '未找到图片' }
  50. return {
  51. success: true,
  52. coordinate: { x: out.x, y: out.y, width: out.width, height: out.height },
  53. clickPosition: { x: out.center_x, y: out.center_y }
  54. }
  55. }
  56. async function executeImgCenterPointLocation({ device, template, folderPath }) {
  57. if (!device) return { success: false, error: '缺少设备 ID,无法自动获取截图' }
  58. if (!template || typeof template !== 'string') return { success: false, error: '缺少模板图片路径' }
  59. const baseDir = folderPath && typeof folderPath === 'string' ? folderPath : projectRoot
  60. const templatePath = template.startsWith('/') || template.includes(':') ? template : path.join(baseDir, 'resources', template)
  61. const result = matchImageAndGetCoordinate(device, templatePath)
  62. if (!result.success) return { success: false, error: result.error }
  63. const center = result.clickPosition || { x: result.coordinate.x + result.coordinate.width / 2, y: result.coordinate.y + result.coordinate.height / 2 }
  64. return { success: true, center, coordinate: result.coordinate }
  65. }
  66. module.exports = { tagName, schema, executeImgCenterPointLocation, matchImageAndGetCoordinate }