diff --git a/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js index bf279548..d9e7b9f5 100644 --- a/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand.js @@ -35,11 +35,13 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用 this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数 - this.groupId = options.groupId || `cutout-group-${Date.now()}`; + this.groupId = options.groupId || generateId("lasso-copy-group-"); this.groupName = options.groupName || `选区组`; this.groupLayer = null; // 新增:保存组图层的引用 this.originalLayersLength = 0; // 新增:保存原始图层数量 + this.clippingMaskId = generateId("clipping-mask-"); + // 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象 this._clonedSelectionObject = null; this._initializeClonedSelection(); @@ -105,6 +107,16 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { return false; } + const clippingMask = fabric.util.object.clone(selectionObject); + clippingMask.set({ + id: this.clippingMaskId, + selectable: false, + evented: false, + hasControls: false, + // layerId: this.groupId, + visible: true, + }); + // 获取选区边界信息用于后续定位 const selectionBounds = selectionObject.getBoundingRect(true, true); @@ -185,12 +197,8 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand { selectLayer.fabricObjects = [ this.fabricImage.toObject("id", "layerId", "layerName", "parentId"), ]; - this.groupLayer.clippingMask = this.fabricImage.toObject( - "id", - "layerId", - "layerName", - "parentId" - ); // 设置组图层的fabricObject为遮罩图像 + + this.groupLayer.clippingMask = clippingMask.toObject(["id", "layerId"]); // 设置组图层的fabricObject为遮罩图像 this.groupLayer.children.push(selectLayer); // 插入新组图层 diff --git a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js new file mode 100644 index 00000000..09973b71 --- /dev/null +++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js @@ -0,0 +1,1323 @@ +import { + createLayer, + findInChildLayers, + LayerType, +} from "../utils/layerHelper.js"; +import { createRasterizedImage } from "../utils/selectionToImage.js"; +import { CompositeCommand, Command } from "./Command.js"; +import { + CreateImageLayerCommand, + RemoveLayerCommand, +} from "./LayerCommands.js"; +import { fabric } from "fabric-with-all"; +import { generateId } from "../utils/helper.js"; + +/** + * 套索抠图命令 + * 实现将选区内容抠图到新图层的功能 + */ +export class LassoCutoutCommand 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 || generateId("lasso-group-"); + this.groupName = options.groupName || `选区组`; + + this.clippingMaskId = generateId("clipping-mask-"); + + this.groupLayer = null; // 新增:保存组图层的引用 + this.originalLayersLength = 0; // 新增:保存原始图层数量 + + // 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象 + this._clonedSelectionObject = null; + this._initializeClonedSelection(); + + // 新增:保存原图层信息用于撤销恢复 + this.originalLayer = null; // 保存原图层的完整信息 + this.originalLayerIndex = -1; // 保存原图层在layers数组中的索引 + this.originalFabricObjects = []; // 保存原图层的所有fabric对象(序列化) + this.originalCanvasObjects = []; // 保存从画布中获取的真实fabric对象 + this._initializeOriginalLayerInfo(); + } + + /** + * 初始化克隆的选区对象 + * @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", + ]); + } + } + } + } + + /** + * 初始化原图层信息 + * @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("无法执行套索抠图:参数无效"); + 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; + } + + // 获取源图层的所有对象(包括子图层) + const sourceObjects = this._getLayerObjects(sourceLayer); + if (sourceObjects.length === 0) { + console.error("无法执行套索抠图:源图层没有可见对象"); + return false; + } + + const clippingMask = fabric.util.object.clone(selectionObject); + clippingMask.set({ + id: this.clippingMaskId, + selectable: false, + evented: false, + hasControls: false, + // layerId: this.groupId, + visible: true, + }); + + // 获取选区边界信息用于后续定位 + const selectionBounds = selectionObject.getBoundingRect(true, true); + + // 使用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("创建图像对象失败"); + return false; + } + + // 1. 创建图像图层命令 + 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); + + // 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, + }); + + // 执行清除选区命令 + 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; // 设置新图层的parentId为组图层ID + selectLayer.fabricObjects = [ + this.fabricImage.toObject("id", "layerId", "layerName", "parentId"), + ]; + + this.groupLayer.clippingMask = clippingMask.toObject(["id", "layerId"]); // 设置组图层的fabricObject为遮罩图像 + + 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, + guroupId: this.groupId, + groupName: this.groupName, + }; + } catch (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); + } + } + + 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) { + console.log(`↩️ 移除组图层: ${this.groupId}`); + // 从图层列表中移除组图层 + this.layerManager.layers.value.splice(groupIndex, 1); + } + } + + if (this.fabricImage) { + console.log(`↩️ 移除抠图图像: ${this.fabricImage.id}`); + // 从画布中移除抠图图像 + this.canvas.remove(this.fabricImage); + } + + // 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") { + try { + console.log(`↩️ 撤销子命令: ${command.constructor.name}`); + await command.undo(); + console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); + } catch (error) { + console.error( + `❌ 子命令撤销失败: ${command.constructor.name}`, + error + ); + // 子命令撤销失败不中断整个撤销过程 + } + } + } + + // 注意:不需要调用_restoreOriginalLayer,因为RemoveLayerCommand的undo已经恢复了原图层 + // 但是需要确保画布状态正确,所以手动触发一次渲染 + if (this.canvas) { + this.canvas.renderAll(); + } + + // 3. 清理状态 + this.executedCommands = []; + this.newLayerId = null; + this.cutoutImageUrl = null; + this.fabricImage = null; + this.groupLayer = null; // 清理组图层引用 + // 注意:不重置groupId,因为重做时可能需要使用相同的ID + + // 4. 更新画布和图层交互性 + await this.layerManager.updateLayersObjectsInteractivity(true); + + console.log(`✅ 套索抠图撤销完成`); + return true; + } 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, + // 新增:原图层信息 + 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() : {}, + })), + }; + } + + /** + * 获取图层的所有对象(包括子图层,从画布中查找真实对象) + * @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((fabricRef) => { + if (fabricRef && fabricRef.id) { + // 从画布中查找真实的对象 + const realObject = canvasObjects.find( + (obj) => obj.id === fabricRef.id + ); + if (realObject && realObject.visible) { + objects.push(realObject); + } + } + }); + } + + // 处理单个fabricObject(背景图层等) + if (currentLayer.fabricObject && currentLayer.fabricObject.id) { + const realObject = canvasObjects.find( + (obj) => obj.id === currentLayer.fabricObject.id + ); + if (realObject && realObject.visible) { + objects.push(realObject); + } + } + + // 递归处理子图层 + if (currentLayer.children && Array.isArray(currentLayer.children)) { + currentLayer.children.forEach((childLayer) => { + if (childLayer.visible !== false) { + // 只处理可见的子图层 + collectLayerObjects(childLayer); + } + }); + } + }; + + collectLayerObjects(layer); + + console.log(`从图层 "${layer.name}" 收集到 ${objects.length} 个可见对象`); + 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 + */ + _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对象 + * @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(); + } +} + +/** + * 清除选区命令 + */ +export class ClearSelectionCommand extends Command { + constructor(options = {}) { + super({ + name: "清除选区", + description: "清除当前选区", + saveState: false, + }); + this.selectionManager = options.selectionManager; + + // 序列化保存完整的选区状态 + this.originalSelectionState = null; + this._serializeSelectionState(); + } + + async execute() { + if (!this.selectionManager) { + console.error("无法清除选区:参数无效"); + return false; + } + + // 在执行前再次保存选区状态(以防初始化时没有选区) + if (!this.originalSelectionState) { + this._serializeSelectionState(); + } + + // 清除选区 + this.selectionManager.clearSelection(); + return true; + } + + async undo() { + if (!this.selectionManager || !this.originalSelectionState) return false; + + 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/LassoCutoutCommand.js b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js index 4aaf8ac0..3c04459e 100644 --- a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js @@ -14,7 +14,7 @@ import { generateId } from "../utils/helper.js"; /** * 套索抠图命令 - * 实现将选区内容抠图到新图层的功能 + * 实现将选区内容到新图层遮罩 */ export class LassoCutoutCommand extends CompositeCommand { constructor(options = {}) { @@ -36,8 +36,11 @@ export class LassoCutoutCommand extends CompositeCommand { this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用 this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数 - this.groupId = options.groupId || `cutout-group-${Date.now()}`; + this.groupId = options.groupId || generateId("lasso-group-"); this.groupName = options.groupName || `选区组`; + + this.clippingMaskId = generateId("clipping-mask-"); + this.groupLayer = null; // 新增:保存组图层的引用 this.originalLayersLength = 0; // 新增:保存原始图层数量 @@ -51,6 +54,8 @@ export class LassoCutoutCommand extends CompositeCommand { this.originalFabricObjects = []; // 保存原图层的所有fabric对象(序列化) this.originalCanvasObjects = []; // 保存从画布中获取的真实fabric对象 this._initializeOriginalLayerInfo(); + + this.oldActiveLayerId = this.layerManager.activeLayerId.value; // 保存旧的活动图层ID } /** @@ -144,14 +149,24 @@ export class LassoCutoutCommand extends CompositeCommand { } // 获取源图层的所有对象(包括子图层) - const sourceObjects = this._getLayerObjects(sourceLayer); - if (sourceObjects.length === 0) { - console.error("无法执行套索抠图:源图层没有可见对象"); - return false; - } + // const sourceObjects = this._getLayerObjects(sourceLayer); + // if (sourceObjects.length === 0) { + // console.error("无法执行套索抠图:源图层没有可见对象"); + // return false; + // } + + const clippingMask = fabric.util.object.clone(selectionObject); + clippingMask.set({ + id: this.clippingMaskId, + selectable: false, + evented: false, + hasControls: false, + // layerId: this.groupId, + visible: true, + }); // 获取选区边界信息用于后续定位 - const selectionBounds = selectionObject.getBoundingRect(true, true); + // const selectionBounds = selectionObject.getBoundingRect(true, true); // 使用createRasterizedImage执行抠图操作 // this.cutoutImageUrl = await this._performCutoutWithRasterized( @@ -164,46 +179,34 @@ export class LassoCutoutCommand extends CompositeCommand { // return false; // } - this.fabricImage = await this._performCutoutWithRasterized( - sourceObjects, - selectionObject, - selectionBounds - ); + // this.fabricImage = await this._performCutoutWithRasterized( + // sourceObjects, + // selectionObject, + // selectionBounds + // ); // // 创建fabric图像对象,传递选区边界信息 // this.fabricImage = await this._createFabricImage( // this.cutoutImageUrl, // selectionBounds // ); - if (!this.fabricImage) { - console.error("创建图像对象失败"); - return false; - } + // if (!this.fabricImage) { + // console.error("创建图像对象失败"); + // return false; + // } // 1. 创建图像图层命令 - const createImageLayerCmd = new CreateImageLayerCommand({ - layerManager: this.layerManager, - fabricImage: this.fabricImage, - toolManager: this.toolManager, - layerName: this.newLayerName, - }); + // 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); - - // 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); + // // 执行创建图像图层命令 + // const result = await createImageLayerCmd.execute(); + // this.newLayerId = createImageLayerCmd.newLayerId; + // this.executedCommands.push(createImageLayerCmd); // 3. 清除选区命令 const clearSelectionCmd = new ClearSelectionCommand({ @@ -216,10 +219,10 @@ export class LassoCutoutCommand extends CompositeCommand { this.executedCommands.push(clearSelectionCmd); const topLayerIndex = this.layerManager.layers.value.findIndex( - (layer) => layer.id === this.newLayerId + (layer) => layer.id === this.originalLayer.id ); - const selectLayer = this.layerManager.layers.value[topLayerIndex]; + // const selectLayer = this.layerManager.layers.value[topLayerIndex]; // 创建新的组图层 this.groupLayer = createLayer({ @@ -233,28 +236,47 @@ export class LassoCutoutCommand extends CompositeCommand { children: [], }); - this.fabricImage.set({ - selectable: true, - evented: true, + // this.fabricImage.set({ + // selectable: true, + // evented: true, + // }); + + const selectLayer = createLayer({ + name: `选区空图层`, + type: LayerType.EMPTY, + visible: true, + locked: false, + opacity: 1.0, + fabricObjects: [], + children: [], }); selectLayer.parentId = this.groupId; // 设置新图层的parentId为组图层ID - selectLayer.fabricObjects = [ - this.fabricImage.toObject("id", "layerId", "layerName", "parentId"), - ]; - this.groupLayer.clippingMask = this.fabricImage.toObject( - "id", - "layerId", - "layerName", - "parentId" - ); // 设置组图层的fabricObject为遮罩图像 + // selectLayer.fabricObjects = [ + // this.fabricImage.toObject("id", "layerId", "layerName", "parentId"), + // ]; + // 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); + + this.groupLayer.clippingMask = clippingMask.toObject(["id", "layerId"]); // 设置组图层的fabricObject为遮罩图像 this.groupLayer.children.push(selectLayer); // 插入新组图层 - this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer); + this.layerManager.layers.value.splice(topLayerIndex, 0, this.groupLayer); + + this.layerManager.activeLayerId.value = selectLayer.id; // 设置新组图层为活动图层 this.canvas.discardActiveObject(); - this.canvas.setActiveObject(this.fabricImage); + // this.canvas.setActiveObject(this.fabricImage); await this.layerManager.updateLayersObjectsInteractivity(true); console.log(`套索抠图完成,新图层ID: ${this.newLayerId}`); @@ -328,6 +350,8 @@ export class LassoCutoutCommand extends CompositeCommand { } } + this.layerManager.activeLayerId.value = this.oldActiveLayerId; // 恢复旧的活动图层ID + if (this.fabricImage) { console.log(`↩️ 移除抠图图像: ${this.fabricImage.id}`); // 从画布中移除抠图图像 diff --git a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js index 655aac41..569ab999 100644 --- a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js @@ -14,6 +14,7 @@ import { } from "../utils/helper"; import { createRasterizedImage } from "../utils/rasterizedImage"; import { message } from "ant-design-vue"; +import { restoreFabricObject } from "../utils/objectHelper"; /** * 栅格化图层命令 @@ -82,7 +83,7 @@ export class RasterizeLayerCommand extends Command { this.canvas.renderAll(); // 检查是否有遮罩对象 - const maskObject = this._getMaskObject(); + const maskObject = await this._getMaskObject(); // 创建栅格化图像 const rasterizedImage = await createRasterizedImage({ @@ -392,9 +393,9 @@ export class RasterizeLayerCommand extends Command { * @returns {Object|null} 遮罩对象或null * @private */ - _getMaskObject() { + async _getMaskObject() { // 如果图层有clippingMask,获取对应的fabric对象 - if (this.layer?.clippingMask?.id) { + if (this.layer?.clippingMask) { const { object: maskObject } = findObjectById( this.canvas, this.layer.clippingMask.id @@ -405,6 +406,7 @@ export class RasterizeLayerCommand extends Command { ); return maskObject; } + return await restoreFabricObject(this.layer.clippingMask); } console.log("📎 未找到遮罩对象"); diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue index a3f4d7af..266efc24 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue @@ -601,8 +601,8 @@ function handleLayerClick(layer, event) { layer.children.length > 0 ) { // 如果是组图层,设置第一个子图层为活动图层 - setActiveLayer(layer.children[0].id, { parentId: layer.id }); layerManager?.setAllActiveGroupLayerCanvasObject?.(layer); + setActiveLayer(layer.children[0].id, { parentId: layer.id }); } else { // 否则直接设置当前图层为活动图层 setActiveLayer(layer.id); diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 071c0c64..c3ee6485 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -39,7 +39,6 @@ import { loadImageUrlToLayer, loadImage, } from "./utils/imageHelper.js"; -import { next } from "lodash-es"; // import MinimapPanel from "./components/MinimapPanel.vue"; const KeyboardShortcutHelp = defineAsyncComponent(() => import("./components/KeyboardShortcutHelp.vue") diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index e6fed3f2..c393faab 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -407,8 +407,8 @@ export class CanvasManager { // 居中所有画布元素,包括背景层和其他元素 this.centerAllObjects(); - // 重新渲染画布使变更生效 - this.canvas.renderAll(); + // // 重新渲染画布使变更生效 + // this.canvas.renderAll(); } /** @@ -510,7 +510,7 @@ export class CanvasManager { } // 重新渲染画布 - this.canvas.renderAll(); + // this.canvas.renderAll(); } /** @@ -958,6 +958,9 @@ export class CanvasManager { // 解析JSON字符串 try { const parsedJson = JSON.parse(json); + this.canvasWidth.value = parsedJson.canvasWidth || this.width; + this.canvasHeight.value = parsedJson.canvasHeight || this.height; + this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor; return new Promise(async (resolve, reject) => { const tempLayers = JSON.parse(parsedJson?.layers) || []; @@ -974,6 +977,7 @@ export class CanvasManager { } this.layers.value = tempLayers; + debugger; // this.canvasWidth.value = parsedJson.canvasWidth || this.width; // this.canvasHeight.value = parsedJson.canvasHeight || this.height; diff --git a/src/component/Canvas/CanvasEditor/managers/LayerManager.js b/src/component/Canvas/CanvasEditor/managers/LayerManager.js index b91cf53f..065ed53a 100644 --- a/src/component/Canvas/CanvasEditor/managers/LayerManager.js +++ b/src/component/Canvas/CanvasEditor/managers/LayerManager.js @@ -67,6 +67,8 @@ import { } from "../utils/helper"; import { message } from "ant-design-vue"; import { fabric } from "fabric-with-all"; +import { getOriginObjectInfo } from "../utils/layerUtils"; +import { restoreFabricObject } from "../utils/objectHelper"; /** * 图层管理器 - 负责管理画布上的所有图层 @@ -333,52 +335,94 @@ export class LayerManager { }); // 设置裁剪对象 - layers.forEach((layer) => { + layers.forEach(async (layer) => { if (layer.clippingMask) { - const activeObject = this.canvas.getActiveObject(); - if (activeObject?._objects?.length > 1) { - console.log(activeObject?._objects?.length); - return false; // 如果是多选对象,则不设置裁剪路径 - } + // 反序列化 clippingMask + const clippingMaskFabricObject = await restoreFabricObject( + layer.clippingMask, + this.canvas + ); + clippingMaskFabricObject.clipPath = null; + + clippingMaskFabricObject.set({ + // 设置绝对定位 + // ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位 + absolutePositioned: true, + }); + + // const activeObject = this.canvas.getActiveObject(); + // if (activeObject?._objects?.length > 1) { + // const { object } = findObjectById( + // this.canvas, + // layer.clippingMask?.id + // ); + // if (!object) return; + // const tempClipPath = fabric.util.object.clone(object); + // tempClipPath.clipPath = null; + // tempClipPath.set({ + // // 设置绝对定位 + // ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位 + // absolutePositioned: true, + // }); + // activeObject.clipPath = tempClipPath; + // // 确保选择组正确渲染 + // // activeObject.setCoords(); + // console.log(activeObject?._objects?.length); + // return; // 如果是多选对象,则不设置裁剪路径 + // } // 如果是组图层 则给所有子对象设置裁剪对象 if (layer.type === LayerType.GROUP || layer.children?.length > 0) { layer.children.forEach((childLayer) => { - const { object } = findObjectById( - this.canvas, - layer.clippingMask?.id - ); - if (object) { - const tempClipPath = fabric.util.object.clone(object); - tempClipPath.clipPath = null; - tempClipPath.set({ - // 设置绝对定位 - // ...layer.clippingMask, // 恢复原定位 - absolutePositioned: true, - }); + if (clippingMaskFabricObject) { const childObj = this.canvas .getObjects() .find((o) => o.layerId === childLayer.id); if (childObj) { - childObj.clipPath = tempClipPath; + childObj.clipPath = clippingMaskFabricObject; } } + // const { object } = findObjectById( + // this.canvas, + // layer.clippingMask?.id + // ); + // if (object) { + // const tempClipPath = fabric.util.object.clone(object); + // tempClipPath.clipPath = null; + // tempClipPath.set({ + // // 设置绝对定位 + // // ...layer.clippingMask, // 恢复原定位 + // ...getOriginObjectInfo(layer.clippingMask), + // absolutePositioned: true, + // }); + // const childObj = this.canvas + // .getObjects() + // .find((o) => o.layerId === childLayer.id); + // if (childObj) { + // childObj.clipPath = tempClipPath; + // } + // } }); - } else { - const { object } = findObjectById( - this.canvas, - layer.clippingMask?.id - ); - if (object) { - const tempClipPath = fabric.util.object.clone(object); - tempClipPath.clipPath = null; // 确保克隆的遮罩没有clipPath - tempClipPath.set({ - // 设置绝对定位 - // ...layer.clippingMask, // 恢复原定位 - absolutePositioned: true, - }); - obj.clipPath = tempClipPath; - } + } else if (clippingMaskFabricObject) { + obj.clipPath = clippingMaskFabricObject; } + + // { + // // const { object } = findObjectById( + // // this.canvas, + // // layer.clippingMask?.id + // // ); + // // if (object) { + // // const tempClipPath = fabric.util.object.clone(object); + // // tempClipPath.clipPath = null; // 确保克隆的遮罩没有clipPath + // // tempClipPath.set({ + // // // 设置绝对定位 + // // // ...layer.clippingMask, // 恢复原定位 + // // ...getOriginObjectInfo(layer.clippingMask), + // // absolutePositioned: true, + // // }); + // // obj.clipPath = tempClipPath; + // // } + // } } }); } @@ -932,17 +976,16 @@ export class LayerManager { // 设置激活当前图层下画布中的所有对象,并变成选择组 setAllActiveGroupLayerCanvasObject(layer) { // 获取当前图层下所有元素 - let layerMask = null; // 选择当前组下所有画布元素 const allObjects = layer.children.reduce((acc, child) => { // 如果子图层有fabricObjects,则添加到结果数组 child?.fabricObjects?.forEach((obj) => { const { object } = findObjectById(this.canvas, obj.id); if (object) { - if (!layerMask) { - layerMask = fabric.util.object.clone(object.clipPath); - } - object.clipPath = null; // 确保克隆的遮罩没有clipPath + // if (!layerMask) { + // layerMask = fabric.util.object.clone(object.clipPath); + // } + // object.clipPath = null; // 确保克隆的遮罩没有clipPath acc.push(object); } }); @@ -950,10 +993,10 @@ export class LayerManager { if (child?.fabricObject) { const { object } = findObjectById(this.canvas, child?.fabricObject.id); if (object) { - if (!layerMask) { - layerMask = fabric.util.object.clone(object.clipPath); - } - object.clipPath = null; // 确保克隆的遮罩没有clipPath + // if (!layerMask) { + // layerMask = fabric.util.object.clone(object.clipPath); + // } + // object.clipPath = null; // 确保克隆的遮罩没有clipPath acc.push(object); } } @@ -967,17 +1010,48 @@ export class LayerManager { // 如果有对象,创建选择组 this.canvas.discardActiveObject(); // 取消当前活动对象 + this.canvas.renderAll(); // 确保画布渲染 + + // const { object } = findObjectById(this.canvas, layer.clippingMask?.id); + // 选中多个对象,不是创建组 // 多个对象时创建活动选择组 - const activeSelection = new fabric.ActiveSelection(allObjects, { + let activeSelection = new fabric.ActiveSelection(allObjects, { canvas: this.canvas, }); - activeSelection.clipPath = layerMask; // 保留第一个对象的裁剪路径 + // if (object) { + // const tempClipPath = fabric.util.object.clone(object); + // tempClipPath.clipPath = null; + // tempClipPath.set({ + // // 设置绝对定位 + // // ...layer.clippingMask, // 恢复原定位 + // ...getOriginObjectInfo(layer.clippingMask), + // absolutePositioned: true, + // }); + // activeSelection.clipPath = tempClipPath; // 保留第一个对象的裁剪路径 + // } + + // // 监听选择取消事件,恢复原始裁剪路径 + // const restoreClipPaths = () => { + // allObjects.forEach((obj) => { + // if (obj._originalClipPath !== undefined) { + // obj.clipPath = obj._originalClipPath; + // delete obj._originalClipPath; + // } + // }); + // this.canvas.off("selection:cleared", restoreClipPaths); + // this.canvas.off("selection:updated", restoreClipPaths); + // }; + + // this.canvas.on("selection:cleared", restoreClipPaths); + // this.canvas.on("selection:updated", restoreClipPaths); // 设置活动选择组的属性 this.canvas.setActiveObject(activeSelection); - this.canvas.renderAll(); + activeSelection = null; // 清理引用,避免内存泄漏 + // 确保选择组正确渲染 + // activeSelection.setCoords(); } } diff --git a/src/component/Canvas/CanvasEditor/utils/layerHelper.js b/src/component/Canvas/CanvasEditor/utils/layerHelper.js index 1e35b6d5..0d081e9f 100644 --- a/src/component/Canvas/CanvasEditor/utils/layerHelper.js +++ b/src/component/Canvas/CanvasEditor/utils/layerHelper.js @@ -1,3 +1,5 @@ +import { generateId } from "./helper"; + /** * 图层类型枚举 */ diff --git a/src/component/Canvas/CanvasEditor/utils/layerUtils.js b/src/component/Canvas/CanvasEditor/utils/layerUtils.js index 0c681510..3438c595 100644 --- a/src/component/Canvas/CanvasEditor/utils/layerUtils.js +++ b/src/component/Canvas/CanvasEditor/utils/layerUtils.js @@ -20,11 +20,13 @@ export function buildLayerAssociations(layer, canvasObjects) { } if (layer.clippingMask) { - // clippingMask 可能是一个fabricObject或组 + // clippingMask 可能是一个fabricObject或组 也可能是一个简单对象 const clippingMaskObj = canvasObjects.find( (obj) => obj.id === layer.clippingMask.id ); - layer.clippingMask = clippingMaskObj?.toObject?.(["id"]) || null; + layer.clippingMask = clippingMaskObj + ? clippingMaskObj?.toObject?.(["id"]) + : layer.clippingMask; } // 处理多个fabricObjects关联 @@ -170,12 +172,15 @@ export function simplifyLayers(layers) { opacity: layer.opacity, isBackground: layer.isBackground || false, isFixed: layer.isFixed || false, - clippingMask: layer.clippingMask - ? { - id: layer.clippingMask.id, - type: layer.clippingMask.type, - } - : null, + clippingMask: + layer.clippingMask?.toObject?.(["id", "layerId"]) || + layer.clippingMask || + null, // 可能是一个fabricObject或组 也可能是一个简单对象 + // ? { + // id: layer.clippingMask.id, + // type: layer.clippingMask.type, + // } + // : null, fabricObject: layer.fabricObject ? { id: layer.fabricObject.id, @@ -226,11 +231,21 @@ export function restoreLayers(simplifiedLayers, canvasObjects) { const restoredLayer = { ...layer }; // 恢复clippingMask关联 - if (layer.clippingMask?.id) { + // if (layer.clippingMask?.id) { + // const clippingMaskObj = canvasObjects.find( + // (obj) => obj.id === layer.clippingMask.id + // ); + // restoredLayer.clippingMask = clippingMaskObj || null; + // } + + if (layer.clippingMask) { + // clippingMask 可能是一个fabricObject或组 也可能是一个简单对象 const clippingMaskObj = canvasObjects.find( (obj) => obj.id === layer.clippingMask.id ); - restoredLayer.clippingMask = clippingMaskObj || null; + restoredLayer.clippingMask = clippingMaskObj + ? clippingMaskObj?.toObject?.(["id"]) + : layer.clippingMask; } // 恢复单个fabricObject关联 @@ -334,3 +349,30 @@ export function restoreFromSnapshot(snapshot, canvasObjects) { return restoreLayers(snapshot.data, canvasObjects); } + +/** + * 获取对象的定位信息 + * @param {Object} object 对象 + * @returns {Object} 对象的定位信息 + */ +export function getOriginObjectInfo(object) { + if (!object) { + console.warn("getOriginObjectInfo 请传入有效的fabric对象:", object); + return {}; + } + + // 获取对象的原始信息 + const originInfo = { + left: object.left || 0, + top: object.top || 0, + scaleX: object.scaleX || 1, + scaleY: object.scaleY || 1, + angle: object.angle || 0, + flipX: object.flipX || false, + flipY: object.flipY || false, + skewX: object.skewX || 0, + skewY: object.skewY || 0, + }; + + return originInfo; +} diff --git a/src/component/Canvas/CanvasEditor/utils/objectHelper.js b/src/component/Canvas/CanvasEditor/utils/objectHelper.js new file mode 100644 index 00000000..87b31c97 --- /dev/null +++ b/src/component/Canvas/CanvasEditor/utils/objectHelper.js @@ -0,0 +1,63 @@ +import { fabric } from "fabric-with-all"; + +/** + * 将序列化对象恢复为 fabric 对象 + * @param {Object} serializedObject - toObject() 生成的对象 + * @param {fabric.Canvas} canvas - 目标画布 + * @returns {Promise} 恢复的 fabric 对象 + */ +export async function restoreFabricObject(serializedObject, canvas) { + return new Promise((resolve, reject) => { + const objectType = serializedObject.type; + // 定义恢复后的处理函数 + const handleRestoredObject = (fabricObject) => { + if (!fabricObject) { + reject(new Error(`无法恢复 ${objectType} 类型的对象`)); + return; + } + + // 恢复自定义属性 + if (serializedObject.id) fabricObject.id = serializedObject.id; + if (serializedObject.layerId) + fabricObject.layerId = serializedObject.layerId; + if (serializedObject.layerName) + fabricObject.layerName = serializedObject.layerName; + + // 更新坐标 + fabricObject.setCoords(); + + // 添加到画布 + // canvas.add(fabricObject); + + resolve(fabricObject); + }; + + // 根据类型选择恢复方法 + switch (objectType) { + case "rect": + fabric.Rect.fromObject(serializedObject, handleRestoredObject); + break; + case "circle": + fabric.Circle.fromObject(serializedObject, handleRestoredObject); + break; + case "path": + fabric.Path.fromObject(serializedObject, handleRestoredObject); + break; + case "image": + fabric.Image.fromObject(serializedObject, handleRestoredObject); + break; + case "group": + fabric.Group.fromObject(serializedObject, handleRestoredObject); + break; + default: + // 使用通用方法 + fabric.util.enlivenObjects([serializedObject], (objects) => { + if (objects && objects[0]) { + handleRestoredObject(objects[0]); + } else { + reject(new Error("对象恢复失败")); + } + }); + } + }); +}