/** * 增强版液化管理器 * 整合WebGL和CPU实现,智能选择最佳渲染方式 */ import { LiquifyWebGLManager } from "./LiquifyWebGLManager"; import { LiquifyCPUManager } from "./LiquifyCPUManager"; import { findLayerRecursively, LayerType } from "../../utils/layerHelper"; import i18n from "@/lang/index.ts"; const {t} = i18n.global; export class EnhancedLiquifyManager { /** * 创建增强版液化管理器 * @param {Object} options 配置选项 */ constructor(options = {}) { this.config = { // 性能阈值:图像超过此尺寸会尝试使用WebGL webglSizeThreshold: options.webglSizeThreshold || 1000 * 1000, // 默认100万像素 // 是否强制使用CPU模式 forceCPU: options.forceCPU || false, // 是否强制使用WebGL模式 forceWebGL: options.forceWebGL || false, // 网格大小 gridSize: options.gridSize || 15, // 最大变形强度 maxStrength: options.maxStrength || 100, // 平滑迭代次数 smoothingIterations: options.smoothingIterations || 2, // 网格弹性因子 relaxFactor: options.relaxFactor || 0.25, // WebGL网格精度 meshResolution: options.meshResolution || 64, }; // 性能监控 this.performance = { lastOperationTime: 0, renderTimes: [], // 最近的渲染时间记录 isPerformanceIssue: false, // 是否存在性能问题 operationCount: 0, // 操作计数 }; // 初始化标志 this.initialized = false; // 当前参数 this.params = { size: 50, // 工具尺寸 pressure: 0.5, // 压力大小 (0-1) distortion: 0, // 失真程度 (0-1) power: 0.5, // 动力/强度 (0-1) }; // 液化工具模式 this.modes = { PUSH: "push", CLOCKWISE: "clockwise", COUNTERCLOCKWISE: "counterclockwise", PINCH: "pinch", EXPAND: "expand", CRYSTAL: "crystal", EDGE: "edge", RECONSTRUCT: "reconstruct", }; // 当前模式 this.currentMode = this.modes.PUSH; // 图像数据和目标对象 this.originalImageData = null; this.currentImageData = null; this.targetObject = null; this.targetLayerId = null; // 创建渲染器实例 this.webglRenderer = null; this.cpuRenderer = null; // 当前激活的渲染器 this.activeRenderer = null; this.renderMode = "unknown"; // 'webgl', 'cpu', 'unknown' // 画布和管理器引用 this.canvas = options.canvas || null; this.layerManager = options.layerManager || null; // 渲染器状态 this.isWebGLAvailable = LiquifyWebGLManager.isSupported(); } /** * 初始化液化管理器 * @param {Object} options 配置选项 * @returns {Boolean} 是否初始化成功 */ initialize(options = {}) { if (options.canvas) this.canvas = options.canvas; if (options.layerManager) this.layerManager = options.layerManager; if (!this.canvas || !this.layerManager) { console.error("液化管理器初始化失败:缺少canvas或layerManager"); return false; } // 记录初始化时间,用于性能监控 this.performance.lastInitTime = Date.now(); // 创建CPU渲染器 (始终创建作为备选) this.cpuRenderer = new LiquifyCPUManager({ gridSize: this.config.gridSize, maxStrength: this.config.maxStrength, smoothingIterations: this.config.smoothingIterations, relaxFactor: this.config.relaxFactor, }); // 检查是否应创建WebGL渲染器 if (this.isWebGLAvailable && !this.config.forceCPU) { this.webglRenderer = new LiquifyWebGLManager({ gridSize: this.config.gridSize, maxStrength: this.config.maxStrength, meshResolution: this.config.meshResolution, }); } this.initialized = true; return true; } /** * 为液化操作准备图像 * @param {Object|String} target 目标对象或图层ID * @returns {Promise} 准备结果 */ async prepareForLiquify(target) { if (!this.initialized) { throw new Error("液化管理器未初始化"); } let targetObject, targetLayerId; // 处理传入的是图层ID的情况 if (typeof target === "string") { targetLayerId = target; const { layer } = findLayerRecursively( this.layerManager.layers?.value ?? this.layerManager.layers, targetLayerId ); // 检查图层是否存在和是否有对象 let hasObjects = false; if (layer) { if (layer.type === "background" && layer.fabricObject) { hasObjects = true; targetObject = layer.fabricObject; } else if (layer.fabricObjects && layer.fabricObjects.length > 0) { hasObjects = true; targetObject = layer.fabricObjects[0]; } } if (!hasObjects) { throw new Error("目标图层为空或不存在"); } } else if (typeof target === "object") { // 传入的是对象 targetObject = target; const { layer } = findLayerRecursively( this.layerManager.layers?.value ?? this.layerManager.layers, targetObject.layerId ); if (layer) { targetLayerId = layer.id; } else { throw new Error("无法找到目标对象所属图层"); } } else { throw new Error("无效的目标参数"); } // 检查是否为图像对象 if (!targetObject || targetObject.type !== "image") { throw new Error("目标对象不是图像,无法进行液化操作"); } // 保存目标对象引用 this.targetObject = targetObject; this.targetLayerId = targetLayerId; // 获取图像数据 const imageData = await this._getImageData(targetObject); if (!imageData) { throw new Error("无法获取图像数据"); } // 保存原始图像数据 this.originalImageData = imageData; this.currentImageData = this._cloneImageData(imageData); // 检查图像大小,选择适合的渲染器 await this._selectRenderer(imageData); // 预热选定的渲染器 await this._warmupRenderer(imageData); return { targetObject: this.targetObject, targetLayerId: this.targetLayerId, imageData: this.currentImageData, originalImageData: this.originalImageData, renderMode: this.renderMode, }; } /** * 根据图像大小和设备性能选择渲染器 * @param {ImageData} imageData 图像数据 * @private */ async _selectRenderer(imageData) { // 计算图像大小 const pixelCount = imageData.width * imageData.height; console.log( `液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}` ); // 默认使用CPU渲染器 this.activeRenderer = this.cpuRenderer; this.renderMode = "cpu"; // 如果配置强制使用WebGL if (this.config.forceWebGL && this.isWebGLAvailable && this.webglRenderer) { console.log("液化功能: 强制使用WebGL渲染模式"); this.activeRenderer = this.webglRenderer; this.renderMode = "webgl"; return; } // 如果配置强制使用CPU if (this.config.forceCPU) { console.log("液化功能: 强制使用CPU渲染模式"); return; } // 根据图像大小和WebGL可用性决定 if ( pixelCount > this.config.webglSizeThreshold / 2 && // 降低阈值,让更多尺寸的图像使用WebGL this.isWebGLAvailable && this.webglRenderer ) { // 切换到WebGL渲染器 console.log("液化功能: 自动选择WebGL渲染模式(基于图像尺寸)"); this.activeRenderer = this.webglRenderer; this.renderMode = "webgl"; } else { console.log( `液化功能: 使用CPU渲染模式${ !this.isWebGLAvailable ? " (WebGL不可用)" : "" }` ); } } /** * 预热渲染器 * @param {ImageData} imageData 图像数据 * @private */ async _warmupRenderer(imageData) { // 创建图像元素 const img = document.createElement("img"); // 将ImageData转换为URL const canvas = document.createElement("canvas"); canvas.width = imageData.width; canvas.height = imageData.height; const ctx = canvas.getContext("2d"); ctx.putImageData(imageData, 0, 0); // 使用Promise等待图像加载 await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; img.src = canvas.toDataURL(); }); // 初始化当前渲染器 if (this.activeRenderer) { if (this.renderMode === "webgl") { this.activeRenderer.initialize(img); } else { this.activeRenderer.initialize(imageData); } } } /** * 设置液化模式 * @param {String} mode 模式名称 */ setMode(mode) { if (Object.values(this.modes).includes(mode)) { this.currentMode = mode; // 同步更新当前渲染器 if (this.activeRenderer) { this.activeRenderer.setMode(mode); } return true; } return false; } /** * 设置液化参数 * @param {String} param 参数名称 * @param {Number} value 参数值 */ setParam(param, value) { if (param in this.params) { this.params[param] = value; // 同步更新当前渲染器 - 关键修复:确保参数正确传递 if ( this.activeRenderer && typeof this.activeRenderer.setParam === "function" ) { console.log(`EnhancedLiquifyManager 设置参数: ${param}=${value}`); this.activeRenderer.setParam(param, value); } else { console.warn( `EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪` ); } return true; } console.warn(`EnhancedLiquifyManager: 无效参数 ${param}`); return false; } /** * 批量设置参数 * @param {Object} params 参数对象 */ setParams(params) { console.log("EnhancedLiquifyManager 批量设置参数:", params); if (params && typeof params === "object") { Object.entries(params).forEach(([key, value]) => { this.setParam(key, value); }); } } /** * 获取当前参数 * @returns {Object} 当前参数对象 */ getParams() { return { ...this.params }; } /** * 重置参数为默认值 */ resetParams() { this.params = { size: 50, pressure: 0.5, distortion: 0, power: 0.5, }; // 同步更新当前渲染器 if (this.activeRenderer) { this.activeRenderer.resetParams(); } } /** * 开始液化操作(记录初始点) * @param {Number} x 初始X坐标 * @param {Number} y 初始Y坐标 */ startLiquifyOperation(x, y) { if ( this.activeRenderer && typeof this.activeRenderer.startDeformation === "function" ) { this.activeRenderer.startDeformation(x, y); } console.log( `开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})` ); } /** * 结束液化操作 */ endLiquifyOperation() { if ( this.activeRenderer && typeof this.activeRenderer.endDeformation === "function" ) { this.activeRenderer.endDeformation(); } console.log(`结束液化操作,渲染模式=${this.renderMode}`); } /** * 应用液化变形 * @param {Object} target 目标对象 * @param {String} mode 液化模式 * @param {Object} params 液化参数 * @param {Number} x 操作中心点X坐标 (图像像素坐标) * @param {Number} y 操作中心点Y坐标 (图像像素坐标) * @returns {Promise} 处理后的图像数据 */ async applyLiquify(target, mode, params, x, y) { // 性能追踪开始 const startTime = performance.now(); // 如果首次调用,先准备环境 if (!this.targetObject || this.targetObject !== target) { await this.prepareForLiquify(target); } // 更新模式和参数 if (mode) this.setMode(mode); if (params) { for (const [key, value] of Object.entries(params)) { this.setParam(key, value); } } // 验证坐标是否在图像范围内 if (!this.originalImageData) { console.error("缺少原始图像数据"); return null; } const imageWidth = this.originalImageData.width; const imageHeight = this.originalImageData.height; // 坐标边界检查 if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) { console.warn( `液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}` ); return null; } console.log( `应用液化变形: 模式=${mode}, 图像坐标=(${x}, ${y}), 图像尺寸=${imageWidth}x${imageHeight}` ); // 检查并应用变形 if (this.activeRenderer && typeof x === "number" && typeof y === "number") { // 应用变形 let result; if (this.renderMode === "webgl") { // WebGL渲染器:传入图像像素坐标 result = this.activeRenderer.applyDeformation(x, y); } else { // CPU渲染器:传入图像像素坐标 result = this.activeRenderer.applyDeformation(x, y); } // 更新当前图像数据 if (result) { this.currentImageData = result; } // 性能追踪结束 const endTime = performance.now(); this._trackPerformance(endTime - startTime); return result; } console.error("无法应用液化变形:渲染器未初始化或坐标无效"); return null; } /** * 追踪性能数据 * @param {Number} time 操作耗时(毫秒) * @private */ _trackPerformance(time) { this.performance.lastOperationTime = time; this.performance.operationCount++; // 维护最近10次操作的耗时记录 this.performance.renderTimes.push(time); if (this.performance.renderTimes.length > 10) { this.performance.renderTimes.shift(); } // 计算平均耗时 const avgTime = this.performance.renderTimes.reduce((sum, t) => sum + t, 0) / this.performance.renderTimes.length; // 检测性能问题 this.performance.isPerformanceIssue = avgTime > 100; // 如果平均耗时超过100毫秒 // 输出性能信息(调试用) if (this.performance.operationCount % 10 === 0) { console.log( `液化性能数据: 模式=${this.renderMode}, 平均耗时=${avgTime.toFixed( 2 )}ms, 图像尺寸=${this.originalImageData?.width}x${ this.originalImageData?.height }` ); } // 如果使用WebGL但性能差,可以考虑切换到优化的CPU实现 if ( this.renderMode === "webgl" && this.performance.isPerformanceIssue && this.performance.operationCount > 5 ) { console.warn("WebGL液化性能不佳,考虑切换到CPU模式"); // 注意:这里不自动切换,因为可能会导致中途渲染结果不一致 } } setRealtimeUpdater(realtimeUpdater) { this.realtimeUpdater = realtimeUpdater; } /** * 重置液化操作 * @returns {ImageData} 重置后的图像数据 */ reset() { if (!this.activeRenderer) return null; // 使用当前渲染器重置 const result = this.activeRenderer.reset(); // 更新当前图像数据 if (result) { this.currentImageData = result; } return result; } /** * 检查图层是否可以液化 * @param {String} layerId 图层ID * @returns {Object} 检查结果 */ checkLayerForLiquify(layerId) { if (!this.layerManager) { return { valid: false, message: "图层管理器未初始化", needsRasterization: false, isImage: false, isEmpty: true, isGroup: false, }; } // 获取图层 // const layer = this.layerManager.getLayerById(layerId); const { layer } = findLayerRecursively( this.layerManager.layers?.value ?? this.layerManager.layers, layerId ); if (!layer) { return { valid: false, message: "图层不存在", needsRasterization: false, isImage: false, isEmpty: true, isGroup: false, }; } // 检查图层是否为空 let objectsToCheck = []; if (layer.isBackground || layer.type === "background" || layer.isFixed) { // 背景图层使用 fabricObject (单数) if (layer.fabricObject) { objectsToCheck = [layer.fabricObject]; } } else { // 普通图层使用 fabricObjects (复数) objectsToCheck = layer.fabricObjects || []; } if (objectsToCheck.length === 0) { return { valid: false, message: t('Canvas.layerEmptyNoLiquidation'), needsRasterization: false, isImage: false, isEmpty: true, isGroup: false, }; } // 检查是否为单一图像 const singleObject = objectsToCheck.length === 1; const isImage = singleObject && (objectsToCheck[0].type === "image" || objectsToCheck[0].type === "rasterized-layer"); // 检查是否为组 const isGroup = objectsToCheck.some((obj) => obj.type === "group") || layer.type === LayerType.GROUP || layer.children?.length > 0; // 如果不是单一图像,需要栅格化 const needsRasterization = !isImage || isGroup; return { valid: isImage && !isGroup, message: isImage ? "图层可以进行液化操作" : "需要先将图层栅格化", needsRasterization: needsRasterization, isImage: isImage, isEmpty: false, isGroup: isGroup, }; } /** * 获取图像数据 * @param {Object} fabricObject Fabric图像对象 * @returns {Promise} 图像数据 * @private */ async _getImageData(fabricObject) { return new Promise((resolve, reject) => { try { console.log("开始获取图像数据,对象类型:", fabricObject.type); console.log("对象属性:", { width: fabricObject.width, height: fabricObject.height, hasElement: !!fabricObject._element, hasSrc: !!fabricObject.getSrc, }); // 检查基本属性 if (!fabricObject.width || !fabricObject.height) { reject(new Error("图像对象缺少有效的宽度或高度")); return; } // 创建临时canvas - 使用原始图像尺寸,不考虑fabric对象的缩放 const tempCanvas = document.createElement("canvas"); tempCanvas.width = fabricObject.width; tempCanvas.height = fabricObject.height; const tempCtx = tempCanvas.getContext("2d"); console.log( `创建临时Canvas,尺寸: ${tempCanvas.width}x${tempCanvas.height}` ); // 处理不同的图像源 if (fabricObject._element) { console.log("使用 _element 绘制图像"); // 检查_element是否有效 if ( !fabricObject._element.complete && fabricObject._element.tagName === "IMG" ) { console.log("图像未加载完成,等待加载..."); fabricObject._element.onload = () => { try { tempCtx.drawImage( fabricObject._element, 0, 0, fabricObject.width, fabricObject.height ); const imageData = tempCtx.getImageData( 0, 0, tempCanvas.width, tempCanvas.height ); console.log("✅ 图像加载完成后获取数据成功"); resolve(imageData); } catch (error) { console.error("图像加载后绘制失败:", error); reject(error); } }; fabricObject._element.onerror = () => { reject(new Error("图像加载失败")); }; return; } // 直接绘制已加载的图像 tempCtx.drawImage( fabricObject._element, 0, 0, fabricObject.width, fabricObject.height ); } else if ( fabricObject.getSrc && typeof fabricObject.getSrc === "function" ) { console.log("使用 getSrc() 方法获取图像源"); // 通过URL创建图像 const img = new Image(); img.crossOrigin = "anonymous"; // 避免跨域问题 img.onload = () => { try { console.log( `图像加载成功,原始尺寸: ${img.naturalWidth}x${img.naturalHeight}` ); tempCtx.drawImage( img, 0, 0, fabricObject.width, fabricObject.height ); const imageData = tempCtx.getImageData( 0, 0, tempCanvas.width, tempCanvas.height ); console.log("✅ 通过URL获取图像数据成功"); resolve(imageData); } catch (error) { console.error("绘制图像失败:", error); reject(error); } }; img.onerror = (error) => { console.error("图像加载失败:", error); reject(new Error("无法加载图像URL: " + fabricObject.getSrc())); }; const srcUrl = fabricObject.getSrc(); console.log("加载图像URL:", srcUrl); img.src = srcUrl; return; } else if (fabricObject.src) { console.log("使用 src 属性获取图像源"); // 通过src属性创建图像 const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => { try { tempCtx.drawImage( img, 0, 0, fabricObject.width, fabricObject.height ); const imageData = tempCtx.getImageData( 0, 0, tempCanvas.width, tempCanvas.height ); console.log("✅ 通过src属性获取图像数据成功"); resolve(imageData); } catch (error) { console.error("通过src绘制图像失败:", error); reject(error); } }; img.onerror = (error) => { console.error("通过src加载图像失败:", error); reject(new Error("无法加载图像src: " + fabricObject.src)); }; console.log("加载图像src:", fabricObject.src); img.src = fabricObject.src; return; } else { console.error("无法找到有效的图像源"); reject( new Error("图像对象缺少有效的图像源(_element, getSrc, 或 src)") ); return; } // 如果走到这里,说明使用了_element直接绘制 try { const imageData = tempCtx.getImageData( 0, 0, tempCanvas.width, tempCanvas.height ); console.log( `✅ 获取图像数据成功: 对象尺寸=${fabricObject.width}x${fabricObject.height}, ` + `对象缩放=(${fabricObject.scaleX}, ${fabricObject.scaleY}), ` + `图像数据尺寸=${imageData.width}x${imageData.height}` ); resolve(imageData); } catch (error) { console.error("获取ImageData失败:", error); reject(new Error("无法从Canvas获取图像数据: " + error.message)); } } catch (error) { console.error("_getImageData 执行失败:", error); reject(error); } }); } /** * 克隆图像数据 * @param {ImageData} imageData 原始图像数据 * @returns {ImageData} 克隆的图像数据 * @private */ _cloneImageData(imageData) { if (!imageData) return null; // 使用新的浏览器API直接复制 if (typeof ImageData.prototype.constructor === "function") { try { return new ImageData( new Uint8ClampedArray(imageData.data), imageData.width, imageData.height ); } catch (e) { console.warn("使用备选方法克隆ImageData"); } } // 备选方法 const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = imageData.width; canvas.height = imageData.height; ctx.putImageData(imageData, 0, 0); return ctx.getImageData(0, 0, imageData.width, imageData.height); } /** * 释放资源 */ dispose() { // 释放渲染器资源 if (this.webglRenderer) { this.webglRenderer.dispose(); this.webglRenderer = null; } if (this.cpuRenderer) { this.cpuRenderer.dispose(); this.cpuRenderer = null; } // 清除引用 this.activeRenderer = null; this.canvas = null; this.layerManager = null; this.targetObject = null; this.originalImageData = null; this.currentImageData = null; this.initialized = false; this.renderMode = "unknown"; } /** * 获取当前状态信息 * @returns {Object} 状态信息 */ getStatus() { return { initialized: this.initialized, renderMode: this.renderMode, isWebGLAvailable: this.isWebGLAvailable, currentMode: this.currentMode, params: { ...this.params }, performance: { ...this.performance }, imageSize: this.originalImageData ? `${this.originalImageData.width}x${this.originalImageData.height}` : "N/A", }; } }