diff --git a/src/assets/icons/CPlusTop.svg b/src/assets/icons/CPlusTop.svg new file mode 100644 index 00000000..7b291df6 --- /dev/null +++ b/src/assets/icons/CPlusTop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/component/Canvas/CanvasEditor/commands/ClearSelectionContentCommand.js b/src/component/Canvas/CanvasEditor/commands/ClearSelectionContentCommand.js new file mode 100644 index 00000000..82bbe129 --- /dev/null +++ b/src/component/Canvas/CanvasEditor/commands/ClearSelectionContentCommand.js @@ -0,0 +1,527 @@ +import { fabric } from "fabric-with-all"; +import { Command } from "./Command"; +import { createRasterizedImage } from "../utils/selectionToImage"; +import { deepClone, findObjectById, generateId } from "../utils/helper"; +import { findLayerRecursively } from "../utils/layerHelper"; + +/** + * 从选区中删除内容命令 + * 使用栅格化方式清除选区内容,支持复杂选区形状 + */ +export class ClearSelectionContentCommand extends Command { + constructor(options = {}) { + super({ + name: "清除选区内容", + description: "使用栅格化方式删除选区中的内容", + saveState: true, + }); + this.canvas = options.canvas; + this.layers = options.layerManager?.layers; + this.layerManager = options.layerManager; + this.selectionManager = options.selectionManager; + this.targetLayerId = options.layerManager.getActiveLayerId(); // 默认使用当前活动图层 + this.removedObjects = []; + + this.oldLayer = [...this.layers.value]; // 获取原图层对象 + + const { layer } = + findLayerRecursively(this.layers.value, this.targetLayerId) || {}; + + // 栅格化相关属性 + this.originalLayerBackup = null; + + const { object: originalLayerObject } = findObjectById( + this.canvas, + layer.fabricObjects?.[0]?.id + ); + + // this.originalLayerObject = fabric.util.object.clone(originalLayerObject); // 获取原图层对象 + + this.oldLayerObjects = originalLayerObject.toObject([ + "id", + "layerId", + "layerName", + ]); // 获取原图层的所有对象 + + this.rasterizedImage = null; + this.targetLayer = findLayerRecursively( + this.canvas, + this.targetLayerId + )?.layer; + + this.layerRasterized = false; + + // 高清设置 + this.highResolutionEnabled = options.highResolutionEnabled !== false; + this.baseResolutionScale = options.baseResolutionScale || 2; + + // 支持外部传入选区对象,优先使用外部传入的选区对象 + if (options.selectionObject) { + // 外部传入的选区对象,直接使用 + this.selectionObject = options.selectionObject; + console.log("清除选区内容:使用外部传入的选区对象"); + } else { + // 从选区管理器获取选区对象 + const currentSelection = this.selectionManager?.getSelectionObject(); + if (currentSelection) { + this.selectionObject = fabric.util.object.clone(currentSelection); + console.log("清除选区内容:从选区管理器获取选区对象"); + } else { + this.selectionObject = null; + console.warn("清除选区内容:没有可用的选区对象"); + } + } + } + + async execute() { + if (!this.canvas || !this.layerManager) { + console.error("无法清除选区内容:参数无效"); + return false; + } + + try { + // 获取选区 + + if (!this.selectionObject) { + console.error("无法清除选区内容:当前没有选区"); + return false; + } + + // 确定目标图层 + const layerId = + this.targetLayerId || this.layerManager.getActiveLayerId(); + this.targetLayer = this.layerManager.getLayerById(layerId); + if (!this.targetLayer) { + console.error("无法清除选区内容:目标图层无效"); + return false; + } + + // 备份原图层状态 + // this.originalLayerBackup = JSON.parse(JSON.stringify(this.targetLayer)); + + // 获取图层的所有对象 + const layerObjects = this._getLayerObjects(this.targetLayer); + if (layerObjects.length === 0) { + console.warn("目标图层没有对象,无需清除"); + return true; + } + + // 创建反转选区(保留选区外的内容) + const invertedSelection = await this._createInvertedSelection( + this.selectionObject + ); + if (!invertedSelection) { + console.error("创建反转选区失败"); + return false; + } + + // 使用栅格化方式清除选区内容 + await this._rasterizeLayerWithClearance(layerObjects, invertedSelection); + + // 清除选区 + this.selectionManager?.clearSelection?.(); + + console.log("✅ 选区内容清除完成"); + return { cleared: true, layerId: this.targetLayer.id }; + } catch (error) { + console.error("清除选区内容失败:", error); + // 尝试恢复原状态 + await this._restoreOriginalState(); + throw error; + } + } + + async undo() { + try { + this.layers.value = this.oldLayer; // 恢复原图层对象 + + // 恢复原图层状态 + // 移除栅格化后的图像 + if ( + this.rasterizedImage && + this.canvas.getObjects().includes(this.rasterizedImage) + ) { + this.canvas.remove(this.rasterizedImage); + } + + // 恢复图层的fabricObjects + // 重新创建并添加对象到画布 + if (this.targetLayer.fabricObjects.length > 0) { + await new Promise((resolve) => { + fabric.util.enlivenObjects([this.oldLayerObjects], (objects) => { + objects.forEach((obj) => { + // 确保对象有正确的ID和图层信息 + obj.set({ + layerId: this.targetLayer.id, + layerName: this.targetLayer.name, + selectable: true, + evented: true, + }); + + // 添加到画布 + this.canvas.add(obj); + + this.targetLayer.fabricObjects = [ + obj.toObject(["id", "layerId", "layerName", "type", "custom"]), + ]; + }); + + this.canvas.renderAll(); + resolve(); + }); + }); + } + + await this.layerManager?.updateLayersObjectsInteractivity?.(true); + + this.layerRasterized = false; + this.rasterizedImage = null; + + console.log("✅ 清除选区内容撤销完成"); + return true; + } catch (error) { + console.error("❌ 撤销清除选区内容失败:", error); + return false; + } + } + + /** + * 使用栅格化方式清除选区内容 + * @param {Array} layerObjects 图层对象数组 + * @param {Object} invertedSelection 反转选区 + * @private + */ + async _rasterizeLayerWithClearance(layerObjects, invertedSelection) { + try { + console.log("🖼️ 开始栅格化图层并清除选区内容"); + + // 确定缩放因子 + 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生成栅格化图像,使用反转选区作为裁剪路径 + this.rasterizedImage = await createRasterizedImage({ + canvas: this.canvas, + fabricObjects: layerObjects, + clipPath: invertedSelection, // 使用反转选区,保留选区外的内容 + trimWhitespace: false, // 保持原始尺寸 + trimPadding: 0, + quality: 1.0, + format: "png", + scaleFactor: scaleFactor, + preserveOriginalQuality: true, + selectionManager: this.selectionManager, + }); + + if (!this.rasterizedImage) { + throw new Error("栅格化图层失败"); + } + + // 移除原图层的所有对象 + for (const obj of layerObjects) { + if (this.canvas.getObjects().includes(obj)) { + this.canvas.remove(obj); + } + } + + // 设置栅格化图像的属性 + this.rasterizedImage.set({ + id: generateId("cleared_"), + layerId: this.targetLayer.id, + layerName: this.targetLayer.name, + selectable: true, + evented: true, + }); + + // 添加栅格化图像到画布和图层 + this.canvas.add(this.rasterizedImage); + this.targetLayer.fabricObjects = [ + this.rasterizedImage.toObject("id", "layerId", "layerName", "parentId"), + ]; + + this.layerRasterized = true; + this.canvas.renderAll(); + + console.log("✅ 图层栅格化清除完成"); + } catch (error) { + console.error("栅格化图层清除失败:", error); + throw error; + } + } + + /** + * 创建反转选区 + * @param {Object} selectionObject 原选区对象 + * @returns {Object} 反转选区对象 + * @private + */ + async _createInvertedSelection(selectionObject) { + try { + // 获取目标图层的所有对象 + const layerObjects = this._getLayerObjects(this.targetLayer); + if (layerObjects.length === 0) { + console.warn("目标图层没有对象,无法创建反选区"); + return null; + } + + // 计算图层对象的边界框 + const layerBounds = this._calculateLayerBounds(layerObjects); + if (!layerBounds) { + console.warn("无法计算图层边界框"); + return null; + } + + console.log("📐 图层边界框:", layerBounds); + console.log("🎯 选区路径:", selectionObject.path); + + // 创建反转选区路径,使用图层边界而不是画布边界 + const pathString = `M ${layerBounds.left} ${layerBounds.top} L ${ + layerBounds.left + layerBounds.width + } ${layerBounds.top} L ${layerBounds.left + layerBounds.width} ${ + layerBounds.top + layerBounds.height + } L ${layerBounds.left} ${layerBounds.top + layerBounds.height} Z ${ + selectionObject.path + }`; + + const invertedPath = new fabric.Path(pathString, { + fillRule: "evenodd", + selectable: false, + evented: false, + fill: "#ffffff", // 确保填充颜色 + stroke: "", + strokeWidth: 0, + id: `inverted_selection_${Date.now()}`, + name: "inverted_selection", + }); + + console.log("✅ 反转选区创建完成,使用图层边界框"); + return invertedPath; + } catch (error) { + console.error("创建反转选区失败:", error); + return null; + } + } + + /** + * 计算图层对象的边界框 + * @param {Array} layerObjects 图层对象数组 + * @returns {Object|null} 边界框 {left, top, width, height} + * @private + */ + _calculateLayerBounds(layerObjects) { + if (!layerObjects || layerObjects.length === 0) { + return null; + } + + let bounds = null; + + layerObjects.forEach((obj) => { + // 获取对象的边界框(包含变换) + const objBounds = obj.getBoundingRect(true, true); + + if (!bounds) { + bounds = { ...objBounds }; + } else { + const right = Math.max( + bounds.left + bounds.width, + objBounds.left + objBounds.width + ); + const bottom = Math.max( + bounds.top + bounds.height, + objBounds.top + objBounds.height + ); + + bounds.left = Math.min(bounds.left, objBounds.left); + bounds.top = Math.min(bounds.top, objBounds.top); + bounds.width = right - bounds.left; + bounds.height = bottom - bounds.top; + } + }); + + // 添加一些边距以确保完整覆盖 + if (bounds) { + const padding = 2; + bounds.left -= padding; + bounds.top -= padding; + bounds.width += padding * 2; + bounds.height += padding * 2; + } + + return bounds; + } + + /** + * 获取图层的所有对象 + * @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); + return objects; + } + + /** + * 恢复原图层状态 + * @private + */ + async _restoreOriginalState() { + try { + if (!this.originalLayerBackup || !this.targetLayer) { + console.warn("没有原图层备份数据或目标图层"); + return; + } + + // 移除栅格化后的图像 + if ( + this.rasterizedImage && + this.canvas.getObjects().includes(this.rasterizedImage) + ) { + this.canvas.remove(this.rasterizedImage); + } + + // 恢复图层的fabricObjects + this.targetLayer.fabricObjects = + this.originalLayerBackup.fabricObjects || []; + + // 重新创建并添加对象到画布 + if (this.targetLayer.fabricObjects.length > 0) { + await this._restoreLayerObjects(this.targetLayer); + } + + this.layerRasterized = false; + this.rasterizedImage = null; + + console.log("✅ 原图层状态恢复完成"); + } catch (error) { + console.error("恢复原图层状态失败:", error); + } + } + + /** + * 恢复图层对象到画布 + * @param {Object} layer 图层对象 + * @private + */ + async _restoreLayerObjects(layer) { + return new Promise((resolve) => { + if (!layer.fabricObjects || layer.fabricObjects.length === 0) { + resolve(); + return; + } + + fabric.util.enlivenObjects(layer.fabricObjects, (objects) => { + objects.forEach((obj) => { + // 确保对象有正确的ID和图层信息 + obj.set({ + layerId: layer.id, + layerName: layer.name, + selectable: true, + evented: true, + }); + + // 添加到画布 + this.canvas.add(obj); + }); + + this.canvas.renderAll(); + resolve(); + }); + }); + } + + _cloneObjectSync(obj) { + // 这是一个简单的深拷贝,不适用于所有场景 + // 在实际应用中,应该使用fabric.js的clone方法 + if (!obj) return null; + return JSON.parse(JSON.stringify(obj)); + } + + async _cloneObject(obj) { + return new Promise((resolve, reject) => { + if (!obj) { + reject(new Error("对象无效,无法克隆")); + return; + } + + try { + if (typeof obj.clone === "function") { + obj.clone((cloned) => { + resolve(cloned); + }); + } else { + // 如果对象没有clone方法(可能是因为它已经是序列化后的对象) + // 在实际代码中需要适当处理这种情况 + resolve(Object.assign({}, obj)); + } + } catch (error) { + reject(error); + } + }); + } + + /** + * 获取命令信息 + * @returns {Object} 命令详细信息 + */ + getInfo() { + return { + name: this.name, + description: this.description, + targetLayerId: this.targetLayer?.id, + targetLayerName: this.targetLayer?.name, + layerRasterized: this.layerRasterized, + hasOriginalBackup: !!this.originalLayerBackup, + removedObjectsCount: this.removedObjects.length, + highResolutionEnabled: this.highResolutionEnabled, + baseResolutionScale: this.baseResolutionScale, + }; + } +} diff --git a/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js new file mode 100644 index 00000000..03b38b64 --- /dev/null +++ b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.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 922a8985..36ae158a 100644 --- a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js @@ -38,9 +38,36 @@ export class LassoCutoutCommand extends CompositeCommand { this.groupLayer = null; // 新增:保存组图层的引用 this.originalLayersLength = 0; // 新增:保存原始图层数量 - // 序列化保存选区对象,用于重做时恢复 - this.serializedSelectionObject = null; - this._serializeSelectionObject(); + // 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象 + 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() { @@ -55,7 +82,7 @@ export class LassoCutoutCommand extends CompositeCommand { // 保存原始图层数量,用于撤销时的验证 this.originalLayersLength = this.layerManager.layers.value.length; - // 获取选区 + // 获取选区:优先使用克隆的选区对象 const selectionObject = await this._getSelectionObject(); if (!selectionObject) { console.error("无法执行套索抠图:当前没有选区"); @@ -650,7 +677,11 @@ export class LassoCutoutCommand extends CompositeCommand { try { obj.clone((cloned) => { - resolve(cloned); + if (cloned) { + resolve(cloned); + } else { + reject(new Error("对象克隆失败")); + } }); } catch (error) { reject(error); @@ -697,16 +728,22 @@ export class LassoCutoutCommand extends CompositeCommand { */ 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("没有序列化的选区对象数据"); + console.error("没有可用的选区对象数据"); return null; } @@ -717,7 +754,6 @@ export class LassoCutoutCommand extends CompositeCommand { const objectType = this.serializedSelectionObject.type; if (objectType === "path") { - // 如果是路径类型(套索选区) fabric.Path.fromObject(this.serializedSelectionObject, (path) => { if (path) { console.log("路径选区对象反序列化成功"); @@ -727,7 +763,6 @@ export class LassoCutoutCommand extends CompositeCommand { } }); } else if (objectType === "polygon") { - // 如果是多边形类型 fabric.Polygon.fromObject( this.serializedSelectionObject, (polygon) => { @@ -740,7 +775,6 @@ export class LassoCutoutCommand extends CompositeCommand { } ); } else if (objectType === "rect") { - // 如果是矩形选区 fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => { if (rect) { console.log("矩形选区对象反序列化成功"); @@ -750,7 +784,6 @@ export class LassoCutoutCommand extends CompositeCommand { } }); } else if (objectType === "ellipse" || objectType === "circle") { - // 如果是椭圆/圆形选区 fabric.Ellipse.fromObject( this.serializedSelectionObject, (ellipse) => { @@ -784,725 +817,6 @@ export class LassoCutoutCommand extends CompositeCommand { } } -/** - * 剪切选区到新图层命令 - * 实现将选区内容剪切到新图层,同时对原图层进行栅格化抠图的功能 - */ -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.originalLayerRasterized = false; - this.originalLayerBackup = null; - this.rasterizedOriginalImage = null; - - // 序列化保存选区对象,用于重做时恢复 - this.serializedSelectionObject = null; - this._serializeSelectionObject(); - } - - 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; - } - - // 备份原图层状态 - this.originalLayerBackup = JSON.parse(JSON.stringify(sourceLayer)); - - // 获取源图层的所有对象(包括子图层) - const sourceObjects = this._getLayerObjects(sourceLayer); - if (sourceObjects.length === 0) { - console.error("无法执行剪切选区:源图层没有可见对象"); - return false; - } - - // 获取选区边界信息用于后续定位 - const selectionBounds = selectionObject.getBoundingRect(true, true); - - // 步骤1: 先创建抠图到新图层(复制选区内容) - this.fabricImage = await this._performCutoutWithRasterized( - sourceObjects, - selectionObject, - selectionBounds - ); - - if (!this.fabricImage) { - console.error("抠图失败"); - return false; - } - - // 步骤2: 创建图像图层命令 - 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); - - // 步骤3: 对原图层进行栅格化,移除选区内容 - await this._rasterizeOriginalLayerWithCutout( - sourceLayer, - selectionObject, - selectionBounds - ); - - // 步骤4: 清除选区命令 - 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 - ); - - 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 && this.originalLayerBackup) { - 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; - } - } - - /** - * 对原图层进行栅格化,移除选区内容 - * @param {Object} sourceLayer 源图层 - * @param {Object} selectionObject 选区对象 - * @param {Object} selectionBounds 选区边界 - * @private - */ - async _rasterizeOriginalLayerWithCutout( - sourceLayer, - selectionObject, - selectionBounds - ) { - try { - console.log("🔪 开始对原图层进行栅格化剪切"); - - // 获取源图层的所有对象 - const sourceObjects = this._getLayerObjects(sourceLayer); - if (sourceObjects.length === 0) { - console.warn("源图层没有对象需要栅格化"); - return; - } - - // 创建反转选区(即选区外的内容) - const invertedSelection = await this._createInvertedSelection( - selectionObject - ); - if (!invertedSelection) { - console.error("创建反转选区失败"); - return; - } - - // 使用反转选区对原图层进行栅格化(保留选区外的内容) - this.rasterizedOriginalImage = await createRasterizedImage({ - canvas: this.canvas, - fabricObjects: sourceObjects, - clipPath: invertedSelection, - trimWhitespace: true, - trimPadding: 2, - quality: 1.0, - format: "png", - scaleFactor: this.baseResolutionScale, - preserveOriginalQuality: true, - selectionManager: this.selectionManager, - }); - - if (!this.rasterizedOriginalImage) { - console.error("原图层栅格化失败"); - return; - } - - // 移除原图层的所有对象 - for (const obj of sourceObjects) { - if (this.canvas.getObjects().includes(obj)) { - this.canvas.remove(obj); - } - } - - // 将栅格化后的图像设置到原图层 - this.rasterizedOriginalImage.set({ - id: generateId("rasterized_"), - layerId: sourceLayer.id, - layerName: sourceLayer.name, - selectable: true, - evented: true, - }); - - // 添加栅格化图像到画布和图层 - this.canvas.add(this.rasterizedOriginalImage); - sourceLayer.fabricObjects = [ - this.rasterizedOriginalImage.toObject( - "id", - "layerId", - "layerName", - "parentId" - ), - ]; - - this.originalLayerRasterized = true; - this.canvas.renderAll(); - - console.log("✅ 原图层栅格化剪切完成"); - } catch (error) { - console.error("原图层栅格化剪切失败:", error); - throw error; - } - } - - /** - * 创建反转选区 - * @param {Object} selectionObject 原选区对象 - * @returns {Object} 反转选区对象 - * @private - */ - async _createInvertedSelection(selectionObject) { - try { - // 获取画布范围 - const canvasRect = new fabric.Rect({ - left: 0, - top: 0, - width: this.canvas.width, - height: this.canvas.height, - selectable: false, - }); - - // 创建反选路径 - let invertedPath; - try { - // 使用 fabric.js 的布尔运算创建反转选区 - const canvasPath = canvasRect.toClipPathSVG(); - const selectionPath = selectionObject.toClipPathSVG(); - - // 创建反转选区对象 - const pathString = `M 0 0 L ${this.canvas.width} 0 L ${this.canvas.width} ${this.canvas.height} L 0 ${this.canvas.height} Z ${selectionObject.path}`; - - invertedPath = new fabric.Path(pathString, { - fillRule: "evenodd", - selectable: false, - evented: false, - id: `inverted_selection_${Date.now()}`, - name: "inverted_selection", - }); - } catch (error) { - console.error("无法创建反转选区:", error); - return null; - } - - return invertedPath; - } catch (error) { - console.error("创建反转选区失败:", error); - return null; - } - } - - /** - * 恢复原图层状态 - * @private - */ - async _restoreOriginalLayer() { - try { - if (!this.originalLayerBackup) { - console.warn("没有原图层备份数据"); - return; - } - - // 移除栅格化后的图像 - if ( - this.rasterizedOriginalImage && - this.canvas.getObjects().includes(this.rasterizedOriginalImage) - ) { - this.canvas.remove(this.rasterizedOriginalImage); - } - - // 恢复原图层的对象 - const sourceLayer = this.layerManager.getLayerById( - this.originalLayerBackup.id - ); - if (sourceLayer) { - // 恢复图层的fabricObjects - sourceLayer.fabricObjects = - this.originalLayerBackup.fabricObjects || []; - - // 重新创建并添加对象到画布 - if (sourceLayer.fabricObjects.length > 0) { - await this._restoreLayerObjects(sourceLayer); - } - } - - console.log("✅ 原图层状态恢复完成"); - } catch (error) { - console.error("恢复原图层状态失败:", error); - } - } - - /** - * 恢复图层对象到画布 - * @param {Object} layer 图层对象 - * @private - */ - async _restoreLayerObjects(layer) { - return new Promise((resolve) => { - if (!layer.fabricObjects || layer.fabricObjects.length === 0) { - resolve(); - return; - } - - fabric.util.enlivenObjects(layer.fabricObjects, (objects) => { - objects.forEach((obj) => { - // 确保对象有正确的ID和图层信息 - obj.set({ - layerId: layer.id, - layerName: layer.name, - selectable: true, - evented: true, - }); - - // 添加到画布 - this.canvas.add(obj); - }); - - this.canvas.renderAll(); - resolve(); - }); - }); - } - - /** - * 处理执行错误时的清理 - * @private - */ - async _handleExecutionError() { - try { - // 如果已经创建了新图层,需要进行清理 - if (this.newLayerId) { - try { - const layerIndex = this.layerManager.layers.value.findIndex( - (layer) => layer.id === this.newLayerId - ); - if (layerIndex !== -1) { - this.layerManager.layers.value.splice(layerIndex, 1); - } - console.log(`清理了异常创建的新图层: ${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.originalLayerRasterized) { - await this._restoreOriginalLayer(); - } - - // 尝试回滚已执行的命令 - if (this.executedCommands.length > 0) { - 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(); - } - } catch (rollbackError) { - console.warn( - `回滚命令失败: ${command.constructor.name}`, - rollbackError - ); - } - } - } - } catch (error) { - console.error("错误清理过程中出现问题:", error); - } - } - - /** - * 获取命令信息 - * @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, - originalLayerRasterized: this.originalLayerRasterized, - hasOriginalLayerBackup: !!this.originalLayerBackup, - 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.selectionManager) { - const currentSelection = this.selectionManager.getSelectionObject(); - if (currentSelection) { - return currentSelection; - } - } - - // 如果没有当前选区,尝试从序列化数据恢复 - if (this.serializedSelectionObject) { - return new Promise((resolve, reject) => { - fabric.util.enlivenObjects( - [this.serializedSelectionObject], - (objects) => { - if (objects && objects.length > 0) { - resolve(objects[0]); - } else { - reject(new Error("无法从序列化数据恢复选区对象")); - } - } - ); - }); - } - - return null; - } catch (error) { - console.error("获取选区对象失败:", error); - return null; - } - } -} - /** * 清除选区命令 */ @@ -1514,9 +828,10 @@ export class ClearSelectionCommand extends Command { saveState: false, }); this.selectionManager = options.selectionManager; - this.originalSelection = options.selectionManager - ? options.selectionManager.getSelectionPath() - : null; + + // 序列化保存完整的选区状态 + this.originalSelectionState = null; + this._serializeSelectionState(); } async execute() { @@ -1525,9 +840,9 @@ export class ClearSelectionCommand extends Command { return false; } - // 保存原始选区 - if (!this.originalSelection) { - this.originalSelection = this.selectionManager.getSelectionPath(); + // 在执行前再次保存选区状态(以防初始化时没有选区) + if (!this.originalSelectionState) { + this._serializeSelectionState(); } // 清除选区 @@ -1536,10 +851,288 @@ export class ClearSelectionCommand extends Command { } async undo() { - if (!this.selectionManager || !this.originalSelection) return false; + if (!this.selectionManager || !this.originalSelectionState) return false; - // 恢复原始选区 - this.selectionManager.setSelectionFromPath(this.originalSelection); + try { + // 从序列化状态恢复选区 + await this._restoreSelectionState(); + return true; + } catch (error) { + console.error("恢复选区状态失败:", error); + return false; + } + } + + /** + * 重做操作(与execute相同) + * @returns {Promise} 执行结果 + */ + async redo() { + return await this.execute(); + } + + /** + * 序列化选区状态 + * @private + */ + _serializeSelectionState() { + try { + if (!this.selectionManager) { + console.warn("选区管理器不存在,无法序列化选区状态"); + return; + } + + const selectionObject = this.selectionManager.getSelectionObject(); + if (!selectionObject) { + console.log("当前没有选区对象,无需序列化"); + return; + } + + // 序列化选区对象和相关状态 + this.originalSelectionState = { + // 选区对象的序列化数据 + selectionObjectData: selectionObject.toObject([ + "id", + "layerId", + "layerName", + "parentId", + ]), + // 选区路径数据 + selectionPath: this.selectionManager.getSelectionPath(), + // 羽化值 + featherAmount: this.selectionManager.getFeatherAmount(), + // 选区ID + selectionId: selectionObject.id, + // 选区类型 + selectionType: selectionObject.type, + // 选区在画布中的位置 + position: { + left: selectionObject.left, + top: selectionObject.top, + originX: selectionObject.originX, + originY: selectionObject.originY, + }, + // 选区样式 + selectionStyle: { + stroke: selectionObject.stroke, + strokeWidth: selectionObject.strokeWidth, + strokeDashArray: selectionObject.strokeDashArray, + fill: selectionObject.fill, + shadow: selectionObject.shadow + ? { + color: selectionObject.shadow.color, + blur: selectionObject.shadow.blur, + offsetX: selectionObject.shadow.offsetX, + offsetY: selectionObject.shadow.offsetY, + } + : null, + }, + // 选区管理器的内部状态 + managerState: { + isActive: this.selectionManager.isActive, + currentTool: this.selectionManager.currentTool, + selectionType: this.selectionManager.selectionType, + }, + }; + + console.log("选区状态已序列化保存", { + selectionId: this.originalSelectionState.selectionId, + selectionType: this.originalSelectionState.selectionType, + featherAmount: this.originalSelectionState.featherAmount, + }); + } catch (error) { + console.error("序列化选区状态失败:", error); + this.originalSelectionState = null; + } + } + + /** + * 从序列化状态恢复选区 + * @private + */ + async _restoreSelectionState() { + return new Promise((resolve, reject) => { + try { + if (!this.originalSelectionState) { + console.warn("没有保存的选区状态"); + resolve(); + return; + } + + const { + selectionObjectData, + featherAmount, + selectionStyle, + position, + managerState, + } = this.originalSelectionState; + + // 根据选区对象类型进行反序列化 + const objectType = selectionObjectData.type; + + const restoreSelection = (restoredObject) => { + if (!restoredObject) { + reject(new Error("选区对象反序列化失败")); + return; + } + + try { + // 恢复选区ID和基本属性 + restoredObject.set({ + id: this.originalSelectionState.selectionId, + layerId: selectionObjectData.layerId, + layerName: selectionObjectData.layerName, + parentId: selectionObjectData.parentId, + }); + + // 恢复位置信息 + if (position) { + restoredObject.set({ + left: position.left, + top: position.top, + originX: position.originX || "left", + originY: position.originY || "top", + }); + } + + // 恢复样式 + if (selectionStyle) { + restoredObject.set({ + stroke: selectionStyle.stroke, + strokeWidth: selectionStyle.strokeWidth, + strokeDashArray: selectionStyle.strokeDashArray, + fill: selectionStyle.fill, + selectable: false, + evented: false, + excludeFromExport: true, + hoverCursor: "default", + moveCursor: "default", + }); + + // 恢复阴影(羽化效果) + if (selectionStyle.shadow) { + restoredObject.shadow = new fabric.Shadow( + selectionStyle.shadow + ); + } + } + + // 确保对象坐标正确设置 + restoredObject.setCoords(); + + // 通过选区管理器设置选区对象,这会正确更新内部状态 + this.selectionManager.setSelectionObject(restoredObject); + + // 恢复羽化值 + this.selectionManager.setFeatherAmount(featherAmount || 0); + + // 恢复选区管理器的内部状态 + if (managerState) { + // 注意:不要强制设置isActive状态,因为这可能影响工具切换 + // this.selectionManager.isActive = managerState.isActive; + this.selectionManager.currentTool = managerState.currentTool; + this.selectionManager.selectionType = managerState.selectionType; + } + + // 触发选区变化回调 + if ( + this.selectionManager.onSelectionChanged && + typeof this.selectionManager.onSelectionChanged === "function" + ) { + this.selectionManager.onSelectionChanged(); + } + + console.log("选区状态恢复成功", { + id: restoredObject.id, + type: restoredObject.type, + featherAmount: featherAmount, + }); + resolve(); + } catch (error) { + console.error("设置恢复的选区对象失败:", error); + reject(error); + } + }; + + // 根据对象类型进行反序列化 + if (objectType === "path") { + fabric.Path.fromObject(selectionObjectData, restoreSelection); + } else if (objectType === "polygon") { + fabric.Polygon.fromObject(selectionObjectData, restoreSelection); + } else if (objectType === "rect") { + fabric.Rect.fromObject(selectionObjectData, restoreSelection); + } else if (objectType === "ellipse" || objectType === "circle") { + fabric.Ellipse.fromObject(selectionObjectData, restoreSelection); + } else { + // 通用对象反序列化 + fabric.util.enlivenObjects([selectionObjectData], (objects) => { + if (objects && objects.length > 0) { + restoreSelection(objects[0]); + } else { + reject(new Error("通用选区对象反序列化失败")); + } + }); + } + } catch (error) { + console.error("恢复选区状态失败:", error); + reject(error); + } + }); + } + + /** + * 验证选区状态是否正确恢复 + * @private + */ + _validateRestoredState() { + if (!this.selectionManager || !this.originalSelectionState) { + return false; + } + + const currentSelection = this.selectionManager.getSelectionObject(); + if (!currentSelection) { + console.warn("选区对象未正确恢复"); + return false; + } + + // 验证基本属性 + const originalId = this.originalSelectionState.selectionId; + if (currentSelection.id !== originalId) { + console.warn( + `选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}` + ); + return false; + } + + // 验证羽化值 + const currentFeather = this.selectionManager.getFeatherAmount(); + const originalFeather = this.originalSelectionState.featherAmount; + if (currentFeather !== originalFeather) { + console.warn( + `羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}` + ); + return false; + } + + console.log("选区状态验证通过"); return true; } + + /** + * 获取命令信息 + * @returns {Object} 命令详细信息 + */ + getInfo() { + return { + name: this.name, + description: this.description, + hasOriginalState: !!this.originalSelectionState, + selectionType: this.originalSelectionState?.selectionType || null, + featherAmount: this.originalSelectionState?.featherAmount || 0, + selectionId: this.originalSelectionState?.selectionId || null, + managerState: this.originalSelectionState?.managerState || null, + canUndo: !!this.originalSelectionState, + canRedo: true, + }; + } } diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js index 3bfe09e5..253b6ee5 100644 --- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js @@ -34,13 +34,21 @@ export class AddLayerCommand extends Command { this.insertIndex = options.insertIndex; this.oldActiveLayerId = null; this.beforeLayers = [...this.layers.value]; // 备份原图层列表 + + this.options = options.options || {}; } execute() { // 保存当前活动图层ID this.oldActiveLayerId = this.activeLayerId.value; - // 执行添加图层操作 + // 执行添加顶级图层操作 + if (this.options?.insertTop) { + this.layers.value.splice(0, 0, this.newLayer); + this.activeLayerId.value = this.newLayer.id; + return this.newLayer.id; + } + // 智能插入图层到合适位置 this._insertLayerAtCorrectPosition(this.newLayer); // if (this.insertIndex !== undefined && this.insertIndex !== null) { diff --git a/src/component/Canvas/CanvasEditor/commands/SelectionCommands.js b/src/component/Canvas/CanvasEditor/commands/SelectionCommands.js index 88e5f2e8..4a3f25d2 100644 --- a/src/component/Canvas/CanvasEditor/commands/SelectionCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/SelectionCommands.js @@ -428,379 +428,3 @@ export class CopySelectionToNewLayerCommand extends CompositeCommand { }); } } - -/** - * 从选区中删除内容命令 - * 使用栅格化方式清除选区内容,支持复杂选区形状 - */ -export class ClearSelectionContentCommand extends Command { - constructor(options = {}) { - super({ - name: "清除选区内容", - description: "使用栅格化方式删除选区中的内容", - saveState: true, - }); - this.canvas = options.canvas; - this.layerManager = options.layerManager; - this.selectionManager = options.selectionManager; - this.targetLayerId = options.targetLayerId; - this.removedObjects = []; - - // 栅格化相关属性 - this.originalLayerBackup = null; - this.rasterizedImage = null; - this.targetLayer = null; - this.layerRasterized = false; - - // 高清设置 - this.highResolutionEnabled = options.highResolutionEnabled !== false; - this.baseResolutionScale = options.baseResolutionScale || 2; - } - - async execute() { - if (!this.canvas || !this.layerManager || !this.selectionManager) { - console.error("无法清除选区内容:参数无效"); - return false; - } - - try { - // 获取选区 - const selectionObject = this.selectionManager.getSelectionObject(); - if (!selectionObject) { - console.error("无法清除选区内容:当前没有选区"); - return false; - } - - // 确定目标图层 - const layerId = - this.targetLayerId || this.layerManager.getActiveLayerId(); - this.targetLayer = this.layerManager.getLayerById(layerId); - if (!this.targetLayer) { - console.error("无法清除选区内容:目标图层无效"); - return false; - } - - // 备份原图层状态 - this.originalLayerBackup = JSON.parse(JSON.stringify(this.targetLayer)); - - // 获取图层的所有对象 - const layerObjects = this._getLayerObjects(this.targetLayer); - if (layerObjects.length === 0) { - console.warn("目标图层没有对象,无需清除"); - return true; - } - - // 创建反转选区(保留选区外的内容) - const invertedSelection = await this._createInvertedSelection( - selectionObject - ); - if (!invertedSelection) { - console.error("创建反转选区失败"); - return false; - } - - // 使用栅格化方式清除选区内容 - await this._rasterizeLayerWithClearance(layerObjects, invertedSelection); - - // 清除选区 - this.selectionManager.clearSelection(); - - console.log("✅ 选区内容清除完成"); - return { cleared: true, layerId: this.targetLayer.id }; - } catch (error) { - console.error("清除选区内容失败:", error); - // 尝试恢复原状态 - await this._restoreOriginalState(); - throw error; - } - } - - async undo() { - try { - console.log("↩️ 开始撤销清除选区内容操作"); - - if (this.layerRasterized && this.originalLayerBackup) { - await this._restoreOriginalState(); - } else if (this.removedObjects.length > 0) { - // 恢复被删除的对象(兼容旧版本) - for (const item of this.removedObjects) { - if (item.object && item.layerId) { - const clonedObj = await this._cloneObject(item.object); - this.layerManager.addObjectToLayer(clonedObj, item.layerId); - } - } - } - - console.log("✅ 清除选区内容撤销完成"); - return true; - } catch (error) { - console.error("❌ 撤销清除选区内容失败:", error); - return false; - } - } - - /** - * 使用栅格化方式清除选区内容 - * @param {Array} layerObjects 图层对象数组 - * @param {Object} invertedSelection 反转选区 - * @private - */ - async _rasterizeLayerWithClearance(layerObjects, invertedSelection) { - try { - console.log("🖼️ 开始栅格化图层并清除选区内容"); - - // 确定缩放因子 - 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生成栅格化图像,使用反转选区作为裁剪路径 - this.rasterizedImage = await createRasterizedImage({ - canvas: this.canvas, - fabricObjects: layerObjects, - clipPath: invertedSelection, // 使用反转选区,保留选区外的内容 - trimWhitespace: false, // 保持原始尺寸 - trimPadding: 0, - quality: 1.0, - format: "png", - scaleFactor: scaleFactor, - preserveOriginalQuality: true, - selectionManager: this.selectionManager, - }); - - if (!this.rasterizedImage) { - throw new Error("栅格化图层失败"); - } - - // 移除原图层的所有对象 - for (const obj of layerObjects) { - if (this.canvas.getObjects().includes(obj)) { - this.canvas.remove(obj); - } - } - - // 设置栅格化图像的属性 - this.rasterizedImage.set({ - id: generateId("cleared_"), - layerId: this.targetLayer.id, - layerName: this.targetLayer.name, - selectable: true, - evented: true, - }); - - // 添加栅格化图像到画布和图层 - this.canvas.add(this.rasterizedImage); - this.targetLayer.fabricObjects = [ - this.rasterizedImage.toObject("id", "layerId", "layerName", "parentId"), - ]; - - this.layerRasterized = true; - this.canvas.renderAll(); - - console.log("✅ 图层栅格化清除完成"); - } catch (error) { - console.error("栅格化图层清除失败:", error); - throw error; - } - } - - /** - * 创建反转选区 - * @param {Object} selectionObject 原选区对象 - * @returns {Object} 反转选区对象 - * @private - */ - async _createInvertedSelection(selectionObject) { - try { - // 创建反转选区路径 - const pathString = `M 0 0 L ${this.canvas.width} 0 L ${this.canvas.width} ${this.canvas.height} L 0 ${this.canvas.height} Z ${selectionObject.path}`; - - const invertedPath = new fabric.Path(pathString, { - fillRule: "evenodd", - selectable: false, - evented: false, - id: `inverted_selection_${Date.now()}`, - name: "inverted_selection", - }); - - return invertedPath; - } catch (error) { - console.error("创建反转选区失败:", error); - return null; - } - } - - /** - * 获取图层的所有对象 - * @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); - return objects; - } - - /** - * 恢复原图层状态 - * @private - */ - async _restoreOriginalState() { - try { - if (!this.originalLayerBackup || !this.targetLayer) { - console.warn("没有原图层备份数据或目标图层"); - return; - } - - // 移除栅格化后的图像 - if ( - this.rasterizedImage && - this.canvas.getObjects().includes(this.rasterizedImage) - ) { - this.canvas.remove(this.rasterizedImage); - } - - // 恢复图层的fabricObjects - this.targetLayer.fabricObjects = - this.originalLayerBackup.fabricObjects || []; - - // 重新创建并添加对象到画布 - if (this.targetLayer.fabricObjects.length > 0) { - await this._restoreLayerObjects(this.targetLayer); - } - - this.layerRasterized = false; - this.rasterizedImage = null; - - console.log("✅ 原图层状态恢复完成"); - } catch (error) { - console.error("恢复原图层状态失败:", error); - } - } - - /** - * 恢复图层对象到画布 - * @param {Object} layer 图层对象 - * @private - */ - async _restoreLayerObjects(layer) { - return new Promise((resolve) => { - if (!layer.fabricObjects || layer.fabricObjects.length === 0) { - resolve(); - return; - } - - fabric.util.enlivenObjects(layer.fabricObjects, (objects) => { - objects.forEach((obj) => { - // 确保对象有正确的ID和图层信息 - obj.set({ - layerId: layer.id, - layerName: layer.name, - selectable: true, - evented: true, - }); - - // 添加到画布 - this.canvas.add(obj); - }); - - this.canvas.renderAll(); - resolve(); - }); - }); - } - - _cloneObjectSync(obj) { - // 这是一个简单的深拷贝,不适用于所有场景 - // 在实际应用中,应该使用fabric.js的clone方法 - if (!obj) return null; - return JSON.parse(JSON.stringify(obj)); - } - - async _cloneObject(obj) { - return new Promise((resolve, reject) => { - if (!obj) { - reject(new Error("对象无效,无法克隆")); - return; - } - - try { - if (typeof obj.clone === "function") { - obj.clone((cloned) => { - resolve(cloned); - }); - } else { - // 如果对象没有clone方法(可能是因为它已经是序列化后的对象) - // 在实际代码中需要适当处理这种情况 - resolve(Object.assign({}, obj)); - } - } catch (error) { - reject(error); - } - }); - } - - /** - * 获取命令信息 - * @returns {Object} 命令详细信息 - */ - getInfo() { - return { - name: this.name, - description: this.description, - targetLayerId: this.targetLayer?.id, - targetLayerName: this.targetLayer?.name, - layerRasterized: this.layerRasterized, - hasOriginalBackup: !!this.originalLayerBackup, - removedObjectsCount: this.removedObjects.length, - highResolutionEnabled: this.highResolutionEnabled, - baseResolutionScale: this.baseResolutionScale, - }; - } -} diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue index 42ed71c8..19462630 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue @@ -11,7 +11,7 @@ import LayersList from "./LayersList.vue"; // 引入 LayersList 组件 // GroupLayersCommand, // UngroupLayersCommand, // } from "../../commands/LayerCommands"; -import { generateId } from "../../utils/helper"; +import { findObjectById, generateId } from "../../utils/helper"; const props = defineProps({ // layers: Array, @@ -26,6 +26,7 @@ const props = defineProps({ const emit = defineEmits([ "add-layer", + "add-top-layer", "add-group-layer", "set-active-layer", "toggle-layer-visibility", @@ -92,6 +93,13 @@ const selectedLayers = computed(() => { ); }); +// 计算属性:获取当前是否激活子图层 +const isChildLayerActive = computed(() => { + const { parent } = findLayerRecursively(layers.value, props.activeLayerId); + if (parent) return true; // 如果有父图层,则表示是子图层 + return false; // 否则是根图层 +}); + // 计算属性:检查是否有选中的图层可以分组 const canGroupLayers = computed(() => { // 直接基于 selectedLayerIds 和 sortableRootLayers 计算,确保响应式 @@ -142,6 +150,9 @@ const canToggleLock = computed(() => { function addLayer() { emit("add-layer"); } +function addTopLayer() { + emit("add-top-layer"); +} function addGroupLayer() { emit("add-group-layer"); @@ -590,6 +601,7 @@ function handleLayerClick(layer, event) { ) { // 如果是组图层,设置第一个子图层为活动图层 setActiveLayer(layer.children[0].id, { parentId: layer.id }); + layerManager?.setAllActiveGroupLayerCanvasObject?.(layer); } else { // 否则直接设置当前图层为活动图层 setActiveLayer(layer.id); @@ -1252,6 +1264,14 @@ const forwardEvent = (eventName, ...args) => {
+
+ +
@@ -1002,6 +1025,7 @@ defineExpose({ :thumbnailManager="canvasManager.thumbnailManager" :showFixedLayer="showFixedLayer" @add-layer="addLayer" + @add-top-layer="addTopLayer" @set-active-layer="setActiveLayer" @toggle-layer-visibility="toggleLayerVisibility" @move-layer-up="moveLayerUp" diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index c775fcfb..e6fed3f2 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -1059,7 +1059,7 @@ export class CanvasManager { console.log("画布JSON数据加载完成"); // 画布初始化事件 - this.handleCanvasInit?.(ture); + this.handleCanvasInit?.(true); resolve(); } catch (error) { console.error("恢复图层数据失败:", error); diff --git a/src/component/Canvas/CanvasEditor/managers/LayerManager.js b/src/component/Canvas/CanvasEditor/managers/LayerManager.js index ed014d7f..5c74aa00 100644 --- a/src/component/Canvas/CanvasEditor/managers/LayerManager.js +++ b/src/component/Canvas/CanvasEditor/managers/LayerManager.js @@ -377,24 +377,23 @@ export class LayerManager { */ async createLayer(name = null, type = LayerType.EMPTY, options = {}) { // 生成唯一ID - const layerId = - options.id || - options.layerId || - generateId("layer_") || - `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`; - const layerIndex = this.layers.value.length; + const layerId = options.id || options.layerId || generateId("layer_"); + // 计算普通图层数量(非背景、非固定) + const normalLayersCount = this.layers.value.filter( + (layer) => !layer.isBackground && !layer.isFixed + ).length; // 计算插入位置,如果没有指定insertIndex,则根据当前选中图层决定插入位置 // 添加到图层列表 - let insertIndex = this._getInsertIndexAboveActiveLayer(); - if (options.insertIndex !== undefined) { - insertIndex = options.insertIndex; - } + // let insertIndex = this._getInsertIndexAboveActiveLayer(); + // if (options.insertIndex !== undefined) { + // insertIndex = options.insertIndex; + // } // 创建新图层 const newLayer = createLayer({ id: layerId, - name: name || `图层 ${layerIndex + 1}`, + name: name || `图层 ${normalLayersCount + 1}`, type: type, visible: true, locked: false, @@ -411,14 +410,9 @@ export class LayerManager { layers: this.layers, newLayer: newLayer, activeLayerId: this.activeLayerId, - insertIndex: insertIndex, + options, }); - // 计算普通图层数量(非背景、非固定) - const normalLayersCount = this.layers.value.filter( - (layer) => !layer.isBackground && !layer.isFixed - ).length; - // 如果有额外选项,设置命令的undoable属性 command.undoable = options.undoable; @@ -921,6 +915,48 @@ export class LayerManager { } } + // 设置激活当前图层下画布中的所有对象,并变成选择组 + setAllActiveGroupLayerCanvasObject(layer) { + // 获取当前图层下所有元素 + + // 选择当前组下所有画布元素 + const allObjects = layer.children.reduce((acc, child) => { + // 如果子图层有fabricObjects,则添加到结果数组 + child?.fabricObjects?.forEach((obj) => { + const { object } = findObjectById(this.canvas, obj.id); + if (object) { + acc.push(object); + } + }); + + if (child?.fabricObject) { + const { object } = findObjectById(this.canvas, child?.fabricObject.id); + if (object) { + acc.push(object); + } + } + + return acc; + }, []); + + if (allObjects.length) { + // 切换到选择模式 + this?.toolManager?.setTool(OperationType.SELECT); + + // 如果有对象,创建选择组 + this.canvas.discardActiveObject(); // 取消当前活动对象 + // 选中多个对象,不是创建组 + // 多个对象时创建活动选择组 + const activeSelection = new fabric.ActiveSelection(allObjects, { + canvas: this.canvas, + }); + + // 设置活动选择组的属性 + this.canvas.setActiveObject(activeSelection); + this.canvas.renderAll(); + } + } + /** * 切换图层可见性 * @param {string} layerId 图层ID @@ -943,6 +979,22 @@ export class LayerManager { } } + /** + * 查询图层可见性 + * @param {string} layerId 图层ID + * @returns {boolean} 图层是否可见 + */ + getLayerVisibility(layerId) { + // 查找图层 + const { layer } = findLayerRecursively(this.layers.value, layerId); + if (!layer) { + console.error(`图层 ${layerId} 不存在`); + return false; + } + // 返回图层的可见性状态 + return layer.visible; + } + /** * 切换图层锁定状态 * @param {string} layerId 图层ID diff --git a/src/component/Canvas/CanvasEditor/utils/layerHelper.js b/src/component/Canvas/CanvasEditor/utils/layerHelper.js index 80e33332..b513991b 100644 --- a/src/component/Canvas/CanvasEditor/utils/layerHelper.js +++ b/src/component/Canvas/CanvasEditor/utils/layerHelper.js @@ -475,7 +475,10 @@ export function cloneLayer(layer) { export function findLayerRecursively(layers, layerId, parent = null) { try { if (!layers || !Array.isArray(layers) || !layerId) { - return null; + return { + layer: null, + parent: null, + }; } // 在当前图层列表中查找 @@ -499,11 +502,17 @@ export function findLayerRecursively(layers, layerId, parent = null) { } } catch (error) { console.error(`查找图层 ${layerId} 时出错:`, error); - return null; + return { + layer: null, + parent: null, + }; } console.warn(`图层 ${layerId} 未找到`); - return null; + return { + layer: null, + parent: null, + }; } /**