cut-check.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /**
  2. * 步骤:
  3. * 1. 创建startCutCheck()函数
  4. * 2. 创建变量mergedImgPath 用来接收外部传入的需要切割的图片路径(输入)
  5. * 3. 创建变量cutPanelImgDir 用来接收外部传入的保存切割图片的文件夹路径(输出)
  6. * 4. 调用cutPanels()函数切割红线(不是绿线)格子
  7. * 5. 将切割好的图片保存到cutPanelImgDir目录
  8. * 6. 返回切割好的图片路径数组
  9. */
  10. import fs from 'fs';
  11. import path from 'path';
  12. import { fileURLToPath } from 'url';
  13. import { execSync } from 'child_process';
  14. import { getPythonPath } from './python-path.js';
  15. const __filename = fileURLToPath(import.meta.url);
  16. const __dirname = path.dirname(__filename);
  17. function getProjectRoot() {
  18. return path.join(__dirname, '..');
  19. }
  20. /**
  21. * 步骤1: 创建startCutCheck()函数
  22. * @param {string} mergedImgPath - 步骤2: 需要用来切割的图片路径(输入)
  23. * @param {string} cutPanelImgDir - 步骤3: 保存切割图片的文件夹路径(输出)
  24. * @returns {Promise<Array>} 步骤6: 返回切割好的图片路径数组
  25. */
  26. async function startCutCheck(mergedImgPath, cutPanelImgDir) {
  27. try {
  28. console.log('🚀 开始切割格子流程...');
  29. // 步骤2: 创建变量mergedImgPath 用来接收外部传入的需要切割的图片路径(输入)
  30. console.log('\n📷 步骤2: 验证需要切割的图片路径参数');
  31. if (!mergedImgPath) {
  32. throw new Error('步骤2失败: mergedImgPath 参数不能为空');
  33. }
  34. if (!fs.existsSync(mergedImgPath)) {
  35. throw new Error(`步骤2失败: 需要切割的图片文件不存在 - ${mergedImgPath}`);
  36. }
  37. console.log(`✅ 需要切割的图片路径: ${mergedImgPath}`);
  38. // 步骤3: 创建变量cutPanelImgDir 用来接收外部传入的保存切割图片的文件夹路径(输出)
  39. console.log('\n📂 步骤3: 验证切割图片保存目录参数');
  40. if (!cutPanelImgDir) {
  41. throw new Error('步骤3失败: cutPanelImgDir 参数不能为空');
  42. }
  43. // 确保输出目录存在
  44. if (!fs.existsSync(cutPanelImgDir)) {
  45. fs.mkdirSync(cutPanelImgDir, { recursive: true });
  46. }
  47. console.log(`✅ 切割图片保存目录: ${cutPanelImgDir}`);
  48. // 步骤4: 调用cutPanels()函数切割红线(不是绿线)格子
  49. console.log('\n✂️ 步骤4: 开始切割红线格子...');
  50. const cutFiles = await cutPanels(mergedImgPath, cutPanelImgDir);
  51. console.log(`✅ 格子切割完成: 生成 ${cutFiles.length} 个图片文件`);
  52. // 步骤5: 将切割好的图片保存到cutPanelImgDir目录
  53. console.log('\n💾 步骤5: 验证切割结果保存完成...');
  54. for (let i = 0; i < cutFiles.length; i++) {
  55. const file = cutFiles[i];
  56. if (!fs.existsSync(file)) {
  57. throw new Error(`步骤5失败: 切割文件未生成 - ${file}`);
  58. }
  59. const stats = fs.statSync(file);
  60. console.log(`✅ 格子 ${i + 1}: ${path.basename(file)} (${Math.round(stats.size / 1024)}KB)`);
  61. }
  62. // 步骤6: 返回切割好的图片路径数组
  63. console.log('\n📋 步骤6: 准备返回切割好的图片路径数组...');
  64. console.log(`📄 图片路径列表:`);
  65. cutFiles.forEach((file, index) => {
  66. console.log(` ${index + 1}. ${path.basename(file)} -> ${file}`);
  67. });
  68. console.log(`✅ 步骤6完成: 图片路径数组已准备就绪 (${cutFiles.length} 个路径)`);
  69. console.log('\n🎉 所有步骤完成!');
  70. console.log(`📊 共切割 ${cutFiles.length} 个格子图片`);
  71. console.log(`📁 保存目录: ${cutPanelImgDir}`);
  72. return cutFiles; // 步骤6: 直接返回切割好的图片路径数组
  73. } catch (error) {
  74. console.error(`\n❌ 切割格子失败: ${error.message}`);
  75. throw error;
  76. }
  77. }
  78. /**
  79. * 步骤4: 调用cutPanels()函数切割红线格子
  80. * @param {string} mergedImgPath - 合并图片路径
  81. * @param {string} cutPanelImgDir - 输出目录
  82. * @returns {Array<string>} 切割后的文件路径数组
  83. */
  84. async function cutPanels(mergedImgPath, cutPanelImgDir) {
  85. const projectRoot = getProjectRoot();
  86. const pythonEnv = getPythonPath();
  87. // 查找格子信息JSON文件(从合并图片同目录查找)
  88. const mergedImgDir = path.dirname(mergedImgPath);
  89. const imageName = path.basename(mergedImgPath, path.extname(mergedImgPath)).replace('_merged', '');
  90. const panelsJsonPath = path.join(mergedImgDir, `${imageName}_text_check_region.json`);
  91. console.log(`📋 查找格子信息: ${path.basename(panelsJsonPath)}`);
  92. if (!fs.existsSync(panelsJsonPath)) {
  93. throw new Error(`格子信息文件不存在: ${panelsJsonPath}`);
  94. }
  95. // 读取格子信息
  96. const panelsData = JSON.parse(fs.readFileSync(panelsJsonPath, 'utf-8'));
  97. const panels = panelsData.panels || [];
  98. if (panels.length === 0) {
  99. throw new Error('未找到任何格子信息');
  100. }
  101. console.log(`✅ 读取到 ${panels.length} 个格子信息`);
  102. // 创建切割Python脚本
  103. const cutScript = path.join(projectRoot, 'python', 'generate-anim', 'cut_panels.py');
  104. if (!fs.existsSync(cutScript)) {
  105. console.log('📝 创建切割Python脚本...');
  106. createCutScript(cutScript);
  107. }
  108. // 调用Python脚本切割图片
  109. const command = `"${pythonEnv}" "${cutScript}" "${mergedImgPath}" "${panelsJsonPath}" "${cutPanelImgDir}"`;
  110. console.log('✂️ 正在切割格子...');
  111. try {
  112. execSync(command, {
  113. encoding: 'utf-8',
  114. stdio: 'pipe',
  115. cwd: projectRoot,
  116. env: {
  117. ...process.env,
  118. PYTHONIOENCODING: 'utf-8',
  119. PYTHONUTF8: '1'
  120. },
  121. shell: true
  122. });
  123. } catch (error) {
  124. throw new Error(`Python切割脚本执行失败: ${error.message}`);
  125. }
  126. // 获取切割结果文件列表
  127. const cutFiles = fs.readdirSync(cutPanelImgDir)
  128. .filter(f => f.match(/^check\d+\.png$/)) // 匹配 check1.png, check2.png 等
  129. .sort((a, b) => {
  130. const numA = parseInt(a.match(/check(\d+)/)?.[1] || '0');
  131. const numB = parseInt(b.match(/check(\d+)/)?.[1] || '0');
  132. return numA - numB;
  133. })
  134. .map(f => path.join(cutPanelImgDir, f));
  135. if (cutFiles.length === 0) {
  136. throw new Error('未生成任何切割文件');
  137. }
  138. return cutFiles;
  139. }
  140. /**
  141. * 创建切割格子的Python脚本
  142. */
  143. function createCutScript(scriptPath) {
  144. const scriptContent = `#!/usr/bin/env python3
  145. # -*- coding: utf-8 -*-
  146. """
  147. 简单的格子切割脚本
  148. """
  149. import cv2
  150. import json
  151. import sys
  152. from pathlib import Path
  153. import numpy as np
  154. def cut_panels(image_path, panels_json_path, output_dir):
  155. """切割格子图片"""
  156. # 读取图片
  157. img_data = np.fromfile(str(image_path), dtype=np.uint8)
  158. img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
  159. if img is None:
  160. raise ValueError(f"无法读取图片: {image_path}")
  161. print(f"[INFO] 图片尺寸: {img.shape[1]}x{img.shape[0]}")
  162. # 读取格子信息
  163. with open(panels_json_path, 'r', encoding='utf-8') as f:
  164. data = json.load(f)
  165. panels = data.get('panels', [])
  166. if not panels:
  167. raise ValueError("未找到格子信息")
  168. print(f"[INFO] 找到 {len(panels)} 个格子")
  169. # 确保输出目录存在
  170. Path(output_dir).mkdir(parents=True, exist_ok=True)
  171. # 按从右到左、从上到下排序
  172. sorted_panels = sorted(panels, key=lambda p: (p.get('y', 0), -p.get('x', 0)))
  173. # 切割每个格子
  174. for i, panel in enumerate(sorted_panels, 1):
  175. x = int(panel.get('x', 0))
  176. y = int(panel.get('y', 0))
  177. w = int(panel.get('width', 0))
  178. h = int(panel.get('height', 0))
  179. # 切割区域
  180. panel_img = img[y:y+h, x:x+w]
  181. # 保存文件
  182. output_file = Path(output_dir) / f"panel_{i}.png"
  183. success, encoded = cv2.imencode('.png', panel_img)
  184. if success:
  185. encoded.tofile(str(output_file))
  186. print(f"[{i}/{len(sorted_panels)}] 保存: {output_file.name} ({w}x{h})")
  187. print(f"✅ 切割完成: {len(sorted_panels)} 个格子")
  188. if __name__ == '__main__':
  189. if len(sys.argv) != 4:
  190. print("用法: python cut_panels.py <图片路径> <JSON路径> <输出目录>")
  191. sys.exit(1)
  192. try:
  193. cut_panels(sys.argv[1], sys.argv[2], sys.argv[3])
  194. except Exception as e:
  195. print(f"[ERROR] {e}")
  196. sys.exit(1)
  197. `;
  198. // 确保目录存在
  199. const scriptDir = path.dirname(scriptPath);
  200. if (!fs.existsSync(scriptDir)) {
  201. fs.mkdirSync(scriptDir, { recursive: true });
  202. }
  203. fs.writeFileSync(scriptPath, scriptContent, 'utf-8');
  204. }
  205. /**
  206. * 导出函数供外部调用
  207. */
  208. export { startCutCheck };