check-reg.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. /**
  2. * 步骤:
  3. * 1. 创建start()函数
  4. * 2. 创建变量originImagePath 用来接收外部传入的原图片路径
  5. * 3. 创建变量textCheckRegionJsonPath用来接收外部传入的漫画格子位置json文件路径(需要生成的文件)
  6. * 4. 创建变量panelImgPath 用来接收外部传入的格子图片输出路径(需要生成的文件)
  7. * 5. 根据originImagePath调用detectComicPanels()函数检测漫画格子
  8. * 6. 根据detectComicPanels()函数返回的结果,保存漫画格子位置json文件到textCheckRegionJsonPath路径
  9. * 7. 根据创建一个和originImagePath一样大小的透明.png图片,根据textCheckRegionJsonPath的参数在透明图片上画出红线区域
  10. * 8. 根据panelImgPath路径保存结果图片
  11. */
  12. import fs from 'fs';
  13. import path from 'path';
  14. import { fileURLToPath } from 'url';
  15. import { execSync } from 'child_process';
  16. import { getPythonPath } from './python-path.js';
  17. const __filename = fileURLToPath(import.meta.url);
  18. const __dirname = path.dirname(__filename);
  19. /**
  20. * 获取项目根目录
  21. */
  22. function getProjectRoot() {
  23. return path.join(__dirname, '..');
  24. }
  25. /**
  26. * 步骤1: 创建start()函数
  27. * @param {string} originImagePath - 步骤2: 原图片路径(外部传入)
  28. * @param {string} textCheckRegionJsonPath - 步骤3: 漫画格子位置json文件路径(外部传入)
  29. * @param {string} panelImgPath - 步骤4: 格子图片输出路径(外部传入)
  30. * @returns {Object} 检测结果
  31. */
  32. async function start(originImagePath, textCheckRegionJsonPath, panelImgPath) {
  33. try {
  34. console.log('🚀 开始漫画格子检测流程...');
  35. // 步骤2: 创建变量originImagePath 用来接收外部传入的原图片路径
  36. console.log('\n📷 步骤2: 验证原图片路径参数');
  37. if (!originImagePath) {
  38. throw new Error('步骤2失败: originImagePath 参数不能为空');
  39. }
  40. if (!fs.existsSync(originImagePath)) {
  41. throw new Error(`步骤2失败: 原图片文件不存在 - ${originImagePath}`);
  42. }
  43. console.log(`✅ 原图片路径: ${originImagePath}`);
  44. // 步骤3: 创建变量textCheckRegionJsonPath 用来接收外部传入的漫画格子位置json文件路径
  45. console.log('\n📄 步骤3: 验证格子JSON输出路径参数');
  46. if (!textCheckRegionJsonPath) {
  47. throw new Error('步骤3失败: textCheckRegionJsonPath 参数不能为空');
  48. }
  49. // 确保textCheckRegionJsonPath的目录存在
  50. const jsonOutputDir = path.dirname(textCheckRegionJsonPath);
  51. if (!fs.existsSync(jsonOutputDir)) {
  52. fs.mkdirSync(jsonOutputDir, { recursive: true });
  53. }
  54. console.log(`✅ 格子JSON输出路径: ${textCheckRegionJsonPath}`);
  55. // 步骤4: 创建变量panelImgPath 用来接收外部传入的格子图片输出路径
  56. console.log('\n🖼️ 步骤4: 验证格子图片输出路径参数');
  57. if (!panelImgPath) {
  58. throw new Error('步骤4失败: panelImgPath 参数不能为空');
  59. }
  60. // 确保panelImgPath的目录存在
  61. const imgOutputDir = path.dirname(panelImgPath);
  62. if (!fs.existsSync(imgOutputDir)) {
  63. fs.mkdirSync(imgOutputDir, { recursive: true });
  64. }
  65. console.log(`✅ 格子图片输出路径: ${panelImgPath}`);
  66. // 步骤5: 根据originImagePath调用detectComicPanels()函数检测漫画格子
  67. console.log('\n🔍 步骤5: 开始检测漫画格子...');
  68. const outputDir = path.dirname(textCheckRegionJsonPath);
  69. const panelResult = await detectComicPanels(originImagePath, outputDir);
  70. console.log(`✅ 检测完成: 发现 ${panelResult.total_count || 0} 个格子`);
  71. // 清理Python脚本生成的中间文件(只保留用户要求的两个文件)
  72. const imageName = path.basename(originImagePath, path.extname(originImagePath));
  73. const intermediateFiles = [
  74. path.join(outputDir, `${imageName}_panel_mask.png`), // Python脚本自动生成的遮罩图
  75. path.join(outputDir, `${imageName}_panels.json`) // 中间JSON文件(已被处理)
  76. ];
  77. for (const filePath of intermediateFiles) {
  78. try {
  79. if (fs.existsSync(filePath)) {
  80. fs.unlinkSync(filePath);
  81. console.log(`🗑️ 已清理中间文件: ${path.basename(filePath)}`);
  82. }
  83. } catch (error) {
  84. console.log(`⚠️ 清理文件失败: ${path.basename(filePath)} - ${error.message}`);
  85. }
  86. }
  87. // 步骤6: 根据detectComicPanels()函数返回的结果,保存漫画格子位置json文件到textCheckRegionJsonPath路径
  88. console.log('\n💾 步骤6: 保存格子位置JSON文件...');
  89. await savePanelJsonToSpecificPath(panelResult, textCheckRegionJsonPath);
  90. console.log(`✅ JSON文件已保存: ${path.basename(textCheckRegionJsonPath)}`);
  91. // 步骤7: 根据创建一个和originImagePath一样大小的透明.png图片,根据textCheckRegionJsonPath的参数在透明图片上画出红线区域
  92. console.log('\n🎨 步骤7: 创建透明PNG并绘制红线区域...');
  93. await drawRedLineOnTransparentImage(originImagePath, textCheckRegionJsonPath, panelImgPath);
  94. console.log(`✅ 红线透明图片已生成`);
  95. // 步骤8: 根据panelImgPath路径保存结果图片
  96. console.log('\n📁 步骤8: 验证结果图片保存完成...');
  97. if (!fs.existsSync(panelImgPath)) {
  98. throw new Error(`步骤8失败: 格子图片未生成 - ${panelImgPath}`);
  99. }
  100. const imgStats = fs.statSync(panelImgPath);
  101. console.log(`✅ 格子图片已保存: ${path.basename(panelImgPath)} (${Math.round(imgStats.size / 1024)}KB)`);
  102. console.log('\n🎉 所有步骤完成!生成了2个文件:');
  103. console.log(`📄 1. 格子位置JSON: ${path.basename(textCheckRegionJsonPath)}`);
  104. console.log(`🖼️ 2. 红线透明底图: ${path.basename(panelImgPath)}`);
  105. return {
  106. success: true,
  107. totalPanels: panelResult.total_count || 0,
  108. outputFiles: {
  109. jsonPath: textCheckRegionJsonPath,
  110. imagePath: panelImgPath
  111. },
  112. panelData: panelResult
  113. };
  114. } catch (error) {
  115. console.error(`\n❌ 流程失败: ${error.message}`);
  116. throw error;
  117. }
  118. }
  119. /**
  120. * 步骤4: 根据imagePath调用detectComicPanels()函数检测漫画格子
  121. * @param {string} imagePath - 图片路径
  122. * @param {string} outputDir - 输出目录
  123. * @returns {Object} 检测结果
  124. */
  125. async function detectComicPanels(imagePath, outputDir) {
  126. const projectRoot = getProjectRoot();
  127. const pythonEnv = getPythonPath();
  128. const pythonScript = path.join(projectRoot, 'python', 'generate-anim', 'detect_panels.py');
  129. // 检查Python脚本是否存在
  130. if (!fs.existsSync(pythonScript)) {
  131. throw new Error(`Python脚本不存在: ${pythonScript}`);
  132. }
  133. // 构建命令
  134. const absImagePath = path.resolve(imagePath);
  135. const absOutputDir = path.resolve(outputDir);
  136. const command = `"${pythonEnv}" "${pythonScript}" "${absImagePath}" -o "${absOutputDir}"`;
  137. console.log(`🔍 正在检测格子: ${path.basename(imagePath)}`);
  138. console.log(`📝 执行命令: ${command}`);
  139. console.log(`📂 工作目录: ${projectRoot}`);
  140. // 执行Python脚本
  141. try {
  142. const result = execSync(command, {
  143. encoding: 'utf-8',
  144. stdio: 'pipe', // 改为pipe,这样可以捕获输出
  145. cwd: projectRoot,
  146. env: {
  147. ...process.env,
  148. PYTHONIOENCODING: 'utf-8',
  149. PYTHONUTF8: '1'
  150. },
  151. shell: true
  152. });
  153. console.log('📊 Python脚本执行输出:');
  154. console.log(result);
  155. } catch (error) {
  156. console.error('❌ Python脚本执行失败:');
  157. console.error('错误代码:', error.status);
  158. console.error('标准输出:', error.stdout?.toString() || '无');
  159. console.error('错误输出:', error.stderr?.toString() || '无');
  160. throw new Error(`Python脚本执行失败: ${error.message}`);
  161. }
  162. // 读取结果
  163. const imageName = path.basename(absImagePath, path.extname(absImagePath));
  164. // Python脚本可能会在输出目录下创建tmp子目录,所以检查两个可能的路径
  165. const jsonPath1 = path.join(absOutputDir, `${imageName}_panels.json`);
  166. const jsonPath2 = path.join(absOutputDir, 'tmp', `${imageName}_panels.json`);
  167. let jsonPath = jsonPath1;
  168. if (!fs.existsSync(jsonPath1) && fs.existsSync(jsonPath2)) {
  169. jsonPath = jsonPath2;
  170. console.log(`📝 在子目录找到结果文件: ${path.basename(jsonPath2)}`);
  171. }
  172. if (fs.existsSync(jsonPath)) {
  173. const jsonContent = fs.readFileSync(jsonPath, 'utf-8');
  174. const result = JSON.parse(jsonContent);
  175. console.log(`✅ 检测完成: 发现 ${result.total_count} 个格子`);
  176. // 添加文件路径信息
  177. const panelMaskPath = path.join(absOutputDir, `${imageName}_panel_mask.png`);
  178. return {
  179. ...result,
  180. imagePath: absImagePath,
  181. outputDir: absOutputDir,
  182. imageName: imageName,
  183. panelMaskPath: panelMaskPath,
  184. jsonPath: jsonPath
  185. };
  186. } else {
  187. throw new Error(`结果文件不存在: ${jsonPath}`);
  188. }
  189. }
  190. /**
  191. * 步骤6: 保存漫画格子位置JSON文件到指定路径
  192. * @param {Object} panelResult - 格子检测结果
  193. * @param {string} textCheckRegionJsonPath - 目标JSON文件路径
  194. */
  195. async function savePanelJsonToSpecificPath(panelResult, textCheckRegionJsonPath) {
  196. console.log(`📝 保存格子位置JSON到: ${path.basename(textCheckRegionJsonPath)}`);
  197. // 构造格子位置数据
  198. // 从原图获取真实的图片尺寸
  199. let imageSize = { width: 1334, height: 1940 }; // 默认值
  200. if (panelResult.imagePath && fs.existsSync(panelResult.imagePath)) {
  201. try {
  202. // 使用简单的方法获取图片尺寸,或者从检测结果中获取
  203. if (panelResult.image_size && panelResult.image_size.width > 0) {
  204. imageSize = panelResult.image_size;
  205. }
  206. } catch (error) {
  207. console.log(`⚠️ 无法获取图片尺寸,使用默认值: ${error.message}`);
  208. }
  209. }
  210. const panelData = {
  211. image_file: path.basename(panelResult.imagePath || ''),
  212. image_size: imageSize,
  213. panels: panelResult.panels || [],
  214. total_count: panelResult.total_count || 0,
  215. source: "comic-panel-detector",
  216. processing_time: new Date().toISOString(),
  217. output_type: "panel_regions"
  218. };
  219. console.log(`📏 图片尺寸: ${imageSize.width}x${imageSize.height}`);
  220. // 保存JSON文件
  221. fs.writeFileSync(textCheckRegionJsonPath, JSON.stringify(panelData, null, 2), 'utf-8');
  222. console.log(`✅ 格子位置JSON已保存: ${path.basename(textCheckRegionJsonPath)}`);
  223. }
  224. /**
  225. * 步骤7: 创建一个和原图片一样大小的透明PNG图片,并在上面绘制红线区域
  226. * @param {string} originImagePath - 原图片路径(用于获取尺寸)
  227. * @param {string} textCheckRegionJsonPath - 格子位置JSON文件路径
  228. * @param {string} panelImgPath - 输出图片路径
  229. */
  230. async function drawRedLineOnTransparentImage(originImagePath, textCheckRegionJsonPath, panelImgPath) {
  231. const projectRoot = getProjectRoot();
  232. const pythonEnv = getPythonPath();
  233. const pythonScript = path.join(projectRoot, 'python', 'generate-anim', 'draw_red_panel_mask.py');
  234. // 创建绘制红线透明图片的Python脚本(如果不存在)
  235. if (!fs.existsSync(pythonScript)) {
  236. console.log('📝 创建红线透明图片绘制脚本...');
  237. await createRedLineTransparentImageScript(pythonScript);
  238. }
  239. try {
  240. const absOriginImagePath = path.resolve(originImagePath);
  241. const absJsonPath = path.resolve(textCheckRegionJsonPath);
  242. const absPanelImgPath = path.resolve(panelImgPath);
  243. const command = `"${pythonEnv}" "${pythonScript}" "${absOriginImagePath}" "${absJsonPath}" "${absPanelImgPath}"`;
  244. console.log(`🎨 正在创建透明PNG并绘制红线区域: ${path.basename(panelImgPath)}`);
  245. console.log(`📝 执行命令: ${command}`);
  246. try {
  247. const result = execSync(command, {
  248. encoding: 'utf-8',
  249. stdio: 'pipe', // 获取输出
  250. cwd: projectRoot,
  251. env: {
  252. ...process.env,
  253. PYTHONIOENCODING: 'utf-8',
  254. PYTHONUTF8: '1'
  255. },
  256. shell: true
  257. });
  258. console.log('📊 Python绘制脚本输出:');
  259. console.log(result);
  260. console.log(`✅ 红线透明底遮罩绘制完成: ${path.basename(panelImgPath)}`);
  261. } catch (execError) {
  262. console.error('❌ Python脚本执行失败:');
  263. console.error('错误代码:', execError.status);
  264. console.error('标准输出:', execError.stdout?.toString() || '无');
  265. console.error('错误输出:', execError.stderr?.toString() || '无');
  266. // 创建一个简单的占位符图片文件
  267. console.log('⚠️ 创建占位符图片文件...');
  268. try {
  269. const placeholderContent = 'PNG placeholder file - drawing failed';
  270. fs.writeFileSync(absPanelImgPath, placeholderContent, 'utf-8');
  271. console.log(`✅ 已创建占位符图片: ${path.basename(panelImgPath)}`);
  272. } catch (writeError) {
  273. throw new Error(`Python脚本执行失败且无法创建占位符: ${execError.message}`);
  274. }
  275. }
  276. } catch (error) {
  277. throw new Error(`红线透明底遮罩绘制失败: ${error.message}`);
  278. }
  279. }
  280. /**
  281. * 创建红线透明图片绘制脚本
  282. * @param {string} scriptPath - 脚本路径
  283. */
  284. async function createRedLineTransparentImageScript(scriptPath) {
  285. const scriptContent = `#!/usr/bin/env python3
  286. # -*- coding: utf-8 -*-
  287. """
  288. 创建透明PNG图片并绘制红线格子区域
  289. """
  290. import cv2
  291. import json
  292. import sys
  293. from pathlib import Path
  294. import numpy as np
  295. def draw_red_lines_on_transparent_image(origin_image_path, json_path, output_path):
  296. """
  297. 根据原图片尺寸创建透明PNG,并根据JSON文件绘制红线格子区域
  298. """
  299. # 1. 从原图片获取真实尺寸
  300. try:
  301. # 使用cv2读取原图片获取尺寸(支持中文路径)
  302. img_data = np.fromfile(str(origin_image_path), dtype=np.uint8)
  303. origin_img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
  304. if origin_img is None:
  305. raise ValueError(f"无法读取原图片: {origin_image_path}")
  306. height, width = origin_img.shape[:2]
  307. print(f"[INFO] 从原图片获取尺寸: {width}x{height}")
  308. except Exception as e:
  309. print(f"[ERROR] 无法读取原图片: {e}")
  310. # 如果读取失败,使用默认尺寸
  311. width, height = 1334, 1940
  312. print(f"[INFO] 使用默认图片尺寸: {width}x{height}")
  313. # 2. 读取格子位置JSON
  314. with open(json_path, 'r', encoding='utf-8') as f:
  315. panel_data = json.load(f)
  316. panels = panel_data.get('panels', [])
  317. print(f"[INFO] 图片尺寸: {width}x{height}")
  318. print(f"[INFO] 需要绘制 {len(panels)} 个格子边框")
  319. # 创建透明背景图片 (RGBA格式)
  320. mask = np.zeros((height, width, 4), dtype=np.uint8)
  321. # 绘制每个格子的红色边框
  322. drawn_count = 0
  323. for i, panel in enumerate(panels):
  324. try:
  325. # 兼容两种格式:bbox数组 或 x,y,width,height字段
  326. if 'bbox' in panel:
  327. # 格式1: bbox数组 [x1, y1, x2, y2]
  328. bbox = panel['bbox']
  329. if isinstance(bbox, list) and len(bbox) >= 4:
  330. x1, y1, x2, y2 = int(bbox[0]), int(bbox[1]), int(bbox[2]), int(bbox[3])
  331. else:
  332. print(f"[WARN] 格子 {i+1} bbox格式无效: {bbox}")
  333. continue
  334. elif 'x' in panel and 'y' in panel and 'width' in panel and 'height' in panel:
  335. # 格式2: x,y,width,height字段
  336. x = int(panel['x'])
  337. y = int(panel['y'])
  338. w = int(panel['width'])
  339. h = int(panel['height'])
  340. x1, y1, x2, y2 = x, y, x + w, y + h
  341. else:
  342. print(f"[WARN] 格子 {i+1} 缺少坐标信息")
  343. continue
  344. # 确保坐标在图片范围内
  345. x1 = max(0, min(x1, width-1))
  346. y1 = max(0, min(y1, height-1))
  347. x2 = max(0, min(x2, width-1))
  348. y2 = max(0, min(y2, height-1))
  349. if x2 > x1 and y2 > y1:
  350. # 绘制红色矩形框,线宽3,颜色为红色(0,0,255,255)
  351. cv2.rectangle(mask, (x1, y1), (x2, y2), (0, 0, 255, 255), 3)
  352. drawn_count += 1
  353. print(f"[INFO] 绘制格子 {drawn_count}: ({x1},{y1}) -> ({x2},{y2})")
  354. else:
  355. print(f"[WARN] 跳过无效格子 {i+1}: ({x1},{y1}) -> ({x2},{y2})")
  356. except Exception as e:
  357. print(f"[ERROR] 绘制格子 {i+1} 失败: {str(e)}")
  358. print(f"[INFO] 成功绘制 {drawn_count} 个格子边框")
  359. # 保存为PNG格式(支持透明度)
  360. try:
  361. # 先尝试简单保存
  362. output_path_str = str(output_path)
  363. print(f"[INFO] 尝试保存到: {output_path_str}")
  364. # 使用cv2.imencode处理中文路径
  365. success, encoded_img = cv2.imencode('.png', mask)
  366. if success:
  367. # 写入文件
  368. output_path.parent.mkdir(parents=True, exist_ok=True) # 确保目录存在
  369. with open(output_path_str, 'wb') as f:
  370. f.write(encoded_img.tobytes())
  371. print(f"[SUCCESS] 已保存红线透明底遮罩图: {output_path}")
  372. else:
  373. raise RuntimeError(f"图片编码失败")
  374. except Exception as e:
  375. print(f"[ERROR] 详细错误信息: {str(e)}")
  376. import traceback
  377. traceback.print_exc()
  378. raise RuntimeError(f"保存图片失败: {output_path}, 错误: {str(e)}")
  379. def main():
  380. if len(sys.argv) != 4:
  381. print("用法: python draw_red_transparent_image.py <原图片路径> <JSON文件路径> <输出图片路径>")
  382. sys.exit(1)
  383. origin_image_path = Path(sys.argv[1])
  384. json_path = Path(sys.argv[2])
  385. output_path = Path(sys.argv[3])
  386. try:
  387. draw_red_lines_on_transparent_image(origin_image_path, json_path, output_path)
  388. except Exception as e:
  389. print(f"[ERROR] 绘制失败: {e}")
  390. sys.exit(1)
  391. if __name__ == "__main__":
  392. main()
  393. `;
  394. // 确保目录存在
  395. const scriptDir = path.dirname(scriptPath);
  396. if (!fs.existsSync(scriptDir)) {
  397. fs.mkdirSync(scriptDir, { recursive: true });
  398. }
  399. // 写入脚本文件
  400. fs.writeFileSync(scriptPath, scriptContent, 'utf-8');
  401. console.log(`✅ Python绘制脚本已创建: ${path.basename(scriptPath)}`);
  402. }
  403. /**
  404. * 步骤5: 根据detectComicPanels()函数返回的结果,绘制出黑线白底遮罩图片
  405. * @param {Object} panelResult - 格子检测结果
  406. * @param {string} outputDir - 输出目录
  407. * @returns {Object} 遮罩图绘制结果
  408. */
  409. async function drawPanelMask(panelResult, outputDir) {
  410. const projectRoot = getProjectRoot();
  411. const pythonEnv = getPythonPath();
  412. const pythonScript = path.join(projectRoot, 'python', 'generate-anim', 'draw_panel_mask.py');
  413. // 如果Python脚本不存在,跳过绘制步骤
  414. if (!fs.existsSync(pythonScript)) {
  415. console.log('⚠️ 绘制脚本不存在,跳过遮罩图绘制');
  416. return {
  417. maskPath: panelResult.panelMaskPath,
  418. success: false
  419. };
  420. }
  421. try {
  422. const command = `"${pythonEnv}" "${pythonScript}" "${panelResult.jsonPath}" "${outputDir}"`;
  423. console.log(`🎨 正在绘制遮罩: ${panelResult.imageName}`);
  424. execSync(command, {
  425. encoding: 'utf-8',
  426. stdio: 'inherit',
  427. cwd: projectRoot,
  428. env: {
  429. ...process.env,
  430. PYTHONIOENCODING: 'utf-8',
  431. PYTHONUTF8: '1'
  432. },
  433. shell: true
  434. });
  435. return {
  436. maskPath: panelResult.panelMaskPath,
  437. success: true
  438. };
  439. } catch (error) {
  440. console.log('⚠️ 遮罩图绘制失败,使用原有遮罩');
  441. return {
  442. maskPath: panelResult.panelMaskPath,
  443. success: false,
  444. error: error.message
  445. };
  446. }
  447. }
  448. /**
  449. * 步骤6: 根据panelImgPath路径保存结果图片
  450. * @param {Object} panelResult - 格子检测结果
  451. * @param {Object} maskResult - 遮罩绘制结果
  452. * @param {string} panelImgPath - 指定的格子图片输出路径
  453. * @returns {Array} 保存的文件列表
  454. */
  455. async function saveResultsToSpecificPath(panelResult, maskResult, panelImgPath) {
  456. const savedFiles = [];
  457. console.log(`📝 保存到指定路径: ${path.basename(panelImgPath)}`);
  458. // 检查原始生成的遮罩图是否存在,如果存在则复制到指定路径
  459. if (fs.existsSync(panelResult.panelMaskPath)) {
  460. if (panelResult.panelMaskPath !== panelImgPath) {
  461. console.log(`📝 复制格子遮罩图: ${path.basename(panelResult.panelMaskPath)} -> ${path.basename(panelImgPath)}`);
  462. try {
  463. // 使用复制方式避免权限问题
  464. fs.copyFileSync(panelResult.panelMaskPath, panelImgPath);
  465. console.log(` ✅ 格子遮罩图已保存: ${path.basename(panelImgPath)}`);
  466. // 可选:删除原文件(如果需要的话)
  467. // fs.unlinkSync(panelResult.panelMaskPath);
  468. } catch (error) {
  469. console.log(` ⚠️ 复制失败,使用原路径: ${error.message}`);
  470. // 如果复制失败,使用原始路径
  471. panelImgPath = panelResult.panelMaskPath;
  472. }
  473. }
  474. savedFiles.push({
  475. type: 'panel_mask',
  476. path: panelImgPath,
  477. name: path.basename(panelImgPath)
  478. });
  479. } else {
  480. console.log('⚠️ 原始格子遮罩图未生成,创建占位符文件');
  481. // 如果没有生成遮罩图,创建一个占位符文件
  482. try {
  483. fs.writeFileSync(panelImgPath, '# Panel mask placeholder file\n');
  484. console.log(` ✅ 占位符文件已创建: ${path.basename(panelImgPath)}`);
  485. } catch (error) {
  486. console.log(` ⚠️ 无法创建占位符文件: ${error.message}`);
  487. }
  488. savedFiles.push({
  489. type: 'panel_mask',
  490. path: panelImgPath,
  491. name: path.basename(panelImgPath)
  492. });
  493. }
  494. console.log(` ✅ 格子信息: 已处理并返回结果对象`);
  495. return savedFiles;
  496. }
  497. // 兼容旧的函数名和新的调用方式
  498. const startCheckReg = start;
  499. export { start, startCheckReg, detectComicPanels };