接入画布
This commit is contained in:
589
src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js
Normal file
589
src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js
Normal file
@@ -0,0 +1,589 @@
|
||||
import { Command } from "./Command";
|
||||
//import { fabric } from "fabric-with-all";
|
||||
|
||||
/**
|
||||
* 创建背景图层命令
|
||||
*/
|
||||
export class CreateBackgroundLayerCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "创建背景图层",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.backgroundLayer = options.backgroundLayer;
|
||||
this.canvasManager = options.canvasManager;
|
||||
this.historyManager = options.historyManager;
|
||||
|
||||
this.beforeLayers = [...this.layers.value]; // 备份原图层列表
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 检查是否已经存在背景图层
|
||||
const existingBgLayer = this.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
if (existingBgLayer) {
|
||||
console.warn("已存在背景层,不重复创建");
|
||||
return existingBgLayer.id;
|
||||
}
|
||||
|
||||
// 创建背景矩形对象
|
||||
const bgObject = this._createBackgroundObject();
|
||||
|
||||
// 将背景对象添加到图层中
|
||||
this.backgroundLayer.fabricObject = bgObject;
|
||||
|
||||
// 添加图层到最底部
|
||||
this.layers.value.push(this.backgroundLayer);
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(bgObject);
|
||||
|
||||
// 渲染画布
|
||||
this.canvas.renderAll();
|
||||
|
||||
return this.backgroundLayer.id;
|
||||
}
|
||||
|
||||
undo() {
|
||||
// 从图层列表中删除背景图层
|
||||
const bgLayerIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
if (bgLayerIndex !== -1) {
|
||||
this.layers.value.splice(bgLayerIndex, 1);
|
||||
}
|
||||
|
||||
// 从画布中移除背景对象
|
||||
if (this.backgroundLayer.fabricObject) {
|
||||
this.canvas.remove(this.backgroundLayer.fabricObject);
|
||||
}
|
||||
|
||||
// 渲染画布
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建背景矩形对象
|
||||
* @returns {Object} fabric.js 矩形对象
|
||||
* @private
|
||||
*/
|
||||
_createBackgroundObject() {
|
||||
// 计算画布尺寸
|
||||
const canvasWidth = this.canvas.width;
|
||||
const canvasHeight = this.canvas.height;
|
||||
|
||||
// 确保背景色为白色,如果没有设置或者是透明的话
|
||||
const backgroundColor =
|
||||
this.backgroundLayer.backgroundColor &&
|
||||
this.backgroundLayer.backgroundColor !== "transparent"
|
||||
? this.backgroundLayer.backgroundColor
|
||||
: "#ffffff";
|
||||
|
||||
const rect = new fabric.Rect({
|
||||
left: canvasWidth / 2,
|
||||
top: canvasHeight / 2,
|
||||
width: this.backgroundLayer.canvasWidth,
|
||||
height: this.backgroundLayer.canvasHeight,
|
||||
fill: backgroundColor,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
hoverCursor: "default",
|
||||
id: `bg_object_${this.backgroundLayer.id}`,
|
||||
layerId: this.backgroundLayer.id,
|
||||
layerName: this.backgroundLayer.name,
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
isBackground: true, // 标记为背景对象
|
||||
});
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
layerId: this.backgroundLayer.id,
|
||||
layerName: this.backgroundLayer.name,
|
||||
width: this.backgroundLayer.canvasWidth,
|
||||
height: this.backgroundLayer.canvasHeight,
|
||||
backgroundColor: this.backgroundLayer.backgroundColor,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新背景属性命令(如背景颜色)
|
||||
*/
|
||||
export class UpdateBackgroundCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "更新背景属性",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
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";
|
||||
}
|
||||
|
||||
execute() {
|
||||
if (!this.bgLayer) {
|
||||
console.error("未找到背景图层");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新背景图层属性
|
||||
this.bgLayer.backgroundColor = this.backgroundColor;
|
||||
|
||||
// 更新背景对象属性
|
||||
if (this.bgLayer.fabricObject) {
|
||||
this.bgLayer.fabricObject.set("fill", this.backgroundColor);
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (!this.bgLayer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 恢复背景图层属性
|
||||
this.bgLayer.backgroundColor = this.oldBackgroundColor;
|
||||
|
||||
// 恢复背景对象属性
|
||||
if (this.bgLayer.fabricObject) {
|
||||
this.bgLayer.fabricObject.set("fill", this.oldBackgroundColor);
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
layerId: this.bgLayer?.id,
|
||||
oldColor: this.oldBackgroundColor,
|
||||
newColor: this.backgroundColor,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整画布和背景大小命令
|
||||
*/
|
||||
export class BackgroundSizeCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "调整背景大小",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.canvasManager = options.canvasManager;
|
||||
this.newWidth = options.newWidth;
|
||||
this.newHeight = options.newHeight;
|
||||
this.historyManager = options.historyManager;
|
||||
|
||||
// 记录原尺寸
|
||||
this.oldWidth = this.canvas.width;
|
||||
this.oldHeight = this.canvas.height;
|
||||
|
||||
// 查找背景图层
|
||||
this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 调整画布大小
|
||||
this.canvas.setWidth(this.newWidth);
|
||||
this.canvas.setHeight(this.newHeight);
|
||||
|
||||
// 如果使用 CanvasManager,通知它画布大小变化
|
||||
if (
|
||||
this.canvasManager &&
|
||||
typeof this.canvasManager.updateCanvasSize === "function"
|
||||
) {
|
||||
this.canvasManager.updateCanvasSize(this.newWidth, this.newHeight);
|
||||
}
|
||||
|
||||
// 调整背景对象大小
|
||||
if (this.bgLayer && this.bgLayer.fabricObject) {
|
||||
// 保持原有的背景颜色,如果没有设置则使用白色
|
||||
const currentFill =
|
||||
this.bgLayer.fabricObject.fill ||
|
||||
this.bgLayer.backgroundColor ||
|
||||
"#ffffff";
|
||||
|
||||
this.bgLayer.fabricObject.set({
|
||||
width: this.newWidth,
|
||||
height: this.newHeight,
|
||||
fill: currentFill, // 保持原有颜色
|
||||
});
|
||||
|
||||
// 更新图层记录的尺寸
|
||||
this.bgLayer.canvasWidth = this.newWidth;
|
||||
this.bgLayer.canvasHeight = this.newHeight;
|
||||
}
|
||||
|
||||
// 渲染画布
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
// 恢复画布大小
|
||||
this.canvas.setWidth(this.oldWidth);
|
||||
this.canvas.setHeight(this.oldHeight);
|
||||
|
||||
// 如果使用 CanvasManager,通知它画布大小恢复
|
||||
if (
|
||||
this.canvasManager &&
|
||||
typeof this.canvasManager.updateCanvasSize === "function"
|
||||
) {
|
||||
this.canvasManager.updateCanvasSize(this.oldWidth, this.oldHeight);
|
||||
}
|
||||
|
||||
// 恢复背景对象大小
|
||||
if (this.bgLayer && this.bgLayer.fabricObject) {
|
||||
this.bgLayer.fabricObject.set({
|
||||
width: this.oldWidth,
|
||||
height: this.oldHeight,
|
||||
});
|
||||
|
||||
// 恢复图层记录的尺寸
|
||||
this.bgLayer.canvasWidth = this.oldWidth;
|
||||
this.bgLayer.canvasHeight = this.oldHeight;
|
||||
}
|
||||
|
||||
// 渲染画布
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
oldWidth: this.oldWidth,
|
||||
oldHeight: this.oldHeight,
|
||||
newWidth: this.newWidth,
|
||||
newHeight: this.newHeight,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整背景大小并等比缩放所有其他元素的命令
|
||||
*/
|
||||
export class BackgroundSizeWithScaleCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "调整背景大小并缩放元素",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.canvasManager = options.canvasManager;
|
||||
this.newWidth = options.newWidth;
|
||||
this.newHeight = options.newHeight;
|
||||
this.historyManager = options.historyManager;
|
||||
|
||||
// 缩放策略:'uniform' | 'fill' | 'fit' | 'stretch'
|
||||
this.scaleStrategy = options.scaleStrategy || "uniform";
|
||||
|
||||
// 记录原尺寸
|
||||
this.oldWidth = this.canvas.width;
|
||||
this.oldHeight = this.canvas.height;
|
||||
|
||||
// 查找背景图层
|
||||
this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
// 计算缩放比例
|
||||
const scaleXRatio = this.newWidth / this.oldWidth;
|
||||
const scaleYRatio = this.newHeight / this.oldHeight;
|
||||
|
||||
// 根据策略计算缩放比例和偏移
|
||||
this._calculateScaleAndOffset(scaleXRatio, scaleYRatio);
|
||||
|
||||
// 存储所有非背景对象的原始状态
|
||||
this.objectStates = [];
|
||||
this._saveOriginalStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存所有非背景对象的原始状态
|
||||
* @private
|
||||
*/
|
||||
_saveOriginalStates() {
|
||||
this.canvas.getObjects().forEach((obj) => {
|
||||
if (!obj.isBackground) {
|
||||
// 检查对象是否已经有原始状态记录
|
||||
if (!obj._originalState) {
|
||||
// 第一次记录原始状态
|
||||
obj._originalState = {
|
||||
left: obj.left,
|
||||
top: obj.top,
|
||||
scaleX: obj.scaleX || 1,
|
||||
scaleY: obj.scaleY || 1,
|
||||
width: obj.width,
|
||||
height: obj.height,
|
||||
// 记录基准画布尺寸
|
||||
baseCanvasWidth: this.oldWidth,
|
||||
baseCanvasHeight: this.oldHeight,
|
||||
};
|
||||
}
|
||||
|
||||
this.objectStates.push({
|
||||
obj: obj,
|
||||
// 使用原始状态而不是当前状态
|
||||
left: obj._originalState.left,
|
||||
top: obj._originalState.top,
|
||||
scaleX: obj._originalState.scaleX,
|
||||
scaleY: obj._originalState.scaleY,
|
||||
width: obj._originalState.width,
|
||||
height: obj._originalState.height,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据缩放策略计算缩放比例和偏移量
|
||||
* @param {number} scaleXRatio X轴缩放比例
|
||||
* @param {number} scaleYRatio Y轴缩放比例
|
||||
* @private
|
||||
*/
|
||||
_calculateScaleAndOffset(scaleXRatio, scaleYRatio) {
|
||||
switch (this.scaleStrategy) {
|
||||
case "uniform":
|
||||
// 统一缩放:使用平均值,保持相对比例的同时允许适度的形变
|
||||
this.uniformScale = Math.sqrt(scaleXRatio * scaleYRatio);
|
||||
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
|
||||
this.offsetY =
|
||||
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
break;
|
||||
|
||||
case "fit":
|
||||
// 适应模式:使用较小值,确保所有内容都在画布内,可能有留白
|
||||
this.uniformScale = Math.min(scaleXRatio, scaleYRatio);
|
||||
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
|
||||
this.offsetY =
|
||||
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
break;
|
||||
|
||||
case "fill":
|
||||
// 填充模式:使用较大值,填满画布,可能有部分内容被裁切
|
||||
this.uniformScale = Math.max(scaleXRatio, scaleYRatio);
|
||||
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
|
||||
this.offsetY =
|
||||
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
break;
|
||||
|
||||
case "stretch":
|
||||
// 拉伸模式:不保持宽高比,完全适应新尺寸
|
||||
this.scaleX = scaleXRatio;
|
||||
this.scaleY = scaleYRatio;
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
// 默认使用uniform模式
|
||||
this.uniformScale = Math.sqrt(scaleXRatio * scaleYRatio);
|
||||
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
|
||||
this.offsetY =
|
||||
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 调整画布大小
|
||||
this.canvas.setWidth(this.newWidth);
|
||||
this.canvas.setHeight(this.newHeight);
|
||||
|
||||
// 如果使用 CanvasManager,通知它画布大小变化
|
||||
if (
|
||||
this.canvasManager &&
|
||||
typeof this.canvasManager.updateCanvasSize === "function"
|
||||
) {
|
||||
this.canvasManager.updateCanvasSize(this.newWidth, this.newHeight);
|
||||
}
|
||||
|
||||
// 调整背景对象大小和位置
|
||||
if (this.bgLayer && this.bgLayer.fabricObject) {
|
||||
// 保持原有的背景颜色,如果没有设置则使用白色
|
||||
const currentFill =
|
||||
this.bgLayer.fabricObject.fill ||
|
||||
this.bgLayer.backgroundColor ||
|
||||
"#ffffff";
|
||||
|
||||
this.bgLayer.fabricObject.set({
|
||||
width: this.newWidth,
|
||||
height: this.newHeight,
|
||||
left: this.newWidth / 2,
|
||||
top: this.newHeight / 2,
|
||||
fill: currentFill, // 保持原有颜色
|
||||
});
|
||||
|
||||
// 更新图层记录的尺寸
|
||||
this.bgLayer.canvasWidth = this.newWidth;
|
||||
this.bgLayer.canvasHeight = this.newHeight;
|
||||
}
|
||||
|
||||
// 计算基于原始画布的缩放比例
|
||||
const baseScaleX =
|
||||
this.newWidth /
|
||||
this.objectStates[0]?.obj._originalState?.baseCanvasWidth ||
|
||||
this.newWidth / this.oldWidth;
|
||||
const baseScaleY =
|
||||
this.newHeight /
|
||||
this.objectStates[0]?.obj._originalState?.baseCanvasHeight ||
|
||||
this.newHeight / this.oldHeight;
|
||||
|
||||
// 根据策略缩放所有非背景对象
|
||||
this.objectStates.forEach((state) => {
|
||||
const obj = state.obj;
|
||||
|
||||
if (this.scaleStrategy === "stretch") {
|
||||
// 拉伸模式:使用不同的X和Y缩放比例
|
||||
obj.set({
|
||||
left: state.left * baseScaleX,
|
||||
top: state.top * baseScaleY,
|
||||
scaleX: state.scaleX * baseScaleX,
|
||||
scaleY: state.scaleY * baseScaleY,
|
||||
});
|
||||
} else {
|
||||
// 其他模式:计算基于原始状态的统一缩放比例
|
||||
const baseUniformScale = Math.sqrt(baseScaleX * baseScaleY);
|
||||
const baseOffsetX =
|
||||
(this.newWidth -
|
||||
(obj._originalState?.baseCanvasWidth || this.oldWidth) *
|
||||
baseUniformScale) /
|
||||
2;
|
||||
const baseOffsetY =
|
||||
(this.newHeight -
|
||||
(obj._originalState?.baseCanvasHeight || this.oldHeight) *
|
||||
baseUniformScale) /
|
||||
2;
|
||||
|
||||
obj.set({
|
||||
left: state.left * baseUniformScale + baseOffsetX,
|
||||
top: state.top * baseUniformScale + baseOffsetY,
|
||||
scaleX: state.scaleX * baseUniformScale,
|
||||
scaleY: state.scaleY * baseUniformScale,
|
||||
});
|
||||
}
|
||||
|
||||
obj.setCoords();
|
||||
});
|
||||
|
||||
// 渲染画布
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
// 恢复画布大小
|
||||
this.canvas.setWidth(this.oldWidth);
|
||||
this.canvas.setHeight(this.oldHeight);
|
||||
|
||||
// 如果使用 CanvasManager,通知它画布大小恢复
|
||||
if (
|
||||
this.canvasManager &&
|
||||
typeof this.canvasManager.updateCanvasSize === "function"
|
||||
) {
|
||||
this.canvasManager.updateCanvasSize(this.oldWidth, this.oldHeight);
|
||||
}
|
||||
|
||||
// 恢复背景对象大小和位置
|
||||
if (this.bgLayer && this.bgLayer.fabricObject) {
|
||||
this.bgLayer.fabricObject.set({
|
||||
width: this.oldWidth,
|
||||
height: this.oldHeight,
|
||||
left: this.oldWidth / 2,
|
||||
top: this.oldHeight / 2,
|
||||
});
|
||||
|
||||
// 恢复图层记录的尺寸
|
||||
this.bgLayer.canvasWidth = this.oldWidth;
|
||||
this.bgLayer.canvasHeight = this.oldHeight;
|
||||
}
|
||||
|
||||
// 恢复所有非背景对象的当前状态(而不是原始状态,因为可能有其他操作)
|
||||
this.objectStates.forEach((state) => {
|
||||
// 计算恢复到之前画布尺寸时的状态
|
||||
const obj = state.obj;
|
||||
const originalState = obj._originalState;
|
||||
|
||||
if (originalState) {
|
||||
const baseScaleX = this.oldWidth / originalState.baseCanvasWidth;
|
||||
const baseScaleY = this.oldHeight / originalState.baseCanvasHeight;
|
||||
const baseUniformScale = Math.sqrt(baseScaleX * baseScaleY);
|
||||
const baseOffsetX =
|
||||
(this.oldWidth - originalState.baseCanvasWidth * baseUniformScale) /
|
||||
2;
|
||||
const baseOffsetY =
|
||||
(this.oldHeight - originalState.baseCanvasHeight * baseUniformScale) /
|
||||
2;
|
||||
|
||||
obj.set({
|
||||
left: originalState.left * baseUniformScale + baseOffsetX,
|
||||
top: originalState.top * baseUniformScale + baseOffsetY,
|
||||
scaleX: originalState.scaleX * baseUniformScale,
|
||||
scaleY: originalState.scaleY * baseUniformScale,
|
||||
});
|
||||
} else {
|
||||
// 降级到原来的逻辑
|
||||
obj.set({
|
||||
left: state.left,
|
||||
top: state.top,
|
||||
scaleX: state.scaleX,
|
||||
scaleY: state.scaleY,
|
||||
});
|
||||
}
|
||||
|
||||
obj.setCoords();
|
||||
});
|
||||
|
||||
// 渲染画布
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
const info = {
|
||||
name: this.name,
|
||||
oldWidth: this.oldWidth,
|
||||
oldHeight: this.oldHeight,
|
||||
newWidth: this.newWidth,
|
||||
newHeight: this.newHeight,
|
||||
scaleStrategy: this.scaleStrategy,
|
||||
objectCount: this.objectStates.length,
|
||||
};
|
||||
|
||||
if (this.scaleStrategy === "stretch") {
|
||||
info.scaleX = this.scaleX;
|
||||
info.scaleY = this.scaleY;
|
||||
} else {
|
||||
info.uniformScale = this.uniformScale;
|
||||
info.offsetX = this.offsetX;
|
||||
info.offsetY = this.offsetY;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
698
src/component/Canvas/CanvasEditor/commands/BrushCommands.js
Normal file
698
src/component/Canvas/CanvasEditor/commands/BrushCommands.js
Normal file
@@ -0,0 +1,698 @@
|
||||
import { Command } from "./Command";
|
||||
import { BrushStore } from "../store/BrushStore";
|
||||
|
||||
/**
|
||||
* 笔刷属性设置基类命令
|
||||
* 所有笔刷属性设置命令的基类
|
||||
*/
|
||||
class BaseBrushCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.brushStore = options.brushStore || BrushStore;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 笔刷大小设置命令
|
||||
*/
|
||||
export class BrushSizeCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {Number} options.size 要设置的笔刷大小
|
||||
* @param {Number} options.previousSize 之前的笔刷大小(可选)
|
||||
* @param {Object} options.brushStore BrushStore实例(可选)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
name: `设置笔刷大小: ${options.size}`,
|
||||
description: `将笔刷大小从 ${options.previousSize || "?"} 设为 ${
|
||||
options.size
|
||||
}`,
|
||||
});
|
||||
|
||||
this.size = options.size;
|
||||
this.previousSize = options.previousSize || this.brushStore.state.size;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 记录当前大小用于撤销
|
||||
if (this.previousSize === null) {
|
||||
this.previousSize = this.brushStore.state.size;
|
||||
}
|
||||
|
||||
// 执行设置
|
||||
this.brushStore.setBrushSize(this.size);
|
||||
return this.size;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.brushStore.setBrushSize(this.previousSize);
|
||||
return this.previousSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 笔刷颜色设置命令
|
||||
*/
|
||||
export class BrushColorCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {String} options.color 要设置的颜色
|
||||
* @param {String} options.previousColor 之前的颜色(可选)
|
||||
* @param {Object} options.brushStore BrushStore实例(可选)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
name: `设置笔刷颜色: ${options.color}`,
|
||||
description: `将笔刷颜色从 ${options.previousColor || "?"} 设为 ${
|
||||
options.color
|
||||
}`,
|
||||
});
|
||||
|
||||
this.color = options.color;
|
||||
this.previousColor = options.previousColor || this.brushStore.state.color;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 记录当前颜色用于撤销
|
||||
if (this.previousColor === null) {
|
||||
this.previousColor = this.brushStore.state.color;
|
||||
}
|
||||
|
||||
// 执行设置
|
||||
this.brushStore.setBrushColor(this.color);
|
||||
return this.color;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.brushStore.setBrushColor(this.previousColor);
|
||||
return this.previousColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 笔刷透明度设置命令
|
||||
*/
|
||||
export class BrushOpacityCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {Number} options.opacity 要设置的透明度 (0-1)
|
||||
* @param {Number} options.previousOpacity 之前的透明度(可选)
|
||||
* @param {Object} options.brushStore BrushStore实例(可选)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
name: `设置笔刷透明度: ${options.opacity}`,
|
||||
description: `将笔刷透明度从 ${options.previousOpacity || "?"} 设为 ${
|
||||
options.opacity
|
||||
}`,
|
||||
});
|
||||
|
||||
this.opacity = options.opacity;
|
||||
this.previousOpacity =
|
||||
options.previousOpacity || this.brushStore.state.opacity;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 记录当前透明度用于撤销
|
||||
if (this.previousOpacity === null) {
|
||||
this.previousOpacity = this.brushStore.state.opacity;
|
||||
}
|
||||
|
||||
// 执行设置
|
||||
this.brushStore.setBrushOpacity(this.opacity);
|
||||
return this.opacity;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.brushStore.setBrushOpacity(this.previousOpacity);
|
||||
return this.previousOpacity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 笔刷类型设置命令
|
||||
*/
|
||||
export class BrushTypeCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {String} options.brushType 要设置的笔刷类型
|
||||
* @param {String} options.previousType 之前的笔刷类型(可选)
|
||||
* @param {Object} options.brushStore BrushStore实例(可选)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
name: `设置笔刷类型: ${options.brushType}`,
|
||||
description: `将笔刷类型从 ${options.previousType || "?"} 设为 ${
|
||||
options.brushType
|
||||
}`,
|
||||
});
|
||||
|
||||
this.brushType = options.brushType;
|
||||
this.previousType = options.previousType || this.brushStore.state.type;
|
||||
this.brushManager = options.brushManager;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 记录当前类型用于撤销
|
||||
if (this.previousType === null) {
|
||||
this.previousType = this.brushStore.state.type;
|
||||
}
|
||||
|
||||
// 执行设置
|
||||
// this.brushStore.setBrushType(this.brushType);
|
||||
this.brushManager.setBrushType(this.brushType);
|
||||
return this.brushType;
|
||||
}
|
||||
|
||||
undo() {
|
||||
// this.brushStore.setBrushType(this.previousType);
|
||||
this.brushManager.setBrushType(this.previousType);
|
||||
return this.previousType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 材质设置命令
|
||||
*/
|
||||
export class TextureCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {Boolean} options.enabled 是否启用材质
|
||||
* @param {String} options.path 材质路径
|
||||
* @param {Number} options.scale 材质缩放
|
||||
* @param {Object} options.previous 之前的材质设置(可选)
|
||||
* @param {Object} options.brushStore BrushStore实例(可选)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
name: options.enabled ? "启用笔刷材质" : "禁用笔刷材质",
|
||||
description: options.enabled
|
||||
? `启用材质: ${options.path || "[默认]"}`
|
||||
: "禁用笔刷材质",
|
||||
});
|
||||
|
||||
this.enabled = options.enabled;
|
||||
this.path = options.path;
|
||||
this.scale = options.scale;
|
||||
|
||||
// 保存之前状态用于撤销
|
||||
this.previous = options.previous || {
|
||||
enabled: this.brushStore.state.textureEnabled,
|
||||
path: this.brushStore.state.texturePath,
|
||||
scale: this.brushStore.state.textureScale,
|
||||
};
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 记录当前状态用于撤销
|
||||
if (!this.previous) {
|
||||
this.previous = {
|
||||
enabled: this.brushStore.state.textureEnabled,
|
||||
path: this.brushStore.state.texturePath,
|
||||
scale: this.brushStore.state.textureScale,
|
||||
};
|
||||
}
|
||||
|
||||
// 执行设置
|
||||
this.brushStore.setTextureEnabled(this.enabled);
|
||||
|
||||
if (this.path) {
|
||||
this.brushStore.setTexturePath(this.path);
|
||||
}
|
||||
|
||||
if (this.scale !== undefined) {
|
||||
this.brushStore.setTextureScale(this.scale);
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: this.enabled,
|
||||
path: this.path,
|
||||
scale: this.scale,
|
||||
};
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (!this.previous) return null;
|
||||
|
||||
this.brushStore.setTextureEnabled(this.previous.enabled);
|
||||
this.brushStore.setTexturePath(this.previous.path);
|
||||
this.brushStore.setTextureScale(this.previous.scale);
|
||||
|
||||
return this.previous;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设应用命令
|
||||
*/
|
||||
export class BrushPresetCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {Number|Object} options.preset 预设索引或预设对象
|
||||
* @param {Object} options.previousState 之前的状态(可选)
|
||||
* @param {Object} options.brushStore BrushStore实例(可选)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
const presetName =
|
||||
typeof options.preset === "object"
|
||||
? options.preset.name
|
||||
: `预设 ${options.preset}`;
|
||||
|
||||
super({
|
||||
...options,
|
||||
name: `应用笔刷预设: ${presetName}`,
|
||||
description: `应用预设: ${presetName}`,
|
||||
});
|
||||
|
||||
this.preset = options.preset;
|
||||
|
||||
// 保存之前状态用于撤销
|
||||
this.previousState = options.previousState || {
|
||||
size: this.brushStore.state.size,
|
||||
color: this.brushStore.state.color,
|
||||
opacity: this.brushStore.state.opacity,
|
||||
type: this.brushStore.state.type,
|
||||
textureEnabled: this.brushStore.state.textureEnabled,
|
||||
textureScale: this.brushStore.state.textureScale,
|
||||
texturePath: this.brushStore.state.texturePath,
|
||||
};
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 记录当前状态用于撤销
|
||||
if (!this.previousState) {
|
||||
this.previousState = {
|
||||
size: this.brushStore.state.size,
|
||||
color: this.brushStore.state.color,
|
||||
opacity: this.brushStore.state.opacity,
|
||||
type: this.brushStore.state.type,
|
||||
textureEnabled: this.brushStore.state.textureEnabled,
|
||||
textureScale: this.brushStore.state.textureScale,
|
||||
texturePath: this.brushStore.state.texturePath,
|
||||
};
|
||||
}
|
||||
|
||||
// 应用预设
|
||||
if (typeof this.preset === "number") {
|
||||
this.brushStore.applyPreset(this.preset);
|
||||
} else if (typeof this.preset === "object") {
|
||||
// 应用自定义预设对象
|
||||
if (this.preset.size !== undefined)
|
||||
this.brushStore.setBrushSize(this.preset.size);
|
||||
if (this.preset.color !== undefined)
|
||||
this.brushStore.setBrushColor(this.preset.color);
|
||||
if (this.preset.opacity !== undefined)
|
||||
this.brushStore.setBrushOpacity(this.preset.opacity);
|
||||
if (this.preset.type !== undefined)
|
||||
this.brushStore.setBrushType(this.preset.type);
|
||||
|
||||
if (this.preset.textureEnabled !== undefined) {
|
||||
this.brushStore.setTextureEnabled(this.preset.textureEnabled);
|
||||
}
|
||||
|
||||
if (this.preset.texturePath !== undefined) {
|
||||
this.brushStore.setTexturePath(this.preset.texturePath);
|
||||
}
|
||||
|
||||
if (this.preset.textureScale !== undefined) {
|
||||
this.brushStore.setTextureScale(this.preset.textureScale);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (!this.previousState) return false;
|
||||
|
||||
// 恢复之前的状态
|
||||
this.brushStore.setBrushSize(this.previousState.size);
|
||||
this.brushStore.setBrushColor(this.previousState.color);
|
||||
this.brushStore.setBrushOpacity(this.previousState.opacity);
|
||||
this.brushStore.setBrushType(this.previousState.type);
|
||||
this.brushStore.setTextureEnabled(this.previousState.textureEnabled);
|
||||
this.brushStore.setTextureScale(this.previousState.textureScale);
|
||||
this.brushStore.setTexturePath(this.previousState.texturePath);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 笔刷属性命令
|
||||
* 用于修改笔刷的任意属性,包括特殊属性
|
||||
*/
|
||||
export class BrushPropertyCommand extends Command {
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} options 命令选项
|
||||
* @param {String} options.propertyId 属性ID
|
||||
* @param {any} options.value 新的属性值
|
||||
* @param {Object} options.brushStore BrushStore实例
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "笔刷属性更改",
|
||||
description: `更改笔刷属性 ${options.propertyId}`,
|
||||
options,
|
||||
});
|
||||
|
||||
this.propertyId = options.propertyId;
|
||||
this.newValue = options.value;
|
||||
this.oldValue = null;
|
||||
this.brushStore = options.brushStore || BrushStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* @returns {Boolean} 是否执行成功
|
||||
*/
|
||||
execute() {
|
||||
if (!this.brushStore) {
|
||||
console.error("BrushStore不可用");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存旧值用于撤销
|
||||
this.oldValue = this.brushStore.getPropertyValue(this.propertyId);
|
||||
|
||||
// 更新属性值
|
||||
this.brushStore.updatePropertyValue(this.propertyId, this.newValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销命令
|
||||
* @param {Object} context 命令上下文
|
||||
* @returns {Boolean} 是否撤销成功
|
||||
*/
|
||||
undo() {
|
||||
if (!this.brushStore || this.oldValue === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 恢复旧值
|
||||
this.brushStore.updatePropertyValue(this.propertyId, this.oldValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 材质选择命令
|
||||
*/
|
||||
export class TextureSelectionCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {String} options.textureId 要设置的材质ID
|
||||
* @param {String} options.previousTextureId 之前的材质ID(可选)
|
||||
* @param {Object} options.brushManager 笔刷管理器实例
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
name: `切换纹理材质`,
|
||||
description: `切换到材质: ${options.textureId}`,
|
||||
});
|
||||
|
||||
this.textureId = options.textureId;
|
||||
this.previousTextureId = options.previousTextureId;
|
||||
this.brushManager = options.brushManager;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 记录当前材质用于撤销
|
||||
const currentBrush = this.brushManager.activeBrush;
|
||||
if (currentBrush && currentBrush.getCurrentTexture) {
|
||||
const currentTexture = currentBrush.getCurrentTexture();
|
||||
this.previousTextureId = currentTexture ? currentTexture.id : null;
|
||||
}
|
||||
|
||||
// 确保当前是材质笔刷
|
||||
if (this.brushManager.getCurrentBrushType() !== "texture") {
|
||||
this.brushManager.setBrushType("texture");
|
||||
}
|
||||
|
||||
// 设置材质
|
||||
const activeBrush = this.brushManager.activeBrush;
|
||||
if (activeBrush && activeBrush.setTextureById) {
|
||||
activeBrush.setTextureById(this.textureId);
|
||||
}
|
||||
|
||||
return this.textureId;
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (!this.previousTextureId) return false;
|
||||
|
||||
// 确保当前是材质笔刷
|
||||
if (this.brushManager.getCurrentBrushType() !== "texture") {
|
||||
this.brushManager.setBrushType("texture");
|
||||
}
|
||||
|
||||
// 恢复之前的材质
|
||||
const activeBrush = this.brushManager.activeBrush;
|
||||
if (activeBrush && activeBrush.setTextureById) {
|
||||
activeBrush.setTextureById(this.previousTextureId);
|
||||
}
|
||||
|
||||
return this.previousTextureId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 材质属性设置命令
|
||||
*/
|
||||
export class TexturePropertyCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {String} options.property 要设置的属性名称 (scale, rotation, offsetX, offsetY等)
|
||||
* @param {any} options.value 要设置的属性值
|
||||
* @param {any} options.previousValue 之前的属性值(可选)
|
||||
* @param {Object} options.brushManager 笔刷管理器实例
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
name: `设置材质属性: ${options.property}`,
|
||||
description: `将材质${options.property}从 ${
|
||||
options.previousValue || "?"
|
||||
} 设为 ${options.value}`,
|
||||
});
|
||||
|
||||
this.property = options.property;
|
||||
this.value = options.value;
|
||||
this.previousValue = options.previousValue;
|
||||
this.brushManager = options.brushManager;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 确保当前是材质笔刷
|
||||
if (this.brushManager.getCurrentBrushType() !== "texture") {
|
||||
this.brushManager.setBrushType("texture");
|
||||
}
|
||||
|
||||
const activeBrush = this.brushManager.activeBrush;
|
||||
if (!activeBrush || !activeBrush.setTextureProperty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 记录当前值用于撤销
|
||||
if (this.previousValue === undefined) {
|
||||
this.previousValue = activeBrush.getTextureProperty(this.property);
|
||||
}
|
||||
|
||||
// 设置新值
|
||||
activeBrush.setTextureProperty(this.property, this.value);
|
||||
return this.value;
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this.previousValue === undefined) return false;
|
||||
|
||||
// 确保当前是材质笔刷
|
||||
if (this.brushManager.getCurrentBrushType() !== "texture") {
|
||||
this.brushManager.setBrushType("texture");
|
||||
}
|
||||
|
||||
const activeBrush = this.brushManager.activeBrush;
|
||||
if (activeBrush && activeBrush.setTextureProperty) {
|
||||
activeBrush.setTextureProperty(this.property, this.previousValue);
|
||||
}
|
||||
|
||||
return this.previousValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 材质预设应用命令
|
||||
*/
|
||||
export class TexturePresetCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {String|Object} options.preset 预设ID或预设对象
|
||||
* @param {Object} options.previousState 之前的材质状态(可选)
|
||||
* @param {Object} options.brushManager 笔刷管理器实例
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
const presetName =
|
||||
typeof options.preset === "object" ? options.preset.name : options.preset;
|
||||
|
||||
super({
|
||||
...options,
|
||||
name: `应用材质预设: ${presetName}`,
|
||||
description: `应用材质预设: ${presetName}`,
|
||||
});
|
||||
|
||||
this.preset = options.preset;
|
||||
this.previousState = options.previousState;
|
||||
this.brushManager = options.brushManager;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 确保当前是材质笔刷
|
||||
if (this.brushManager.getCurrentBrushType() !== "texture") {
|
||||
this.brushManager.setBrushType("texture");
|
||||
}
|
||||
|
||||
const activeBrush = this.brushManager.activeBrush;
|
||||
if (!activeBrush || !activeBrush.applyTexturePreset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 记录当前状态用于撤销
|
||||
if (!this.previousState) {
|
||||
this.previousState = activeBrush.getCurrentTextureState();
|
||||
}
|
||||
|
||||
// 应用预设
|
||||
activeBrush.applyTexturePreset(this.preset);
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (!this.previousState) return false;
|
||||
|
||||
// 确保当前是材质笔刷
|
||||
if (this.brushManager.getCurrentBrushType() !== "texture") {
|
||||
this.brushManager.setBrushType("texture");
|
||||
}
|
||||
|
||||
const activeBrush = this.brushManager.activeBrush;
|
||||
if (activeBrush && activeBrush.restoreTextureState) {
|
||||
activeBrush.restoreTextureState(this.previousState);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 纹理上传命令
|
||||
*/
|
||||
export class TextureUploadCommand extends BaseBrushCommand {
|
||||
/**
|
||||
* @param {Object} options 命令选项
|
||||
* @param {File} options.file 要上传的纹理文件
|
||||
* @param {String} options.name 纹理名称(可选)
|
||||
* @param {String} options.category 纹理分类(可选)
|
||||
* @param {Object} options.texturePresetManager 纹理预设管理器实例
|
||||
* @param {Object} options.brushManager 笔刷管理器实例
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
...options,
|
||||
name: `上传纹理: ${options.name || options.file?.name || '未知'}`,
|
||||
description: `上传自定义纹理文件`,
|
||||
});
|
||||
|
||||
this.file = options.file;
|
||||
this.name = options.name || options.file?.name?.replace(/\.[^/.]+$/, "") || "自定义纹理";
|
||||
this.category = options.category || "自定义材质";
|
||||
this.texturePresetManager = options.texturePresetManager;
|
||||
this.brushManager = options.brushManager;
|
||||
this.uploadedTextureId = null;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.file || !this.texturePresetManager) {
|
||||
throw new Error('缺少必要的文件或纹理预设管理器');
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建文件 data URL
|
||||
const dataUrl = await this._fileToDataUrl(this.file);
|
||||
|
||||
// 添加到纹理预设管理器
|
||||
this.uploadedTextureId = this.texturePresetManager.addCustomTexture({
|
||||
name: this.name,
|
||||
category: this.category,
|
||||
file: this.file,
|
||||
dataUrl: dataUrl,
|
||||
preview: dataUrl,
|
||||
});
|
||||
|
||||
// 如果是纹理笔刷,自动应用新上传的纹理
|
||||
if (this.brushManager) {
|
||||
if (this.brushManager.getCurrentBrushType() !== "texture") {
|
||||
this.brushManager.setBrushType("texture");
|
||||
}
|
||||
|
||||
const activeBrush = this.brushManager.activeBrush;
|
||||
if (activeBrush && activeBrush.updateProperty) {
|
||||
activeBrush.updateProperty("textureSelector", this.uploadedTextureId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
textureId: this.uploadedTextureId,
|
||||
dataUrl: dataUrl,
|
||||
name: this.name
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('纹理上传失败:', error);
|
||||
throw new Error(`纹理上传失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (!this.uploadedTextureId || !this.texturePresetManager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从纹理预设管理器中移除上传的纹理
|
||||
try {
|
||||
this.texturePresetManager.removeCustomTexture(this.uploadedTextureId);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('撤销纹理上传失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件转换为 data URL
|
||||
* @private
|
||||
* @param {File} file
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
_fileToDataUrl(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
resolve(event.target.result);
|
||||
};
|
||||
reader.onerror = (error) => {
|
||||
reject(error);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
296
src/component/Canvas/CanvasEditor/commands/Command.js
Normal file
296
src/component/Canvas/CanvasEditor/commands/Command.js
Normal file
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* 基础命令类
|
||||
* 所有命令都应该继承这个类
|
||||
*/
|
||||
export class Command {
|
||||
constructor(options = {}) {
|
||||
this.name = options.name || "未命名命令";
|
||||
this.description = options.description || "";
|
||||
this.undoable = options.undoable !== false; // 默认可撤销
|
||||
this.timestamp = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* @returns {*} 执行结果,可以是Promise
|
||||
*/
|
||||
execute() {
|
||||
throw new Error("子类必须实现execute方法");
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销命令
|
||||
* @returns {*} 撤销结果,可以是Promise
|
||||
*/
|
||||
undo() {
|
||||
if (!this.undoable) {
|
||||
throw new Error("此命令不支持撤销");
|
||||
}
|
||||
throw new Error("可撤销命令必须实现undo方法");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令信息
|
||||
*/
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
undoable: this.undoable,
|
||||
timestamp: this.timestamp,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复合命令类
|
||||
* 用于批量执行多个命令,替代事务系统
|
||||
*/
|
||||
export class CompositeCommand extends Command {
|
||||
constructor(commands = [], options = {}) {
|
||||
super({
|
||||
name: options.name || "复合命令",
|
||||
description: options.description || "批量执行多个命令",
|
||||
...options,
|
||||
});
|
||||
|
||||
this.commands = Array.isArray(commands) ? commands : [];
|
||||
this.executedCommands = []; // 记录已执行的命令,用于撤销
|
||||
this.isExecuting = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加子命令
|
||||
*/
|
||||
addCommand(command) {
|
||||
if (!command || typeof command.execute !== "function") {
|
||||
throw new Error("无效的命令对象");
|
||||
}
|
||||
this.commands.push(command);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加子命令
|
||||
*/
|
||||
addCommands(commands) {
|
||||
if (Array.isArray(commands)) {
|
||||
commands.forEach((cmd) => this.addCommand(cmd));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行所有子命令(串行执行)
|
||||
*/
|
||||
async execute() {
|
||||
if (this.isExecuting) {
|
||||
throw new Error("复合命令正在执行中");
|
||||
}
|
||||
|
||||
this.isExecuting = true;
|
||||
this.executedCommands = [];
|
||||
|
||||
try {
|
||||
const results = [];
|
||||
|
||||
// 串行执行所有子命令
|
||||
for (const command of this.commands) {
|
||||
try {
|
||||
console.log(`📦 复合命令执行子命令: ${command.constructor.name}`);
|
||||
|
||||
const result = command.execute();
|
||||
|
||||
// 如果是异步命令,等待完成
|
||||
const finalResult = this._isPromise(result) ? await result : result;
|
||||
|
||||
results.push(finalResult);
|
||||
this.executedCommands.push(command);
|
||||
|
||||
console.log(`✅ 子命令执行成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令执行失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
|
||||
// 执行失败时,撤销已执行的命令
|
||||
await this._rollbackExecutedCommands();
|
||||
throw new Error(`复合命令执行失败:${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.isExecuting = false;
|
||||
console.log(`✅ 复合命令执行完成,共执行 ${results.length} 个子命令`);
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.isExecuting = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销所有已执行的子命令(逆序撤销)
|
||||
*/
|
||||
async undo() {
|
||||
if (this.isExecuting) {
|
||||
throw new Error("复合命令正在执行中,无法撤销");
|
||||
}
|
||||
|
||||
if (this.executedCommands.length === 0) {
|
||||
console.warn("没有已执行的子命令需要撤销");
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`↩️ 开始撤销复合命令,共 ${this.executedCommands.length} 个子命令`
|
||||
);
|
||||
|
||||
try {
|
||||
// 逆序撤销已执行的命令
|
||||
const commands = [...this.executedCommands].reverse();
|
||||
const results = [];
|
||||
|
||||
for (const command of commands) {
|
||||
if (typeof command.undo === "function") {
|
||||
try {
|
||||
console.log(`↩️ 撤销子命令: ${command.constructor.name}`);
|
||||
|
||||
const result = command.undo();
|
||||
|
||||
// 如果是异步撤销,等待完成
|
||||
const finalResult = this._isPromise(result) ? await result : result;
|
||||
|
||||
results.push(finalResult);
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
// 撤销失败不中断整个撤销过程,但要记录错误
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ 子命令不支持撤销: ${command.constructor.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.executedCommands = [];
|
||||
console.log(`✅ 复合命令撤销完成`);
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error("❌ 复合命令撤销过程中发生错误:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚已执行的命令(内部使用)
|
||||
* @private
|
||||
*/
|
||||
async _rollbackExecutedCommands() {
|
||||
console.log(`🔄 开始回滚已执行的 ${this.executedCommands.length} 个子命令`);
|
||||
|
||||
const commands = [...this.executedCommands].reverse();
|
||||
|
||||
for (const command of commands) {
|
||||
if (typeof command.undo === "function") {
|
||||
try {
|
||||
console.log(`🔄 回滚子命令: ${command.constructor.name}`);
|
||||
const result = command.undo();
|
||||
if (this._isPromise(result)) {
|
||||
await result;
|
||||
}
|
||||
console.log(`✅ 子命令回滚成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令回滚失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
// 回滚失败不中断整个回滚过程
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.executedCommands = [];
|
||||
console.log(`✅ 回滚完成`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查返回值是否为Promise
|
||||
* @private
|
||||
*/
|
||||
_isPromise(value) {
|
||||
return (
|
||||
value &&
|
||||
typeof value === "object" &&
|
||||
typeof value.then === "function" &&
|
||||
typeof value.catch === "function"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取复合命令信息
|
||||
*/
|
||||
getInfo() {
|
||||
return {
|
||||
...super.getInfo(),
|
||||
commandCount: this.commands.length,
|
||||
executedCount: this.executedCommands.length,
|
||||
isExecuting: this.isExecuting,
|
||||
subCommands: this.commands.map((cmd) =>
|
||||
cmd.getInfo ? cmd.getInfo() : cmd.constructor.name
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 函数命令包装器
|
||||
* 将普通函数包装为命令对象
|
||||
*/
|
||||
export class FunctionCommand extends Command {
|
||||
constructor(executeFn, undoFn = null, options = {}) {
|
||||
super({
|
||||
name: options.name || "函数命令",
|
||||
undoable: typeof undoFn === "function",
|
||||
...options,
|
||||
});
|
||||
|
||||
if (typeof executeFn !== "function") {
|
||||
throw new Error("执行函数不能为空");
|
||||
}
|
||||
|
||||
this.executeFn = executeFn;
|
||||
this.undoFn = undoFn;
|
||||
this.executeResult = null; // 保存执行结果用于撤销
|
||||
}
|
||||
|
||||
async execute() {
|
||||
const result = this.executeFn();
|
||||
this.executeResult = this._isPromise(result) ? await result : result;
|
||||
return this.executeResult;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.undoFn) {
|
||||
throw new Error("此函数命令不支持撤销");
|
||||
}
|
||||
|
||||
const result = this.undoFn(this.executeResult);
|
||||
return this._isPromise(result) ? await result : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查返回值是否为Promise
|
||||
* @private
|
||||
*/
|
||||
_isPromise(value) {
|
||||
return (
|
||||
value &&
|
||||
typeof value === "object" &&
|
||||
typeof value.then === "function" &&
|
||||
typeof value.catch === "function"
|
||||
);
|
||||
}
|
||||
}
|
||||
491
src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js
Normal file
491
src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js
Normal file
@@ -0,0 +1,491 @@
|
||||
import { CompositeCommand } from "./Command.js";
|
||||
import { AddLayerCommand, CreateImageLayerCommand } from "./LayerCommands.js";
|
||||
import { ToolCommand } from "./ToolCommands.js";
|
||||
import { ClearSelectionCommand } from "./SelectionCommands.js";
|
||||
import { createLayer, LayerType, OperationType } from "../utils/layerHelper.js";
|
||||
//import { fabric } from "fabric-with-all";
|
||||
|
||||
/**
|
||||
* 套索抠图命令
|
||||
* 实现将选区内容抠图到新图层的功能
|
||||
*/
|
||||
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 || 4; // 基础分辨率倍数,提高到4倍获得更清晰的图像
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.layerManager || !this.selectionManager) {
|
||||
console.error("无法执行套索抠图:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.executedCommands = [];
|
||||
|
||||
// 获取选区
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (!selectionObject) {
|
||||
console.error("无法执行套索抠图:当前没有选区");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确定源图层
|
||||
const sourceLayer = this.layerManager.getActiveLayer();
|
||||
if (!sourceLayer || sourceLayer.fabricObjects.length === 0) {
|
||||
console.error("无法执行套索抠图:源图层无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取选区边界信息用于后续定位
|
||||
const selectionBounds = selectionObject.getBoundingRect(true, true);
|
||||
|
||||
// 执行在当前画布上的抠图操作
|
||||
this.cutoutImageUrl = await this._performCutout(
|
||||
sourceLayer,
|
||||
selectionObject
|
||||
);
|
||||
if (!this.cutoutImageUrl) {
|
||||
console.error("抠图失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建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 clearSelectionCmd = new ClearSelectionCommand({
|
||||
canvas: this.canvas,
|
||||
selectionManager: this.selectionManager,
|
||||
});
|
||||
|
||||
// 执行清除选区命令
|
||||
await clearSelectionCmd.execute();
|
||||
this.executedCommands.push(clearSelectionCmd);
|
||||
|
||||
console.log(`套索抠图完成,新图层ID: ${this.newLayerId}`);
|
||||
return {
|
||||
newLayerId: this.newLayerId,
|
||||
cutoutImageUrl: this.cutoutImageUrl,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("套索抠图过程中出错:", error);
|
||||
|
||||
// 如果已经创建了新图层,需要进行清理
|
||||
if (this.newLayerId) {
|
||||
try {
|
||||
await this.layerManager.removeLayer(this.newLayerId);
|
||||
} catch (cleanupError) {
|
||||
console.warn("清理新图层失败:", cleanupError);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
this.executedCommands = [];
|
||||
this.newLayerId = null;
|
||||
this.cutoutImageUrl = null;
|
||||
this.fabricImage = null;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("撤销套索抠图失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前画布上执行抠图操作
|
||||
* @param {Object} sourceLayer 源图层
|
||||
* @param {Object} selectionObject 选区对象
|
||||
* @returns {String} 抠图结果的DataURL
|
||||
* @private
|
||||
*/
|
||||
async _performCutout(sourceLayer, selectionObject) {
|
||||
try {
|
||||
console.log("=== 开始在当前画布执行抠图 ===");
|
||||
|
||||
// 获取选区边界
|
||||
const selectionBounds = selectionObject.getBoundingRect(true, true);
|
||||
console.log(
|
||||
`选区边界: left=${selectionBounds.left}, top=${selectionBounds.top}, width=${selectionBounds.width}, height=${selectionBounds.height}`
|
||||
);
|
||||
|
||||
// 保存画布当前状态
|
||||
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
|
||||
);
|
||||
console.log(`源图层可见对象数量: ${visibleObjects.length}`);
|
||||
|
||||
if (visibleObjects.length === 0) {
|
||||
throw new Error("源图层没有可见对象");
|
||||
}
|
||||
|
||||
// 如果只有一个对象且已经是组,直接使用
|
||||
if (visibleObjects.length === 1 && visibleObjects[0].type === "group") {
|
||||
tempGroup = visibleObjects[0];
|
||||
console.log("使用现有组对象");
|
||||
} else {
|
||||
// 创建临时组
|
||||
console.log("创建临时组...");
|
||||
|
||||
// 记录原始对象的位置和状态,用于后续恢复
|
||||
originalObjects = visibleObjects.map((obj) => ({
|
||||
object: obj,
|
||||
originalLeft: obj.left,
|
||||
originalTop: obj.top,
|
||||
originalAngle: obj.angle,
|
||||
originalScaleX: obj.scaleX,
|
||||
originalScaleY: obj.scaleY,
|
||||
}));
|
||||
|
||||
// 不需要从画布移除原对象,直接创建组
|
||||
// 克隆对象来创建组,避免影响原对象
|
||||
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
|
||||
);
|
||||
|
||||
console.log(
|
||||
`设备像素比: ${devicePixelRatio}, 基础倍数: ${this.baseResolutionScale}, 最终放大倍数: ${highResolutionScale}`
|
||||
);
|
||||
} else {
|
||||
console.log("高分辨率渲染已禁用,使用1x倍数");
|
||||
}
|
||||
|
||||
// 创建用于导出的高分辨率canvas
|
||||
const exportCanvas = document.createElement("canvas");
|
||||
const exportCtx = exportCanvas.getContext("2d", {
|
||||
alpha: true,
|
||||
willReadFrequently: false,
|
||||
colorSpace: "srgb",
|
||||
});
|
||||
|
||||
// 设置canvas的实际像素尺寸(放大倍数)
|
||||
const actualWidth = Math.round(
|
||||
renderBounds.width * highResolutionScale
|
||||
);
|
||||
const actualHeight = Math.round(
|
||||
renderBounds.height * highResolutionScale
|
||||
);
|
||||
|
||||
exportCanvas.width = actualWidth;
|
||||
exportCanvas.height = actualHeight;
|
||||
|
||||
// 设置canvas的显示尺寸(CSS尺寸,保持与选区一致)
|
||||
exportCanvas.style.width = renderBounds.width + "px";
|
||||
exportCanvas.style.height = renderBounds.height + "px";
|
||||
|
||||
// 启用最高质量渲染设置
|
||||
exportCtx.imageSmoothingEnabled = true;
|
||||
exportCtx.imageSmoothingQuality = "high";
|
||||
|
||||
// 设置文本渲染质量
|
||||
if (exportCtx.textRenderingOptimization) {
|
||||
exportCtx.textRenderingOptimization = "optimizeQuality";
|
||||
}
|
||||
|
||||
// 设置线条和图形的渲染质量
|
||||
exportCtx.lineCap = "round";
|
||||
exportCtx.lineJoin = "round";
|
||||
exportCtx.miterLimit = 10;
|
||||
|
||||
// 设置画布背景为透明
|
||||
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);
|
||||
|
||||
console.log(
|
||||
`抠图完成,选区尺寸: ${renderBounds.width}x${renderBounds.height}, 实际渲染尺寸: ${actualWidth}x${actualHeight}, 放大倍数: ${highResolutionScale}x`
|
||||
);
|
||||
|
||||
return dataUrl;
|
||||
} finally {
|
||||
// 清理和恢复
|
||||
if (tempGroup) {
|
||||
// 移除裁剪路径
|
||||
tempGroup.set({ clipPath: null });
|
||||
|
||||
// 如果是我们创建的临时组,需要移除它
|
||||
if (originalObjects.length > 0) {
|
||||
this.canvas.remove(tempGroup);
|
||||
|
||||
// 恢复原始对象的状态(位置等信息保持不变)
|
||||
originalObjects.forEach(
|
||||
({
|
||||
object,
|
||||
originalLeft,
|
||||
originalTop,
|
||||
originalAngle,
|
||||
originalScaleX,
|
||||
originalScaleY,
|
||||
}) => {
|
||||
// 确保对象仍然在画布上且状态正确
|
||||
if (!this.canvas.getObjects().includes(object)) {
|
||||
this.canvas.add(object);
|
||||
}
|
||||
|
||||
// 恢复原始变换状态(如果需要的话)
|
||||
object.set({
|
||||
left: originalLeft,
|
||||
top: originalTop,
|
||||
angle: originalAngle,
|
||||
scaleX: originalScaleX,
|
||||
scaleY: originalScaleY,
|
||||
});
|
||||
object.setCoords();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复画布状态
|
||||
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;
|
||||
}
|
||||
|
||||
// 计算画布中心位置
|
||||
const canvasCenter = this.canvas.getCenter();
|
||||
|
||||
// 如果有选区边界信息,使用选区的原始位置和尺寸
|
||||
let targetLeft = canvasCenter.left;
|
||||
let targetTop = canvasCenter.top;
|
||||
let targetWidth = img.width;
|
||||
let targetHeight = img.height;
|
||||
|
||||
if (selectionBounds) {
|
||||
// 使用选区的原始位置
|
||||
targetLeft = selectionBounds.left + selectionBounds.width / 2;
|
||||
targetTop = selectionBounds.top + selectionBounds.height / 2;
|
||||
|
||||
// 确保图像显示尺寸与选区尺寸一致
|
||||
targetWidth = selectionBounds.width;
|
||||
targetHeight = selectionBounds.height;
|
||||
|
||||
console.log(
|
||||
`设置图像位置: left=${targetLeft}, top=${targetTop}, 尺寸: ${targetWidth}x${targetHeight}`
|
||||
);
|
||||
}
|
||||
|
||||
// 计算缩放比例以匹配目标尺寸
|
||||
const scaleX = targetWidth / img.width;
|
||||
const scaleY = targetHeight / img.height;
|
||||
|
||||
// 设置图像属性
|
||||
img.set({
|
||||
left: targetLeft,
|
||||
top: targetTop,
|
||||
scaleX: scaleX,
|
||||
scaleY: scaleY,
|
||||
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();
|
||||
|
||||
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) => {
|
||||
resolve(cloned);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
2801
src/component/Canvas/CanvasEditor/commands/LayerCommands.js
Normal file
2801
src/component/Canvas/CanvasEditor/commands/LayerCommands.js
Normal file
File diff suppressed because it is too large
Load Diff
578
src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js
Normal file
578
src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js
Normal file
@@ -0,0 +1,578 @@
|
||||
import { Command, FunctionCommand } from "./Command";
|
||||
|
||||
/**
|
||||
* 液化命令基类
|
||||
* 所有液化相关命令的基类
|
||||
*/
|
||||
export class LiquifyCommand extends Command {
|
||||
/**
|
||||
* 创建液化命令
|
||||
* @param {Object} options 配置选项
|
||||
* @param {Object} options.canvas Fabric.js画布实例
|
||||
* @param {Object} options.layerManager 图层管理器实例
|
||||
* @param {Object} options.liquifyManager 液化管理器实例
|
||||
* @param {String} options.mode 液化模式
|
||||
* @param {Object} options.params 液化参数
|
||||
* @param {Object} options.targetObject 目标对象
|
||||
* @param {ImageData} options.originalData 原始图像数据
|
||||
* @param {ImageData} options.resultData 变形后图像数据
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
name: options.name || `液化操作: ${options.mode || "未知模式"}`,
|
||||
description:
|
||||
options.description ||
|
||||
`使用${options.mode || "未知模式"}模式进行液化操作`,
|
||||
});
|
||||
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.liquifyManager = options.liquifyManager;
|
||||
this.mode = options.mode;
|
||||
this.params = options.params || {};
|
||||
this.targetObject = options.targetObject;
|
||||
this.targetLayerId = options.targetLayerId;
|
||||
this.originalData = options.originalData; // 操作前的图像数据
|
||||
this.resultData = options.resultData; // 操作后的图像数据
|
||||
this.savedState = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行液化操作
|
||||
* @returns {Promise} 执行结果
|
||||
*/
|
||||
async execute() {
|
||||
if (!this.canvas || !this.targetObject) {
|
||||
throw new Error("液化命令缺少必要的画布或目标对象");
|
||||
}
|
||||
|
||||
if (!this.resultData) {
|
||||
// 如果没有预先计算的结果数据,现场执行变形
|
||||
this.resultData = await this.liquifyManager.applyLiquify(
|
||||
this.targetObject,
|
||||
this.mode,
|
||||
this.params
|
||||
);
|
||||
}
|
||||
|
||||
// 保存执行前的状态
|
||||
this.savedState = await this._saveObjectState();
|
||||
|
||||
// 更新画布上的对象
|
||||
await this._updateObjectWithResult();
|
||||
|
||||
// 刷新Canvas
|
||||
this.canvas.renderAll();
|
||||
|
||||
return this.resultData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销液化操作
|
||||
* @returns {Promise} 撤销结果
|
||||
*/
|
||||
async undo() {
|
||||
if (!this.canvas || !this.targetObject || !this.savedState) {
|
||||
throw new Error("无法撤销:缺少必要的状态信息");
|
||||
}
|
||||
|
||||
// 恢复对象到原始状态
|
||||
await this._restoreObjectState();
|
||||
|
||||
// 刷新Canvas
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存对象状态
|
||||
* @private
|
||||
*/
|
||||
async _saveObjectState() {
|
||||
if (!this.targetObject) return null;
|
||||
|
||||
// 对于图像对象,我们需要保存src和元数据
|
||||
const state = {
|
||||
src: this.targetObject.getSrc ? this.targetObject.getSrc() : null,
|
||||
element: this.targetObject._element
|
||||
? this.targetObject._element.cloneNode(true)
|
||||
: null,
|
||||
filters: this.targetObject.filters ? [...this.targetObject.filters] : [],
|
||||
originalData: this.originalData,
|
||||
targetLayerId: this.targetLayerId,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复对象状态
|
||||
* @private
|
||||
*/
|
||||
async _restoreObjectState() {
|
||||
if (!this.targetObject || !this.savedState) return false;
|
||||
|
||||
// 获取当前图层对象
|
||||
const layer = this.layerManager.getLayerById(this.savedState.targetLayerId);
|
||||
if (!layer) return false;
|
||||
|
||||
// 恢复原始图像
|
||||
if (this.savedState.element && this.targetObject.setElement) {
|
||||
this.targetObject.setElement(this.savedState.element);
|
||||
|
||||
// 恢复滤镜
|
||||
if (this.savedState.filters) {
|
||||
this.targetObject.filters = [...this.savedState.filters];
|
||||
this.targetObject.applyFilters();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用变形结果更新对象
|
||||
* @private
|
||||
*/
|
||||
async _updateObjectWithResult() {
|
||||
if (!this.targetObject || !this.resultData) return false;
|
||||
|
||||
// 创建临时canvas来渲染结果数据
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
tempCanvas.width = this.resultData.width;
|
||||
tempCanvas.height = this.resultData.height;
|
||||
const tempCtx = tempCanvas.getContext("2d");
|
||||
tempCtx.putImageData(this.resultData, 0, 0);
|
||||
console.log("临时Canvas创建成功 _updateObjectWithResult", this.resultData);
|
||||
// 更新Fabric图像
|
||||
await new Promise((resolve) => {
|
||||
fabric.Image.fromURL(tempCanvas.toDataURL(), (img) => {
|
||||
// 保留原对象的属性
|
||||
img.set({
|
||||
left: this.targetObject.left,
|
||||
top: this.targetObject.top,
|
||||
scaleX: this.targetObject.scaleX,
|
||||
scaleY: this.targetObject.scaleY,
|
||||
angle: this.targetObject.angle,
|
||||
flipX: this.targetObject.flipX,
|
||||
flipY: this.targetObject.flipY,
|
||||
opacity: this.targetObject.opacity,
|
||||
});
|
||||
|
||||
// 替换Canvas上的对象
|
||||
const index = this.canvas.getObjects().indexOf(this.targetObject);
|
||||
if (index !== -1) {
|
||||
this.canvas.remove(this.targetObject);
|
||||
this.canvas.insertAt(img, index);
|
||||
this.targetObject = img;
|
||||
}
|
||||
|
||||
// 确保图层引用更新
|
||||
const layer = this.layerManager.getLayerById(this.targetLayerId);
|
||||
if (layer) {
|
||||
if (
|
||||
layer.type === "background" &&
|
||||
layer.fabricObject === this.targetObject
|
||||
) {
|
||||
layer.fabricObject = img;
|
||||
} else if (layer.fabricObjects) {
|
||||
const objIndex = layer.fabricObjects.indexOf(this.targetObject);
|
||||
if (objIndex !== -1) {
|
||||
layer.fabricObjects[objIndex] = img;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图层栅格化命令
|
||||
* 用于将复杂图层栅格化为单一图像,以便进行液化操作
|
||||
*/
|
||||
export class RasterizeForLiquifyCommand extends Command {
|
||||
/**
|
||||
* 创建栅格化命令
|
||||
* @param {Object} options 配置选项
|
||||
* @param {Object} options.canvas Fabric.js画布实例
|
||||
* @param {Object} options.layerManager 图层管理器实例
|
||||
* @param {String} options.layerId 需要栅格化的图层ID
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
name: options.name || "栅格化图层",
|
||||
description: options.description || "将图层栅格化为单一图像以便液化操作",
|
||||
});
|
||||
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.layerId = options.layerId;
|
||||
this.originalLayer = null;
|
||||
this.rasterizedImageObj = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行栅格化操作
|
||||
* @returns {Promise<Object>} 栅格化后的图像对象
|
||||
*/
|
||||
async execute() {
|
||||
if (!this.canvas || !this.layerManager || !this.layerId) {
|
||||
throw new Error("栅格化命令缺少必要参数");
|
||||
}
|
||||
|
||||
// 保存原始图层信息
|
||||
this.originalLayer = this.layerManager.getLayerById(this.layerId);
|
||||
if (!this.originalLayer) {
|
||||
throw new Error(`图层ID不存在: ${this.layerId}`);
|
||||
}
|
||||
|
||||
// 栅格化图层
|
||||
const rasterizedImage = await this.layerManager.rasterizeLayer(
|
||||
this.layerId
|
||||
);
|
||||
if (!rasterizedImage) {
|
||||
throw new Error("栅格化图层失败");
|
||||
}
|
||||
|
||||
this.rasterizedImageObj = rasterizedImage;
|
||||
return rasterizedImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销栅格化操作
|
||||
* 注意:完整撤销栅格化是复杂的,这里提供近似还原
|
||||
* @returns {Promise<boolean>} 撤销结果
|
||||
*/
|
||||
async undo() {
|
||||
if (!this.canvas || !this.layerManager || !this.originalLayer) {
|
||||
throw new Error("无法撤销:缺少必要的状态信息");
|
||||
}
|
||||
|
||||
// 恢复图层为原始状态是复杂的,这里可能需要与LayerManager协作
|
||||
// 这个实现可能需要根据实际的LayerManager功能来调整
|
||||
const restored = await this.layerManager.restoreLayerFromBackup(
|
||||
this.layerId
|
||||
);
|
||||
if (!restored) {
|
||||
console.warn("无法完全还原栅格化前的图层状态");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 液化工具初始化命令
|
||||
* 用于初始化液化工具的状态,不执行实际操作
|
||||
*/
|
||||
export class InitLiquifyToolCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "初始化液化工具",
|
||||
description: "准备液化工具工作环境",
|
||||
undoable: false, // 这个命令不需要撤销
|
||||
});
|
||||
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.liquifyManager = options.liquifyManager;
|
||||
this.toolManager = options.toolManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行初始化
|
||||
*/
|
||||
execute() {
|
||||
if (this.liquifyManager) {
|
||||
this.liquifyManager.initialize({
|
||||
canvas: this.canvas,
|
||||
layerManager: this.layerManager,
|
||||
});
|
||||
}
|
||||
|
||||
// 通知各管理器进入液化模式
|
||||
this.toolManager?.notifyObservers("LIQUIFY");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
// 不需要撤销初始化
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 液化操作命令 - 针对单次变形操作
|
||||
* 用于实现可撤销的单次液化变形
|
||||
*/
|
||||
export class LiquifyDeformCommand extends LiquifyCommand {
|
||||
/**
|
||||
* 创建液化变形命令
|
||||
* @param {Object} options 配置选项
|
||||
* @param {Number} options.x 变形中心X坐标
|
||||
* @param {Number} options.y 变形中心Y坐标
|
||||
* @param {ImageData} options.beforeData 变形前的图像数据
|
||||
* @param {ImageData} options.afterData 变形后的图像数据
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
...options,
|
||||
name: `液化变形: ${options.mode || "未知模式"}`,
|
||||
description: `在(${options.x}, ${options.y})应用${
|
||||
options.mode || "未知模式"
|
||||
}变形`,
|
||||
});
|
||||
|
||||
this.x = options.x;
|
||||
this.y = options.y;
|
||||
this.beforeData = options.beforeData;
|
||||
this.afterData = options.afterData;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.afterData) {
|
||||
// 如果没有预计算的结果,实时计算
|
||||
await this.liquifyManager.prepareForLiquify(this.targetObject);
|
||||
this.afterData = await this.liquifyManager.applyLiquify(
|
||||
this.targetObject,
|
||||
this.mode,
|
||||
this.params,
|
||||
this.x,
|
||||
this.y
|
||||
);
|
||||
}
|
||||
|
||||
// 保存当前状态
|
||||
this.savedState = await this._saveObjectState();
|
||||
|
||||
// 应用变形结果
|
||||
await this._updateObjectWithImageData(this.afterData);
|
||||
|
||||
this.canvas.renderAll();
|
||||
return this.afterData;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.beforeData) {
|
||||
throw new Error("无法撤销:缺少变形前的数据");
|
||||
}
|
||||
|
||||
// 恢复到变形前的状态
|
||||
await this._updateObjectWithImageData(this.beforeData);
|
||||
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用图像数据更新对象
|
||||
* @param {ImageData} imageData 图像数据
|
||||
* @private
|
||||
*/
|
||||
async _updateObjectWithImageData(imageData) {
|
||||
return new Promise((resolve) => {
|
||||
// 创建临时canvas
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
tempCanvas.width = imageData.width;
|
||||
tempCanvas.height = imageData.height;
|
||||
const tempCtx = tempCanvas.getContext("2d");
|
||||
tempCtx.putImageData(imageData, 0, 0);
|
||||
|
||||
// 从canvas创建新的fabric图像
|
||||
fabric.Image.fromURL(tempCanvas.toDataURL(), (img) => {
|
||||
// 保留原对象的变换属性
|
||||
img.set({
|
||||
left: this.targetObject.left,
|
||||
top: this.targetObject.top,
|
||||
scaleX: this.targetObject.scaleX,
|
||||
scaleY: this.targetObject.scaleY,
|
||||
angle: this.targetObject.angle,
|
||||
flipX: this.targetObject.flipX,
|
||||
flipY: this.targetObject.flipY,
|
||||
opacity: this.targetObject.opacity,
|
||||
});
|
||||
|
||||
// 替换canvas上的对象
|
||||
const index = this.canvas.getObjects().indexOf(this.targetObject);
|
||||
if (index !== -1) {
|
||||
this.canvas.remove(this.targetObject);
|
||||
this.canvas.insertAt(img, index);
|
||||
this.targetObject = img;
|
||||
|
||||
// 更新图层引用
|
||||
const layer = this.layerManager.getLayerById(this.targetLayerId);
|
||||
if (layer) {
|
||||
if (
|
||||
layer.type === "background" &&
|
||||
(layer.fabricObject === this.savedState?.originalObject ||
|
||||
layer.fabricObject === this.targetObject)
|
||||
) {
|
||||
layer.fabricObject = img;
|
||||
} else if (layer.fabricObjects) {
|
||||
const objIndex = layer.fabricObjects.findIndex(
|
||||
(obj) =>
|
||||
obj === this.savedState?.originalObject ||
|
||||
obj === this.targetObject
|
||||
);
|
||||
if (objIndex !== -1) {
|
||||
layer.fabricObjects[objIndex] = img;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复合液化命令 - 用于组合多个液化操作
|
||||
* 支持一次撤销多个相关的液化变形
|
||||
*/
|
||||
export class CompositeLiquifyCommand extends Command {
|
||||
/**
|
||||
* 创建复合液化命令
|
||||
* @param {Object} options 配置选项
|
||||
* @param {Array} options.commands 子命令列表
|
||||
* @param {String} options.name 命令名称
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
name: options.name || "液化操作组合",
|
||||
description:
|
||||
options.description || `包含${options.commands?.length || 0}个液化操作`,
|
||||
});
|
||||
|
||||
this.commands = options.commands || [];
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.liquifyManager = options.liquifyManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加子命令
|
||||
* @param {Command} command 要添加的命令
|
||||
*/
|
||||
addCommand(command) {
|
||||
this.commands.push(command);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
const results = [];
|
||||
|
||||
for (const command of this.commands) {
|
||||
try {
|
||||
const result = await command.execute();
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
// 如果有命令失败,尝试回滚已执行的命令
|
||||
console.error("复合液化命令执行失败:", error);
|
||||
await this.undo();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
// 逆序撤销所有子命令
|
||||
const errors = [];
|
||||
|
||||
for (let i = this.commands.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
await this.commands[i].undo();
|
||||
} catch (error) {
|
||||
console.error(`撤销子命令${i}失败:`, error);
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`复合命令撤销部分失败: ${errors.length}个错误`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查命令是否可以执行
|
||||
* @returns {Boolean} 是否可执行
|
||||
*/
|
||||
canExecute() {
|
||||
return (
|
||||
this.commands.length > 0 &&
|
||||
this.commands.every((cmd) => (cmd.canExecute ? cmd.canExecute() : true))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 液化重置命令 - 将图像恢复到原始状态
|
||||
*/
|
||||
export class LiquifyResetCommand extends LiquifyCommand {
|
||||
constructor(options) {
|
||||
super({
|
||||
...options,
|
||||
name: "重置液化",
|
||||
description: "将图像恢复到液化前的原始状态",
|
||||
});
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.liquifyManager || !this.targetObject) {
|
||||
throw new Error("无法重置:缺少必要的管理器或目标对象");
|
||||
}
|
||||
|
||||
// 保存当前状态
|
||||
this.savedState = await this._saveObjectState();
|
||||
|
||||
// 重置液化管理器
|
||||
const resetData = this.liquifyManager.reset();
|
||||
if (!resetData) {
|
||||
throw new Error("重置失败:没有原始数据");
|
||||
}
|
||||
|
||||
// 应用重置结果
|
||||
await this._updateObjectWithResult();
|
||||
|
||||
this.canvas.renderAll();
|
||||
return resetData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助函数:创建液化重置命令
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {LiquifyResetCommand} 重置命令实例
|
||||
*/
|
||||
export function createLiquifyResetCommand(options) {
|
||||
return new LiquifyResetCommand(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助函数:创建液化变形命令
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {LiquifyDeformCommand} 变形命令实例
|
||||
*/
|
||||
export function createLiquifyDeformCommand(options) {
|
||||
return new LiquifyDeformCommand(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助函数:创建复合液化命令
|
||||
* @param {Object} options 配置选项
|
||||
* @returns {CompositeLiquifyCommand} 复合命令实例
|
||||
*/
|
||||
export function createCompositeLiquifyCommand(options) {
|
||||
return new CompositeLiquifyCommand(options);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 查询类命令示例 - 不需要撤销
|
||||
*/
|
||||
export class GetCanvasInfoCommand {
|
||||
constructor(options) {
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.undoable = false; // 明确标记为不可撤销
|
||||
}
|
||||
|
||||
execute() {
|
||||
return {
|
||||
width: this.canvas.getWidth(),
|
||||
height: this.canvas.getHeight(),
|
||||
zoom: this.canvas.getZoom(),
|
||||
layers: this.layerManager?.getLayers()?.length || 0,
|
||||
objects: this.canvas.getObjects().length,
|
||||
};
|
||||
}
|
||||
|
||||
// 查询类命令不需要实现 undo 方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出类命令示例 - 不需要撤销
|
||||
*/
|
||||
export class ExportCanvasCommand {
|
||||
constructor(options) {
|
||||
this.canvas = options.canvas;
|
||||
this.format = options.format || "png";
|
||||
this.quality = options.quality || 1;
|
||||
this.undoable = false;
|
||||
}
|
||||
|
||||
execute() {
|
||||
return this.canvas.toDataURL(`image/${this.format}`, this.quality);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证类命令示例 - 不需要撤销
|
||||
*/
|
||||
export class ValidateCanvasCommand {
|
||||
constructor(options) {
|
||||
this.canvas = options.canvas;
|
||||
this.undoable = false;
|
||||
}
|
||||
|
||||
execute() {
|
||||
const objects = this.canvas.getObjects();
|
||||
const errors = [];
|
||||
|
||||
objects.forEach((obj, index) => {
|
||||
if (!obj.left || !obj.top) {
|
||||
errors.push(`对象 ${index} 位置无效`);
|
||||
}
|
||||
if (obj.width <= 0 || obj.height <= 0) {
|
||||
errors.push(`对象 ${index} 尺寸无效`);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
objectCount: objects.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统清理命令示例 - 不可逆操作,不需要撤销
|
||||
*/
|
||||
export class CleanupTempDataCommand {
|
||||
constructor(options) {
|
||||
this.canvas = options.canvas;
|
||||
this.undoable = false;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 清理临时数据
|
||||
const cleaned = [];
|
||||
|
||||
// 移除无效对象
|
||||
const objects = this.canvas.getObjects();
|
||||
objects.forEach((obj, index) => {
|
||||
if (obj._isTemp || obj._invalid) {
|
||||
this.canvas.remove(obj);
|
||||
cleaned.push(`临时对象 ${index}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 清理缓存
|
||||
if (this.canvas._clearCache) {
|
||||
this.canvas._clearCache();
|
||||
cleaned.push("画布缓存");
|
||||
}
|
||||
|
||||
return {
|
||||
cleaned,
|
||||
count: cleaned.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,942 @@
|
||||
import { OperationType } from "../utils/layerHelper";
|
||||
import { Command } from "./Command";
|
||||
import { generateId } from "../utils/helper";
|
||||
|
||||
/**
|
||||
* 设置活动图层命令
|
||||
*/
|
||||
export class SetActiveLayerCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "设置活动图层",
|
||||
saveState: false,
|
||||
});
|
||||
this.layers = options.layers;
|
||||
this.canvas = options.canvas;
|
||||
this.activeLayerId = options.activeLayerId;
|
||||
this.layerId = options.layerId;
|
||||
this.oldActiveLayerId = this.activeLayerId.value;
|
||||
this.layerManager = options.layerManager;
|
||||
this.oldActiveObjects = [];
|
||||
this.newLayer = null;
|
||||
this.editorMode = options.editorMode;
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.newLayer = this.layers.value.find(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
|
||||
if (!this.newLayer) {
|
||||
console.error(`图层 ${this.layerId} 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果是背景层,不设置为活动图层
|
||||
if (this.newLayer.isBackground) {
|
||||
console.warn("背景层不能设为活动图层");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果图层已锁定,不设置为活动图层
|
||||
if (this.newLayer.locked) {
|
||||
console.warn("锁定图层不能设为活动图层");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.oldActiveObjects = this.canvas.getActiveObjects();
|
||||
|
||||
// 设置为活动图层
|
||||
this.activeLayerId.value = this.layerId;
|
||||
|
||||
// 如果在选择模式下,取消所有选择
|
||||
if (this.editorMode === OperationType.SELECT && this.canvas) {
|
||||
this.canvas.discardActiveObject();
|
||||
|
||||
// 设置为新的图层下的对象为激活,但需要确保对象存在于画布上
|
||||
if (
|
||||
this.newLayer.fabricObjects &&
|
||||
this.newLayer.fabricObjects.length > 0
|
||||
) {
|
||||
const canvasObjects = this.canvas.getObjects();
|
||||
const validObjects = this.newLayer.fabricObjects.filter(
|
||||
(obj) => obj && canvasObjects.includes(obj)
|
||||
);
|
||||
|
||||
if (validObjects.length > 0) {
|
||||
if (validObjects.length === 1) {
|
||||
// 只有一个对象时直接设置
|
||||
this.canvas.setActiveObject(validObjects[0]);
|
||||
} else {
|
||||
// 多个对象时创建活动选择组
|
||||
const activeSelection = new fabric.ActiveSelection(validObjects, {
|
||||
canvas: this.canvas,
|
||||
});
|
||||
this.canvas.setActiveObject(activeSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
// 恢复原活动图层ID
|
||||
this.activeLayerId.value = this.oldActiveLayerId;
|
||||
|
||||
// 如果在选择模式下,恢复取消的所有选择
|
||||
if (this.editorMode === OperationType.SELECT && this.canvas) {
|
||||
this.canvas.discardActiveObject();
|
||||
|
||||
// 修复:确保对象存在于画布上才激活
|
||||
if (this.oldActiveObjects && this.oldActiveObjects.length > 0) {
|
||||
const canvasObjects = this.canvas.getObjects();
|
||||
const validObjects = this.oldActiveObjects.filter(
|
||||
(obj) => obj && canvasObjects.includes(obj)
|
||||
);
|
||||
|
||||
if (validObjects.length > 0) {
|
||||
if (validObjects.length > 1) {
|
||||
// 如果有多个对象,需要创建一个活动选择组
|
||||
const activeSelection = new fabric.ActiveSelection(validObjects, {
|
||||
canvas: this.canvas,
|
||||
});
|
||||
this.canvas.setActiveObject(activeSelection);
|
||||
} else {
|
||||
// 只有一个对象时直接设置
|
||||
this.canvas.setActiveObject(validObjects[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
layerId: this.layerId,
|
||||
oldActiveLayerId: this.oldActiveLayerId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加对象到图层命令
|
||||
*/
|
||||
export class AddObjectToLayerCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "添加对象到图层",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.layerId = options.layerId;
|
||||
this.fabricObject = options.fabricObject;
|
||||
|
||||
// 保存对象原始状态和ID
|
||||
this.objectId =
|
||||
this.fabricObject.id ||
|
||||
`obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
this.originalObjectState = this.fabricObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
]);
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 查找目标图层
|
||||
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
||||
|
||||
if (!layer) {
|
||||
console.error(`图层 ${this.layerId} 不存在`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果是背景层,不允许添加对象
|
||||
if (layer.isBackground) {
|
||||
console.warn("不能向背景层添加对象");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 为对象生成唯一ID
|
||||
this.fabricObject.id = this.objectId;
|
||||
|
||||
// 设置对象与图层的关联
|
||||
this.fabricObject.layerId = this.layerId;
|
||||
this.fabricObject.layerName = layer.name;
|
||||
|
||||
// 设置对象可操作可选择
|
||||
this.fabricObject.selectable = true;
|
||||
this.fabricObject.evented = true;
|
||||
|
||||
// 将对象添加到画布
|
||||
this.canvas.add(this.fabricObject);
|
||||
|
||||
// 将对象添加到图层的fabricObjects数组
|
||||
layer.fabricObjects = layer.fabricObjects || [];
|
||||
layer.fabricObjects.push(this.fabricObject);
|
||||
|
||||
this.canvas.discardActiveObject();
|
||||
|
||||
// 确保对象确实存在于画布上才激活
|
||||
const canvasObjects = this.canvas.getObjects();
|
||||
const validObjects = layer.fabricObjects.filter(
|
||||
(obj) => obj && canvasObjects.includes(obj)
|
||||
);
|
||||
|
||||
if (validObjects.length > 0) {
|
||||
if (validObjects.length === 1) {
|
||||
// 只有一个对象时直接设置
|
||||
this.canvas.setActiveObject(validObjects[0]);
|
||||
} else {
|
||||
// 多个对象时创建活动选择组
|
||||
const activeSelection = new fabric.ActiveSelection(validObjects, {
|
||||
canvas: this.canvas,
|
||||
});
|
||||
this.canvas.setActiveObject(activeSelection);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新画布
|
||||
this.canvas.renderAll();
|
||||
|
||||
return this.fabricObject;
|
||||
}
|
||||
|
||||
undo() {
|
||||
// 查找图层
|
||||
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
||||
|
||||
if (!layer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从图层的fabricObjects数组中移除对象
|
||||
if (layer.fabricObjects) {
|
||||
layer.fabricObjects = layer.fabricObjects.filter(
|
||||
(obj) => obj.id !== this.objectId
|
||||
);
|
||||
}
|
||||
// 从画布移除对象
|
||||
const object = this.canvas
|
||||
.getObjects()
|
||||
.find((obj) => obj.id === this.objectId);
|
||||
if (object) {
|
||||
// 先丢弃活动对象,避免控制点渲染错误
|
||||
this.canvas.discardActiveObject();
|
||||
this.canvas.remove(object);
|
||||
// 更新画布
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
layerId: this.layerId,
|
||||
objectId: this.objectId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从图层中移除对象命令
|
||||
*/
|
||||
export class RemoveObjectFromLayerCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "从图层中移除对象",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.objectId = options.objectId;
|
||||
|
||||
// 查找对象和图层
|
||||
this.object =
|
||||
typeof options.objectOrId === "object"
|
||||
? options.objectOrId
|
||||
: this.canvas.getObjects().find((obj) => obj.id === this.objectId);
|
||||
|
||||
if (this.object) {
|
||||
this.layerId = this.object.layerId;
|
||||
this.objectData = this.object.toObject(["id", "layerId", "layerName"]);
|
||||
}
|
||||
}
|
||||
|
||||
execute() {
|
||||
if (!this.object) {
|
||||
console.error(`对象 ${this.objectId} 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.layerId) {
|
||||
console.error(`对象 ${this.objectId} 未关联到任何图层`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找图层
|
||||
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
||||
if (!layer) {
|
||||
console.error(`图层 ${this.layerId} 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从画布移除对象
|
||||
this.canvas.remove(this.object);
|
||||
|
||||
// 从图层的fabricObjects数组移除对象
|
||||
if (layer.fabricObjects) {
|
||||
layer.fabricObjects = layer.fabricObjects.filter(
|
||||
(obj) => obj.id !== this.objectId
|
||||
);
|
||||
}
|
||||
|
||||
// 更新画布
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (!this.objectData || !this.layerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找图层
|
||||
const layer = this.layers.value.find((l) => l.id === this.layerId);
|
||||
if (!layer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 恢复对象到画布
|
||||
fabric.util.enlivenObjects([this.objectData], (objects) => {
|
||||
const restoredObject = objects[0];
|
||||
|
||||
// 将对象添加到画布
|
||||
this.canvas.add(restoredObject);
|
||||
|
||||
// 将对象添加回图层
|
||||
layer.fabricObjects = layer.fabricObjects || [];
|
||||
layer.fabricObjects.push(restoredObject);
|
||||
|
||||
// 更新画布
|
||||
this.canvas.renderAll();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
objectId: this.objectId,
|
||||
layerId: this.layerId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更换固定图层图像命令
|
||||
* 专门用于更换固定图层(如背景图层)的图像
|
||||
*/
|
||||
export class ChangeFixedImageCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.imageUrl = options.imageUrl;
|
||||
this.targetLayerType = options.targetLayerType || "background"; // 'background', 'fixed', etc.
|
||||
this.position = options.position || { x: 0, y: 0 };
|
||||
this.scale = options.scale || { x: 1, y: 1 };
|
||||
this.preserveTransform = options.preserveTransform !== false; // 默认保留变换
|
||||
|
||||
// 用于回滚的状态
|
||||
this.previousImage = null;
|
||||
this.previousTransform = null;
|
||||
this.targetLayer = null;
|
||||
this.isExecuted = false;
|
||||
|
||||
// 错误处理
|
||||
this.maxRetries = options.maxRetries || 3;
|
||||
this.retryCount = 0;
|
||||
this.timeoutMs = options.timeoutMs || 10000;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
try {
|
||||
this.validateInputs();
|
||||
|
||||
// 查找目标图层
|
||||
this.targetLayer = this.findTargetLayer();
|
||||
if (!this.targetLayer) {
|
||||
throw new Error(`找不到目标图层类型: ${this.targetLayerType}`);
|
||||
}
|
||||
|
||||
// 保存当前状态用于回滚
|
||||
await this.saveCurrentState();
|
||||
|
||||
// 加载新图像
|
||||
const newImage = await this.loadImageWithRetry();
|
||||
|
||||
// 应用图像到图层
|
||||
await this.applyImageToLayer(newImage);
|
||||
|
||||
this.isExecuted = true;
|
||||
|
||||
// 触发成功事件
|
||||
this.emitEvent("image:changed", {
|
||||
layerId: this.targetLayer.id,
|
||||
newImageUrl: this.imageUrl,
|
||||
command: this,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
layerId: this.targetLayer.id,
|
||||
imageUrl: this.imageUrl,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("ChangeFixedImageCommand执行失败:", error);
|
||||
|
||||
// 如果已经执行了部分操作,尝试回滚
|
||||
if (this.isExecuted) {
|
||||
try {
|
||||
await this.undo();
|
||||
} catch (rollbackError) {
|
||||
console.error("回滚失败:", rollbackError);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.isExecuted || !this.targetLayer) {
|
||||
throw new Error("命令未执行或目标图层不存在");
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.previousImage) {
|
||||
// 恢复之前的图像
|
||||
await this.restorePreviousImage();
|
||||
} else {
|
||||
// 如果没有之前的图像,移除当前图像
|
||||
await this.removeCurrentImage();
|
||||
}
|
||||
|
||||
this.isExecuted = false;
|
||||
|
||||
// 触发撤销事件
|
||||
this.emitEvent("image:reverted", {
|
||||
layerId: this.targetLayer.id,
|
||||
command: this,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: "reverted",
|
||||
layerId: this.targetLayer.id,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("ChangeFixedImageCommand撤销失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
validateInputs() {
|
||||
if (!this.canvas) throw new Error("Canvas实例是必需的");
|
||||
if (!this.layerManager) throw new Error("LayerManager实例是必需的");
|
||||
if (!this.imageUrl) throw new Error("图像URL是必需的");
|
||||
|
||||
// 验证URL格式
|
||||
try {
|
||||
new URL(this.imageUrl);
|
||||
} catch {
|
||||
throw new Error("无效的图像URL格式");
|
||||
}
|
||||
}
|
||||
|
||||
findTargetLayer() {
|
||||
const layers = this.layerManager.layers?.value || [];
|
||||
|
||||
switch (this.targetLayerType) {
|
||||
case "background":
|
||||
return layers.find((layer) => layer.isBackground);
|
||||
case "fixed":
|
||||
return layers.find((layer) => layer.isFixed);
|
||||
default:
|
||||
return layers.find((layer) => layer.type === this.targetLayerType);
|
||||
}
|
||||
}
|
||||
|
||||
async saveCurrentState() {
|
||||
if (!this.targetLayer.fabricObject) return;
|
||||
|
||||
const currentObj = this.targetLayer.fabricObject;
|
||||
|
||||
// 保存当前图像URL(如果存在)
|
||||
this.previousImage = {
|
||||
url: currentObj.getSrc ? currentObj.getSrc() : null,
|
||||
element: currentObj._element ? currentObj._element.cloneNode() : null,
|
||||
};
|
||||
|
||||
// 保存变换状态
|
||||
this.previousTransform = {
|
||||
left: currentObj.left,
|
||||
top: currentObj.top,
|
||||
scaleX: currentObj.scaleX,
|
||||
scaleY: currentObj.scaleY,
|
||||
angle: currentObj.angle,
|
||||
flipX: currentObj.flipX,
|
||||
flipY: currentObj.flipY,
|
||||
opacity: currentObj.opacity,
|
||||
};
|
||||
}
|
||||
|
||||
async loadImageWithRetry() {
|
||||
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
||||
try {
|
||||
return await this.loadImage();
|
||||
} catch (error) {
|
||||
this.retryCount = attempt;
|
||||
|
||||
if (attempt === this.maxRetries) {
|
||||
throw new Error(
|
||||
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// 指数退避重试
|
||||
const delay = Math.pow(2, attempt) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
console.warn(
|
||||
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
||||
error.message
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(
|
||||
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
||||
);
|
||||
}, this.timeoutMs);
|
||||
|
||||
fabric.Image.fromURL(
|
||||
this.imageUrl,
|
||||
(img) => {
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (!img || !img.getElement()) {
|
||||
reject(new Error("图像加载失败或无效"));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(img);
|
||||
},
|
||||
{
|
||||
crossOrigin: "anonymous",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async applyImageToLayer(newImage) {
|
||||
const currentObj = this.targetLayer.fabricObject;
|
||||
|
||||
// 设置基本属性
|
||||
newImage.set({
|
||||
id: currentObj?.id || generateId(),
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
isBackground: this.targetLayer.isBackground,
|
||||
isFixed: this.targetLayer.isFixed,
|
||||
});
|
||||
|
||||
// 应用位置和变换
|
||||
if (this.preserveTransform && this.previousTransform) {
|
||||
newImage.set(this.previousTransform);
|
||||
} else {
|
||||
newImage.set({
|
||||
left: this.position.x,
|
||||
top: this.position.y,
|
||||
scaleX: this.scale.x,
|
||||
scaleY: this.scale.y,
|
||||
});
|
||||
}
|
||||
|
||||
// 移除旧对象(如果存在)
|
||||
if (currentObj) {
|
||||
this.canvas.remove(currentObj);
|
||||
}
|
||||
|
||||
// 添加新图像
|
||||
this.canvas.add(newImage);
|
||||
newImage.setCoords();
|
||||
|
||||
// 更新图层引用
|
||||
this.targetLayer.fabricObject = newImage;
|
||||
|
||||
// 更新图层管理器
|
||||
this.layerManager.updateLayerObject(this.targetLayer.id, newImage);
|
||||
|
||||
// 重新渲染画布
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
async restorePreviousImage() {
|
||||
if (!this.previousImage.url) return;
|
||||
|
||||
const restoredImage = await this.loadImageFromUrl(this.previousImage.url);
|
||||
|
||||
// 恢复之前的变换
|
||||
if (this.previousTransform) {
|
||||
restoredImage.set(this.previousTransform);
|
||||
}
|
||||
|
||||
// 设置图层属性
|
||||
restoredImage.set({
|
||||
id: this.targetLayer.fabricObject?.id || generateId(),
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
isBackground: this.targetLayer.isBackground,
|
||||
isFixed: this.targetLayer.isFixed,
|
||||
});
|
||||
|
||||
// 替换当前对象
|
||||
if (this.targetLayer.fabricObject) {
|
||||
this.canvas.remove(this.targetLayer.fabricObject);
|
||||
}
|
||||
|
||||
this.canvas.add(restoredImage);
|
||||
restoredImage.setCoords();
|
||||
|
||||
// 更新引用
|
||||
this.targetLayer.fabricObject = restoredImage;
|
||||
this.layerManager.updateLayerObject(this.targetLayer.id, restoredImage);
|
||||
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
async removeCurrentImage() {
|
||||
if (this.targetLayer.fabricObject) {
|
||||
this.canvas.remove(this.targetLayer.fabricObject);
|
||||
this.targetLayer.fabricObject = null;
|
||||
this.layerManager.updateLayerObject(this.targetLayer.id, null);
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
loadImageFromUrl(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fabric.Image.fromURL(
|
||||
url,
|
||||
(img) => {
|
||||
if (!img || !img.getElement()) {
|
||||
reject(new Error("恢复图像加载失败"));
|
||||
return;
|
||||
}
|
||||
resolve(img);
|
||||
},
|
||||
{ crossOrigin: "anonymous" }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
emitEvent(eventName, data) {
|
||||
if (this.canvas && this.canvas.fire) {
|
||||
this.canvas.fire(eventName, data);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取命令信息用于调试
|
||||
getCommandInfo() {
|
||||
return {
|
||||
type: "ChangeFixedImageCommand",
|
||||
targetLayerType: this.targetLayerType,
|
||||
imageUrl: this.imageUrl,
|
||||
isExecuted: this.isExecuted,
|
||||
retryCount: this.retryCount,
|
||||
targetLayerId: this.targetLayer?.id,
|
||||
preserveTransform: this.preserveTransform,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向图层添加图像命令
|
||||
* 用于向指定图层添加新的图像对象
|
||||
*/
|
||||
export class AddImageToLayerCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.imageUrl = options.imageUrl;
|
||||
this.layerId = options.layerId;
|
||||
this.position = options.position || { x: 100, y: 100 };
|
||||
this.scale = options.scale || { x: 1, y: 1 };
|
||||
this.zIndex = options.zIndex || null; // 可选的层级控制
|
||||
|
||||
// 用于回滚的状态
|
||||
this.addedObject = null;
|
||||
this.targetLayer = null;
|
||||
this.isExecuted = false;
|
||||
|
||||
// 错误处理
|
||||
this.maxRetries = options.maxRetries || 3;
|
||||
this.retryCount = 0;
|
||||
this.timeoutMs = options.timeoutMs || 10000;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
try {
|
||||
this.validateInputs();
|
||||
|
||||
// 查找目标图层
|
||||
this.targetLayer = this.findTargetLayer();
|
||||
if (!this.targetLayer) {
|
||||
throw new Error(`找不到目标图层: ${this.layerId}`);
|
||||
}
|
||||
|
||||
// 检查图层是否可编辑
|
||||
this.validateLayerEditability();
|
||||
|
||||
// 加载新图像
|
||||
const newImage = await this.loadImageWithRetry();
|
||||
|
||||
// 添加图像到图层
|
||||
await this.addImageToLayer(newImage);
|
||||
|
||||
this.isExecuted = true;
|
||||
|
||||
// 触发成功事件
|
||||
this.emitEvent("image:added", {
|
||||
layerId: this.layerId,
|
||||
objectId: this.addedObject.id,
|
||||
imageUrl: this.imageUrl,
|
||||
command: this,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
layerId: this.layerId,
|
||||
objectId: this.addedObject.id,
|
||||
imageUrl: this.imageUrl,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("AddImageToLayerCommand执行失败:", error);
|
||||
|
||||
// 如果已经添加了对象,尝试移除
|
||||
if (this.addedObject) {
|
||||
try {
|
||||
await this.undo();
|
||||
} catch (rollbackError) {
|
||||
console.error("回滚失败:", rollbackError);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.isExecuted || !this.addedObject) {
|
||||
throw new Error("命令未执行或没有添加的对象");
|
||||
}
|
||||
|
||||
try {
|
||||
// 移除添加的对象
|
||||
this.canvas.remove(this.addedObject);
|
||||
|
||||
// 从图层管理器中移除
|
||||
this.layerManager.removeObjectFromLayer(
|
||||
this.addedObject.id,
|
||||
this.layerId
|
||||
);
|
||||
|
||||
this.isExecuted = false;
|
||||
|
||||
// 触发撤销事件
|
||||
this.emitEvent("image:removed", {
|
||||
layerId: this.layerId,
|
||||
objectId: this.addedObject.id,
|
||||
command: this,
|
||||
});
|
||||
|
||||
// 重新渲染
|
||||
this.canvas.renderAll();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: "removed",
|
||||
layerId: this.layerId,
|
||||
objectId: this.addedObject.id,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("AddImageToLayerCommand撤销失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
validateInputs() {
|
||||
if (!this.canvas) throw new Error("Canvas实例是必需的");
|
||||
if (!this.layerManager) throw new Error("LayerManager实例是必需的");
|
||||
if (!this.imageUrl) throw new Error("图像URL是必需的");
|
||||
if (!this.layerId) throw new Error("图层ID是必需的");
|
||||
|
||||
// 验证URL格式
|
||||
try {
|
||||
new URL(this.imageUrl);
|
||||
} catch {
|
||||
throw new Error("无效的图像URL格式");
|
||||
}
|
||||
}
|
||||
|
||||
findTargetLayer() {
|
||||
const layers = this.layerManager.layers?.value || [];
|
||||
return layers.find((layer) => layer.id === this.layerId);
|
||||
}
|
||||
|
||||
validateLayerEditability() {
|
||||
if (this.targetLayer.locked) {
|
||||
throw new Error("目标图层已锁定,无法添加对象");
|
||||
}
|
||||
|
||||
if (!this.targetLayer.visible) {
|
||||
console.warn("目标图层不可见,添加的对象可能不会显示");
|
||||
}
|
||||
}
|
||||
|
||||
async loadImageWithRetry() {
|
||||
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
||||
try {
|
||||
return await this.loadImage();
|
||||
} catch (error) {
|
||||
this.retryCount = attempt;
|
||||
|
||||
if (attempt === this.maxRetries) {
|
||||
throw new Error(
|
||||
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// 指数退避重试
|
||||
const delay = Math.pow(2, attempt) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
console.warn(
|
||||
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
||||
error.message
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(
|
||||
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
||||
);
|
||||
}, this.timeoutMs);
|
||||
|
||||
fabric.Image.fromURL(
|
||||
this.imageUrl,
|
||||
(img) => {
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (!img || !img.getElement()) {
|
||||
reject(new Error("图像加载失败或无效"));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(img);
|
||||
},
|
||||
{
|
||||
crossOrigin: "anonymous",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async addImageToLayer(newImage) {
|
||||
// 生成唯一ID
|
||||
const objectId = generateId();
|
||||
|
||||
// 设置图像属性
|
||||
newImage.set({
|
||||
id: objectId,
|
||||
layerId: this.layerId,
|
||||
layerName: this.targetLayer.name,
|
||||
left: this.position.x,
|
||||
top: this.position.y,
|
||||
scaleX: this.scale.x,
|
||||
scaleY: this.scale.y,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
});
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(newImage);
|
||||
|
||||
// 设置层级
|
||||
if (this.zIndex !== null) {
|
||||
this.setObjectZIndex(newImage, this.zIndex);
|
||||
}
|
||||
|
||||
newImage.setCoords();
|
||||
|
||||
// 保存引用用于回滚
|
||||
this.addedObject = newImage;
|
||||
|
||||
// 添加到图层管理器
|
||||
this.layerManager.addObjectToLayer(newImage, this.layerId);
|
||||
|
||||
// 重新渲染画布
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
setObjectZIndex(object, zIndex) {
|
||||
if (zIndex === "top") {
|
||||
object.bringToFront();
|
||||
} else if (zIndex === "bottom") {
|
||||
object.sendToBack();
|
||||
} else if (typeof zIndex === "number") {
|
||||
object.moveTo(zIndex);
|
||||
}
|
||||
}
|
||||
|
||||
emitEvent(eventName, data) {
|
||||
if (this.canvas && this.canvas.fire) {
|
||||
this.canvas.fire(eventName, data);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取命令信息用于调试
|
||||
getCommandInfo() {
|
||||
return {
|
||||
type: "AddImageToLayerCommand",
|
||||
layerId: this.layerId,
|
||||
imageUrl: this.imageUrl,
|
||||
position: this.position,
|
||||
scale: this.scale,
|
||||
isExecuted: this.isExecuted,
|
||||
retryCount: this.retryCount,
|
||||
addedObjectId: this.addedObject?.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
379
src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js
Normal file
379
src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js
Normal file
@@ -0,0 +1,379 @@
|
||||
import { OperationType } from "../utils/layerHelper.js";
|
||||
import { Command, CompositeCommand } from "./Command.js";
|
||||
//import { fabric } from "fabric-with-all";
|
||||
|
||||
/**
|
||||
* 批量初始化红绿图模式命令
|
||||
* 将衣服底图添加到背景层、红绿图添加到固定图层、调整位置和大小,以及设置画布背景为白色等操作合并到一个命令中
|
||||
* 减少页面闪烁,一次性渲染完成
|
||||
*/
|
||||
export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "批量初始化红绿图模式",
|
||||
description: "一次性完成红绿图模式的所有初始化操作",
|
||||
});
|
||||
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.toolManager = options.toolManager;
|
||||
this.clothingImageUrl = options.clothingImageUrl;
|
||||
this.redGreenImageUrl = options.redGreenImageUrl;
|
||||
this.onImageGenerated = options.onImageGenerated;
|
||||
this.normalLayerOpacity = options.normalLayerOpacity || 0.4;
|
||||
|
||||
// 存储原始状态以便撤销
|
||||
this.originalCanvasBackground = null;
|
||||
this.originalBackgroundObject = null;
|
||||
this.originalFixedObjects = null;
|
||||
this.originalNormalObjects = null;
|
||||
this.originalNormalOpacities = new Map();
|
||||
this.originalToolState = null;
|
||||
|
||||
// 存储加载的图片对象
|
||||
this.clothingImage = null;
|
||||
this.redGreenImage = null;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
try {
|
||||
// 禁用画布渲染以避免闪烁
|
||||
this.canvas.renderOnAddRemove = false;
|
||||
|
||||
// 1. 设置画布背景为白色
|
||||
this.originalCanvasBackground = this.canvas.backgroundColor;
|
||||
this.canvas.setBackgroundColor('#ffffff', () => {});
|
||||
|
||||
// 2. 查找图层结构
|
||||
const layers = this.layerManager.layers?.value || [];
|
||||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||||
const normalLayers = layers.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
|
||||
if (!backgroundLayer || !fixedLayer || normalLayers.length === 0) {
|
||||
throw new Error("缺少必要的图层结构");
|
||||
}
|
||||
|
||||
const normalLayer = normalLayers[0]; // 使用第一个普通图层
|
||||
|
||||
// 3. 保存原始状态
|
||||
this.originalBackgroundObject = backgroundLayer.fabricObject ? {
|
||||
...backgroundLayer.fabricObject.toObject(),
|
||||
ref: backgroundLayer.fabricObject
|
||||
} : null;
|
||||
|
||||
this.originalFixedObjects = fixedLayer.fabricObject
|
||||
? [fixedLayer.fabricObject]
|
||||
: [];
|
||||
|
||||
this.originalNormalObjects = normalLayer.fabricObjects
|
||||
? [...normalLayer.fabricObjects]
|
||||
: [];
|
||||
|
||||
// 保存普通图层透明度
|
||||
normalLayers.forEach((layer) => {
|
||||
this.originalNormalOpacities.set(layer.id, layer.opacity || 1);
|
||||
if (layer.fabricObjects) {
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
this.originalNormalOpacities.set(
|
||||
`${layer.id}_${obj.id || "unknown"}`,
|
||||
obj.opacity || 1
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 保存工具状态
|
||||
if (this.toolManager) {
|
||||
this.originalToolState = {
|
||||
currentTool: this.toolManager.getCurrentTool(),
|
||||
isRedGreenMode: this.toolManager.isRedGreenMode,
|
||||
};
|
||||
}
|
||||
|
||||
// 4. 确保背景图层大小正确
|
||||
await this._setupBackgroundLayer(backgroundLayer);
|
||||
|
||||
// 5. 并行加载两个图片
|
||||
const [clothingImg, redGreenImg] = await Promise.all([
|
||||
this._loadImage(this.clothingImageUrl),
|
||||
this._loadImage(this.redGreenImageUrl)
|
||||
]);
|
||||
|
||||
// 6. 设置衣服底图到固定图层
|
||||
await this._setupClothingImage(clothingImg, fixedLayer);
|
||||
|
||||
// 7. 设置红绿图到普通图层,位置和大小与衣服底图一致
|
||||
await this._setupRedGreenImage(redGreenImg, normalLayer, this.clothingImage);
|
||||
|
||||
// 8. 设置普通图层透明度
|
||||
this._setupNormalLayerOpacity(normalLayers);
|
||||
|
||||
// 9. 配置工具管理器
|
||||
this._setupToolManager();
|
||||
|
||||
// 10. 重新启用渲染并执行一次性渲染
|
||||
this.canvas.renderOnAddRemove = true;
|
||||
this.canvas.renderAll();
|
||||
|
||||
console.log("批量红绿图模式初始化完成", {
|
||||
衣服底图: this.clothingImageUrl,
|
||||
红绿图: this.redGreenImageUrl,
|
||||
普通图层透明度: `${Math.round(this.normalLayerOpacity * 100)}%`,
|
||||
画布背景: "白色",
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
// 恢复渲染
|
||||
this.canvas.renderOnAddRemove = true;
|
||||
console.error("批量红绿图模式初始化失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async undo() {
|
||||
try {
|
||||
// 禁用渲染
|
||||
this.canvas.renderOnAddRemove = false;
|
||||
|
||||
// 1. 恢复画布背景
|
||||
if (this.originalCanvasBackground !== null) {
|
||||
this.canvas.setBackgroundColor(this.originalCanvasBackground, () => {});
|
||||
}
|
||||
|
||||
// 2. 恢复图层对象
|
||||
const layers = this.layerManager.layers?.value || [];
|
||||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||||
const normalLayers = layers.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
|
||||
// 移除当前添加的对象
|
||||
if (this.clothingImage) {
|
||||
this.canvas.remove(this.clothingImage);
|
||||
}
|
||||
if (this.redGreenImage) {
|
||||
this.canvas.remove(this.redGreenImage);
|
||||
}
|
||||
|
||||
// 恢复背景图层
|
||||
if (backgroundLayer && this.originalBackgroundObject) {
|
||||
if (this.originalBackgroundObject.ref) {
|
||||
backgroundLayer.fabricObject = this.originalBackgroundObject.ref;
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复固定图层
|
||||
if (fixedLayer) {
|
||||
fixedLayer.fabricObject = this.originalFixedObjects.length > 0
|
||||
? this.originalFixedObjects[0]
|
||||
: null;
|
||||
|
||||
if (fixedLayer.fabricObject) {
|
||||
this.canvas.add(fixedLayer.fabricObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复普通图层
|
||||
if (normalLayers.length > 0) {
|
||||
const normalLayer = normalLayers[0];
|
||||
normalLayer.fabricObjects = [...this.originalNormalObjects];
|
||||
this.originalNormalObjects.forEach((obj) => {
|
||||
this.canvas.add(obj);
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 恢复透明度
|
||||
normalLayers.forEach((layer) => {
|
||||
if (this.originalNormalOpacities.has(layer.id)) {
|
||||
layer.opacity = this.originalNormalOpacities.get(layer.id);
|
||||
}
|
||||
|
||||
if (layer.fabricObjects) {
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
const key = `${layer.id}_${obj.id || "unknown"}`;
|
||||
if (this.originalNormalOpacities.has(key)) {
|
||||
obj.opacity = this.originalNormalOpacities.get(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 恢复工具状态
|
||||
if (this.toolManager && this.originalToolState) {
|
||||
this.toolManager.isRedGreenMode = this.originalToolState.isRedGreenMode;
|
||||
if (this.originalToolState.currentTool) {
|
||||
this.toolManager.setTool(this.originalToolState.currentTool);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 重新启用渲染
|
||||
this.canvas.renderOnAddRemove = true;
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.canvas.renderOnAddRemove = true;
|
||||
console.error("撤销批量红绿图模式初始化失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置背景图层
|
||||
*/
|
||||
async _setupBackgroundLayer(backgroundLayer) {
|
||||
let backgroundObject = backgroundLayer.fabricObject;
|
||||
|
||||
if (!backgroundObject) {
|
||||
// 创建白色背景矩形
|
||||
backgroundObject = new fabric.Rect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: this.canvas.width,
|
||||
height: this.canvas.height,
|
||||
fill: "#ffffff",
|
||||
selectable: false,
|
||||
evented: false,
|
||||
isBackground: true,
|
||||
layerId: backgroundLayer.id,
|
||||
layerName: backgroundLayer.name,
|
||||
});
|
||||
|
||||
this.canvas.add(backgroundObject);
|
||||
this.canvas.sendToBack(backgroundObject);
|
||||
backgroundLayer.fabricObject = backgroundObject;
|
||||
} else {
|
||||
// 更新现有背景对象大小
|
||||
backgroundObject.set({
|
||||
width: this.canvas.width,
|
||||
height: this.canvas.height,
|
||||
left: 0,
|
||||
top: 0,
|
||||
fill: "#ffffff", // 确保背景是白色
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载图片
|
||||
*/
|
||||
async _loadImage(imageUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fabric.Image.fromURL(
|
||||
imageUrl,
|
||||
(img) => {
|
||||
if (!img) {
|
||||
reject(new Error(`无法加载图片: ${imageUrl}`));
|
||||
return;
|
||||
}
|
||||
resolve(img);
|
||||
},
|
||||
{ crossOrigin: "anonymous" }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置衣服底图
|
||||
*/
|
||||
async _setupClothingImage(img, fixedLayer) {
|
||||
// 计算图片缩放,保持上下留边距
|
||||
const margin = 50;
|
||||
const maxWidth = this.canvas.width - margin * 2;
|
||||
const maxHeight = this.canvas.height - margin * 2;
|
||||
const scale = Math.min(maxWidth / img.width, maxHeight / img.height);
|
||||
|
||||
img.set({
|
||||
scaleX: scale,
|
||||
scaleY: scale,
|
||||
left: this.canvas.width / 2,
|
||||
top: this.canvas.height / 2,
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
selectable: false,
|
||||
evented: false,
|
||||
layerId: fixedLayer.id,
|
||||
layerName: fixedLayer.name,
|
||||
});
|
||||
|
||||
// 清除固定图层原有内容
|
||||
if (fixedLayer.fabricObject) {
|
||||
this.canvas.remove(fixedLayer.fabricObject);
|
||||
}
|
||||
|
||||
// 添加到画布和固定图层
|
||||
this.canvas.add(img);
|
||||
fixedLayer.fabricObject = img;
|
||||
this.clothingImage = img;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置红绿图
|
||||
*/
|
||||
async _setupRedGreenImage(img, normalLayer, clothingImage) {
|
||||
if (!clothingImage) {
|
||||
throw new Error("衣服底图未加载,无法设置红绿图位置");
|
||||
}
|
||||
|
||||
// 使用与衣服底图完全相同的属性
|
||||
img.set({
|
||||
scaleX: clothingImage.scaleX,
|
||||
scaleY: clothingImage.scaleY,
|
||||
left: clothingImage.left,
|
||||
top: clothingImage.top,
|
||||
originX: clothingImage.originX,
|
||||
originY: clothingImage.originY,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
layerId: normalLayer.id,
|
||||
layerName: normalLayer.name,
|
||||
});
|
||||
|
||||
// 清除普通图层原有内容
|
||||
if (normalLayer.fabricObjects) {
|
||||
normalLayer.fabricObjects.forEach((obj) => {
|
||||
this.canvas.remove(obj);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加到画布和普通图层
|
||||
this.canvas.add(img);
|
||||
normalLayer.fabricObjects = [img];
|
||||
this.redGreenImage = img;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置普通图层透明度
|
||||
*/
|
||||
_setupNormalLayerOpacity(normalLayers) {
|
||||
normalLayers.forEach((layer) => {
|
||||
// 设置图层透明度
|
||||
layer.opacity = this.normalLayerOpacity;
|
||||
|
||||
// 更新图层中所有对象的透明度
|
||||
if (layer.fabricObjects) {
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
obj.opacity = this.normalLayerOpacity;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具管理器
|
||||
*/
|
||||
_setupToolManager() {
|
||||
if (this.toolManager) {
|
||||
// 设置红绿图模式
|
||||
this.toolManager.isRedGreenMode = true;
|
||||
|
||||
// 切换到红色笔刷工具
|
||||
this.toolManager.setTool(OperationType.RED_BRUSH);
|
||||
}
|
||||
}
|
||||
}
|
||||
578
src/component/Canvas/CanvasEditor/commands/SelectionCommands.js
Normal file
578
src/component/Canvas/CanvasEditor/commands/SelectionCommands.js
Normal file
@@ -0,0 +1,578 @@
|
||||
import { Command, CompositeCommand } from "./Command.js";
|
||||
//import { fabric } from "fabric-with-all";
|
||||
import { createLayer, LayerType } from "../utils/layerHelper.js";
|
||||
|
||||
/**
|
||||
* 创建选区命令
|
||||
*/
|
||||
export class CreateSelectionCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: options.name || "创建选区",
|
||||
description: "在画布上创建选区",
|
||||
saveState: false,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.selectionObject = options.selectionObject;
|
||||
this.selectionType = options.selectionType || "rectangle";
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.selectionManager || !this.selectionObject) {
|
||||
console.error("无法创建选区:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将选择对象添加到选区管理器
|
||||
this.selectionManager.setSelectionObject(this.selectionObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.selectionManager) return false;
|
||||
this.selectionManager.clearSelection();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反转选区命令
|
||||
*/
|
||||
export class InvertSelectionCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "反转选区",
|
||||
description: "反转当前选区",
|
||||
saveState: false,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.originalSelection = options.selectionManager
|
||||
? options.selectionManager.getSelectionPath()
|
||||
: null;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.selectionManager) {
|
||||
console.error("无法反转选区:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存原始选区
|
||||
if (!this.originalSelection) {
|
||||
this.originalSelection = this.selectionManager.getSelectionPath();
|
||||
}
|
||||
|
||||
// 反转选区
|
||||
const result = await this.selectionManager.invertSelection();
|
||||
return result;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.selectionManager || !this.originalSelection) return false;
|
||||
|
||||
// 恢复原始选区
|
||||
this.selectionManager.setSelectionFromPath(this.originalSelection);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到选区命令
|
||||
*/
|
||||
export class AddToSelectionCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "添加到选区",
|
||||
description: "将新的选区添加到现有选区",
|
||||
saveState: false,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.newSelection = options.newSelection;
|
||||
this.originalSelection = options.selectionManager
|
||||
? options.selectionManager.getSelectionPath()
|
||||
: null;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.selectionManager || !this.newSelection) {
|
||||
console.error("无法添加到选区:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存原始选区
|
||||
if (!this.originalSelection) {
|
||||
this.originalSelection = this.selectionManager.getSelectionPath();
|
||||
}
|
||||
|
||||
// 添加到选区
|
||||
const result = await this.selectionManager.addToSelection(
|
||||
this.newSelection
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.selectionManager || !this.originalSelection) return false;
|
||||
|
||||
// 恢复原始选区
|
||||
this.selectionManager.setSelectionFromPath(this.originalSelection);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从选区中移除命令
|
||||
*/
|
||||
export class RemoveFromSelectionCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "从选区中移除",
|
||||
description: "从现有选区中移除指定区域",
|
||||
saveState: false,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.removeSelection = options.removeSelection;
|
||||
this.originalSelection = options.selectionManager
|
||||
? options.selectionManager.getSelectionPath()
|
||||
: null;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.selectionManager || !this.removeSelection) {
|
||||
console.error("无法从选区中移除:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存原始选区
|
||||
if (!this.originalSelection) {
|
||||
this.originalSelection = this.selectionManager.getSelectionPath();
|
||||
}
|
||||
|
||||
// 从选区中移除
|
||||
const result = await this.selectionManager.removeFromSelection(
|
||||
this.removeSelection
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.selectionManager || !this.originalSelection) return false;
|
||||
|
||||
// 恢复原始选区
|
||||
this.selectionManager.setSelectionFromPath(this.originalSelection);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除选区命令
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 羽化选区命令
|
||||
*/
|
||||
export class FeatherSelectionCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "羽化选区",
|
||||
description: "对当前选区应用羽化效果",
|
||||
saveState: false,
|
||||
});
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.featherAmount = options.featherAmount || 5;
|
||||
this.originalSelection = options.selectionManager
|
||||
? options.selectionManager.getSelectionPath()
|
||||
: null;
|
||||
this.originalFeatherAmount = options.selectionManager
|
||||
? options.selectionManager.getFeatherAmount()
|
||||
: 0;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.selectionManager) {
|
||||
console.error("无法羽化选区:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存原始选区和羽化值
|
||||
if (!this.originalSelection) {
|
||||
this.originalSelection = this.selectionManager.getSelectionPath();
|
||||
this.originalFeatherAmount = this.selectionManager.getFeatherAmount();
|
||||
}
|
||||
|
||||
// 应用羽化
|
||||
const result = await this.selectionManager.featherSelection(
|
||||
this.featherAmount
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.selectionManager || !this.originalSelection) return false;
|
||||
|
||||
// 恢复原始选区和羽化值
|
||||
this.selectionManager.setSelectionFromPath(this.originalSelection);
|
||||
this.selectionManager.setFeatherAmount(this.originalFeatherAmount);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充选区命令
|
||||
*/
|
||||
export class FillSelectionCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "填充选区",
|
||||
description: "使用指定颜色填充当前选区",
|
||||
saveState: false,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.color = options.color || "#000000";
|
||||
this.targetLayerId = options.targetLayerId;
|
||||
this.createdObjectIds = [];
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.layerManager || !this.selectionManager) {
|
||||
console.error("无法填充选区:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取选区路径
|
||||
const selectionPath = this.selectionManager.getSelectionObject();
|
||||
if (!selectionPath) {
|
||||
console.error("无法填充选区:当前没有选区");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确定目标图层
|
||||
const layerId = this.targetLayerId || this.layerManager.getActiveLayerId();
|
||||
if (!layerId) {
|
||||
console.error("无法填充选区:没有活动图层");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建填充对象
|
||||
const fillObject = new fabric.Path(selectionPath.path, {
|
||||
fill: this.color,
|
||||
stroke: null,
|
||||
opacity: 1,
|
||||
id: `fill_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
|
||||
layerId: layerId,
|
||||
selectable: false,
|
||||
});
|
||||
|
||||
// 应用羽化效果(如果有)
|
||||
const featherAmount = this.selectionManager.getFeatherAmount();
|
||||
if (featherAmount > 0) {
|
||||
fillObject.shadow = new fabric.Shadow({
|
||||
color: this.color,
|
||||
blur: featherAmount,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// 添加到图层
|
||||
this.layerManager.addObjectToLayer(layerId, fillObject);
|
||||
this.createdObjectIds.push(fillObject.id);
|
||||
|
||||
// 清空选区
|
||||
this.selectionManager.clearSelection();
|
||||
return true;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.layerManager || this.createdObjectIds.length === 0) return false;
|
||||
|
||||
// 移除创建的填充对象
|
||||
for (const id of this.createdObjectIds) {
|
||||
const layerObj = this._findObjectInLayers(id);
|
||||
if (layerObj) {
|
||||
this.layerManager.removeObjectFromLayer(layerObj.layerId, id);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_findObjectInLayers(objectId) {
|
||||
if (!this.layerManager) return null;
|
||||
|
||||
const layers = this.layerManager.layers.value;
|
||||
for (const layer of layers) {
|
||||
if (layer.fabricObjects) {
|
||||
const obj = layer.fabricObjects.find((obj) => obj.id === objectId);
|
||||
if (obj) return { object: obj, layerId: layer.id };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制选区内容到新图层命令
|
||||
*/
|
||||
export class CopySelectionToNewLayerCommand extends CompositeCommand {
|
||||
constructor(options = {}) {
|
||||
super([], {
|
||||
name: "复制选区到新图层",
|
||||
description: "将选区中的内容复制到新图层",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.sourceLayerId = options.sourceLayerId;
|
||||
this.newLayerName = options.newLayerName || "选区复制";
|
||||
this.newLayerId = null;
|
||||
this.copiedObjectIds = [];
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.layerManager || !this.selectionManager) {
|
||||
console.error("无法复制选区:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取选区
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (!selectionObject) {
|
||||
console.error("无法复制选区:当前没有选区");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确定源图层
|
||||
const sourceId =
|
||||
this.sourceLayerId || this.layerManager.getActiveLayerId();
|
||||
const sourceLayer = this.layerManager.getLayerById(sourceId);
|
||||
if (!sourceLayer || !sourceLayer.fabricObjects) {
|
||||
console.error("无法复制选区:源图层无效或为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建新图层
|
||||
this.newLayerId = await this.layerManager.createLayer(
|
||||
this.newLayerName,
|
||||
LayerType.EMPTY
|
||||
);
|
||||
|
||||
// 获取选区内的对象
|
||||
const objectsToCopy = sourceLayer.fabricObjects.filter((obj) => {
|
||||
return this.selectionManager.isObjectInSelection(obj);
|
||||
});
|
||||
|
||||
if (objectsToCopy.length === 0) {
|
||||
console.warn("选区内没有对象可复制");
|
||||
return true; // 仍然返回成功,因为已创建了新图层
|
||||
}
|
||||
|
||||
// 复制对象到新图层
|
||||
for (const obj of objectsToCopy) {
|
||||
// 克隆对象
|
||||
const clonedObj = await this._cloneObject(obj);
|
||||
// 设置新的ID和图层ID
|
||||
const newId = `copy_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
clonedObj.id = newId;
|
||||
clonedObj.layerId = this.newLayerId;
|
||||
|
||||
// 添加到新图层
|
||||
await this.layerManager.addObjectToLayer(this.newLayerId, clonedObj);
|
||||
this.copiedObjectIds.push(newId);
|
||||
}
|
||||
|
||||
// 设置新图层为活动图层
|
||||
this.layerManager.setActiveLayer(this.newLayerId);
|
||||
|
||||
// 清空选区
|
||||
this.selectionManager.clearSelection();
|
||||
|
||||
return {
|
||||
newLayerId: this.newLayerId,
|
||||
copiedCount: this.copiedObjectIds.length,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("复制选区过程中出错:", error);
|
||||
|
||||
// 如果已经创建了新图层,需要进行清理
|
||||
if (this.newLayerId) {
|
||||
try {
|
||||
await this.layerManager.removeLayer(this.newLayerId);
|
||||
} catch (cleanupError) {
|
||||
console.warn("清理新图层失败:", cleanupError);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async _cloneObject(obj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!obj) {
|
||||
reject(new Error("对象无效,无法克隆"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
obj.clone((cloned) => {
|
||||
resolve(cloned);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从选区中删除内容命令
|
||||
*/
|
||||
export class ClearSelectionContentCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "清除选区内容",
|
||||
description: "删除选区中的内容",
|
||||
saveState: false,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.targetLayerId = options.targetLayerId;
|
||||
this.removedObjects = [];
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.layerManager || !this.selectionManager) {
|
||||
console.error("无法清除选区内容:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取选区
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (!selectionObject) {
|
||||
console.error("无法清除选区内容:当前没有选区");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确定目标图层
|
||||
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;
|
||||
|
||||
// 恢复被删除的对象
|
||||
for (const item of this.removedObjects) {
|
||||
if (item.object && item.layerId) {
|
||||
const clonedObj = await this._cloneObject(item.object);
|
||||
this.layerManager.addObjectToLayer(item.layerId, clonedObj);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_cloneObjectSync(obj) {
|
||||
// 这是一个简单的深拷贝,不适用于所有场景
|
||||
// 在实际应用中,应该使用fabric.js的clone方法
|
||||
if (!obj) return null;
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
async _cloneObject(obj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!obj) {
|
||||
reject(new Error("对象无效,无法克隆"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof obj.clone === "function") {
|
||||
obj.clone((cloned) => {
|
||||
resolve(cloned);
|
||||
});
|
||||
} else {
|
||||
// 如果对象没有clone方法(可能是因为它已经是序列化后的对象)
|
||||
// 在实际代码中需要适当处理这种情况
|
||||
resolve(Object.assign({}, obj));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 导入套索抠图命令
|
||||
export { LassoCutoutCommand } from "./LassoCutoutCommand.js";
|
||||
134
src/component/Canvas/CanvasEditor/commands/StateCommands.js
Normal file
134
src/component/Canvas/CanvasEditor/commands/StateCommands.js
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Command } from "./Command";
|
||||
|
||||
/**
|
||||
* 对象变换命令
|
||||
* 轻量级命令,只记录对象的变换属性变化(位置、缩放、旋转)
|
||||
* 不保存整个对象或画布状态,只关注变换属性
|
||||
*/
|
||||
export class TransformCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: options.name || "对象变换",
|
||||
description: options.description || "移动、缩放或旋转对象",
|
||||
saveState: false, // 自己管理状态,避免递归
|
||||
});
|
||||
|
||||
this.canvas = options.canvas;
|
||||
this.objectId = options.objectId;
|
||||
this.initialState = options.initialState || null;
|
||||
this.finalState = options.finalState || null;
|
||||
this.objectType = options.objectType || "object";
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* 如果是首次执行,记录初始和最终状态
|
||||
* 如果是重做,应用最终状态
|
||||
*/
|
||||
execute() {
|
||||
if (!this.finalState) {
|
||||
console.warn("没有最终状态可应用");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找目标对象
|
||||
const targetObject = this._findObject(this.objectId);
|
||||
if (!targetObject) {
|
||||
console.warn(`未找到ID为 ${this.objectId} 的对象`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 应用最终变换状态
|
||||
this._applyTransform(targetObject, this.finalState);
|
||||
|
||||
// 触发画布更新
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销命令
|
||||
* 应用初始状态
|
||||
*/
|
||||
undo() {
|
||||
if (!this.initialState) {
|
||||
console.warn("没有初始状态可恢复");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找目标对象
|
||||
const targetObject = this._findObject(this.objectId);
|
||||
if (!targetObject) {
|
||||
console.warn(`未找到ID为 ${this.objectId} 的对象`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 应用初始变换状态
|
||||
this._applyTransform(targetObject, this.initialState);
|
||||
|
||||
// 触发画布更新
|
||||
this.canvas.renderAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找对象
|
||||
* @private
|
||||
*/
|
||||
_findObject(objectId) {
|
||||
if (!this.canvas) return null;
|
||||
return this.canvas.getObjects().find((obj) => obj.id === objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用变换状态到对象
|
||||
* @private
|
||||
*/
|
||||
_applyTransform(object, transformState) {
|
||||
if (!object || !transformState) return;
|
||||
|
||||
// 应用变换属性,只设置真正变化的值
|
||||
Object.entries(transformState).forEach(([key, value]) => {
|
||||
object.set(key, value);
|
||||
});
|
||||
|
||||
// 确保对象更新
|
||||
object.setCoords();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令信息
|
||||
*/
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
objectId: this.objectId,
|
||||
objectType: this.objectType,
|
||||
changedProps: this.finalState ? Object.keys(this.finalState) : [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获对象的变换状态
|
||||
* @static
|
||||
*/
|
||||
static captureTransformState(object) {
|
||||
if (!object) return null;
|
||||
|
||||
// 只捕获变换相关的属性
|
||||
return {
|
||||
left: object.left,
|
||||
top: object.top,
|
||||
scaleX: object.scaleX,
|
||||
scaleY: object.scaleY,
|
||||
angle: object.angle,
|
||||
flipX: object.flipX,
|
||||
flipY: object.flipY,
|
||||
skewX: object.skewX,
|
||||
skewY: object.skewY,
|
||||
};
|
||||
}
|
||||
}
|
||||
304
src/component/Canvas/CanvasEditor/commands/TextCommands.js
Normal file
304
src/component/Canvas/CanvasEditor/commands/TextCommands.js
Normal file
@@ -0,0 +1,304 @@
|
||||
import { Command } from "./Command";
|
||||
|
||||
/**
|
||||
* 文本内容命令
|
||||
* 用于更改文本图层的文本内容
|
||||
*/
|
||||
export class TextContentCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本内容",
|
||||
description: "修改文本图层的文本内容",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.newText = options.newText;
|
||||
this.oldText = this.textObject.text;
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set("text", this.newText);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set("text", this.oldText);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本字体命令
|
||||
* 用于更改文本图层的字体
|
||||
*/
|
||||
export class TextFontCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本字体",
|
||||
description: "修改文本图层的字体",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.newFont = options.newFont;
|
||||
this.oldFont = this.textObject.fontFamily;
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set("fontFamily", this.newFont);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set("fontFamily", this.oldFont);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本尺寸命令
|
||||
* 用于更改文本图层的字体大小
|
||||
*/
|
||||
export class TextSizeCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本尺寸",
|
||||
description: "修改文本图层的字体大小",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.newSize = options.newSize;
|
||||
this.oldSize = this.textObject.fontSize;
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set("fontSize", this.newSize);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set("fontSize", this.oldSize);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本颜色命令
|
||||
* 用于更改文本图层的颜色
|
||||
*/
|
||||
export class TextColorCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本颜色",
|
||||
description: "修改文本图层的颜色",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.newColor = options.newColor;
|
||||
this.oldColor = this.textObject.fill;
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set("fill", this.newColor);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set("fill", this.oldColor);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本对齐方式命令
|
||||
* 用于更改文本图层的对齐方式
|
||||
*/
|
||||
export class TextAlignCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本对齐",
|
||||
description: "修改文本图层的对齐方式",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.newAlign = options.newAlign;
|
||||
this.oldAlign = this.textObject.textAlign;
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set("textAlign", this.newAlign);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set("textAlign", this.oldAlign);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本样式命令
|
||||
* 用于更改文本图层的样式(粗体、斜体、下划线等)
|
||||
*/
|
||||
export class TextStyleCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本样式",
|
||||
description: "修改文本图层的样式",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.property = options.property; // 'fontWeight', 'fontStyle', 'underline', 'linethrough', 'overline'
|
||||
this.newValue = options.newValue;
|
||||
this.oldValue = this.textObject[this.property];
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set(this.property, this.newValue);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set(this.property, this.oldValue);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本间距命令
|
||||
* 用于更改文本图层的字符间距或行高
|
||||
*/
|
||||
export class TextSpacingCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本间距",
|
||||
description: "修改文本图层的字符间距或行高",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.property = options.property; // 'charSpacing' 或 'lineHeight'
|
||||
this.newValue = options.newValue;
|
||||
this.oldValue = this.textObject[this.property];
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set(this.property, this.newValue);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set(this.property, this.oldValue);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本背景颜色命令
|
||||
* 用于更改文本图层的背景颜色
|
||||
*/
|
||||
export class TextBackgroundCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本背景",
|
||||
description: "修改文本图层的背景颜色",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.newColor = options.newColor;
|
||||
this.oldColor = this.textObject.textBackgroundColor;
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set("textBackgroundColor", this.newColor);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set("textBackgroundColor", this.oldColor);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本透明度命令
|
||||
* 用于更改文本图层的透明度
|
||||
*/
|
||||
export class TextOpacityCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "修改文本透明度",
|
||||
description: "修改文本图层的透明度",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.newOpacity = options.newOpacity;
|
||||
this.oldOpacity = this.textObject.opacity;
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.textObject.set("opacity", this.newOpacity);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.textObject.set("opacity", this.oldOpacity);
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组合文本编辑命令
|
||||
* 用于一次性应用多个文本属性更改
|
||||
*/
|
||||
export class CompositeTextCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "组合文本编辑",
|
||||
description: "组合多个文本编辑操作",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.textObject = options.textObject;
|
||||
this.changes = options.changes; // {property: newValue} 形式的对象
|
||||
this.oldValues = {};
|
||||
|
||||
// 保存所有属性的旧值
|
||||
for (const property in this.changes) {
|
||||
if (this.textObject[property] !== undefined) {
|
||||
this.oldValues[property] = this.textObject[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
execute() {
|
||||
for (const property in this.changes) {
|
||||
this.textObject.set(property, this.changes[property]);
|
||||
}
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
for (const property in this.oldValues) {
|
||||
this.textObject.set(property, this.oldValues[property]);
|
||||
}
|
||||
this.canvas.renderAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
54
src/component/Canvas/CanvasEditor/commands/ToolCommands.js
Normal file
54
src/component/Canvas/CanvasEditor/commands/ToolCommands.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Command } from "./Command";
|
||||
|
||||
/**
|
||||
* 工具切换命令
|
||||
* 用于切换编辑器的工具模式(如绘画、选择、橡皮擦等)
|
||||
*/
|
||||
export class ToolCommand extends Command {
|
||||
/**
|
||||
* 创建一个工具切换命令
|
||||
* @param {Object} options 配置选项
|
||||
* @param {Object} options.toolManager 工具管理器实例
|
||||
* @param {String} options.tool 要设置的工具名称
|
||||
* @param {String} options.previousTool 先前的工具名称(可选,如果不提供会在执行时记录)
|
||||
* @param {Boolean} options.saveState 是否保存画布状态(默认为false)
|
||||
*/
|
||||
constructor(options) {
|
||||
super({
|
||||
...options,
|
||||
name: `切换工具: ${options.tool}`,
|
||||
description: `将工具切换为 ${options.tool}`,
|
||||
});
|
||||
|
||||
this.toolManager = options.toolManager;
|
||||
this.tool = options.tool;
|
||||
this.previousTool = options.previousTool || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具切换
|
||||
* @returns {String} 设置的工具名称
|
||||
*/
|
||||
execute() {
|
||||
if (!this.toolManager) return null;
|
||||
|
||||
// 记录当前工具(用于撤销)
|
||||
if (!this.previousTool) {
|
||||
this.previousTool = this.toolManager.getCurrentTool();
|
||||
}
|
||||
|
||||
// 切换工具
|
||||
return this.toolManager.setTool(this.tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销工具切换
|
||||
* @returns {String} 恢复的工具名称
|
||||
*/
|
||||
undo() {
|
||||
if (!this.toolManager || !this.previousTool) return null;
|
||||
|
||||
// 恢复到先前工具
|
||||
return this.toolManager.setTool(this.previousTool);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user