/** * fun 标签:img-cropping * 根据区域坐标裁剪截图指定区域并保存 */ const path = require('path') const fs = require('fs') const { spawnSync } = require('child_process') const { captureScreenshot } = require('../../../adb/adb-screencap.js') const tagName = 'img-cropping' 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 imgCropScriptPath = path.join(projectRoot, 'python', 'scripts', 'img-crop.py') /** 解析 area 为 { x, y, width, height } */ function parseAreaToRect(area) { const obj = typeof area === 'string' ? JSON.parse(area) : area if (obj.topLeft && obj.bottomRight) { return { x: parseInt(obj.topLeft.x, 10), y: parseInt(obj.topLeft.y, 10), width: parseInt(obj.bottomRight.x - obj.topLeft.x, 10), height: parseInt(obj.bottomRight.y - obj.topLeft.y, 10), } } if (obj.topLeft && obj.topRight && obj.bottomLeft && obj.bottomRight) { return { x: parseInt(obj.topLeft.x, 10), y: parseInt(obj.topLeft.y, 10), width: parseInt(obj.bottomRight.x - obj.topLeft.x, 10), height: parseInt(obj.bottomRight.y - obj.topLeft.y, 10), } } if (obj.x != null && obj.y != null && obj.width != null && obj.height != null) { return { x: parseInt(obj.x, 10), y: parseInt(obj.y, 10), width: parseInt(obj.width, 10), height: parseInt(obj.height, 10), } } return null } /** 构建截图路径 */ function buildScreenshotPath(folderPath) { return path.join(folderPath, 'history', 'ScreenShot.png') } /** 构建保存路径 */ function buildSavePath(savePath, folderPath) { return savePath.includes(':') ? savePath : path.join(folderPath, savePath) } /** 解析 Python 可执行路径(与 config 中 pythonPath / pythonVenvPath 一致) */ function getPythonExe() { 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' } /** 调用 Python 裁剪图片并保存 */ function cropAndSaveImage(inputPath, outputPath, x, y, width, height) { const pythonExe = getPythonExe() const r = spawnSync(pythonExe, [imgCropScriptPath, inputPath, outputPath, String(x), String(y), String(width), String(height)], { encoding: 'utf-8', timeout: 10000, cwd: projectRoot }) if (r.status !== 0) { return { success: false, error: (r.stderr || r.stdout || '').trim() || '裁剪失败' } } return { success: true } } /** 执行 img-cropping */ async function executeImgCropping({ area, savePath, folderPath, device }) { const rect = parseAreaToRect(area) if (!rect || rect.width <= 0 || rect.height <= 0) { return { success: false, error: '区域坐标格式不正确' } } const screenshotPath = buildScreenshotPath(folderPath) const outputPath = buildSavePath(savePath, folderPath) if (device) { const cap = captureScreenshot(device, screenshotPath) if (!cap.success) return { success: false, error: cap.error } } const crop = cropAndSaveImage(screenshotPath, outputPath, rect.x, rect.y, rect.width, rect.height) if (!crop.success) return { success: false, error: crop.error } return { success: true } } module.exports = { tagName, executeImgCropping, cropAndSaveImage }