From 943b49c1d7238d9c4ff5e37fe5422cd863054402 Mon Sep 17 00:00:00 2001 From: bighuixiang <472705331@qq.com> Date: Wed, 9 Jul 2025 00:22:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B6=B2=E5=8C=96=E6=92=A4=E9=94=80?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D+=E9=80=89=E5=8F=96?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=E9=80=BB=E8=BE=91+=E5=8F=B3=E9=94=AE?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=BB=84=E5=9B=BE=E5=B1=82=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commands/CutSelectionToNewLayerCommand.js | 902 ++++++++---------- .../CutSelectionToNewLayerCommand_old.js | 888 +++++++++++++++++ .../commands/LassoCutoutCommand.js | 180 +++- .../CanvasEditor/commands/LayerCommands.js | 90 +- .../CanvasEditor/commands/LiquifyCommands.js | 139 ++- .../CanvasEditor/components/LiquifyPanel.vue | 1 + .../components/SelectionPanel.vue | 4 +- .../liquify/EnhancedLiquifyManager.js | 2 +- .../Canvas/CanvasEditor/utils/layerHelper.js | 7 +- 9 files changed, 1668 insertions(+), 545 deletions(-) create mode 100644 src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand_old.js diff --git a/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js index 03b38b64..bf279548 100644 --- a/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js @@ -18,32 +18,27 @@ import { ClearSelectionContentCommand } from "./ClearSelectionContentCommand.js" export class CutSelectionToNewLayerCommand extends CompositeCommand { constructor(options = {}) { super([], { - name: "剪切选区到新图层", - description: "将选区复制到新图层并作为组遮罩", + name: "套索抠图", + description: "将选区抠图到新图层", }); this.canvas = options.canvas; this.layerManager = options.layerManager; this.selectionManager = options.selectionManager; this.toolManager = options.toolManager; this.sourceLayerId = options.sourceLayerId; - this.newLayerName = options.newLayerName || "剪切"; + this.newLayerName = options.newLayerName || "抠图"; this.newLayerId = null; this.cutoutImageUrl = null; this.fabricImage = null; this.executedCommands = []; - // 高清截图选项 this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用 this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数 - this.groupId = options.groupId || `cut-group-${Date.now()}`; - this.groupName = options.groupName || `剪切组`; - this.groupLayer = null; // 保存组图层的引用 - this.originalLayersLength = 0; // 保存原始图层数量 - - // 复制的选区对象,用作遮罩 - this.clippingMaskObject = null; - this.sourceLayerId = null; // 保存源图层ID + this.groupId = options.groupId || `cutout-group-${Date.now()}`; + this.groupName = options.groupName || `选区组`; + this.groupLayer = null; // 新增:保存组图层的引用 + this.originalLayersLength = 0; // 新增:保存原始图层数量 // 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象 this._clonedSelectionObject = null; @@ -62,9 +57,9 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { this._clonedSelectionObject = await this._cloneObject( selectionObject ); - console.log("剪切选区:选区对象已克隆保存"); + console.log("套索抠图:选区对象已克隆保存"); } catch (error) { - console.error("剪切选区:克隆选区对象失败:", error); + console.error("套索抠图:克隆选区对象失败:", error); // 备用方案:序列化保存 this.serializedSelectionObject = selectionObject.toObject([ "id", @@ -79,7 +74,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { async execute() { if (!this.canvas || !this.layerManager || !this.selectionManager) { - console.error("无法执行剪切选区:参数无效"); + console.error("无法执行套索抠图:参数无效"); return false; } @@ -89,56 +84,58 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { // 保存原始图层数量,用于撤销时的验证 this.originalLayersLength = this.layerManager.layers.value.length; - // 获取选区 + // 获取选区:优先使用克隆的选区对象 const selectionObject = await this._getSelectionObject(); if (!selectionObject) { - console.error("无法执行剪切选区:当前没有选区"); + console.error("无法执行套索抠图:当前没有选区"); return false; } // 确定源图层 const sourceLayer = this.layerManager.getActiveLayer(); if (!sourceLayer) { - console.error("无法执行剪切选区:没有活动图层"); + console.error("无法执行套索抠图:源图层无效"); return false; } - // 保存源图层ID - this.sourceLayerId = sourceLayer.id; - // 获取源图层的所有对象(包括子图层) const sourceObjects = this._getLayerObjects(sourceLayer); if (sourceObjects.length === 0) { - console.error("无法执行剪切选区:源图层没有可见对象"); + console.error("无法执行套索抠图:源图层没有可见对象"); return false; } - // 保存原图层状态用于撤销 - await this._backupOriginalLayer(sourceLayer, sourceObjects); - // 获取选区边界信息用于后续定位 const selectionBounds = selectionObject.getBoundingRect(true, true); - // 步骤1: 先创建抠图到新图层(复制选区内容) + // 使用createRasterizedImage执行抠图操作 + // this.cutoutImageUrl = await this._performCutoutWithRasterized( + // sourceObjects, + // selectionObject, + // selectionBounds + // ); + // if (!this.cutoutImageUrl) { + // console.error("抠图失败"); + // return false; + // } + this.fabricImage = await this._performCutoutWithRasterized( sourceObjects, selectionObject, selectionBounds ); + // // 创建fabric图像对象,传递选区边界信息 + // this.fabricImage = await this._createFabricImage( + // this.cutoutImageUrl, + // selectionBounds + // ); if (!this.fabricImage) { - console.error("抠图失败"); + console.error("创建图像对象失败"); return false; } - // 步骤2: 对原图层进行栅格化处理,移除选区内容 - await this._rasterizeOriginalLayerWithCutout( - sourceLayer, - sourceObjects, - selectionObject - ); - - // 步骤3: 创建图像图层命令 + // 1. 创建图像图层命令 const createImageLayerCmd = new CreateImageLayerCommand({ layerManager: this.layerManager, fabricImage: this.fabricImage, @@ -151,7 +148,16 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { this.newLayerId = createImageLayerCmd.newLayerId; this.executedCommands.push(createImageLayerCmd); - // 步骤4: 创建组图层并设置剪切结果为遮罩 + // 2. 清除选区命令 + const clearSelectionCmd = new ClearSelectionCommand({ + canvas: this.canvas, + selectionManager: this.selectionManager, + }); + + // 执行清除选区命令 + await clearSelectionCmd.execute(); + this.executedCommands.push(clearSelectionCmd); + const topLayerIndex = this.layerManager.layers.value.findIndex( (layer) => layer.id === this.newLayerId ); @@ -161,7 +167,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { // 创建新的组图层 this.groupLayer = createLayer({ id: this.groupId, - name: this.groupName || `剪切组`, + name: this.groupName || `选区组`, type: LayerType.GROUP, visible: true, locked: false, @@ -175,7 +181,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { evented: true, }); - selectLayer.parentId = this.groupId; + selectLayer.parentId = this.groupId; // 设置新图层的parentId为组图层ID selectLayer.fabricObjects = [ this.fabricImage.toObject("id", "layerId", "layerName", "parentId"), ]; @@ -184,9 +190,9 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { "layerId", "layerName", "parentId" - ); - this.groupLayer.children.push(selectLayer); + ); // 设置组图层的fabricObject为遮罩图像 + this.groupLayer.children.push(selectLayer); // 插入新组图层 this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer); @@ -194,248 +200,116 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { this.canvas.setActiveObject(this.fabricImage); await this.layerManager.updateLayersObjectsInteractivity(true); - console.log(`剪切选区完成,新图层ID: ${this.newLayerId}`); + console.log(`套索抠图完成,新图层ID: ${this.newLayerId}`); return { newLayerId: this.newLayerId, cutoutImageUrl: this.cutoutImageUrl, - groupId: this.groupId, + guroupId: this.groupId, groupName: this.groupName, }; } catch (error) { - console.error("剪切选区过程中出错:", error); + console.error("套索抠图过程中出错:", error); + + // 如果已经创建了新图层,需要进行清理 + if (this.newLayerId) { + try { + await this.layerManager.removeLayer(this.newLayerId); + } catch (cleanupError) { + console.warn("清理新图层失败:", cleanupError); + } + } + + // 清理组图层(如果已创建) + if (this.groupLayer && this.groupId) { + try { + const groupIndex = this.layerManager.layers.value.findIndex( + (layer) => layer.id === this.groupId + ); + if (groupIndex !== -1) { + this.layerManager.layers.value.splice(groupIndex, 1); + console.log(`清理了异常创建的组图层: ${this.groupId}`); + } + this.groupLayer = null; + } catch (cleanupError) { + console.warn("清理组图层失败:", cleanupError); + } + } + + // 尝试回滚已执行的命令 + if (this.executedCommands.length > 0) { + try { + for (let i = this.executedCommands.length - 1; i >= 0; i--) { + const command = this.executedCommands[i]; + if (command && typeof command.undo === "function") { + await command.undo(); + } + } + this.executedCommands = []; + } catch (rollbackError) { + console.warn("回滚已执行命令失败:", rollbackError); + } + } - // 错误清理和回滚 - await this._handleExecutionError(); throw error; } } async undo() { try { - console.log(`↩️ 开始撤销剪切选区操作`); + console.log(`↩️ 开始撤销套索抠图操作`); // 1. 首先移除组图层(如果存在) if (this.groupId) { const groupIndex = this.layerManager.layers.value.findIndex( (layer) => layer.id === this.groupId ); + if (groupIndex !== -1) { + console.log(`↩️ 移除组图层: ${this.groupId}`); + // 从图层列表中移除组图层 this.layerManager.layers.value.splice(groupIndex, 1); - console.log(`移除了组图层: ${this.groupId}`); } } if (this.fabricImage) { - // 从画布移除抠图对象 - if (this.canvas.getObjects().includes(this.fabricImage)) { - this.canvas.remove(this.fabricImage); - } + console.log(`↩️ 移除抠图图像: ${this.fabricImage.id}`); + // 从画布中移除抠图图像 + this.canvas.remove(this.fabricImage); } - // 2. 恢复原图层状态(关键:在撤销子命令之前恢复原图层) - if (this.originalLayerRasterized) { - await this._restoreOriginalLayer(); - } - - // 3. 逆序撤销所有已执行的子命令 + // 2. 逆序撤销所有已执行的子命令 for (let i = this.executedCommands.length - 1; i >= 0; i--) { const command = this.executedCommands[i]; - try { - if (command && typeof command.undo === "function") { + if (command && typeof command.undo === "function") { + try { + console.log(`↩️ 撤销子命令: ${command.constructor.name}`); await command.undo(); console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); + } catch (error) { + console.error( + `❌ 子命令撤销失败: ${command.constructor.name}`, + error + ); + // 子命令撤销失败不中断整个撤销过程 } - } catch (error) { - console.error( - `❌ 子命令撤销失败: ${command.constructor.name}`, - error - ); - // 子命令撤销失败不中断整个撤销过程 } } - // 4. 清理状态 + // 3. 清理状态 this.executedCommands = []; this.newLayerId = null; this.cutoutImageUrl = null; this.fabricImage = null; - this.groupLayer = null; + this.groupLayer = null; // 清理组图层引用 + // 注意:不重置groupId,因为重做时可能需要使用相同的ID - // 重置栅格化标记 - this.originalLayerRasterized = false; - this.rasterizedOriginalImage = null; - - // 5. 更新画布和图层交互性 + // 4. 更新画布和图层交互性 await this.layerManager.updateLayersObjectsInteractivity(true); - console.log(`✅ 剪切选区撤销完成`); + console.log(`✅ 套索抠图撤销完成`); return true; } catch (error) { - console.error("❌ 撤销剪切选区失败:", error); - return false; - } - } - - /** - * 恢复原图层状态 - 关键修复方法 - * @private - */ - async _restoreOriginalLayer() { - try { - console.log("🔄 开始恢复原图层状态..."); - - if (!this.originalLayerBackup || !this.sourceLayerId) { - console.warn("没有原图层备份数据或源图层ID"); - return; - } - - // 1. 移除栅格化后的图像 - if (this.rasterizedOriginalImage) { - if (this.canvas.getObjects().includes(this.rasterizedOriginalImage)) { - this.canvas.remove(this.rasterizedOriginalImage); - console.log("移除了栅格化图像"); - } - } - - // 2. 获取当前的源图层引用 - const sourceLayer = this.layerManager.getLayerById(this.sourceLayerId); - if (!sourceLayer) { - console.error(`找不到源图层: ${this.sourceLayerId}`); - return; - } - - // 3. 清空当前图层的对象列表 - sourceLayer.fabricObjects = []; - - // 4. 恢复原始对象数据到图层 - if (this.originalLayerObjectsData.length > 0) { - // 使用备份的序列化数据重建对象 - await this._restoreObjectsFromBackup(sourceLayer); - } else { - // 备用方案:恢复原始的fabricObjects数组 - sourceLayer.fabricObjects = - this.originalLayerBackup.fabricObjects || []; - - if (sourceLayer.fabricObjects.length > 0) { - await this._restoreLayerObjects(sourceLayer); - } - } - - console.log( - `✅ 原图层状态恢复完成,恢复了 ${sourceLayer.fabricObjects.length} 个对象` - ); - } catch (error) { - console.error("恢复原图层状态失败:", error); - throw error; - } - } - - /** - * 从备份数据恢复对象 - 修复版本 - * @param {Object} layer 目标图层 - * @private - */ - async _restoreObjectsFromBackup(layer) { - return new Promise((resolve, reject) => { - if ( - !this.originalLayerObjectsData || - this.originalLayerObjectsData.length === 0 - ) { - console.warn("没有对象备份数据"); - resolve(); - return; - } - - try { - console.log( - `开始从备份恢复 ${this.originalLayerObjectsData.length} 个对象...` - ); - - // 使用fabric.util.enlivenObjects重建对象 - fabric.util.enlivenObjects( - this.originalLayerObjectsData, - (restoredObjects) => { - try { - let successCount = 0; - - restoredObjects.forEach((obj, index) => { - if (obj) { - // 确保对象有正确的属性 - obj.set({ - layerId: layer.id, - layerName: layer.name, - selectable: true, - evented: true, - visible: true, - }); - - // 添加到画布 - this.canvas.add(obj); - - // 添加到图层的fabricObjects数组 - layer.fabricObjects.push( - obj.toObject([ - "id", - "layerId", - "layerName", - "parentId", - "selectable", - "evented", - "visible", - ]) - ); - - successCount++; - console.log( - `恢复对象 ${index + 1}/${restoredObjects.length}: ${ - obj.id || obj.type - }` - ); - } else { - console.warn(`对象 ${index + 1} 恢复失败`); - } - }); - - // 重新渲染画布 - this.canvas.renderAll(); - - console.log( - `✅ 成功恢复了 ${successCount}/${this.originalLayerObjectsData.length} 个对象` - ); - resolve(); - } catch (error) { - console.error("处理恢复的对象时出错:", error); - reject(error); - } - } - ); - } catch (error) { - console.error("恢复对象时出错:", error); - reject(error); - } - }); - } - - /** - * 重做操作 - * @returns {Promise} 执行结果 - */ - async redo() { - try { - console.log(`🔄 开始重做剪切选区操作`); - - // 重做时重新执行整个操作 - const result = await this.execute(); - - if (result) { - console.log(`✅ 剪切选区重做完成`); - return true; - } else { - console.log(`❌ 剪切选区重做失败`); - return false; - } - } catch (error) { - console.error("❌ 重做剪切选区失败:", error); + console.error("❌ 撤销套索抠图失败:", error); return false; } } @@ -466,62 +340,6 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { }; } - /** - * 使用createRasterizedImage执行抠图操作 - * @param {Array} sourceObjects 源对象数组 - * @param {Object} selectionObject 选区对象 - * @param {Object} selectionBounds 选区边界 - * @returns {fabric.Image} 抠图结果的fabric图像对象 - * @private - */ - async _performCutoutWithRasterized( - sourceObjects, - selectionObject, - selectionBounds - ) { - try { - console.log("=== 开始使用createRasterizedImage执行剪切抠图 ==="); - console.log(`源对象数量: ${sourceObjects.length}`); - console.log(`选区边界:`, selectionBounds); - - // 确定缩放因子,确保高质量输出 - let scaleFactor = this.baseResolutionScale; - if (this.highResolutionEnabled) { - const currentZoom = this.canvas.getZoom?.() || 1; - scaleFactor = Math.max( - scaleFactor || this.canvas?.getRetinaScaling?.(), - currentZoom - ); - scaleFactor = Math.min(scaleFactor, 3); - } - - // 使用createRasterizedImage生成栅格化图像,将选区作为裁剪路径 - const rasterizedImage = await createRasterizedImage({ - canvas: this.canvas, - fabricObjects: sourceObjects, - clipPath: selectionObject, - trimWhitespace: true, - trimPadding: 2, - quality: 1.0, - format: "png", - scaleFactor: scaleFactor, - preserveOriginalQuality: true, - selectionManager: this.selectionManager, - }); - - if (!rasterizedImage) { - console.error("高质量剪切抠图失败"); - return null; - } - - console.log(`✅ 高质量剪切抠图完成,缩放因子: ${scaleFactor}x`); - return rasterizedImage; - } catch (error) { - console.error("使用createRasterizedImage执行剪切抠图失败:", error); - throw error; - } - } - /** * 获取图层的所有对象(包括子图层,从画布中查找真实对象) * @param {Object} layer 图层对象 @@ -539,12 +357,13 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects) ) { - currentLayer.fabricObjects.forEach((fabricObj) => { - if (fabricObj && fabricObj.id) { + currentLayer.fabricObjects.forEach((fabricRef) => { + if (fabricRef && fabricRef.id) { + // 从画布中查找真实的对象 const realObject = canvasObjects.find( - (canvasObj) => canvasObj.id === fabricObj.id + (obj) => obj.id === fabricRef.id ); - if (realObject && realObject.visible !== false) { + if (realObject && realObject.visible) { objects.push(realObject); } } @@ -554,9 +373,9 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { // 处理单个fabricObject(背景图层等) if (currentLayer.fabricObject && currentLayer.fabricObject.id) { const realObject = canvasObjects.find( - (canvasObj) => canvasObj.id === currentLayer.fabricObject.id + (obj) => obj.id === currentLayer.fabricObject.id ); - if (realObject && realObject.visible !== false) { + if (realObject && realObject.visible) { objects.push(realObject); } } @@ -564,7 +383,10 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { // 递归处理子图层 if (currentLayer.children && Array.isArray(currentLayer.children)) { currentLayer.children.forEach((childLayer) => { - collectLayerObjects(childLayer); + if (childLayer.visible !== false) { + // 只处理可见的子图层 + collectLayerObjects(childLayer); + } }); } }; @@ -575,6 +397,300 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { return objects; } + /** + * 使用createRasterizedImage执行抠图操作 + * @param {Array} sourceObjects 源对象数组 + * @param {Object} selectionObject 选区对象 + * @param {Object} selectionBounds 选区边界 + * @returns {String} 抠图结果的DataURL + * @private + */ + async _performCutoutWithRasterized( + sourceObjects, + selectionObject, + selectionBounds + ) { + try { + console.log("=== 开始使用createRasterizedImage执行抠图 ==="); + console.log(`源对象数量: ${sourceObjects.length}`); + console.log(`选区边界:`, selectionBounds); + + // 确定缩放因子,确保高质量输出 + let scaleFactor = this.baseResolutionScale; + if (this.highResolutionEnabled) { + const devicePixelRatio = window.devicePixelRatio || 1; + scaleFactor = Math.max(scaleFactor, devicePixelRatio * 1.5); + } + + // 使用createRasterizedImage生成栅格化图像,将选区作为裁剪路径 + const rasterizedDataURL = await createRasterizedImage({ + canvas: this.canvas, + fabricObjects: sourceObjects, + clipPath: selectionObject, // 使用选区作为裁剪路径 + trimWhitespace: true, + trimPadding: 2, + quality: 1.0, + format: "png", + scaleFactor: scaleFactor, + // isReturenDataURL: true, // 返回DataURL + preserveOriginalQuality: true, // 启用高质量模式 + selectionManager: this.selectionManager, // 传递选区管理器,用于获取羽化值 + }); + + if (!rasterizedDataURL) { + throw new Error("栅格化生成图像失败"); + } + + console.log(`✅ 高质量抠图完成,缩放因子: ${scaleFactor}x`); + return rasterizedDataURL; + } catch (error) { + console.error("使用createRasterizedImage执行抠图失败:", error); + + // 如果createRasterizedImage失败,回退到原始方法 + console.log("⚠️ 回退到原始抠图方法..."); + return await this._performCutout( + { fabricObjects: sourceObjects }, + selectionObject + ); + } + } + + /** + * 原始抠图方法(作为备用方案) + * @param {Object} sourceLayer 源图层 + * @param {Object} selectionObject 选区对象 + * @returns {String} 抠图结果的DataURL + * @private + */ + async _performCutout(sourceLayer, selectionObject) { + try { + console.log("=== 使用原始方法执行抠图 ==="); + + // 获取选区边界 + const selectionBounds = selectionObject.getBoundingRect(true, true); + + // 保存画布当前状态 + const originalActiveObject = this.canvas.getActiveObject(); + const originalSelection = this.canvas.selection; + + // 临时禁用画布选择 + this.canvas.selection = false; + this.canvas.discardActiveObject(); + + let tempGroup = null; + let originalObjects = []; + + try { + // 收集源图层中的可见对象 + const visibleObjects = sourceLayer.fabricObjects.filter( + (obj) => obj.visible + ); + + if (visibleObjects.length === 0) { + throw new Error("源图层没有可见对象"); + } + + // 如果只有一个对象且已经是组,直接使用 + if (visibleObjects.length === 1 && visibleObjects[0].type === "group") { + tempGroup = visibleObjects[0]; + } else { + // 创建临时组 + const clonedObjects = []; + for (const obj of visibleObjects) { + const cloned = await this._cloneObject(obj); + clonedObjects.push(cloned); + } + + tempGroup = new fabric.Group(clonedObjects, { + selectable: false, + evented: false, + }); + + this.canvas.add(tempGroup); + } + + // 设置选区为裁剪路径 + const clipPath = await this._cloneObject(selectionObject); + clipPath.set({ + fill: "", + stroke: "", + absolutePositioned: true, + originX: "left", + originY: "top", + }); + + tempGroup.set({ + clipPath: clipPath, + }); + + this.canvas.renderAll(); + + // 计算渲染区域 + const renderBounds = { + left: selectionBounds.left, + top: selectionBounds.top, + width: selectionBounds.width, + height: selectionBounds.height, + }; + + // 设置高分辨率倍数 + let highResolutionScale = 1; + if (this.highResolutionEnabled) { + const devicePixelRatio = window.devicePixelRatio || 1; + highResolutionScale = Math.max( + this.baseResolutionScale, + devicePixelRatio * 2 + ); + } + + // 创建用于导出的高分辨率canvas + const exportCanvas = document.createElement("canvas"); + const exportCtx = exportCanvas.getContext("2d", { + alpha: true, + willReadFrequently: false, + colorSpace: "srgb", + }); + + const actualWidth = Math.round( + renderBounds.width * highResolutionScale + ); + const actualHeight = Math.round( + renderBounds.height * highResolutionScale + ); + + exportCanvas.width = actualWidth; + exportCanvas.height = actualHeight; + exportCanvas.style.width = renderBounds.width + "px"; + exportCanvas.style.height = renderBounds.height + "px"; + + exportCtx.imageSmoothingEnabled = true; + exportCtx.imageSmoothingQuality = "high"; + exportCtx.clearRect(0, 0, actualWidth, actualHeight); + + const vpt = this.canvas.viewportTransform; + const zoom = this.canvas.getZoom(); + + exportCtx.save(); + exportCtx.scale(highResolutionScale, highResolutionScale); + exportCtx.translate(-renderBounds.left, -renderBounds.top); + + if (zoom !== 1 || vpt[4] !== 0 || vpt[5] !== 0) { + exportCtx.transform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]); + } + + tempGroup.render(exportCtx); + exportCtx.restore(); + + const dataUrl = exportCanvas.toDataURL("image/png", 1.0); + return dataUrl; + } finally { + // 清理和恢复 + if (tempGroup) { + tempGroup.set({ clipPath: null }); + if (originalObjects.length > 0) { + this.canvas.remove(tempGroup); + } + } + + this.canvas.selection = originalSelection; + if (originalActiveObject) { + this.canvas.setActiveObject(originalActiveObject); + } + this.canvas.renderAll(); + } + } catch (error) { + console.error("原始方法执行抠图失败:", error); + return null; + } + } + + /** + * 从DataURL创建fabric图像对象 + * @param {String} dataUrl 图像DataURL + * @param {Object} selectionBounds 选区边界信息 + * @returns {fabric.Image} fabric图像对象 + * @private + */ + async _createFabricImage(dataUrl, selectionBounds) { + return new Promise((resolve, reject) => { + fabric.Image.fromURL( + dataUrl, + (img) => { + if (!img) { + reject(new Error("无法从DataURL创建图像")); + return; + } + + // 使用选区的位置作为图像位置 + let targetLeft = selectionBounds.left + selectionBounds.width / 2; + let targetTop = selectionBounds.top + selectionBounds.height / 2; + + // 设置图像属性,保持选区的原始尺寸 + img.set({ + left: targetLeft, + top: targetTop, + originX: "center", + originY: "center", + selectable: true, + evented: true, + hasControls: true, + hasBorders: true, + cornerStyle: "circle", + cornerColor: "#007aff", + cornerSize: 10, + transparentCorners: false, + borderColor: "#007aff", + borderScaleFactor: 2, + // 优化图像渲染质量 + objectCaching: false, // 禁用缓存以确保最佳质量 + statefullCache: true, + noScaleCache: false, + }); + + // 更新坐标 + img.setCoords(); + + console.log( + `图像创建完成,位置: (${targetLeft}, ${targetTop}), 尺寸: ${img.width}x${img.height}` + ); + resolve(img); + }, + { + crossOrigin: "anonymous", + // 确保图像以最高质量加载 + quality: 1.0, + } + ); + }); + } + + /** + * 克隆fabric对象 + * @param {Object} obj fabric对象 + * @returns {Object} 克隆的对象 + * @private + */ + async _cloneObject(obj) { + return new Promise((resolve, reject) => { + if (!obj) { + reject(new Error("对象无效,无法克隆")); + return; + } + + try { + obj.clone((cloned) => { + if (cloned) { + resolve(cloned); + } else { + reject(new Error("对象克隆失败")); + } + }); + } catch (error) { + reject(error); + } + }); + } + /** * 序列化选区对象 * @private @@ -701,188 +817,4 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { return null; } } - - /** - * 克隆fabric对象 - * @param {Object} obj fabric对象 - * @returns {Object} 克隆的对象 - * @private - */ - async _cloneObject(obj) { - return new Promise((resolve, reject) => { - if (!obj) { - reject(new Error("对象无效,无法克隆")); - return; - } - - try { - obj.clone((cloned) => { - if (cloned) { - resolve(cloned); - } else { - reject(new Error("对象克隆失败")); - } - }); - } catch (error) { - reject(error); - } - }); - } - - /** - * 备份原图层状态 - * @param {Object} sourceLayer 源图层 - * @param {Array} sourceObjects 源对象数组 - * @private - */ - async _backupOriginalLayer(sourceLayer, sourceObjects) { - try { - console.log("🔄 备份原图层状态..."); - - // 深度复制图层对象 - this.originalLayerBackup = JSON.parse(JSON.stringify(sourceLayer)); - - // 备份图层中的所有对象数据 - this.originalLayerObjectsData = []; - for (const obj of sourceObjects) { - if (obj && obj.toObject) { - const objData = obj.toObject([ - "id", - "layerId", - "layerName", - "parentId", - "selectable", - "evented", - "visible", - ]); - this.originalLayerObjectsData.push(objData); - } - } - - this.originalLayerRasterized = false; // 标记原图层是否已栅格化 - - console.log( - `✅ 原图层状态已备份,对象数量: ${this.originalLayerObjectsData.length}` - ); - } catch (error) { - console.error("备份原图层状态失败:", error); - throw error; - } - } - - /** - * 对原图层进行栅格化处理,移除选区内容 - * @param {Object} sourceLayer 源图层 - * @param {Array} sourceObjects 源对象数组 - * @param {Object} selectionObject 选区对象 - * @private - */ - async _rasterizeOriginalLayerWithCutout( - sourceLayer, - sourceObjects, - selectionObject - ) { - try { - console.log("=== 开始对原图层进行栅格化处理,移除选区内容 ==="); - - // 克隆选区对象,确保不影响原对象 - const clonedSelectionObject = await this._cloneObject(selectionObject); - - // 直接使用 ClearSelectionContentCommand 的正确实现,传入选区对象 - const clearCommand = new ClearSelectionContentCommand({ - canvas: this.canvas, - layerManager: this.layerManager, - selectionManager: this.selectionManager, - selectionObject: clonedSelectionObject, // 传入选区对象 - highResolutionEnabled: this.highResolutionEnabled, - baseResolutionScale: this.baseResolutionScale, - }); - - // 临时设置目标图层为当前源图层 - clearCommand.targetLayerId = sourceLayer.id; - clearCommand.targetLayer = sourceLayer; - - // 执行清除选区内容命令 - await clearCommand.execute(); - - // 获取清除后的栅格化图像 - this.rasterizedOriginalImage = clearCommand.rasterizedImage; - this.originalLayerRasterized = true; // 标记原图层已栅格化 - - console.log("✅ 原图层栅格化处理完成,选区内容已移除"); - } catch (error) { - console.error("原图层栅格化处理失败:", error); - throw error; - } - } - - /** - * 创建反向剪切路径(选区外的内容) - * @param {Object} selectionObject 选区对象 - * @param {Array} sourceObjects 源对象数组 - * @returns {Object} 反向剪切路径 - * @private - */ - async _createInvertedClipPath(selectionObject, sourceObjects) { - try { - // 计算所有源对象的边界 - let minLeft = Infinity, - minTop = Infinity, - maxRight = -Infinity, - maxBottom = -Infinity; - - sourceObjects.forEach((obj) => { - const bounds = obj.getBoundingRect(true, true); - minLeft = Math.min(minLeft, bounds.left); - minTop = Math.min(minTop, bounds.top); - maxRight = Math.max(maxRight, bounds.left + bounds.width); - maxBottom = Math.max(maxBottom, bounds.top + bounds.height); - }); - - // 扩展边界以确保完整覆盖 - const padding = 50; - const canvasBounds = { - left: minLeft - padding, - top: minTop - padding, - width: maxRight - minLeft + padding * 2, - height: maxBottom - minTop + padding * 2, - }; - - // 创建画布大小的矩形 - const canvasRect = new fabric.Rect({ - left: canvasBounds.left, - top: canvasBounds.top, - width: canvasBounds.width, - height: canvasBounds.height, - fill: "rgba(255,255,255,1)", - stroke: "", - selectable: false, - evented: false, - absolutePositioned: true, - originX: "left", - originY: "top", - }); - - // 克隆选区对象作为要移除的部分 - const cutoutPath = await this._cloneObject(selectionObject); - cutoutPath.set({ - fill: "rgba(0,0,0,1)", // 黑色表示要移除的区域 - stroke: "", - absolutePositioned: true, - globalCompositeOperation: "destination-out", // 关键:使用destination-out混合模式 - }); - - // 创建复合路径:画布矩形 - 选区 = 反向选区 - const invertedClipPath = new fabric.Group([canvasRect, cutoutPath], { - selectable: false, - evented: false, - absolutePositioned: true, - }); - - return invertedClipPath; - } catch (error) { - console.error("创建反向剪切路径失败:", error); - return null; - } - } } diff --git a/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand_old.js b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand_old.js new file mode 100644 index 00000000..03b38b64 --- /dev/null +++ b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand_old.js @@ -0,0 +1,888 @@ +import { + createLayer, + findInChildLayers, + LayerType, +} from "../utils/layerHelper.js"; +import { createRasterizedImage } from "../utils/selectionToImage.js"; +import { CompositeCommand, Command } from "./Command.js"; +import { CreateImageLayerCommand } from "./LayerCommands.js"; +import { fabric } from "fabric-with-all"; +import { generateId } from "../utils/helper.js"; +import { ClearSelectionCommand } from "./LassoCutoutCommand.js"; +import { ClearSelectionContentCommand } from "./ClearSelectionContentCommand.js"; + +/** + * 剪切选区到新图层命令 + * 实现将选区内容复制到新图层,并将复制的选区作为组的遮罩 + */ +export class CutSelectionToNewLayerCommand extends CompositeCommand { + constructor(options = {}) { + super([], { + name: "剪切选区到新图层", + description: "将选区复制到新图层并作为组遮罩", + }); + this.canvas = options.canvas; + this.layerManager = options.layerManager; + this.selectionManager = options.selectionManager; + this.toolManager = options.toolManager; + this.sourceLayerId = options.sourceLayerId; + this.newLayerName = options.newLayerName || "剪切"; + this.newLayerId = null; + this.cutoutImageUrl = null; + this.fabricImage = null; + this.executedCommands = []; + + // 高清截图选项 + this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用 + this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数 + + this.groupId = options.groupId || `cut-group-${Date.now()}`; + this.groupName = options.groupName || `剪切组`; + this.groupLayer = null; // 保存组图层的引用 + this.originalLayersLength = 0; // 保存原始图层数量 + + // 复制的选区对象,用作遮罩 + this.clippingMaskObject = null; + this.sourceLayerId = null; // 保存源图层ID + + // 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象 + this._clonedSelectionObject = null; + this._initializeClonedSelection(); + } + + /** + * 初始化克隆的选区对象 + * @private + */ + async _initializeClonedSelection() { + if (this.selectionManager) { + const selectionObject = this.selectionManager.getSelectionObject(); + if (selectionObject) { + try { + this._clonedSelectionObject = await this._cloneObject( + selectionObject + ); + console.log("剪切选区:选区对象已克隆保存"); + } catch (error) { + console.error("剪切选区:克隆选区对象失败:", error); + // 备用方案:序列化保存 + this.serializedSelectionObject = selectionObject.toObject([ + "id", + "layerId", + "layerName", + "parentId", + ]); + } + } + } + } + + async execute() { + if (!this.canvas || !this.layerManager || !this.selectionManager) { + console.error("无法执行剪切选区:参数无效"); + return false; + } + + try { + this.executedCommands = []; + + // 保存原始图层数量,用于撤销时的验证 + this.originalLayersLength = this.layerManager.layers.value.length; + + // 获取选区 + const selectionObject = await this._getSelectionObject(); + if (!selectionObject) { + console.error("无法执行剪切选区:当前没有选区"); + return false; + } + + // 确定源图层 + const sourceLayer = this.layerManager.getActiveLayer(); + if (!sourceLayer) { + console.error("无法执行剪切选区:没有活动图层"); + return false; + } + + // 保存源图层ID + this.sourceLayerId = sourceLayer.id; + + // 获取源图层的所有对象(包括子图层) + const sourceObjects = this._getLayerObjects(sourceLayer); + if (sourceObjects.length === 0) { + console.error("无法执行剪切选区:源图层没有可见对象"); + return false; + } + + // 保存原图层状态用于撤销 + await this._backupOriginalLayer(sourceLayer, sourceObjects); + + // 获取选区边界信息用于后续定位 + const selectionBounds = selectionObject.getBoundingRect(true, true); + + // 步骤1: 先创建抠图到新图层(复制选区内容) + this.fabricImage = await this._performCutoutWithRasterized( + sourceObjects, + selectionObject, + selectionBounds + ); + + if (!this.fabricImage) { + console.error("抠图失败"); + return false; + } + + // 步骤2: 对原图层进行栅格化处理,移除选区内容 + await this._rasterizeOriginalLayerWithCutout( + sourceLayer, + sourceObjects, + selectionObject + ); + + // 步骤3: 创建图像图层命令 + const createImageLayerCmd = new CreateImageLayerCommand({ + layerManager: this.layerManager, + fabricImage: this.fabricImage, + toolManager: this.toolManager, + layerName: this.newLayerName, + }); + + // 执行创建图像图层命令 + const result = await createImageLayerCmd.execute(); + this.newLayerId = createImageLayerCmd.newLayerId; + this.executedCommands.push(createImageLayerCmd); + + // 步骤4: 创建组图层并设置剪切结果为遮罩 + const topLayerIndex = this.layerManager.layers.value.findIndex( + (layer) => layer.id === this.newLayerId + ); + + const selectLayer = this.layerManager.layers.value[topLayerIndex]; + + // 创建新的组图层 + this.groupLayer = createLayer({ + id: this.groupId, + name: this.groupName || `剪切组`, + type: LayerType.GROUP, + visible: true, + locked: false, + opacity: 1.0, + fabricObjects: [], + children: [], + }); + + this.fabricImage.set({ + selectable: true, + evented: true, + }); + + selectLayer.parentId = this.groupId; + selectLayer.fabricObjects = [ + this.fabricImage.toObject("id", "layerId", "layerName", "parentId"), + ]; + this.groupLayer.clippingMask = this.fabricImage.toObject( + "id", + "layerId", + "layerName", + "parentId" + ); + this.groupLayer.children.push(selectLayer); + + // 插入新组图层 + this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer); + + this.canvas.discardActiveObject(); + this.canvas.setActiveObject(this.fabricImage); + await this.layerManager.updateLayersObjectsInteractivity(true); + + console.log(`剪切选区完成,新图层ID: ${this.newLayerId}`); + return { + newLayerId: this.newLayerId, + cutoutImageUrl: this.cutoutImageUrl, + groupId: this.groupId, + groupName: this.groupName, + }; + } catch (error) { + console.error("剪切选区过程中出错:", error); + + // 错误清理和回滚 + await this._handleExecutionError(); + throw error; + } + } + + async undo() { + try { + console.log(`↩️ 开始撤销剪切选区操作`); + + // 1. 首先移除组图层(如果存在) + if (this.groupId) { + const groupIndex = this.layerManager.layers.value.findIndex( + (layer) => layer.id === this.groupId + ); + if (groupIndex !== -1) { + this.layerManager.layers.value.splice(groupIndex, 1); + console.log(`移除了组图层: ${this.groupId}`); + } + } + + if (this.fabricImage) { + // 从画布移除抠图对象 + if (this.canvas.getObjects().includes(this.fabricImage)) { + this.canvas.remove(this.fabricImage); + } + } + + // 2. 恢复原图层状态(关键:在撤销子命令之前恢复原图层) + if (this.originalLayerRasterized) { + await this._restoreOriginalLayer(); + } + + // 3. 逆序撤销所有已执行的子命令 + for (let i = this.executedCommands.length - 1; i >= 0; i--) { + const command = this.executedCommands[i]; + try { + if (command && typeof command.undo === "function") { + await command.undo(); + console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); + } + } catch (error) { + console.error( + `❌ 子命令撤销失败: ${command.constructor.name}`, + error + ); + // 子命令撤销失败不中断整个撤销过程 + } + } + + // 4. 清理状态 + this.executedCommands = []; + this.newLayerId = null; + this.cutoutImageUrl = null; + this.fabricImage = null; + this.groupLayer = null; + + // 重置栅格化标记 + this.originalLayerRasterized = false; + this.rasterizedOriginalImage = null; + + // 5. 更新画布和图层交互性 + await this.layerManager.updateLayersObjectsInteractivity(true); + + console.log(`✅ 剪切选区撤销完成`); + return true; + } catch (error) { + console.error("❌ 撤销剪切选区失败:", error); + return false; + } + } + + /** + * 恢复原图层状态 - 关键修复方法 + * @private + */ + async _restoreOriginalLayer() { + try { + console.log("🔄 开始恢复原图层状态..."); + + if (!this.originalLayerBackup || !this.sourceLayerId) { + console.warn("没有原图层备份数据或源图层ID"); + return; + } + + // 1. 移除栅格化后的图像 + if (this.rasterizedOriginalImage) { + if (this.canvas.getObjects().includes(this.rasterizedOriginalImage)) { + this.canvas.remove(this.rasterizedOriginalImage); + console.log("移除了栅格化图像"); + } + } + + // 2. 获取当前的源图层引用 + const sourceLayer = this.layerManager.getLayerById(this.sourceLayerId); + if (!sourceLayer) { + console.error(`找不到源图层: ${this.sourceLayerId}`); + return; + } + + // 3. 清空当前图层的对象列表 + sourceLayer.fabricObjects = []; + + // 4. 恢复原始对象数据到图层 + if (this.originalLayerObjectsData.length > 0) { + // 使用备份的序列化数据重建对象 + await this._restoreObjectsFromBackup(sourceLayer); + } else { + // 备用方案:恢复原始的fabricObjects数组 + sourceLayer.fabricObjects = + this.originalLayerBackup.fabricObjects || []; + + if (sourceLayer.fabricObjects.length > 0) { + await this._restoreLayerObjects(sourceLayer); + } + } + + console.log( + `✅ 原图层状态恢复完成,恢复了 ${sourceLayer.fabricObjects.length} 个对象` + ); + } catch (error) { + console.error("恢复原图层状态失败:", error); + throw error; + } + } + + /** + * 从备份数据恢复对象 - 修复版本 + * @param {Object} layer 目标图层 + * @private + */ + async _restoreObjectsFromBackup(layer) { + return new Promise((resolve, reject) => { + if ( + !this.originalLayerObjectsData || + this.originalLayerObjectsData.length === 0 + ) { + console.warn("没有对象备份数据"); + resolve(); + return; + } + + try { + console.log( + `开始从备份恢复 ${this.originalLayerObjectsData.length} 个对象...` + ); + + // 使用fabric.util.enlivenObjects重建对象 + fabric.util.enlivenObjects( + this.originalLayerObjectsData, + (restoredObjects) => { + try { + let successCount = 0; + + restoredObjects.forEach((obj, index) => { + if (obj) { + // 确保对象有正确的属性 + obj.set({ + layerId: layer.id, + layerName: layer.name, + selectable: true, + evented: true, + visible: true, + }); + + // 添加到画布 + this.canvas.add(obj); + + // 添加到图层的fabricObjects数组 + layer.fabricObjects.push( + obj.toObject([ + "id", + "layerId", + "layerName", + "parentId", + "selectable", + "evented", + "visible", + ]) + ); + + successCount++; + console.log( + `恢复对象 ${index + 1}/${restoredObjects.length}: ${ + obj.id || obj.type + }` + ); + } else { + console.warn(`对象 ${index + 1} 恢复失败`); + } + }); + + // 重新渲染画布 + this.canvas.renderAll(); + + console.log( + `✅ 成功恢复了 ${successCount}/${this.originalLayerObjectsData.length} 个对象` + ); + resolve(); + } catch (error) { + console.error("处理恢复的对象时出错:", error); + reject(error); + } + } + ); + } catch (error) { + console.error("恢复对象时出错:", error); + reject(error); + } + }); + } + + /** + * 重做操作 + * @returns {Promise} 执行结果 + */ + async redo() { + try { + console.log(`🔄 开始重做剪切选区操作`); + + // 重做时重新执行整个操作 + const result = await this.execute(); + + if (result) { + console.log(`✅ 剪切选区重做完成`); + return true; + } else { + console.log(`❌ 剪切选区重做失败`); + return false; + } + } catch (error) { + console.error("❌ 重做剪切选区失败:", error); + return false; + } + } + + /** + * 获取命令信息 + * @returns {Object} 命令详细信息 + */ + getInfo() { + return { + name: this.name, + description: this.description, + newLayerId: this.newLayerId, + newLayerName: this.newLayerName, + groupId: this.groupId, + groupName: this.groupName, + executedCommandsCount: this.executedCommands.length, + hasGroupLayer: !!this.groupLayer, + sourceLayerId: this.sourceLayerId, + highResolutionEnabled: this.highResolutionEnabled, + baseResolutionScale: this.baseResolutionScale, + hasSerializedSelection: !!this.serializedSelectionObject, + selectionType: this.serializedSelectionObject?.type || null, + subCommands: this.executedCommands.map((cmd) => ({ + name: cmd.constructor.name, + info: cmd.getInfo ? cmd.getInfo() : {}, + })), + }; + } + + /** + * 使用createRasterizedImage执行抠图操作 + * @param {Array} sourceObjects 源对象数组 + * @param {Object} selectionObject 选区对象 + * @param {Object} selectionBounds 选区边界 + * @returns {fabric.Image} 抠图结果的fabric图像对象 + * @private + */ + async _performCutoutWithRasterized( + sourceObjects, + selectionObject, + selectionBounds + ) { + try { + console.log("=== 开始使用createRasterizedImage执行剪切抠图 ==="); + console.log(`源对象数量: ${sourceObjects.length}`); + console.log(`选区边界:`, selectionBounds); + + // 确定缩放因子,确保高质量输出 + let scaleFactor = this.baseResolutionScale; + if (this.highResolutionEnabled) { + const currentZoom = this.canvas.getZoom?.() || 1; + scaleFactor = Math.max( + scaleFactor || this.canvas?.getRetinaScaling?.(), + currentZoom + ); + scaleFactor = Math.min(scaleFactor, 3); + } + + // 使用createRasterizedImage生成栅格化图像,将选区作为裁剪路径 + const rasterizedImage = await createRasterizedImage({ + canvas: this.canvas, + fabricObjects: sourceObjects, + clipPath: selectionObject, + trimWhitespace: true, + trimPadding: 2, + quality: 1.0, + format: "png", + scaleFactor: scaleFactor, + preserveOriginalQuality: true, + selectionManager: this.selectionManager, + }); + + if (!rasterizedImage) { + console.error("高质量剪切抠图失败"); + return null; + } + + console.log(`✅ 高质量剪切抠图完成,缩放因子: ${scaleFactor}x`); + return rasterizedImage; + } catch (error) { + console.error("使用createRasterizedImage执行剪切抠图失败:", error); + throw error; + } + } + + /** + * 获取图层的所有对象(包括子图层,从画布中查找真实对象) + * @param {Object} layer 图层对象 + * @returns {Array} 真实的fabric对象数组 + * @private + */ + _getLayerObjects(layer) { + const objects = []; + const canvasObjects = this.canvas.getObjects(); + + // 递归获取图层及其子图层的所有对象 + const collectLayerObjects = (currentLayer) => { + // 处理图层的fabricObjects + if ( + currentLayer.fabricObjects && + Array.isArray(currentLayer.fabricObjects) + ) { + currentLayer.fabricObjects.forEach((fabricObj) => { + if (fabricObj && fabricObj.id) { + const realObject = canvasObjects.find( + (canvasObj) => canvasObj.id === fabricObj.id + ); + if (realObject && realObject.visible !== false) { + objects.push(realObject); + } + } + }); + } + + // 处理单个fabricObject(背景图层等) + if (currentLayer.fabricObject && currentLayer.fabricObject.id) { + const realObject = canvasObjects.find( + (canvasObj) => canvasObj.id === currentLayer.fabricObject.id + ); + if (realObject && realObject.visible !== false) { + objects.push(realObject); + } + } + + // 递归处理子图层 + if (currentLayer.children && Array.isArray(currentLayer.children)) { + currentLayer.children.forEach((childLayer) => { + collectLayerObjects(childLayer); + }); + } + }; + + collectLayerObjects(layer); + + console.log(`从图层 "${layer.name}" 收集到 ${objects.length} 个可见对象`); + return objects; + } + + /** + * 序列化选区对象 + * @private + */ + _serializeSelectionObject() { + try { + if (!this.selectionManager) { + console.warn("选区管理器不存在,无法序列化选区对象"); + return; + } + + const selectionObject = this.selectionManager.getSelectionObject(); + if (!selectionObject) { + console.warn("当前没有选区对象,无法序列化"); + return; + } + + // 将选区对象转换为可序列化的对象 + this.serializedSelectionObject = selectionObject.toObject([ + "id", + "layerId", + "layerName", + "parentId", + ]); + + console.log("选区对象已序列化保存"); + } catch (error) { + console.error("序列化选区对象失败:", error); + this.serializedSelectionObject = null; + } + } + + /** + * 反序列化选区对象 + * @returns {Promise} 选区对象 + * @private + */ + async _getSelectionObject() { + try { + // 优先使用克隆的选区对象 + if (this._clonedSelectionObject) { + console.log("使用克隆的选区对象"); + return await this._cloneObject(this._clonedSelectionObject); + } + + // 尝试从选区管理器获取当前选区 + const currentSelection = this.selectionManager.getSelectionObject(); + if (currentSelection) { + console.log("从选区管理器获取到当前选区"); + return currentSelection; + } + + // 最后使用序列化数据恢复(备用方案) + if (!this.serializedSelectionObject) { + console.error("没有可用的选区对象数据"); + return null; + } + + console.log("从序列化数据恢复选区对象"); + + // 根据选区对象类型进行反序列化 + return new Promise((resolve, reject) => { + const objectType = this.serializedSelectionObject.type; + + if (objectType === "path") { + fabric.Path.fromObject(this.serializedSelectionObject, (path) => { + if (path) { + console.log("路径选区对象反序列化成功"); + resolve(path); + } else { + reject(new Error("路径选区对象反序列化失败")); + } + }); + } else if (objectType === "polygon") { + fabric.Polygon.fromObject( + this.serializedSelectionObject, + (polygon) => { + if (polygon) { + console.log("多边形选区对象反序列化成功"); + resolve(polygon); + } else { + reject(new Error("多边形选区对象反序列化失败")); + } + } + ); + } else if (objectType === "rect") { + fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => { + if (rect) { + console.log("矩形选区对象反序列化成功"); + resolve(rect); + } else { + reject(new Error("矩形选区对象反序列化失败")); + } + }); + } else if (objectType === "ellipse" || objectType === "circle") { + fabric.Ellipse.fromObject( + this.serializedSelectionObject, + (ellipse) => { + if (ellipse) { + console.log("椭圆选区对象反序列化成功"); + resolve(ellipse); + } else { + reject(new Error("椭圆选区对象反序列化失败")); + } + } + ); + } else { + // 通用对象反序列化 + fabric.util.enlivenObjects( + [this.serializedSelectionObject], + (objects) => { + if (objects && objects.length > 0) { + console.log("通用选区对象反序列化成功"); + resolve(objects[0]); + } else { + reject(new Error("通用选区对象反序列化失败")); + } + } + ); + } + }); + } catch (error) { + console.error("获取选区对象失败:", error); + return null; + } + } + + /** + * 克隆fabric对象 + * @param {Object} obj fabric对象 + * @returns {Object} 克隆的对象 + * @private + */ + async _cloneObject(obj) { + return new Promise((resolve, reject) => { + if (!obj) { + reject(new Error("对象无效,无法克隆")); + return; + } + + try { + obj.clone((cloned) => { + if (cloned) { + resolve(cloned); + } else { + reject(new Error("对象克隆失败")); + } + }); + } catch (error) { + reject(error); + } + }); + } + + /** + * 备份原图层状态 + * @param {Object} sourceLayer 源图层 + * @param {Array} sourceObjects 源对象数组 + * @private + */ + async _backupOriginalLayer(sourceLayer, sourceObjects) { + try { + console.log("🔄 备份原图层状态..."); + + // 深度复制图层对象 + this.originalLayerBackup = JSON.parse(JSON.stringify(sourceLayer)); + + // 备份图层中的所有对象数据 + this.originalLayerObjectsData = []; + for (const obj of sourceObjects) { + if (obj && obj.toObject) { + const objData = obj.toObject([ + "id", + "layerId", + "layerName", + "parentId", + "selectable", + "evented", + "visible", + ]); + this.originalLayerObjectsData.push(objData); + } + } + + this.originalLayerRasterized = false; // 标记原图层是否已栅格化 + + console.log( + `✅ 原图层状态已备份,对象数量: ${this.originalLayerObjectsData.length}` + ); + } catch (error) { + console.error("备份原图层状态失败:", error); + throw error; + } + } + + /** + * 对原图层进行栅格化处理,移除选区内容 + * @param {Object} sourceLayer 源图层 + * @param {Array} sourceObjects 源对象数组 + * @param {Object} selectionObject 选区对象 + * @private + */ + async _rasterizeOriginalLayerWithCutout( + sourceLayer, + sourceObjects, + selectionObject + ) { + try { + console.log("=== 开始对原图层进行栅格化处理,移除选区内容 ==="); + + // 克隆选区对象,确保不影响原对象 + const clonedSelectionObject = await this._cloneObject(selectionObject); + + // 直接使用 ClearSelectionContentCommand 的正确实现,传入选区对象 + const clearCommand = new ClearSelectionContentCommand({ + canvas: this.canvas, + layerManager: this.layerManager, + selectionManager: this.selectionManager, + selectionObject: clonedSelectionObject, // 传入选区对象 + highResolutionEnabled: this.highResolutionEnabled, + baseResolutionScale: this.baseResolutionScale, + }); + + // 临时设置目标图层为当前源图层 + clearCommand.targetLayerId = sourceLayer.id; + clearCommand.targetLayer = sourceLayer; + + // 执行清除选区内容命令 + await clearCommand.execute(); + + // 获取清除后的栅格化图像 + this.rasterizedOriginalImage = clearCommand.rasterizedImage; + this.originalLayerRasterized = true; // 标记原图层已栅格化 + + console.log("✅ 原图层栅格化处理完成,选区内容已移除"); + } catch (error) { + console.error("原图层栅格化处理失败:", error); + throw error; + } + } + + /** + * 创建反向剪切路径(选区外的内容) + * @param {Object} selectionObject 选区对象 + * @param {Array} sourceObjects 源对象数组 + * @returns {Object} 反向剪切路径 + * @private + */ + async _createInvertedClipPath(selectionObject, sourceObjects) { + try { + // 计算所有源对象的边界 + let minLeft = Infinity, + minTop = Infinity, + maxRight = -Infinity, + maxBottom = -Infinity; + + sourceObjects.forEach((obj) => { + const bounds = obj.getBoundingRect(true, true); + minLeft = Math.min(minLeft, bounds.left); + minTop = Math.min(minTop, bounds.top); + maxRight = Math.max(maxRight, bounds.left + bounds.width); + maxBottom = Math.max(maxBottom, bounds.top + bounds.height); + }); + + // 扩展边界以确保完整覆盖 + const padding = 50; + const canvasBounds = { + left: minLeft - padding, + top: minTop - padding, + width: maxRight - minLeft + padding * 2, + height: maxBottom - minTop + padding * 2, + }; + + // 创建画布大小的矩形 + const canvasRect = new fabric.Rect({ + left: canvasBounds.left, + top: canvasBounds.top, + width: canvasBounds.width, + height: canvasBounds.height, + fill: "rgba(255,255,255,1)", + stroke: "", + selectable: false, + evented: false, + absolutePositioned: true, + originX: "left", + originY: "top", + }); + + // 克隆选区对象作为要移除的部分 + const cutoutPath = await this._cloneObject(selectionObject); + cutoutPath.set({ + fill: "rgba(0,0,0,1)", // 黑色表示要移除的区域 + stroke: "", + absolutePositioned: true, + globalCompositeOperation: "destination-out", // 关键:使用destination-out混合模式 + }); + + // 创建复合路径:画布矩形 - 选区 = 反向选区 + const invertedClipPath = new fabric.Group([canvasRect, cutoutPath], { + selectable: false, + evented: false, + absolutePositioned: true, + }); + + return invertedClipPath; + } catch (error) { + console.error("创建反向剪切路径失败:", error); + return null; + } + } +} diff --git a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js index 36ae158a..4aaf8ac0 100644 --- a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js @@ -5,7 +5,10 @@ import { } from "../utils/layerHelper.js"; import { createRasterizedImage } from "../utils/selectionToImage.js"; import { CompositeCommand, Command } from "./Command.js"; -import { CreateImageLayerCommand } from "./LayerCommands.js"; +import { + CreateImageLayerCommand, + RemoveLayerCommand, +} from "./LayerCommands.js"; import { fabric } from "fabric-with-all"; import { generateId } from "../utils/helper.js"; @@ -41,6 +44,13 @@ export class LassoCutoutCommand extends CompositeCommand { // 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象 this._clonedSelectionObject = null; this._initializeClonedSelection(); + + // 新增:保存原图层信息用于撤销恢复 + this.originalLayer = null; // 保存原图层的完整信息 + this.originalLayerIndex = -1; // 保存原图层在layers数组中的索引 + this.originalFabricObjects = []; // 保存原图层的所有fabric对象(序列化) + this.originalCanvasObjects = []; // 保存从画布中获取的真实fabric对象 + this._initializeOriginalLayerInfo(); } /** @@ -70,6 +80,43 @@ export class LassoCutoutCommand extends CompositeCommand { } } + /** + * 初始化原图层信息 + * @private + */ + _initializeOriginalLayerInfo() { + if (this.layerManager) { + const activeLayer = this.layerManager.getActiveLayer(); + if (activeLayer) { + // 保存原图层的完整信息 + this.originalLayer = JSON.parse(JSON.stringify(activeLayer)); // 深拷贝 + + // 保存原图层在layers数组中的索引 + this.originalLayerIndex = this.layerManager.layers.value.findIndex( + (layer) => layer.id === activeLayer.id + ); + + // 获取并序列化原图层的所有fabric对象 + const sourceObjects = this._getLayerObjects(activeLayer); + this.originalCanvasObjects = sourceObjects; // 保存真实对象引用 + this.originalFabricObjects = sourceObjects.map((obj) => + obj.toObject([ + "id", + "layerId", + "layerName", + "parentId", + "type", + "custom", + ]) + ); + + console.log( + `套索抠图:已保存原图层信息,图层名: ${activeLayer.name},对象数量: ${sourceObjects.length}` + ); + } + } + } + async execute() { if (!this.canvas || !this.layerManager || !this.selectionManager) { console.error("无法执行套索抠图:参数无效"); @@ -146,7 +193,19 @@ export class LassoCutoutCommand extends CompositeCommand { this.newLayerId = createImageLayerCmd.newLayerId; this.executedCommands.push(createImageLayerCmd); - // 2. 清除选区命令 + // 2. 删除原图层命令 + const removeOriginalLayerCmd = new RemoveLayerCommand({ + canvas: this.canvas, + layers: this.layerManager.layers, + layerId: this.originalLayer.id, + activeLayerId: this.layerManager.activeLayerId, + }); + + // 执行删除原图层命令 + await removeOriginalLayerCmd.execute(); + this.executedCommands.push(removeOriginalLayerCmd); + + // 3. 清除选区命令 const clearSelectionCmd = new ClearSelectionCommand({ canvas: this.canvas, selectionManager: this.selectionManager, @@ -276,6 +335,7 @@ export class LassoCutoutCommand extends CompositeCommand { } // 2. 逆序撤销所有已执行的子命令 + // RemoveLayerCommand的undo会自动恢复原图层,所以不需要再调用_restoreOriginalLayer for (let i = this.executedCommands.length - 1; i >= 0; i--) { const command = this.executedCommands[i]; if (command && typeof command.undo === "function") { @@ -293,6 +353,12 @@ export class LassoCutoutCommand extends CompositeCommand { } } + // 注意:不需要调用_restoreOriginalLayer,因为RemoveLayerCommand的undo已经恢复了原图层 + // 但是需要确保画布状态正确,所以手动触发一次渲染 + if (this.canvas) { + this.canvas.renderAll(); + } + // 3. 清理状态 this.executedCommands = []; this.newLayerId = null; @@ -331,6 +397,13 @@ export class LassoCutoutCommand extends CompositeCommand { baseResolutionScale: this.baseResolutionScale, hasSerializedSelection: !!this.serializedSelectionObject, selectionType: this.serializedSelectionObject?.type || null, + // 新增:原图层信息 + hasOriginalLayer: !!this.originalLayer, + originalLayerName: this.originalLayer?.name || null, + originalLayerId: this.originalLayer?.id || null, + originalLayerIndex: this.originalLayerIndex, + originalFabricObjectsCount: this.originalFabricObjects.length, + originalCanvasObjectsCount: this.originalCanvasObjects.length, subCommands: this.executedCommands.map((cmd) => ({ name: cmd.constructor.name, info: cmd.getInfo ? cmd.getInfo() : {}, @@ -815,6 +888,109 @@ export class LassoCutoutCommand extends CompositeCommand { return null; } } + + /** + * 恢复原图层和其fabric对象 + * @private + */ + async _restoreOriginalLayer() { + if (!this.originalLayer) { + console.warn("没有保存的原图层信息,无法恢复"); + return false; + } + + try { + console.log(`↩️ 开始恢复原图层: ${this.originalLayer.name}`); + + // 1. 恢复图层到原位置 + if (this.originalLayerIndex !== -1) { + this.layerManager.layers.value.splice( + this.originalLayerIndex, + 0, + this.originalLayer + ); + } else { + // 如果没有保存索引,添加到末尾 + this.layerManager.layers.value.push(this.originalLayer); + } + + // 2. 恢复fabric对象到画布 + if (this.originalFabricObjects.length > 0) { + console.log( + `↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象` + ); + + // 使用fabric.util.enlivenObjects批量反序列化对象 + await new Promise((resolve, reject) => { + fabric.util.enlivenObjects( + this.originalFabricObjects, + (restoredObjects) => { + if (!restoredObjects || restoredObjects.length === 0) { + console.warn("没有成功反序列化任何对象"); + resolve(); + return; + } + + // 将恢复的对象添加到画布 + restoredObjects.forEach((obj) => { + if (obj) { + // 确保对象的图层信息正确 + obj.layerId = this.originalLayer.id; + obj.layerName = this.originalLayer.name; + obj.setCoords(); + this.canvas.add(obj); + } + }); + + console.log(`✅ 成功恢复 ${restoredObjects.length} 个fabric对象`); + resolve(); + }, + // 命名空间,用于自定义对象类型 + "" + ); + }); + } + + // 3. 如果恢复的是活动图层,设置为当前活动图层 + if (this.originalLayer.id === this.layerManager.activeLayerId.value) { + this.layerManager.activeLayerId.value = this.originalLayer.id; + } else { + // 如果当前没有活动图层,将恢复的图层设为活动图层 + if (!this.layerManager.activeLayerId.value) { + this.layerManager.activeLayerId.value = this.originalLayer.id; + } + } + + // 4. 重新渲染画布 + this.canvas.renderAll(); + + console.log(`✅ 原图层恢复完成: ${this.originalLayer.name}`); + return true; + } catch (error) { + console.error("❌ 恢复原图层失败:", error); + return false; + } + } + + /** + * 重做操作 + * @returns {Promise} 执行结果 + */ + async redo() { + console.log(`🔄 开始重做套索抠图操作`); + + // 重做操作等同于重新执行,但需要先清理一些状态 + this.executedCommands = []; + this.newLayerId = null; + this.cutoutImageUrl = null; + this.fabricImage = null; + this.groupLayer = null; + + // 重新生成groupId确保唯一性(可选,也可以复用原来的) + // this.groupId = `cutout-group-${Date.now()}`; + + return await this.execute(); + } } /** diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js index 253b6ee5..0a28d67d 100644 --- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js @@ -411,43 +411,63 @@ export class RemoveLayerCommand extends Command { this.removedLayer = this.layers.value[this.layerIndex]; this.isActiveLayer = this.layerId === this.activeLayerId.value; - // 从Canvas中找到真实对象并备份,确保撤销和重做时对象的一致性 + // 从Canvas中找到真实对象并备份,包括当前图层及其所有子图层的对象 this.originalObjects = []; if (this.removedLayer) { - // 从画布中获取真实的对象引用 - this.originalObjects = this.canvas.getObjects().filter((obj) => { - return obj.layerId === this.layerId; - }); + this.originalObjects = this._collectAllLayerObjects(this.removedLayer); } // 备份原活动图层ID this.originalActiveLayerId = this.activeLayerId.value; } + /** + * 收集图层及其所有子图层的画布对象 + * @param {Object} layer 要收集对象的图层 + * @returns {Array} 所有相关的画布对象 + * @private + */ + _collectAllLayerObjects(layer) { + const allObjects = []; + + // 收集当前图层的对象 + const layerObjects = this.canvas.getObjects().filter((obj) => { + return obj.layerId === layer.id; + }); + allObjects.push(...layerObjects); + + // 如果图层有特殊的fabricObject属性(如背景图层) + if (layer.fabricObject) { + const { object } = findObjectById(this.canvas, layer.fabricObject.id); + if (object && !allObjects.includes(object)) { + allObjects.push(object); + } + } + + // 递归收集子图层的对象 + if (layer.children && Array.isArray(layer.children)) { + layer.children.forEach((childLayer) => { + const childObjects = this._collectAllLayerObjects(childLayer); + allObjects.push(...childObjects); + }); + } + + return allObjects; + } + execute() { if (this.layerIndex === -1 || !this.removedLayer) { console.error(`图层 ${this.layerId} 不存在`); return false; } - // 从画布中移除图层中的所有真实对象 + // 从画布中移除图层及其所有子图层中的真实对象 this.originalObjects.forEach((obj) => { if (this.canvas.getObjects().includes(obj)) { this.canvas.remove(obj); } }); - // 如果是背景图层,移除特殊对象 - if (this.removedLayer.isBackground && this.removedLayer.fabricObject) { - const { object } = findObjectById( - this.canvas, - this.removedLayer.fabricObject.id - ); - if (object) { - this.canvas.remove(object); - } - } - // 从图层列表中删除 this.layers.value.splice(this.layerIndex, 1); @@ -470,7 +490,7 @@ export class RemoveLayerCommand extends Command { } console.log( - `✅ 已移除图层: ${this.removedLayer.name} (ID: ${this.layerId})` + `✅ 已移除图层: ${this.removedLayer.name} (ID: ${this.layerId}),包含 ${this.originalObjects.length} 个对象` ); return true; } @@ -482,26 +502,18 @@ export class RemoveLayerCommand extends Command { // 使用优化渲染批处理恢复真实对象到画布 optimizeCanvasRendering(this.canvas, () => { - this.originalObjects.forEach((obj) => { - // 恢复对象到画布 - this.canvas.add(obj); - // 确保对象的图层信息正确 - obj.layerId = this.layerId; - obj.layerName = this.removedLayer.name; - obj.setCoords(); // 更新坐标 - }); - - // 如果是背景图层,恢复特殊对象 - if (this.removedLayer.isBackground && this.removedLayer.fabricObject) { - // 检查对象是否已在画布中 - const { object } = findObjectById( - this.canvas, - this.removedLayer.fabricObject.id - ); - if (!object) { - this.canvas.add(this.removedLayer.fabricObject); - } - } + // 倒序添加对象,确保下标越小的子图层在画布中越靠前 + this.originalObjects + .slice() + .reverse() + .forEach((obj) => { + // 确保对象不在画布中才添加,避免重复添加 + if (!this.canvas.getObjects().includes(obj)) { + this.canvas.add(obj); + // 确保对象的坐标正确 + obj.setCoords(); + } + }); }); // 如果删除的是当前活动图层,恢复活动图层 @@ -510,7 +522,7 @@ export class RemoveLayerCommand extends Command { } console.log( - `↩️ 已恢复图层: ${this.removedLayer.name} (ID: ${this.layerId})` + `↩️ 已恢复图层: ${this.removedLayer.name} (ID: ${this.layerId}),包含 ${this.originalObjects.length} 个对象` ); } } diff --git a/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js b/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js index 387c86ce..e8ede4be 100644 --- a/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js @@ -508,6 +508,9 @@ export class LiquifyStateCommand extends Command { // 获取引用管理器实例 this.refManager = getLiquifyReferenceManager(); + // 实时更新实例 + this.realtimeUpdater = options.realtimeUpdater || null; + // 注册对象到引用管理器 this.objectRefId = this.refManager.registerObject( this.targetObject, @@ -587,17 +590,49 @@ export class LiquifyStateCommand extends Command { this.initialSnapshotId ); - // 恢复液化管理器到初始状态 + // 恢复液化管理器到初始状态 - 关键修复 if (this.liquifyManager) { - if (this.initialLiquifyState) { - this._restoreLiquifyManagerState(this.initialLiquifyState); - } else { - // 如果没有初始状态,重置液化管理器 - this.liquifyManager.reset(); - // 重新准备液化环境 + // if (this.initialLiquifyState) { + // this._restoreLiquifyManagerState(this.initialLiquifyState); + // } else { + // 如果没有初始状态,重置液化管理器并重新初始化 + this.liquifyManager.reset(); + const currentTarget = this.refManager.getObjectRef(this.objectRefId); + if (currentTarget) { + // 重新准备液化环境,使用初始图像数据 + await this.liquifyManager.prepareForLiquify(currentTarget); + + // 强制设置原始图像数据为初始状态 + if (this.liquifyManager.enhancedManager) { + this.liquifyManager.enhancedManager.originalImageData = + this.initialImageData; + this.liquifyManager.enhancedManager.currentImageData = + this._cloneImageData(this.initialImageData); + + // 重置激活渲染器的数据 + if (this.liquifyManager.enhancedManager.activeRenderer) { + const renderer = this.liquifyManager.enhancedManager.activeRenderer; + renderer.originalImageData = this.initialImageData; + renderer.currentImageData = this._cloneImageData( + this.initialImageData + ); + + // 重置CPU渲染器的网格和变形历史 + if (renderer.reset) { + renderer.reset(); + } + // } + } + } + } + + // 确保实时更新器使用正确的目标对象和初始数据 + if (this.realtimeUpdater) { const currentTarget = this.refManager.getObjectRef(this.objectRefId); - if (currentTarget) { - await this.liquifyManager.prepareForLiquify(currentTarget); + this.realtimeUpdater.targetObject = currentTarget; + // 重置实时更新器的内部状态 + if (this.realtimeUpdater.reset) { + this.realtimeUpdater.reset(); } } } @@ -605,7 +640,7 @@ export class LiquifyStateCommand extends Command { this.currentState = "initial"; this.canvas.renderAll(); - console.log("🔄 液化命令撤销完成,恢复初始状态"); + console.log("🔄 液化命令撤销完成,恢复初始状态(已重置所有内部状态)"); return true; } @@ -761,20 +796,22 @@ export class LiquifyStateCommand extends Command { if (!this.liquifyManager) return null; try { + const enhancedManager = this.liquifyManager.enhancedManager; + const state = { // 捕获增强管理器状态 enhancedManagerState: null, // 捕获当前渲染器状态 activeRendererState: null, // 捕获目标对象引用 - targetObjectRef: this.liquifyManager.targetObject, + targetObjectRef: + this.liquifyManager.targetObject ?? enhancedManager.targetObject, // 捕获初始化状态 initialized: this.liquifyManager.initialized || false, }; // 如果有增强管理器,捕获其状态 if (this.liquifyManager.enhancedManager) { - const enhancedManager = this.liquifyManager.enhancedManager; state.enhancedManagerState = { initialized: enhancedManager.initialized, renderMode: enhancedManager.renderMode, @@ -829,6 +866,17 @@ export class LiquifyStateCommand extends Command { this.liquifyManager.targetObject = state.targetObjectRef; } + // 确保实时更新器使用正确的目标对象 + if (this.realtimeUpdater) { + this.realtimeUpdater.targetObject = this.liquifyManager.targetObject; + // 如果有setTargetObject方法,调用它 + if (typeof this.realtimeUpdater.setTargetObject === "function") { + this.realtimeUpdater.setTargetObject( + this.liquifyManager.targetObject + ); + } + } + // 恢复增强管理器状态 if (state.enhancedManagerState && this.liquifyManager.enhancedManager) { const enhancedManager = this.liquifyManager.enhancedManager; @@ -837,6 +885,8 @@ export class LiquifyStateCommand extends Command { enhancedManager.initialized = enhancedState.initialized; enhancedManager.renderMode = enhancedState.renderMode; enhancedManager.targetObject = enhancedState.targetObject; + + // 关键修复:确保图像数据被正确恢复 enhancedManager.originalImageData = enhancedState.originalImageData; enhancedManager.currentImageData = enhancedState.currentImageData; enhancedManager.params = { ...enhancedState.params }; @@ -848,6 +898,8 @@ export class LiquifyStateCommand extends Command { const rendererState = state.activeRendererState; renderer.initialized = rendererState.initialized; + + // 关键修复:强制重置渲染器的图像数据 renderer.originalImageData = rendererState.originalImageData; renderer.currentImageData = rendererState.currentImageData; renderer.params = { ...rendererState.params }; @@ -856,16 +908,45 @@ export class LiquifyStateCommand extends Command { // 恢复网格状态(如果是CPU渲染器) if (rendererState.meshState && renderer.mesh) { this._restoreMeshState(renderer.mesh, rendererState.meshState); + } else if (renderer.mesh && renderer._initMesh) { + // 如果没有保存的网格状态,重新初始化网格 + renderer._initMesh( + rendererState.originalImageData?.width || + renderer.originalImageData?.width, + rendererState.originalImageData?.height || + renderer.originalImageData?.height + ); } - // 恢复变形历史 + // 重置变形历史 if (rendererState.deformHistory) { renderer.deformHistory = [...rendererState.deformHistory]; + } else { + // 如果没有保存的历史,清空变形历史 + renderer.deformHistory = []; + } + + // 重置持续按压相关状态(如果存在) + if (renderer.isHolding !== undefined) { + renderer.isHolding = false; + renderer.pressStartTime = 0; + renderer.pressDuration = 0; + renderer.accumulatedRotation = 0; + renderer.accumulatedScale = 0; + renderer.lastApplyTime = 0; + } + + // 重置拖拽状态(如果存在) + if (renderer.isDragging !== undefined) { + renderer.isDragging = false; + renderer.dragDistance = 0; + renderer.dragAngle = 0; + renderer.isFirstApply = true; } } } - console.log(`🔄 液化管理器状态已恢复`); + console.log(`🔄 液化管理器状态已恢复(包含完整的数据重置)`); } catch (error) { console.error("恢复液化管理器状态失败:", error); } @@ -917,6 +998,36 @@ export class LiquifyStateCommand extends Command { } } } + + /** + * 克隆图像数据 + * @param {ImageData} imageData 原始图像数据 + * @returns {ImageData} 克隆的图像数据 + * @private + */ + _cloneImageData(imageData) { + if (!imageData) return null; + + try { + // 使用新的浏览器API直接复制 + 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); + } + } } /** diff --git a/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue b/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue index 3fa3e589..4159dc12 100644 --- a/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue @@ -1036,6 +1036,7 @@ async function handleMouseUp() { targetObjectId: targetObjectId.value, initialImageData: initialImageData.value, finalImageData: finalImageData, + realtimeUpdater: realtimeUpdater.value, name: `液化操作 - ${currentMode.value}`, description: `应用${currentMode.value}模式的液化变形操作`, }); diff --git a/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue b/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue index 4b072edd..d044fd52 100644 --- a/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue @@ -48,11 +48,11 @@
- {{ $t("拷贝并粘贴") }} + {{ $t("创建") }}
- {{ $t("剪切并粘贴") }} + {{ $t("创建并拷贝") }}
diff --git a/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js b/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js index c8ad3834..bb98d2e6 100644 --- a/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js +++ b/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js @@ -161,7 +161,7 @@ export class EnhancedLiquifyManager { // 传入的是对象 targetObject = target; const { layer } = findInChildLayers( - this.layerManager.layers.value, + this.layerManager.layers?.value ?? this.layerManager.layers, targetObject.layerId ); if (layer) { diff --git a/src/component/Canvas/CanvasEditor/utils/layerHelper.js b/src/component/Canvas/CanvasEditor/utils/layerHelper.js index b513991b..1e35b6d5 100644 --- a/src/component/Canvas/CanvasEditor/utils/layerHelper.js +++ b/src/component/Canvas/CanvasEditor/utils/layerHelper.js @@ -524,7 +524,10 @@ export function findLayerRecursively(layers, layerId, parent = null) { */ export function findInChildLayers(children, layerId, parent) { if (!children || !Array.isArray(children) || !layerId) { - return null; + return { + layer: null, + parent: null, + }; } for (const child of children) { @@ -540,7 +543,7 @@ export function findInChildLayers(children, layerId, parent) { Array.isArray(child.children) ) { const result = findInChildLayers(child.children, layerId, child); - if (result) { + if (result && result.layer) { return result; } }