/** * 步骤: * 1. 创建startCutCheck()函数 * 2. 创建变量mergedImgPath 用来接收外部传入的需要切割的图片路径(输入) * 3. 创建变量cutPanelImgDir 用来接收外部传入的保存切割图片的文件夹路径(输出) * 4. 调用cutPanels()函数切割红线(不是绿线)格子 * 5. 将切割好的图片保存到cutPanelImgDir目录 * 6. 返回切割好的图片路径数组 */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { execSync } from 'child_process'; import { getPythonPath } from './python-path.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); function getProjectRoot() { return path.join(__dirname, '..'); } /** * 步骤1: 创建startCutCheck()函数 * @param {string} mergedImgPath - 步骤2: 需要用来切割的图片路径(输入) * @param {string} cutPanelImgDir - 步骤3: 保存切割图片的文件夹路径(输出) * @returns {Promise} 步骤6: 返回切割好的图片路径数组 */ async function startCutCheck(mergedImgPath, cutPanelImgDir) { try { console.log('🚀 开始切割格子流程...'); // 步骤2: 创建变量mergedImgPath 用来接收外部传入的需要切割的图片路径(输入) console.log('\n📷 步骤2: 验证需要切割的图片路径参数'); if (!mergedImgPath) { throw new Error('步骤2失败: mergedImgPath 参数不能为空'); } if (!fs.existsSync(mergedImgPath)) { throw new Error(`步骤2失败: 需要切割的图片文件不存在 - ${mergedImgPath}`); } console.log(`✅ 需要切割的图片路径: ${mergedImgPath}`); // 步骤3: 创建变量cutPanelImgDir 用来接收外部传入的保存切割图片的文件夹路径(输出) console.log('\n📂 步骤3: 验证切割图片保存目录参数'); if (!cutPanelImgDir) { throw new Error('步骤3失败: cutPanelImgDir 参数不能为空'); } // 确保输出目录存在 if (!fs.existsSync(cutPanelImgDir)) { fs.mkdirSync(cutPanelImgDir, { recursive: true }); } console.log(`✅ 切割图片保存目录: ${cutPanelImgDir}`); // 步骤4: 调用cutPanels()函数切割红线(不是绿线)格子 console.log('\n✂️ 步骤4: 开始切割红线格子...'); const cutFiles = await cutPanels(mergedImgPath, cutPanelImgDir); console.log(`✅ 格子切割完成: 生成 ${cutFiles.length} 个图片文件`); // 步骤5: 将切割好的图片保存到cutPanelImgDir目录 console.log('\n💾 步骤5: 验证切割结果保存完成...'); for (let i = 0; i < cutFiles.length; i++) { const file = cutFiles[i]; if (!fs.existsSync(file)) { throw new Error(`步骤5失败: 切割文件未生成 - ${file}`); } const stats = fs.statSync(file); console.log(`✅ 格子 ${i + 1}: ${path.basename(file)} (${Math.round(stats.size / 1024)}KB)`); } // 步骤6: 返回切割好的图片路径数组 console.log('\n📋 步骤6: 准备返回切割好的图片路径数组...'); console.log(`📄 图片路径列表:`); cutFiles.forEach((file, index) => { console.log(` ${index + 1}. ${path.basename(file)} -> ${file}`); }); console.log(`✅ 步骤6完成: 图片路径数组已准备就绪 (${cutFiles.length} 个路径)`); console.log('\n🎉 所有步骤完成!'); console.log(`📊 共切割 ${cutFiles.length} 个格子图片`); console.log(`📁 保存目录: ${cutPanelImgDir}`); return cutFiles; // 步骤6: 直接返回切割好的图片路径数组 } catch (error) { console.error(`\n❌ 切割格子失败: ${error.message}`); throw error; } } /** * 步骤4: 调用cutPanels()函数切割红线格子 * @param {string} mergedImgPath - 合并图片路径 * @param {string} cutPanelImgDir - 输出目录 * @returns {Array} 切割后的文件路径数组 */ async function cutPanels(mergedImgPath, cutPanelImgDir) { const projectRoot = getProjectRoot(); const pythonEnv = getPythonPath(); // 查找格子信息JSON文件(从合并图片同目录查找) const mergedImgDir = path.dirname(mergedImgPath); const imageName = path.basename(mergedImgPath, path.extname(mergedImgPath)).replace('_merged', ''); const panelsJsonPath = path.join(mergedImgDir, `${imageName}_text_check_region.json`); console.log(`📋 查找格子信息: ${path.basename(panelsJsonPath)}`); if (!fs.existsSync(panelsJsonPath)) { throw new Error(`格子信息文件不存在: ${panelsJsonPath}`); } // 读取格子信息 const panelsData = JSON.parse(fs.readFileSync(panelsJsonPath, 'utf-8')); const panels = panelsData.panels || []; if (panels.length === 0) { throw new Error('未找到任何格子信息'); } console.log(`✅ 读取到 ${panels.length} 个格子信息`); // 创建切割Python脚本 const cutScript = path.join(projectRoot, 'python', 'generate-anim', 'cut_panels.py'); if (!fs.existsSync(cutScript)) { console.log('📝 创建切割Python脚本...'); createCutScript(cutScript); } // 调用Python脚本切割图片 const command = `"${pythonEnv}" "${cutScript}" "${mergedImgPath}" "${panelsJsonPath}" "${cutPanelImgDir}"`; console.log('✂️ 正在切割格子...'); try { execSync(command, { encoding: 'utf-8', stdio: 'pipe', cwd: projectRoot, env: { ...process.env, PYTHONIOENCODING: 'utf-8', PYTHONUTF8: '1' }, shell: true }); } catch (error) { throw new Error(`Python切割脚本执行失败: ${error.message}`); } // 获取切割结果文件列表 const cutFiles = fs.readdirSync(cutPanelImgDir) .filter(f => f.match(/^check\d+\.png$/)) // 匹配 check1.png, check2.png 等 .sort((a, b) => { const numA = parseInt(a.match(/check(\d+)/)?.[1] || '0'); const numB = parseInt(b.match(/check(\d+)/)?.[1] || '0'); return numA - numB; }) .map(f => path.join(cutPanelImgDir, f)); if (cutFiles.length === 0) { throw new Error('未生成任何切割文件'); } return cutFiles; } /** * 创建切割格子的Python脚本 */ function createCutScript(scriptPath) { const scriptContent = `#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 简单的格子切割脚本 """ import cv2 import json import sys from pathlib import Path import numpy as np def cut_panels(image_path, panels_json_path, output_dir): """切割格子图片""" # 读取图片 img_data = np.fromfile(str(image_path), dtype=np.uint8) img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) if img is None: raise ValueError(f"无法读取图片: {image_path}") print(f"[INFO] 图片尺寸: {img.shape[1]}x{img.shape[0]}") # 读取格子信息 with open(panels_json_path, 'r', encoding='utf-8') as f: data = json.load(f) panels = data.get('panels', []) if not panels: raise ValueError("未找到格子信息") print(f"[INFO] 找到 {len(panels)} 个格子") # 确保输出目录存在 Path(output_dir).mkdir(parents=True, exist_ok=True) # 按从右到左、从上到下排序 sorted_panels = sorted(panels, key=lambda p: (p.get('y', 0), -p.get('x', 0))) # 切割每个格子 for i, panel in enumerate(sorted_panels, 1): x = int(panel.get('x', 0)) y = int(panel.get('y', 0)) w = int(panel.get('width', 0)) h = int(panel.get('height', 0)) # 切割区域 panel_img = img[y:y+h, x:x+w] # 保存文件 output_file = Path(output_dir) / f"panel_{i}.png" success, encoded = cv2.imencode('.png', panel_img) if success: encoded.tofile(str(output_file)) print(f"[{i}/{len(sorted_panels)}] 保存: {output_file.name} ({w}x{h})") print(f"✅ 切割完成: {len(sorted_panels)} 个格子") if __name__ == '__main__': if len(sys.argv) != 4: print("用法: python cut_panels.py <图片路径> <输出目录>") sys.exit(1) try: cut_panels(sys.argv[1], sys.argv[2], sys.argv[3]) except Exception as e: print(f"[ERROR] {e}") sys.exit(1) `; // 确保目录存在 const scriptDir = path.dirname(scriptPath); if (!fs.existsSync(scriptDir)) { fs.mkdirSync(scriptDir, { recursive: true }); } fs.writeFileSync(scriptPath, scriptContent, 'utf-8'); } /** * 导出函数供外部调用 */ export { startCutCheck };