diff --git a/src/assets/work/1.PNG b/src/assets/work/1.PNG new file mode 100644 index 00000000..c91dfdf7 Binary files /dev/null and b/src/assets/work/1.PNG differ diff --git a/src/assets/work/2.PNG b/src/assets/work/2.PNG new file mode 100644 index 00000000..81540426 Binary files /dev/null and b/src/assets/work/2.PNG differ diff --git a/src/assets/work/3.PNG b/src/assets/work/3.PNG new file mode 100644 index 00000000..f877ece1 Binary files /dev/null and b/src/assets/work/3.PNG differ diff --git a/src/assets/work/5.PNG b/src/assets/work/5.PNG new file mode 100644 index 00000000..f0d87430 Binary files /dev/null and b/src/assets/work/5.PNG differ diff --git a/src/assets/work/6.PNG b/src/assets/work/6.PNG new file mode 100644 index 00000000..9ccafd2a Binary files /dev/null and b/src/assets/work/6.PNG differ diff --git a/src/assets/work/IMG_0001.PNG b/src/assets/work/IMG_0001.PNG new file mode 100644 index 00000000..139e4ba3 Binary files /dev/null and b/src/assets/work/IMG_0001.PNG differ diff --git a/src/assets/work/IMG_0008.PNG b/src/assets/work/IMG_0008.PNG new file mode 100644 index 00000000..b999a075 Binary files /dev/null and b/src/assets/work/IMG_0008.PNG differ diff --git a/src/assets/work/group_hide.PNG b/src/assets/work/group_hide.PNG new file mode 100644 index 00000000..8ea506bb Binary files /dev/null and b/src/assets/work/group_hide.PNG differ diff --git a/src/assets/work/group_show.PNG b/src/assets/work/group_show.PNG new file mode 100644 index 00000000..3d1e2d45 Binary files /dev/null and b/src/assets/work/group_show.PNG differ diff --git a/src/assets/work/图片1.png b/src/assets/work/图片1.png new file mode 100644 index 00000000..9f7d2b27 Binary files /dev/null and b/src/assets/work/图片1.png differ diff --git a/src/assets/work/图片2.png b/src/assets/work/图片2.png new file mode 100644 index 00000000..015e64f3 Binary files /dev/null and b/src/assets/work/图片2.png differ diff --git a/src/assets/work/图片4.png b/src/assets/work/图片4.png new file mode 100644 index 00000000..afab67c2 Binary files /dev/null and b/src/assets/work/图片4.png differ diff --git a/src/assets/work/图片5.png b/src/assets/work/图片5.png new file mode 100644 index 00000000..a43e8b87 Binary files /dev/null and b/src/assets/work/图片5.png differ diff --git a/src/assets/work/图片6.png b/src/assets/work/图片6.png new file mode 100644 index 00000000..dea147d3 Binary files /dev/null and b/src/assets/work/图片6.png differ diff --git a/src/assets/work/图片7.png b/src/assets/work/图片7.png new file mode 100644 index 00000000..50276080 Binary files /dev/null and b/src/assets/work/图片7.png differ diff --git a/src/assets/work/图片8.png b/src/assets/work/图片8.png new file mode 100644 index 00000000..54914c87 Binary files /dev/null and b/src/assets/work/图片8.png differ diff --git a/src/assets/work/图片9.png b/src/assets/work/图片9.png new file mode 100644 index 00000000..6755cd89 Binary files /dev/null and b/src/assets/work/图片9.png differ diff --git a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js index 50a5559f..9b084017 100644 --- a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js @@ -1,3 +1,4 @@ +import { findObjectById } from "../utils/helper"; import { Command } from "./Command"; import { fabric } from "fabric-with-all"; @@ -129,14 +130,13 @@ export class UpdateBackgroundCommand extends Command { }); this.canvas = options.canvas; this.layers = options.layers; - this.backgroundColor = options.backgroundColor; + this.backgroundColorValue = options.backgroundColorValue; // 使用.value获取实际值 + this.backgroundColor = options.backgroundColor; // this.historyManager = options.historyManager; // 查找背景图层 this.bgLayer = this.layers.value.find((layer) => layer.isBackground); - this.oldBackgroundColor = this.bgLayer - ? this.bgLayer.backgroundColor - : "#ffffff"; + this.oldBackgroundColor = this.bgLayer.backgroundColor; } execute() { @@ -150,10 +150,14 @@ export class UpdateBackgroundCommand extends Command { // 更新背景对象属性 if (this.bgLayer.fabricObject) { - this.bgLayer.fabricObject.set("fill", this.backgroundColor); + const { object } = findObjectById( + this.canvas, + this.bgLayer.fabricObject.id + ); + object.set("fill", this.backgroundColor); this.canvas.renderAll(); } - + this.backgroundColorValue.value = this.backgroundColor; // 设置背景颜色 return true; } @@ -167,10 +171,14 @@ export class UpdateBackgroundCommand extends Command { // 恢复背景对象属性 if (this.bgLayer.fabricObject) { - this.bgLayer.fabricObject.set("fill", this.oldBackgroundColor); + const { object } = findObjectById( + this.canvas, + this.bgLayer.fabricObject.id + ); + object.set("fill", this.oldBackgroundColor); this.canvas.renderAll(); } - + this.backgroundColorValue.value = this.oldBackgroundColor; // 恢复背景颜色 return true; } diff --git a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js index 8cc96ca7..8edc4e5d 100644 --- a/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js @@ -1,3 +1,8 @@ +import { + createLayer, + findInChildLayers, + LayerType, +} from "../utils/layerHelper.js"; import { createRasterizedImage } from "../utils/selectionToImage.js"; import { CompositeCommand } from "./Command.js"; import { CreateImageLayerCommand } from "./LayerCommands.js"; @@ -27,6 +32,15 @@ export class LassoCutoutCommand extends CompositeCommand { // 高清截图选项 this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用 this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数 + + this.groupId = options.groupId || `cutout-group-${Date.now()}`; + this.groupName = options.groupName || `选区组`; + this.groupLayer = null; // 新增:保存组图层的引用 + this.originalLayersLength = 0; // 新增:保存原始图层数量 + + // 序列化保存选区对象,用于重做时恢复 + this.serializedSelectionObject = null; + this._serializeSelectionObject(); } async execute() { @@ -38,8 +52,11 @@ export class LassoCutoutCommand extends CompositeCommand { try { this.executedCommands = []; + // 保存原始图层数量,用于撤销时的验证 + this.originalLayersLength = this.layerManager.layers.value.length; + // 获取选区 - const selectionObject = this.selectionManager.getSelectionObject(); + const selectionObject = await this._getSelectionObject(); if (!selectionObject) { console.error("无法执行套索抠图:当前没有选区"); return false; @@ -112,10 +129,47 @@ export class LassoCutoutCommand extends CompositeCommand { 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.children.push(selectLayer); + // 插入新组图层 + this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer); + + this.layerManager.updateLayersObjectsInteractivity(); + this.canvas.discardActiveObject(); + this.canvas.setActiveObject(this.fabricImage); + this.canvas.renderAll(); console.log(`套索抠图完成,新图层ID: ${this.newLayerId}`); return { newLayerId: this.newLayerId, cutoutImageUrl: this.cutoutImageUrl, + guroupId: this.groupId, + groupName: this.groupName, }; } catch (error) { console.error("套索抠图过程中出错:", error); @@ -129,32 +183,127 @@ export class LassoCutoutCommand extends CompositeCommand { } } + // 清理组图层(如果已创建) + 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 { - // 逆序撤销所有已执行的命令 - for (let i = this.executedCommands.length - 1; i >= 0; i--) { - const command = this.executedCommands[i]; - if (command && typeof command.undo === "function") { - await command.undo(); + 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. 逆序撤销所有已执行的子命令 + 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 + ); + // 子命令撤销失败不中断整个撤销过程 + } + } + } + + // 3. 清理状态 this.executedCommands = []; this.newLayerId = null; this.cutoutImageUrl = null; this.fabricImage = null; + this.groupLayer = null; // 清理组图层引用 + // 注意:不重置groupId,因为重做时可能需要使用相同的ID + // 4. 更新画布和图层交互性 + await this.layerManager.updateLayersObjectsInteractivity(); + + console.log(`✅ 套索抠图撤销完成`); return true; } catch (error) { - console.error("撤销套索抠图失败:", 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() : {}, + })), + }; + } + /** * 获取图层的所有对象(包括子图层,从画布中查找真实对象) * @param {Object} layer 图层对象 @@ -249,6 +398,7 @@ export class LassoCutoutCommand extends CompositeCommand { scaleFactor: scaleFactor, // isReturenDataURL: true, // 返回DataURL preserveOriginalQuality: true, // 启用高质量模式 + selectionManager: this.selectionManager, // 传递选区管理器,用于获取羽化值 }); if (!rasterizedDataURL) { @@ -500,4 +650,129 @@ export class LassoCutoutCommand extends CompositeCommand { } }); } + + /** + * 序列化选区对象 + * @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 { + // 首先尝试从选区管理器获取当前选区 + 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; + } + } } diff --git a/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js b/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js index 1dca9e68..387c86ce 100644 --- a/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js @@ -490,6 +490,7 @@ export class LiquifyStateCommand extends Command { * @param {String} options.targetLayerId 目标图层ID * @param {ImageData} options.initialImageData 初始图像数据 * @param {ImageData} options.finalImageData 最终图像数据 + * @param {Object} options.liquifyManager 液化管理器实例 */ constructor(options) { super({ @@ -502,6 +503,7 @@ export class LiquifyStateCommand extends Command { this.targetObject = options.targetObject; this.targetLayerId = options.targetLayerId; this.targetObjectId = options.targetObjectId; + this.liquifyManager = options.liquifyManager; // 添加液化管理器引用 // 获取引用管理器实例 this.refManager = getLiquifyReferenceManager(); @@ -520,6 +522,10 @@ export class LiquifyStateCommand extends Command { this.initialImageData = options.initialImageData; this.finalImageData = options.finalImageData; + // 保存液化管理器的操作记录状态 + this.initialLiquifyState = null; + this.finalLiquifyState = null; + this.currentState = "initial"; // 创建初始快照 @@ -547,6 +553,19 @@ export class LiquifyStateCommand extends Command { this.finalImageData ); + // 恢复液化管理器到最终状态 + if (this.liquifyManager && this.finalLiquifyState) { + this._restoreLiquifyManagerState(this.finalLiquifyState); + } else if (this.liquifyManager) { + // 如果没有保存的最终状态,重新准备液化环境 + const currentTarget = this.refManager.getObjectRef(this.objectRefId); + if (currentTarget) { + await this.liquifyManager.prepareForLiquify(currentTarget); + // 保存当前的液化管理器状态作为最终状态 + this.finalLiquifyState = this._captureLiquifyManagerState(); + } + } + this.currentState = "final"; this.canvas.renderAll(); @@ -568,6 +587,21 @@ export class LiquifyStateCommand extends Command { this.initialSnapshotId ); + // 恢复液化管理器到初始状态 + if (this.liquifyManager) { + if (this.initialLiquifyState) { + this._restoreLiquifyManagerState(this.initialLiquifyState); + } else { + // 如果没有初始状态,重置液化管理器 + this.liquifyManager.reset(); + // 重新准备液化环境 + const currentTarget = this.refManager.getObjectRef(this.objectRefId); + if (currentTarget) { + await this.liquifyManager.prepareForLiquify(currentTarget); + } + } + } + this.currentState = "initial"; this.canvas.renderAll(); @@ -589,6 +623,11 @@ export class LiquifyStateCommand extends Command { setFinalImageData(finalImageData) { this.finalImageData = finalImageData; this.finalSnapshotId = null; // 重置快照ID,下次执行时重新创建 + + // 捕获当前液化管理器状态作为最终状态 + if (this.liquifyManager) { + this.finalLiquifyState = this._captureLiquifyManagerState(); + } } /** @@ -672,6 +711,12 @@ export class LiquifyStateCommand extends Command { }; this.refManager.stateSnapshots.set(this.initialSnapshotId, snapshot); + + // 捕获初始液化管理器状态 + if (this.liquifyManager) { + this.initialLiquifyState = this._captureLiquifyManagerState(); + } + console.log(`📸 初始状态快照已创建: ${this.initialSnapshotId}`); } } @@ -697,27 +742,180 @@ export class LiquifyStateCommand extends Command { }; this.refManager.stateSnapshots.set(this.finalSnapshotId, snapshot); + + // 捕获最终液化管理器状态 + if (this.liquifyManager) { + this.finalLiquifyState = this._captureLiquifyManagerState(); + } + console.log(`📸 最终状态快照已创建: ${this.finalSnapshotId}`); } } /** - * 计算命令本身的内存使用量 - * @returns {Number} 内存使用量(字节) + * 捕获液化管理器的当前状态 + * @returns {Object} 液化管理器状态 * @private */ - _calculateCommandMemory() { - let bytes = 0; + _captureLiquifyManagerState() { + if (!this.liquifyManager) return null; - // 计算ImageData内存使用 - if (this.initialImageData) { - bytes += this.initialImageData.width * this.initialImageData.height * 4; - } - if (this.finalImageData) { - bytes += this.finalImageData.width * this.finalImageData.height * 4; - } + try { + const state = { + // 捕获增强管理器状态 + enhancedManagerState: null, + // 捕获当前渲染器状态 + activeRendererState: null, + // 捕获目标对象引用 + targetObjectRef: this.liquifyManager.targetObject, + // 捕获初始化状态 + initialized: this.liquifyManager.initialized || false, + }; - return bytes; + // 如果有增强管理器,捕获其状态 + if (this.liquifyManager.enhancedManager) { + const enhancedManager = this.liquifyManager.enhancedManager; + state.enhancedManagerState = { + initialized: enhancedManager.initialized, + renderMode: enhancedManager.renderMode, + targetObject: enhancedManager.targetObject, + originalImageData: enhancedManager.originalImageData, + currentImageData: enhancedManager.currentImageData, + params: { ...enhancedManager.params }, + currentMode: enhancedManager.currentMode, + }; + + // 如果有激活的渲染器,捕获其状态 + if (enhancedManager.activeRenderer) { + const renderer = enhancedManager.activeRenderer; + state.activeRendererState = { + initialized: renderer.initialized, + originalImageData: renderer.originalImageData, + currentImageData: renderer.currentImageData, + params: { ...renderer.params }, + currentMode: renderer.currentMode, + // 对于CPU渲染器,还需要保存网格状态 + meshState: renderer.mesh + ? this._captureMeshState(renderer.mesh) + : null, + // 保存变形历史 + deformHistory: renderer.deformHistory + ? [...renderer.deformHistory] + : [], + }; + } + } + + console.log(`💾 液化管理器状态已捕获:`, state); + return state; + } catch (error) { + console.error("捕获液化管理器状态失败:", error); + return null; + } + } + + /** + * 恢复液化管理器状态 + * @param {Object} state 要恢复的状态 + * @private + */ + _restoreLiquifyManagerState(state) { + if (!this.liquifyManager || !state) return; + + try { + // 恢复基本状态 + this.liquifyManager.initialized = state.initialized; + if (state.targetObjectRef) { + this.liquifyManager.targetObject = state.targetObjectRef; + } + + // 恢复增强管理器状态 + if (state.enhancedManagerState && this.liquifyManager.enhancedManager) { + const enhancedManager = this.liquifyManager.enhancedManager; + const enhancedState = state.enhancedManagerState; + + enhancedManager.initialized = enhancedState.initialized; + enhancedManager.renderMode = enhancedState.renderMode; + enhancedManager.targetObject = enhancedState.targetObject; + enhancedManager.originalImageData = enhancedState.originalImageData; + enhancedManager.currentImageData = enhancedState.currentImageData; + enhancedManager.params = { ...enhancedState.params }; + enhancedManager.currentMode = enhancedState.currentMode; + + // 恢复激活渲染器状态 + if (state.activeRendererState && enhancedManager.activeRenderer) { + const renderer = enhancedManager.activeRenderer; + const rendererState = state.activeRendererState; + + renderer.initialized = rendererState.initialized; + renderer.originalImageData = rendererState.originalImageData; + renderer.currentImageData = rendererState.currentImageData; + renderer.params = { ...rendererState.params }; + renderer.currentMode = rendererState.currentMode; + + // 恢复网格状态(如果是CPU渲染器) + if (rendererState.meshState && renderer.mesh) { + this._restoreMeshState(renderer.mesh, rendererState.meshState); + } + + // 恢复变形历史 + if (rendererState.deformHistory) { + renderer.deformHistory = [...rendererState.deformHistory]; + } + } + } + + console.log(`🔄 液化管理器状态已恢复`); + } catch (error) { + console.error("恢复液化管理器状态失败:", error); + } + } + + /** + * 捕获网格状态 + * @param {Array} mesh 网格数组 + * @returns {Array} 网格状态副本 + * @private + */ + _captureMeshState(mesh) { + if (!mesh || !Array.isArray(mesh)) return null; + + return mesh.map((row) => + row.map((point) => ({ + x: point.x, + y: point.y, + originalX: point.originalX, + originalY: point.originalY, + })) + ); + } + + /** + * 恢复网格状态 + * @param {Array} mesh 目标网格 + * @param {Array} meshState 要恢复的网格状态 + * @private + */ + _restoreMeshState(mesh, meshState) { + if ( + !mesh || + !meshState || + !Array.isArray(mesh) || + !Array.isArray(meshState) + ) + return; + + for (let i = 0; i < Math.min(mesh.length, meshState.length); i++) { + for (let j = 0; j < Math.min(mesh[i].length, meshState[i].length); j++) { + const point = mesh[i][j]; + const statePoint = meshState[i][j]; + + point.x = statePoint.x; + point.y = statePoint.y; + point.originalX = statePoint.originalX; + point.originalY = statePoint.originalY; + } + } } } diff --git a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue index 3517a46d..e78a5445 100644 --- a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue @@ -257,6 +257,12 @@ function setBrushOpacity(opacity) { // 如果工具管理器存在,立即应用此更改 if (toolManager) { toolManager.updateBrushOpacity(opacity); + + // 同时更新颜色以确保透明度生效 + const currentColor = BrushStore.state.color; + if (currentColor) { + toolManager.updateBrushColor(currentColor); + } } } diff --git a/src/component/Canvas/CanvasEditor/components/BrushPanel.vue b/src/component/Canvas/CanvasEditor/components/BrushPanel.vue index 8c977f92..801acf99 100644 --- a/src/component/Canvas/CanvasEditor/components/BrushPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/BrushPanel.vue @@ -345,7 +345,224 @@ --> + +
+
+ {{ $t("阴影设置") }} +
+
+ +
+
+ {{ $t("启用阴影") }} +
+ + +
+
+
+ + + +
+
@@ -625,6 +842,14 @@ const debouncedPropertyCommand = debounce((propId, value) => { commandManager.execute(command, { nonUndoable: true }); }, 200); +// 处理阴影属性变化的防抖函数 +const debouncedShadowCommand = debounce((propId, value) => { + // 通知工具管理器更新阴影 + if (toolManager?.brushManager) { + toolManager.brushManager.updateShadow(); + } +}, 100); + // 处理属性变化 function handlePropertyChange(propId, value) { // 直接更新UI,通过防抖函数延迟创建命令 @@ -632,6 +857,25 @@ function handlePropertyChange(propId, value) { debouncedPropertyCommand(propId, value); } +// 处理阴影属性变化 +function handleShadowPropertyChange(propId, value) { + // 更新BrushStore中的阴影属性 + if (propId === "shadowEnabled") { + BrushStore.setShadowEnabled(value); + } else if (propId === "shadowColor") { + BrushStore.setShadowColor(value); + } else if (propId === "shadowWidth") { + BrushStore.setShadowWidth(value); + } else if (propId === "shadowOffsetX") { + BrushStore.setShadowOffsetX(value); + } else if (propId === "shadowOffsetY") { + BrushStore.setShadowOffsetY(value); + } + + // 通知笔刷管理器更新阴影设置 + debouncedShadowCommand(propId, value); +} + // 使用命令设置笔刷类型 function setBrushTypeWithCommand(type) { const command = new BrushTypeCommand({ @@ -1761,4 +2005,65 @@ const brushStore = BrushStore; background-color: rgba(244, 67, 54, 1); transform: scale(1.2) !important; } + +/* 阴影设置样式 */ +.shadow-preview-container { + display: flex; + flex-direction: column; + gap: 12px; + width: 100%; +} + +.shadow-preview-title { + font-weight: 500; + color: #333; + font-size: 14px; + text-align: center; +} + +.shadow-preview-box { + background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / + 10px 10px; + border-radius: 8px; + padding: 30px; + display: flex; + align-items: center; + justify-content: center; + min-height: 120px; + border: 1px solid rgba(0, 0, 0, 0.05); + position: relative; + overflow: hidden; +} + +.shadow-preview-element { + border-radius: 50%; + transition: all 0.3s ease; + position: relative; + z-index: 1; + background-clip: padding-box; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .brush-panel { + width: 95%; + right: 2.5%; + max-height: 75vh; + } + + .shadow-preview-box { + min-height: 100px; + padding: 20px; + } + + .property-presets { + justify-content: center; + } + + .brush-type-grid, + .presets-container, + .texture-grid { + grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); + } +} diff --git a/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue b/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue index b3589753..3fa3e589 100644 --- a/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LiquifyPanel.vue @@ -332,6 +332,7 @@ async function prepareForLiquify(targetObj) { targetObject: targetObject.value, targetLayerId: targetLayerId.value, originalData: originalImageData.value, + liquifyManager: props.liquifyManager, layerManager: props.layerManager, }); @@ -483,6 +484,7 @@ function showPanel(event) { targetLayerId: targetLayerId.value, originalData: originalImageData.value, layerManager: props.layerManager, + liquifyManager: props.liquifyManager, }); } @@ -739,10 +741,59 @@ function removeCanvasListeners() { _handleMouseUp = null; } +/** + * 获取当前图像的实际状态数据 + * @param {Object} targetObject Fabric图像对象 + * @returns {Promise} 当前图像数据或null + */ +async function getCurrentImageData(targetObject) { + if (!targetObject || !targetObject._element) { + return null; + } + + try { + // 创建临时canvas来获取当前图像状态 + const tempCanvas = document.createElement("canvas"); + const element = targetObject._element; + + // 设置canvas尺寸为原始图像尺寸 + if (originalImageData.value) { + tempCanvas.width = originalImageData.value.width; + tempCanvas.height = originalImageData.value.height; + } else { + tempCanvas.width = element.naturalWidth || element.width; + tempCanvas.height = element.naturalHeight || element.height; + } + const tempCtx = tempCanvas.getContext("2d"); + + // 绘制当前图像到临时canvas + tempCtx.drawImage(element, 0, 0, tempCanvas.width, tempCanvas.height); + + // 获取ImageData + const imageData = tempCtx.getImageData( + 0, + 0, + tempCanvas.width, + tempCanvas.height + ); + + console.log( + "✅ 成功获取当前图像状态,尺寸:", + imageData.width, + "x", + imageData.height + ); + return imageData; + } catch (error) { + console.warn("获取当前图像数据失败:", error); + return null; + } +} + /** * 鼠标按下事件处理 */ -function handleMouseDown(event) { +async function handleMouseDown(event) { if (!isEditing.value || !visible.value || !props.liquifyManager) return; isDrawing.value = true; @@ -760,29 +811,43 @@ function handleMouseDown(event) { lastX.value = pointer.x; lastY.value = pointer.y; - // === 修复:记录初始图像数据 === + // === 修复:记录当前图像状态(而不是原始状态)=== try { const currentTarget = getCurrentTargetObject(); - if (currentTarget && originalImageData.value) { - console.log("🎯 记录液化操作初始状态,对象ID:", targetObjectId.value); + if (currentTarget) { + console.log("🎯 记录液化操作当前状态,对象ID:", targetObjectId.value); - // 记录初始图像数据(深拷贝) - const originalData = originalImageData.value; - initialImageData.value = new ImageData( - new Uint8ClampedArray(originalData.data), - originalData.width, - originalData.height - ); + // 获取当前图像的实际状态(而不是原始状态) + const currentImageData = await getCurrentImageData(currentTarget); + if (currentImageData) { + // 记录当前图像数据(深拷贝) + initialImageData.value = new ImageData( + new Uint8ClampedArray(currentImageData.data), + currentImageData.width, + currentImageData.height + ); + + console.log( + "✅ 当前图像状态已记录,尺寸:", + initialImageData.value.width, + "x", + initialImageData.value.height + ); + } else { + // 如果无法获取当前状态,使用原始状态作为备用 + if (originalImageData.value) { + const originalData = originalImageData.value; + initialImageData.value = new ImageData( + new Uint8ClampedArray(originalData.data), + originalData.width, + originalData.height + ); + console.log("⚠️ 使用原始状态作为备用初始状态"); + } + } // 备用:也保存序列化状态 initialObjectState.value = serializeFabricObject(currentTarget); - - console.log( - "✅ 初始图像数据已记录,尺寸:", - initialImageData.value.width, - "x", - initialImageData.value.height - ); } } catch (error) { console.error("❌ 记录初始状态失败:", error); @@ -965,6 +1030,7 @@ async function handleMouseUp() { currentLiquifyCommand.value = createLiquifyStateCommand({ canvas: props.canvas, layerManager: props.layerManager, + liquifyManager: props.liquifyManager, targetObject: currentTarget, targetLayerId: targetLayerId.value, targetObjectId: targetObjectId.value, @@ -1646,7 +1712,7 @@ function stopPressTimer() { /* 平板适配:最多6列 */ @media screen and (max-width: 768px) { .liquify-modes { - grid-template-columns: repeat(6, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 3px; } @@ -1663,7 +1729,7 @@ function stopPressTimer() { /* 手机适配:最多4列 */ @media screen and (max-width: 480px) { .liquify-modes { - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 2px; } diff --git a/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue b/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue index 2bbb2b1b..84ce6233 100644 --- a/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/SelectionPanel.vue @@ -4,7 +4,7 @@
-
选区工具
+
{{ t("选区工具") }}
@@ -182,6 +182,7 @@