| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /**
- * 步骤:
- * 1. 创建start()函数
- * 2. 创建变量textGreenPanelImgPath
- * 3. 创建变量panelImgPath
- * 4. 创建变量mergedImgPath 用来保存处理好的图片
- * 5. 将两张图片合并成一张图片(textGreenPanelImgPath在下,panelImgPath叠加在textGreenPanelImgPath上,透明区域要保留)
- * 6. 根据mergedImgPath路径保存结果图片(保存文件)
- */
- 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, '..');
- }
- async function startMergeImage(textGreenPanelImgPath, panelImgPath, mergedImgPath) {
- try {
- console.log('🚀 开始图片合并流程...');
-
- // 步骤2: 创建变量textGreenPanelImgPath 用来接收外部传入的绿色框图片路径
- console.log('\n📷 步骤2: 验证绿色框图片路径参数');
- if (!textGreenPanelImgPath) {
- throw new Error('步骤2失败: textGreenPanelImgPath 参数不能为空');
- }
- if (!fs.existsSync(textGreenPanelImgPath)) {
- throw new Error(`步骤2失败: 绿色框图片文件不存在 - ${textGreenPanelImgPath}`);
- }
- console.log(`✅ 绿色框图片路径: ${textGreenPanelImgPath}`);
-
- // 步骤3: 创建变量panelImgPath 用来接收外部传入的红线格子图片路径
- console.log('\n🔴 步骤3: 验证红线格子图片路径参数');
- if (!panelImgPath) {
- throw new Error('步骤3失败: panelImgPath 参数不能为空');
- }
- if (!fs.existsSync(panelImgPath)) {
- throw new Error(`步骤3失败: 红线格子图片文件不存在 - ${panelImgPath}`);
- }
- console.log(`✅ 红线格子图片路径: ${panelImgPath}`);
-
- // 步骤4: 创建变量mergedImgPath 用来保存处理好的图片
- console.log('\n📂 步骤4: 验证合并图片输出路径参数');
- if (!mergedImgPath) {
- throw new Error('步骤4失败: mergedImgPath 参数不能为空');
- }
- // 确保输出目录存在
- const outputDir = path.dirname(mergedImgPath);
- if (!fs.existsSync(outputDir)) {
- fs.mkdirSync(outputDir, { recursive: true });
- }
- console.log(`✅ 合并图片输出路径: ${mergedImgPath}`);
-
- // 步骤5: 将两张图片合并成一张图片(textGreenPanelImgPath在下,panelImgPath在上)
- console.log('\n🔗 步骤5: 开始合并两张图片...');
- console.log(' 📷 底层(背景): 带绿色文字识别框的图片');
- console.log(' 📷 顶层(叠加): 红线透明格子图片');
- console.log(' 🎯 合并方式: 红线透明图片叠加到绿色框图片上');
-
- await mergeImagesWithPython(textGreenPanelImgPath, panelImgPath, mergedImgPath);
- console.log(`✅ 图片合并完成`);
-
- // 步骤6: 根据mergedImgPath路径保存结果图片
- console.log('\n💾 步骤6: 验证结果图片保存完成...');
- if (!fs.existsSync(mergedImgPath)) {
- throw new Error(`步骤6失败: 合并后的图片未生成 - ${mergedImgPath}`);
- }
- const imgStats = fs.statSync(mergedImgPath);
- console.log(`✅ 合并图片已保存: ${path.basename(mergedImgPath)} (${Math.round(imgStats.size / 1024)}KB)`);
-
- console.log('\n🎉 所有步骤完成!');
- console.log(`📄 合并后的图片: ${path.basename(mergedImgPath)}`);
-
- return mergedImgPath;
- } catch (error) {
- console.error(`\n❌ 图片合并失败: ${error.message}`);
- if (error.stack) {
- console.error(error.stack);
- }
- throw error;
- }
- }
- /**
- * 使用Python脚本合并两张图片
- * @param {string} backgroundImgPath - 背景图片路径(底层)
- * @param {string} overlayImgPath - 叠加图片路径(顶层,透明PNG)
- * @param {string} outputImgPath - 输出图片路径
- */
- async function mergeImagesWithPython(backgroundImgPath, overlayImgPath, outputImgPath) {
- const projectRoot = getProjectRoot();
- const pythonEnv = getPythonPath();
- const pythonScript = path.join(projectRoot, 'python', 'generate-anim', 'merge_images.py');
-
- // 创建图片合并的Python脚本(如果不存在)
- if (!fs.existsSync(pythonScript)) {
- console.log('📝 创建图片合并Python脚本...');
- await createImageMergeScript(pythonScript);
- console.log(`✅ Python合并脚本已创建: ${path.basename(pythonScript)}`);
- }
-
- try {
- const absBackgroundPath = path.resolve(backgroundImgPath);
- const absOverlayPath = path.resolve(overlayImgPath);
- const absOutputPath = path.resolve(outputImgPath);
- const command = `"${pythonEnv}" "${pythonScript}" "${absBackgroundPath}" "${absOverlayPath}" "${absOutputPath}"`;
-
- console.log(`🔗 正在合并图片: ${path.basename(backgroundImgPath)} + ${path.basename(overlayImgPath)}`);
- console.log(`📝 执行命令: ${command}`);
-
- const result = execSync(command, {
- encoding: 'utf-8',
- stdio: 'pipe',
- cwd: projectRoot,
- env: {
- ...process.env,
- PYTHONIOENCODING: 'utf-8',
- PYTHONUTF8: '1'
- },
- shell: true
- });
-
- console.log('📊 Python合并脚本输出:');
- console.log(result);
-
- } catch (error) {
- console.error('❌ Python图片合并失败:');
- console.error('错误代码:', error.status);
- console.error('标准输出:', error.stdout?.toString() || '无');
- console.error('错误输出:', error.stderr?.toString() || '无');
- throw new Error(`Python图片合并失败: ${error.message}`);
- }
- }
- /**
- * 创建图片合并的Python脚本
- * @param {string} scriptPath - 脚本路径
- */
- async function createImageMergeScript(scriptPath) {
- const scriptContent = `#!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 合并两张图片:将透明PNG图片叠加到背景图片上
- """
- import cv2
- import numpy as np
- import sys
- from pathlib import Path
- def merge_images(background_path, overlay_path, output_path):
- """
- 将透明PNG图片叠加到背景图片上
-
- 参数:
- background_path: 背景图片路径(底层)
- overlay_path: 叠加图片路径(顶层,支持透明通道)
- output_path: 输出图片路径
- """
- try:
- # 读取背景图片(支持中文路径)
- bg_data = np.fromfile(str(background_path), dtype=np.uint8)
- background = cv2.imdecode(bg_data, cv2.IMREAD_COLOR)
- if background is None:
- raise ValueError(f"无法读取背景图片: {background_path}")
-
- print(f"[INFO] 背景图片尺寸: {background.shape[1]}x{background.shape[0]}")
-
- # 读取叠加图片(支持透明通道)
- overlay_data = np.fromfile(str(overlay_path), dtype=np.uint8)
- overlay = cv2.imdecode(overlay_data, cv2.IMREAD_UNCHANGED)
- if overlay is None:
- raise ValueError(f"无法读取叠加图片: {overlay_path}")
-
- print(f"[INFO] 叠加图片尺寸: {overlay.shape[1]}x{overlay.shape[0]}")
- print(f"[INFO] 叠加图片通道数: {overlay.shape[2] if len(overlay.shape) > 2 else 1}")
-
- # 确保两张图片尺寸一致
- if background.shape[:2] != overlay.shape[:2]:
- print(f"[WARN] 图片尺寸不匹配,调整叠加图片尺寸")
- overlay = cv2.resize(overlay, (background.shape[1], background.shape[0]))
-
- # 处理叠加图片的透明通道
- if len(overlay.shape) == 3 and overlay.shape[2] == 4:
- # RGBA格式,有透明通道
- print(f"[INFO] 处理RGBA透明图片")
-
- # 分离RGB和Alpha通道
- overlay_rgb = overlay[:, :, :3]
- alpha = overlay[:, :, 3] / 255.0 # 归一化到0-1
-
- # 扩展alpha通道到3个维度
- alpha_3ch = np.stack([alpha, alpha, alpha], axis=2)
-
- # Alpha混合
- result = background * (1 - alpha_3ch) + overlay_rgb * alpha_3ch
- result = result.astype(np.uint8)
-
- elif len(overlay.shape) == 3 and overlay.shape[2] == 3:
- # RGB格式,没有透明通道,直接叠加非白色区域
- print(f"[INFO] 处理RGB图片,将白色区域视为透明")
-
- # 创建mask:白色区域(255,255,255)为透明,其他为不透明
- white_mask = np.all(overlay == [255, 255, 255], axis=2)
- alpha = (~white_mask).astype(float) # 非白色区域alpha=1
-
- # 扩展alpha到3个维度
- alpha_3ch = np.stack([alpha, alpha, alpha], axis=2)
-
- # Alpha混合
- result = background * (1 - alpha_3ch) + overlay * alpha_3ch
- result = result.astype(np.uint8)
-
- else:
- # 灰度图或其他格式,简单叠加
- print(f"[INFO] 处理其他格式图片")
- if len(overlay.shape) == 2:
- overlay = cv2.cvtColor(overlay, cv2.COLOR_GRAY2BGR)
- result = cv2.addWeighted(background, 0.7, overlay, 0.3, 0)
-
- print(f"[INFO] 合并完成,结果图片尺寸: {result.shape[1]}x{result.shape[0]}")
-
- # 保存结果图片(支持中文路径)
- success, encoded_img = cv2.imencode('.png', result)
- if success:
- output_path.parent.mkdir(parents=True, exist_ok=True)
- with open(str(output_path), 'wb') as f:
- f.write(encoded_img.tobytes())
- print(f"[SUCCESS] 已保存合并图片: {output_path}")
- else:
- raise RuntimeError(f"图片编码失败: {output_path}")
-
- except Exception as e:
- print(f"[ERROR] 详细错误信息: {str(e)}")
- import traceback
- traceback.print_exc()
- raise RuntimeError(f"合并图片失败: {str(e)}")
- def main():
- if len(sys.argv) != 4:
- print("用法: python merge_images.py <背景图片路径> <叠加图片路径> <输出图片路径>")
- sys.exit(1)
-
- background_path = Path(sys.argv[1])
- overlay_path = Path(sys.argv[2])
- output_path = Path(sys.argv[3])
-
- try:
- merge_images(background_path, overlay_path, output_path)
- except Exception as e:
- print(f"[ERROR] 合并失败: {e}")
- sys.exit(1)
- if __name__ == "__main__":
- main()
- `;
- // 确保目录存在
- const scriptDir = path.dirname(scriptPath);
- if (!fs.existsSync(scriptDir)) {
- fs.mkdirSync(scriptDir, { recursive: true });
- }
- // 写入脚本文件
- fs.writeFileSync(scriptPath, scriptContent, 'utf-8');
- }
- /**
- * 导出函数供外部调用
- */
- export { startMergeImage };
|