diff --git a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js index 7011c361..e8d09f86 100644 --- a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js @@ -130,13 +130,15 @@ export class UpdateBackgroundCommand extends Command { }); this.canvas = options.canvas; this.layers = options.layers; + this.canvasManager = options.canvasManager; this.backgroundColorValue = options.backgroundColorValue; // 使用.value获取实际值 this.backgroundColor = options.backgroundColor; // this.historyManager = options.historyManager; + this.oldColor = options.oldColor; // 旧颜色,用于撤销 // 查找背景图层 this.bgLayer = this.layers.value.find((layer) => layer.isBackground); - this.oldBackgroundColor = this.bgLayer.backgroundColor; + this.oldBackgroundColor = this.oldColor; this.backgroundObject = findObjectById( this.canvas, this.bgLayer.fabricObject.id @@ -159,6 +161,11 @@ export class UpdateBackgroundCommand extends Command { this.canvas.renderAll(); } this.backgroundColorValue.value = this.backgroundColor; // 设置背景颜色 + + // 生成缩略图 + this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.( + this.bgLayer.id + ); return true; } @@ -177,6 +184,12 @@ export class UpdateBackgroundCommand extends Command { this.canvas.renderAll(); } this.backgroundColorValue.value = this.oldBackgroundColor; // 恢复背景颜色 + + // 生成缩略图 + this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.( + this.bgLayer.id + ); + // 如果有旧颜色,恢复到旧颜色 return true; } diff --git a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js index 9e77160e..922a8985 100644 --- a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js @@ -4,10 +4,10 @@ import { LayerType, } from "../utils/layerHelper.js"; import { createRasterizedImage } from "../utils/selectionToImage.js"; -import { CompositeCommand } from "./Command.js"; +import { CompositeCommand, Command } from "./Command.js"; import { CreateImageLayerCommand } from "./LayerCommands.js"; -import { ClearSelectionCommand } from "./SelectionCommands.js"; import { fabric } from "fabric-with-all"; +import { generateId } from "../utils/helper.js"; /** * 套索抠图命令 @@ -167,10 +167,10 @@ export class LassoCutoutCommand extends CompositeCommand { // 插入新组图层 this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer); - await this.layerManager.updateLayersObjectsInteractivity(); this.canvas.discardActiveObject(); this.canvas.setActiveObject(this.fabricImage); - this.canvas.renderAll(); + await this.layerManager.updateLayersObjectsInteractivity(true); + console.log(`套索抠图完成,新图层ID: ${this.newLayerId}`); return { newLayerId: this.newLayerId, @@ -275,7 +275,7 @@ export class LassoCutoutCommand extends CompositeCommand { // 注意:不重置groupId,因为重做时可能需要使用相同的ID // 4. 更新画布和图层交互性 - await this.layerManager.updateLayersObjectsInteractivity(); + await this.layerManager.updateLayersObjectsInteractivity(true); console.log(`✅ 套索抠图撤销完成`); return true; @@ -783,3 +783,763 @@ 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; + } + } +} + +/** + * 清除选区命令 + */ +export class ClearSelectionCommand extends Command { + constructor(options = {}) { + super({ + name: "清除选区", + description: "清除当前选区", + saveState: false, + }); + this.selectionManager = options.selectionManager; + this.originalSelection = options.selectionManager + ? options.selectionManager.getSelectionPath() + : null; + } + + async execute() { + if (!this.selectionManager) { + console.error("无法清除选区:参数无效"); + return false; + } + + // 保存原始选区 + if (!this.originalSelection) { + this.originalSelection = this.selectionManager.getSelectionPath(); + } + + // 清除选区 + this.selectionManager.clearSelection(); + return true; + } + + async undo() { + if (!this.selectionManager || !this.originalSelection) return false; + + // 恢复原始选区 + this.selectionManager.setSelectionFromPath(this.originalSelection); + return true; + } +} diff --git a/src/component/Canvas/CanvasEditor/commands/SelectionCommands.js b/src/component/Canvas/CanvasEditor/commands/SelectionCommands.js index 3acd53d3..88e5f2e8 100644 --- a/src/component/Canvas/CanvasEditor/commands/SelectionCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/SelectionCommands.js @@ -1,6 +1,8 @@ import { Command, CompositeCommand } from "./Command.js"; import { fabric } from "fabric-with-all"; import { createLayer, LayerType } from "../utils/layerHelper.js"; +import { createRasterizedImage } from "../utils/selectionToImage.js"; +import { generateId } from "../utils/helper.js"; /** * 创建选区命令 @@ -168,47 +170,6 @@ export class RemoveFromSelectionCommand extends Command { } } -/** - * 清除选区命令 - */ -export class ClearSelectionCommand extends Command { - constructor(options = {}) { - super({ - name: "清除选区", - description: "清除当前选区", - saveState: false, - }); - this.selectionManager = options.selectionManager; - this.originalSelection = options.selectionManager - ? options.selectionManager.getSelectionPath() - : null; - } - - async execute() { - if (!this.selectionManager) { - console.error("无法清除选区:参数无效"); - return false; - } - - // 保存原始选区 - if (!this.originalSelection) { - this.originalSelection = this.selectionManager.getSelectionPath(); - } - - // 清除选区 - this.selectionManager.clearSelection(); - return true; - } - - async undo() { - if (!this.selectionManager || !this.originalSelection) return false; - - // 恢复原始选区 - this.selectionManager.setSelectionFromPath(this.originalSelection); - return true; - } -} - /** * 羽化选区命令 */ @@ -470,19 +431,30 @@ export class CopySelectionToNewLayerCommand extends CompositeCommand { /** * 从选区中删除内容命令 + * 使用栅格化方式清除选区内容,支持复杂选区形状 */ export class ClearSelectionContentCommand extends Command { constructor(options = {}) { super({ name: "清除选区内容", - description: "删除选区中的内容", - saveState: false, + 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() { @@ -491,56 +463,297 @@ export class ClearSelectionContentCommand extends Command { return false; } - // 获取选区 - const selectionObject = this.selectionManager.getSelectionObject(); - if (!selectionObject) { - 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; } - - // 确定目标图层 - const layerId = this.targetLayerId || this.layerManager.getActiveLayerId(); - const layer = this.layerManager.getLayerById(layerId); - if (!layer || !layer.fabricObjects) { - console.error("无法清除选区内容:目标图层无效或为空"); - return false; - } - - // 找到选区内的对象 - const objectsToRemove = layer.fabricObjects.filter((obj) => { - return this.selectionManager.isObjectInSelection(obj); - }); - - if (objectsToRemove.length === 0) { - console.warn("选区内没有对象需要清除"); - return true; - } - - // 备份被删除的对象 - this.removedObjects = objectsToRemove.map((obj) => ({ - object: this._cloneObjectSync(obj), - layerId: layerId, - })); - - // 从图层中移除对象 - for (const obj of objectsToRemove) { - this.layerManager.removeObjectFromLayer(layerId, obj.id); - } - - return { removedCount: objectsToRemove.length }; } async undo() { - if (!this.layerManager || this.removedObjects.length === 0) return false; + try { + console.log("↩️ 开始撤销清除选区内容操作"); - // 恢复被删除的对象 - for (const item of this.removedObjects) { - if (item.object && item.layerId) { - const clonedObj = await this._cloneObject(item.object); - this.layerManager.addObjectToLayer(item.layerId, clonedObj); + 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; } - return true; + } + + /** + * 使用栅格化方式清除选区内容 + * @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) { @@ -572,7 +785,22 @@ export class ClearSelectionContentCommand extends Command { } }); } -} -// 导入套索抠图命令 -export { LassoCutoutCommand } from "./LassoCutoutCommand.js"; + /** + * 获取命令信息 + * @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/HeaderMenu.vue b/src/component/Canvas/CanvasEditor/components/HeaderMenu.vue index 8df5ab96..e27c006a 100644 --- a/src/component/Canvas/CanvasEditor/components/HeaderMenu.vue +++ b/src/component/Canvas/CanvasEditor/components/HeaderMenu.vue @@ -43,6 +43,7 @@ const emit = defineEmits([ // 笔刷面板相关状态 const showBrushPanel = ref(false); const brushPanelRef = ref(null); +const lastColor = ref("#ffffff"); // 计算属性 // const shouldShowBrushSettings = computed(() => { @@ -82,17 +83,32 @@ function updateCanvasSize( } function updateCanvasColor() { + console.log("更新画布颜色:", props.canvasColor); if (!layerManager) { console.warn("LayerManager 未初始化,无法更改背景色"); return; } // 更新背景层颜色而不是画布颜色 - layerManager.updateBackgroundColor(props.canvasColor); - + layerManager.updateBackgroundColor(props.canvasColor, { + oldColor: lastColor.value, + undoable: true, + }); + lastColor.value = props.canvasColor; emit("canvas-color-change"); } +watch( + () => props.canvasColor, + (newColor) => { + // 更新背景层颜色而不是画布颜色 + layerManager.updateBackgroundColor(newColor, { + oldColor: lastColor.value, + undoable: false, // 不需要撤销 + }); + } +); + // 切换笔刷面板显示状态 function toggleBrushPanel() { // 如果笔刷没有激活 则激活笔刷工具 @@ -222,6 +238,7 @@ function showLayerPanel() { } onMounted(() => { + lastColor.value = props.canvasColor; // 获取工具管理器和笔刷管理器 const brushManager = toolManager?.brushManager; diff --git a/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue b/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue index 84ce6233..3952d29e 100644 --- a/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue @@ -186,14 +186,18 @@ import { useI18n } from "vue-i18n"; import { CreateSelectionCommand, InvertSelectionCommand, - ClearSelectionCommand, FeatherSelectionCommand, FillSelectionCommand, CopySelectionToNewLayerCommand, ClearSelectionContentCommand, - LassoCutoutCommand, } from "../commands/SelectionCommands"; import { ToolCommand } from "../commands/ToolCommands"; +import { + LassoCutoutCommand, + ClearSelectionCommand, + CutSelectionToNewLayerCommand, +} from "../commands/LassoCutoutCommand"; + import { OperationType } from "../utils/layerHelper"; const props = defineProps({ @@ -414,7 +418,7 @@ function copySelectionToNewLayer() { function cutSelectionToNewLayer() { if (!hasSelection.value) return; props.commandManager.execute( - new CopySelectionToNewLayerCommand({ + new CutSelectionToNewLayerCommand({ canvas: props.canvas, layerManager: props.layerManager, selectionManager: props.selectionManager, diff --git a/src/component/Canvas/CanvasEditor/managers/LayerManager.js b/src/component/Canvas/CanvasEditor/managers/LayerManager.js index cbffe1ef..ed014d7f 100644 --- a/src/component/Canvas/CanvasEditor/managers/LayerManager.js +++ b/src/component/Canvas/CanvasEditor/managers/LayerManager.js @@ -1753,7 +1753,7 @@ export class LayerManager { * 更新背景图层颜色 * @param {string} backgroundColor 背景颜色 */ - updateBackgroundColor(backgroundColor) { + updateBackgroundColor(backgroundColor, options = {}) { const backgroundLayer = this.layers.value.find( (layer) => layer.isBackground ); @@ -1768,10 +1768,14 @@ export class LayerManager { canvas: this.canvas, layers: this.layers, canvasManager: this.canvasManager, + layerManger: this, backgroundColor, backgroundColorValue: this.backgroundColor, + oldColor: options.oldColor, }); + command.undoable = isBoolean(options.undoable) ? options.undoable : true; // 设置为可撤销 + // 执行命令 if (this.commandManager) { this.commandManager.execute(command); diff --git a/src/component/Canvas/CanvasEditor/managers/RedGreenModeManager.js b/src/component/Canvas/CanvasEditor/managers/RedGreenModeManager.js index 0c268471..814094a6 100644 --- a/src/component/Canvas/CanvasEditor/managers/RedGreenModeManager.js +++ b/src/component/Canvas/CanvasEditor/managers/RedGreenModeManager.js @@ -133,7 +133,7 @@ export class RedGreenModeManager { this.canvas.on("mouse:up", (event) => { // 可以在这里添加更多逻辑,比如生成图片或更新状态 nextTick(() => { - requestAnimationFrame(async () => { + requestIdleCallback(async () => { if (!this.isInitialized) { console.warn("红绿图模式未初始化,无法处理鼠标事件"); return; diff --git a/src/component/Canvas/CanvasEditor/managers/selection/SelectionManager.js b/src/component/Canvas/CanvasEditor/managers/selection/SelectionManager.js index d269be9a..6bc8e7c2 100644 --- a/src/component/Canvas/CanvasEditor/managers/selection/SelectionManager.js +++ b/src/component/Canvas/CanvasEditor/managers/selection/SelectionManager.js @@ -1,10 +1,8 @@ import { fabric } from "fabric-with-all"; import { generateId } from "../../utils/helper"; import { OperationType } from "../../utils/layerHelper"; -import { - ClearSelectionCommand, - CreateSelectionCommand, -} from "../../commands/SelectionCommands"; +import { CreateSelectionCommand } from "../../commands/SelectionCommands"; +import { ClearSelectionCommand } from "../../commands/LassoCutoutCommand"; /** * 选区管理器