img-cropping.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. /**
  2. * fun 标签:img-cropping
  3. * 根据区域坐标裁剪截图指定区域并保存
  4. */
  5. const path = require('path')
  6. const fs = require('fs')
  7. const { spawnSync } = require('child_process')
  8. const { captureScreenshot } = require('../../adb/adb-screencap.js')
  9. const tagName = 'img-cropping'
  10. const configPath = process.env.STATIC_ROOT
  11. ? path.join(path.dirname(process.env.STATIC_ROOT), 'configs', 'config.js')
  12. : path.join(__dirname, '..', '..', '..', 'configs', 'config.js')
  13. const projectRoot = path.dirname(path.dirname(path.resolve(configPath)))
  14. const config = fs.existsSync(configPath) ? require(configPath) : {}
  15. const imgCropScriptPath = path.join(projectRoot, 'python', 'scripts', 'img-crop.py')
  16. /** 解析 area 为 { x, y, width, height } */
  17. function parseAreaToRect(area) {
  18. const obj = typeof area === 'string' ? JSON.parse(area) : area
  19. if (obj.topLeft && obj.bottomRight) {
  20. return {
  21. x: parseInt(obj.topLeft.x, 10),
  22. y: parseInt(obj.topLeft.y, 10),
  23. width: parseInt(obj.bottomRight.x - obj.topLeft.x, 10),
  24. height: parseInt(obj.bottomRight.y - obj.topLeft.y, 10),
  25. }
  26. }
  27. if (obj.topLeft && obj.topRight && obj.bottomLeft && obj.bottomRight) {
  28. return {
  29. x: parseInt(obj.topLeft.x, 10),
  30. y: parseInt(obj.topLeft.y, 10),
  31. width: parseInt(obj.bottomRight.x - obj.topLeft.x, 10),
  32. height: parseInt(obj.bottomRight.y - obj.topLeft.y, 10),
  33. }
  34. }
  35. if (obj.x != null && obj.y != null && obj.width != null && obj.height != null) {
  36. return {
  37. x: parseInt(obj.x, 10),
  38. y: parseInt(obj.y, 10),
  39. width: parseInt(obj.width, 10),
  40. height: parseInt(obj.height, 10),
  41. }
  42. }
  43. return null
  44. }
  45. /** 构建截图路径 */
  46. function buildScreenshotPath(folderPath) {
  47. return path.join(folderPath, 'history', 'ScreenShot.png')
  48. }
  49. /** 构建保存路径 */
  50. function buildSavePath(savePath, folderPath) {
  51. return savePath.includes(':') ? savePath : path.join(folderPath, savePath)
  52. }
  53. /** 解析 Python 可执行路径(与 config 中 pythonPath / pythonVenvPath 一致) */
  54. function getPythonExe() {
  55. const base = config.pythonPath?.path || config.pythonVenvPath || path.join(projectRoot, 'python', process.arch === 'arm64' ? 'arm64' : 'x64')
  56. const envPy = path.join(base, 'env', 'Scripts', 'python.exe')
  57. const scriptsPy = path.join(base, 'Scripts', 'python.exe')
  58. const pyEmbedded = path.join(base, 'py', 'python.exe')
  59. if (fs.existsSync(envPy)) return envPy
  60. if (fs.existsSync(scriptsPy)) return scriptsPy
  61. if (fs.existsSync(pyEmbedded)) return pyEmbedded
  62. return 'python'
  63. }
  64. /** 调用 Python 裁剪图片并保存 */
  65. function cropAndSaveImage(inputPath, outputPath, x, y, width, height) {
  66. const pythonExe = getPythonExe()
  67. const r = spawnSync(pythonExe, [imgCropScriptPath, inputPath, outputPath, String(x), String(y), String(width), String(height)], {
  68. encoding: 'utf-8',
  69. timeout: 10000,
  70. cwd: projectRoot
  71. })
  72. if (r.status !== 0) {
  73. return { success: false, error: (r.stderr || r.stdout || '').trim() || '裁剪失败' }
  74. }
  75. return { success: true }
  76. }
  77. /** 执行 img-cropping */
  78. async function executeImgCropping({ area, savePath, folderPath, device }) {
  79. const rect = parseAreaToRect(area)
  80. if (!rect || rect.width <= 0 || rect.height <= 0) {
  81. return { success: false, error: '区域坐标格式不正确' }
  82. }
  83. const screenshotPath = buildScreenshotPath(folderPath)
  84. const outputPath = buildSavePath(savePath, folderPath)
  85. if (device) {
  86. const cap = captureScreenshot(device, screenshotPath)
  87. if (!cap.success) return { success: false, error: cap.error }
  88. }
  89. const crop = cropAndSaveImage(screenshotPath, outputPath, rect.x, rect.y, rect.width, rect.height)
  90. if (!crop.success) return { success: false, error: crop.error }
  91. return { success: true }
  92. }
  93. module.exports = { tagName, executeImgCropping, cropAndSaveImage }