2025-06-29 23:29:47 +08:00
|
|
|
|
import {
|
|
|
|
|
|
createLayer,
|
|
|
|
|
|
findInChildLayers,
|
|
|
|
|
|
LayerType,
|
|
|
|
|
|
} from "../utils/layerHelper.js";
|
2025-06-26 00:37:07 +08:00
|
|
|
|
import { createRasterizedImage } from "../utils/selectionToImage.js";
|
2025-07-03 00:04:05 +08:00
|
|
|
|
import { CompositeCommand, Command } from "./Command.js";
|
2025-06-23 00:40:45 +08:00
|
|
|
|
import { CreateImageLayerCommand } from "./LayerCommands.js";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
import { fabric } from "fabric-with-all";
|
2025-07-03 00:04:05 +08:00
|
|
|
|
import { generateId } from "../utils/helper.js";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 套索抠图命令
|
|
|
|
|
|
* 实现将选区内容抠图到新图层的功能
|
|
|
|
|
|
*/
|
|
|
|
|
|
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; // 默认启用
|
2025-06-23 00:40:45 +08:00
|
|
|
|
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
|
2025-06-29 23:29:47 +08:00
|
|
|
|
|
|
|
|
|
|
this.groupId = options.groupId || `cutout-group-${Date.now()}`;
|
|
|
|
|
|
this.groupName = options.groupName || `选区组`;
|
|
|
|
|
|
this.groupLayer = null; // 新增:保存组图层的引用
|
|
|
|
|
|
this.originalLayersLength = 0; // 新增:保存原始图层数量
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化保存选区对象,用于重做时恢复
|
|
|
|
|
|
this.serializedSelectionObject = null;
|
|
|
|
|
|
this._serializeSelectionObject();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.canvas || !this.layerManager || !this.selectionManager) {
|
|
|
|
|
|
console.error("无法执行套索抠图:参数无效");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
|
2025-06-29 23:29:47 +08:00
|
|
|
|
// 保存原始图层数量,用于撤销时的验证
|
|
|
|
|
|
this.originalLayersLength = this.layerManager.layers.value.length;
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 获取选区
|
2025-06-29 23:29:47 +08:00
|
|
|
|
const selectionObject = await this._getSelectionObject();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
if (!selectionObject) {
|
|
|
|
|
|
console.error("无法执行套索抠图:当前没有选区");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确定源图层
|
|
|
|
|
|
const sourceLayer = this.layerManager.getActiveLayer();
|
2025-06-23 00:40:45 +08:00
|
|
|
|
if (!sourceLayer) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
console.error("无法执行套索抠图:源图层无效");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-23 00:40:45 +08:00
|
|
|
|
// 获取源图层的所有对象(包括子图层)
|
|
|
|
|
|
const sourceObjects = this._getLayerObjects(sourceLayer);
|
|
|
|
|
|
if (sourceObjects.length === 0) {
|
|
|
|
|
|
console.error("无法执行套索抠图:源图层没有可见对象");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 获取选区边界信息用于后续定位
|
|
|
|
|
|
const selectionBounds = selectionObject.getBoundingRect(true, true);
|
|
|
|
|
|
|
2025-06-23 00:40:45 +08:00
|
|
|
|
// 使用createRasterizedImage执行抠图操作
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// this.cutoutImageUrl = await this._performCutoutWithRasterized(
|
|
|
|
|
|
// sourceObjects,
|
|
|
|
|
|
// selectionObject,
|
|
|
|
|
|
// selectionBounds
|
|
|
|
|
|
// );
|
|
|
|
|
|
// if (!this.cutoutImageUrl) {
|
|
|
|
|
|
// console.error("抠图失败");
|
|
|
|
|
|
// return false;
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
this.fabricImage = await this._performCutoutWithRasterized(
|
2025-06-23 00:40:45 +08:00
|
|
|
|
sourceObjects,
|
|
|
|
|
|
selectionObject,
|
|
|
|
|
|
selectionBounds
|
2025-06-09 10:25:54 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// // 创建fabric图像对象,传递选区边界信息
|
|
|
|
|
|
// this.fabricImage = await this._createFabricImage(
|
|
|
|
|
|
// this.cutoutImageUrl,
|
|
|
|
|
|
// selectionBounds
|
|
|
|
|
|
// );
|
2025-06-09 10:25:54 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2025-06-29 23:29:47 +08:00
|
|
|
|
const topLayerIndex = this.layerManager.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.newLayerId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const selectLayer = this.layerManager.layers.value[topLayerIndex];
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新的组图层
|
|
|
|
|
|
this.groupLayer = createLayer({
|
|
|
|
|
|
id: this.groupId,
|
|
|
|
|
|
name: this.groupName || `选区组`,
|
|
|
|
|
|
type: LayerType.GROUP,
|
|
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
opacity: 1.0,
|
|
|
|
|
|
fabricObjects: [],
|
|
|
|
|
|
children: [],
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.fabricImage.set({
|
|
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
selectLayer.parentId = this.groupId; // 设置新图层的parentId为组图层ID
|
|
|
|
|
|
selectLayer.fabricObjects = [
|
|
|
|
|
|
this.fabricImage.toObject("id", "layerId", "layerName", "parentId"),
|
|
|
|
|
|
];
|
2025-06-30 18:05:20 +08:00
|
|
|
|
this.groupLayer.clippingMask = this.fabricImage.toObject(
|
|
|
|
|
|
"id",
|
|
|
|
|
|
"layerId",
|
|
|
|
|
|
"layerName",
|
|
|
|
|
|
"parentId"
|
|
|
|
|
|
); // 设置组图层的fabricObject为遮罩图像
|
|
|
|
|
|
|
2025-06-29 23:29:47 +08:00
|
|
|
|
this.groupLayer.children.push(selectLayer);
|
|
|
|
|
|
// 插入新组图层
|
|
|
|
|
|
this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer);
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
this.canvas.setActiveObject(this.fabricImage);
|
2025-07-03 00:04:05 +08:00
|
|
|
|
await this.layerManager.updateLayersObjectsInteractivity(true);
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
console.log(`套索抠图完成,新图层ID: ${this.newLayerId}`);
|
|
|
|
|
|
return {
|
|
|
|
|
|
newLayerId: this.newLayerId,
|
|
|
|
|
|
cutoutImageUrl: this.cutoutImageUrl,
|
2025-06-29 23:29:47 +08:00
|
|
|
|
guroupId: this.groupId,
|
|
|
|
|
|
groupName: this.groupName,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("套索抠图过程中出错:", error);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经创建了新图层,需要进行清理
|
|
|
|
|
|
if (this.newLayerId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this.layerManager.removeLayer(this.newLayerId);
|
|
|
|
|
|
} catch (cleanupError) {
|
|
|
|
|
|
console.warn("清理新图层失败:", cleanupError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-29 23:29:47 +08:00
|
|
|
|
// 清理组图层(如果已创建)
|
|
|
|
|
|
if (this.groupLayer && this.groupId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const groupIndex = this.layerManager.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.groupId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (groupIndex !== -1) {
|
|
|
|
|
|
this.layerManager.layers.value.splice(groupIndex, 1);
|
|
|
|
|
|
console.log(`清理了异常创建的组图层: ${this.groupId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
this.groupLayer = null;
|
|
|
|
|
|
} catch (cleanupError) {
|
|
|
|
|
|
console.warn("清理组图层失败:", cleanupError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试回滚已执行的命令
|
|
|
|
|
|
if (this.executedCommands.length > 0) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
for (let i = this.executedCommands.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const command = this.executedCommands[i];
|
|
|
|
|
|
if (command && typeof command.undo === "function") {
|
|
|
|
|
|
await command.undo();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
} catch (rollbackError) {
|
|
|
|
|
|
console.warn("回滚已执行命令失败:", rollbackError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
try {
|
2025-06-29 23:29:47 +08:00
|
|
|
|
console.log(`↩️ 开始撤销套索抠图操作`);
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 首先移除组图层(如果存在)
|
|
|
|
|
|
if (this.groupId) {
|
|
|
|
|
|
const groupIndex = this.layerManager.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.groupId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (groupIndex !== -1) {
|
|
|
|
|
|
console.log(`↩️ 移除组图层: ${this.groupId}`);
|
|
|
|
|
|
// 从图层列表中移除组图层
|
|
|
|
|
|
this.layerManager.layers.value.splice(groupIndex, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.fabricImage) {
|
|
|
|
|
|
console.log(`↩️ 移除抠图图像: ${this.fabricImage.id}`);
|
|
|
|
|
|
// 从画布中移除抠图图像
|
|
|
|
|
|
this.canvas.remove(this.fabricImage);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 逆序撤销所有已执行的子命令
|
2025-06-09 10:25:54 +08:00
|
|
|
|
for (let i = this.executedCommands.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const command = this.executedCommands[i];
|
|
|
|
|
|
if (command && typeof command.undo === "function") {
|
2025-06-29 23:29:47 +08:00
|
|
|
|
try {
|
|
|
|
|
|
console.log(`↩️ 撤销子命令: ${command.constructor.name}`);
|
|
|
|
|
|
await command.undo();
|
|
|
|
|
|
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
|
|
|
|
|
error
|
|
|
|
|
|
);
|
|
|
|
|
|
// 子命令撤销失败不中断整个撤销过程
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-29 23:29:47 +08:00
|
|
|
|
// 3. 清理状态
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
this.newLayerId = null;
|
|
|
|
|
|
this.cutoutImageUrl = null;
|
|
|
|
|
|
this.fabricImage = null;
|
2025-06-29 23:29:47 +08:00
|
|
|
|
this.groupLayer = null; // 清理组图层引用
|
|
|
|
|
|
// 注意:不重置groupId,因为重做时可能需要使用相同的ID
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 更新画布和图层交互性
|
2025-07-03 00:04:05 +08:00
|
|
|
|
await this.layerManager.updateLayersObjectsInteractivity(true);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-29 23:29:47 +08:00
|
|
|
|
console.log(`✅ 套索抠图撤销完成`);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
2025-06-29 23:29:47 +08:00
|
|
|
|
console.error("❌ 撤销套索抠图失败:", error);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-29 23:29:47 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取命令信息
|
|
|
|
|
|
* @returns {Object} 命令详细信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
description: this.description,
|
|
|
|
|
|
newLayerId: this.newLayerId,
|
|
|
|
|
|
newLayerName: this.newLayerName,
|
|
|
|
|
|
groupId: this.groupId,
|
|
|
|
|
|
groupName: this.groupName,
|
|
|
|
|
|
executedCommandsCount: this.executedCommands.length,
|
|
|
|
|
|
hasGroupLayer: !!this.groupLayer,
|
|
|
|
|
|
sourceLayerId: this.sourceLayerId,
|
|
|
|
|
|
highResolutionEnabled: this.highResolutionEnabled,
|
|
|
|
|
|
baseResolutionScale: this.baseResolutionScale,
|
|
|
|
|
|
hasSerializedSelection: !!this.serializedSelectionObject,
|
|
|
|
|
|
selectionType: this.serializedSelectionObject?.type || null,
|
|
|
|
|
|
subCommands: this.executedCommands.map((cmd) => ({
|
|
|
|
|
|
name: cmd.constructor.name,
|
|
|
|
|
|
info: cmd.getInfo ? cmd.getInfo() : {},
|
|
|
|
|
|
})),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
2025-06-23 00:40:45 +08:00
|
|
|
|
* 获取图层的所有对象(包括子图层,从画布中查找真实对象)
|
|
|
|
|
|
* @param {Object} layer 图层对象
|
|
|
|
|
|
* @returns {Array} 真实的fabric对象数组
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_getLayerObjects(layer) {
|
|
|
|
|
|
const objects = [];
|
|
|
|
|
|
const canvasObjects = this.canvas.getObjects();
|
|
|
|
|
|
|
|
|
|
|
|
// 递归获取图层及其子图层的所有对象
|
|
|
|
|
|
const collectLayerObjects = (currentLayer) => {
|
|
|
|
|
|
// 处理图层的fabricObjects
|
|
|
|
|
|
if (
|
|
|
|
|
|
currentLayer.fabricObjects &&
|
|
|
|
|
|
Array.isArray(currentLayer.fabricObjects)
|
|
|
|
|
|
) {
|
|
|
|
|
|
currentLayer.fabricObjects.forEach((fabricRef) => {
|
|
|
|
|
|
if (fabricRef && fabricRef.id) {
|
|
|
|
|
|
// 从画布中查找真实的对象
|
|
|
|
|
|
const realObject = canvasObjects.find(
|
|
|
|
|
|
(obj) => obj.id === fabricRef.id
|
|
|
|
|
|
);
|
|
|
|
|
|
if (realObject && realObject.visible) {
|
|
|
|
|
|
objects.push(realObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理单个fabricObject(背景图层等)
|
|
|
|
|
|
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
|
|
|
|
|
|
const realObject = canvasObjects.find(
|
|
|
|
|
|
(obj) => obj.id === currentLayer.fabricObject.id
|
|
|
|
|
|
);
|
|
|
|
|
|
if (realObject && realObject.visible) {
|
|
|
|
|
|
objects.push(realObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 递归处理子图层
|
|
|
|
|
|
if (currentLayer.children && Array.isArray(currentLayer.children)) {
|
|
|
|
|
|
currentLayer.children.forEach((childLayer) => {
|
|
|
|
|
|
if (childLayer.visible !== false) {
|
|
|
|
|
|
// 只处理可见的子图层
|
|
|
|
|
|
collectLayerObjects(childLayer);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
collectLayerObjects(layer);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`从图层 "${layer.name}" 收集到 ${objects.length} 个可见对象`);
|
|
|
|
|
|
return objects;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 使用createRasterizedImage执行抠图操作
|
|
|
|
|
|
* @param {Array} sourceObjects 源对象数组
|
|
|
|
|
|
* @param {Object} selectionObject 选区对象
|
|
|
|
|
|
* @param {Object} selectionBounds 选区边界
|
|
|
|
|
|
* @returns {String} 抠图结果的DataURL
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _performCutoutWithRasterized(
|
|
|
|
|
|
sourceObjects,
|
|
|
|
|
|
selectionObject,
|
|
|
|
|
|
selectionBounds
|
|
|
|
|
|
) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log("=== 开始使用createRasterizedImage执行抠图 ===");
|
|
|
|
|
|
console.log(`源对象数量: ${sourceObjects.length}`);
|
|
|
|
|
|
console.log(`选区边界:`, selectionBounds);
|
|
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// 确定缩放因子,确保高质量输出
|
2025-06-23 00:40:45 +08:00
|
|
|
|
let scaleFactor = this.baseResolutionScale;
|
|
|
|
|
|
if (this.highResolutionEnabled) {
|
|
|
|
|
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
|
|
|
|
scaleFactor = Math.max(scaleFactor, devicePixelRatio * 1.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用createRasterizedImage生成栅格化图像,将选区作为裁剪路径
|
|
|
|
|
|
const rasterizedDataURL = await createRasterizedImage({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
fabricObjects: sourceObjects,
|
2025-06-26 00:37:07 +08:00
|
|
|
|
clipPath: selectionObject, // 使用选区作为裁剪路径
|
2025-06-23 00:40:45 +08:00
|
|
|
|
trimWhitespace: true,
|
|
|
|
|
|
trimPadding: 2,
|
|
|
|
|
|
quality: 1.0,
|
|
|
|
|
|
format: "png",
|
|
|
|
|
|
scaleFactor: scaleFactor,
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// isReturenDataURL: true, // 返回DataURL
|
|
|
|
|
|
preserveOriginalQuality: true, // 启用高质量模式
|
2025-06-29 23:29:47 +08:00
|
|
|
|
selectionManager: this.selectionManager, // 传递选区管理器,用于获取羽化值
|
2025-06-23 00:40:45 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!rasterizedDataURL) {
|
|
|
|
|
|
throw new Error("栅格化生成图像失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
console.log(`✅ 高质量抠图完成,缩放因子: ${scaleFactor}x`);
|
2025-06-23 00:40:45 +08:00
|
|
|
|
return rasterizedDataURL;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("使用createRasterizedImage执行抠图失败:", error);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果createRasterizedImage失败,回退到原始方法
|
2025-06-26 00:37:07 +08:00
|
|
|
|
console.log("⚠️ 回退到原始抠图方法...");
|
2025-06-23 00:40:45 +08:00
|
|
|
|
return await this._performCutout(
|
|
|
|
|
|
{ fabricObjects: sourceObjects },
|
|
|
|
|
|
selectionObject
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 原始抠图方法(作为备用方案)
|
2025-06-09 10:25:54 +08:00
|
|
|
|
* @param {Object} sourceLayer 源图层
|
|
|
|
|
|
* @param {Object} selectionObject 选区对象
|
|
|
|
|
|
* @returns {String} 抠图结果的DataURL
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _performCutout(sourceLayer, selectionObject) {
|
|
|
|
|
|
try {
|
2025-06-23 00:40:45 +08:00
|
|
|
|
console.log("=== 使用原始方法执行抠图 ===");
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取选区边界
|
|
|
|
|
|
const selectionBounds = selectionObject.getBoundingRect(true, true);
|
|
|
|
|
|
|
|
|
|
|
|
// 保存画布当前状态
|
|
|
|
|
|
const originalActiveObject = this.canvas.getActiveObject();
|
|
|
|
|
|
const originalSelection = this.canvas.selection;
|
|
|
|
|
|
|
|
|
|
|
|
// 临时禁用画布选择
|
|
|
|
|
|
this.canvas.selection = false;
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
|
|
|
|
|
|
let tempGroup = null;
|
|
|
|
|
|
let originalObjects = [];
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 收集源图层中的可见对象
|
|
|
|
|
|
const visibleObjects = sourceLayer.fabricObjects.filter(
|
|
|
|
|
|
(obj) => obj.visible
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (visibleObjects.length === 0) {
|
|
|
|
|
|
throw new Error("源图层没有可见对象");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果只有一个对象且已经是组,直接使用
|
|
|
|
|
|
if (visibleObjects.length === 1 && visibleObjects[0].type === "group") {
|
|
|
|
|
|
tempGroup = visibleObjects[0];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 创建临时组
|
|
|
|
|
|
const clonedObjects = [];
|
|
|
|
|
|
for (const obj of visibleObjects) {
|
|
|
|
|
|
const cloned = await this._cloneObject(obj);
|
|
|
|
|
|
clonedObjects.push(cloned);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tempGroup = new fabric.Group(clonedObjects, {
|
|
|
|
|
|
selectable: false,
|
|
|
|
|
|
evented: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.add(tempGroup);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置选区为裁剪路径
|
|
|
|
|
|
const clipPath = await this._cloneObject(selectionObject);
|
|
|
|
|
|
clipPath.set({
|
|
|
|
|
|
fill: "",
|
|
|
|
|
|
stroke: "",
|
|
|
|
|
|
absolutePositioned: true,
|
|
|
|
|
|
originX: "left",
|
|
|
|
|
|
originY: "top",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
tempGroup.set({
|
|
|
|
|
|
clipPath: clipPath,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
|
|
|
|
|
|
// 计算渲染区域
|
|
|
|
|
|
const renderBounds = {
|
|
|
|
|
|
left: selectionBounds.left,
|
|
|
|
|
|
top: selectionBounds.top,
|
|
|
|
|
|
width: selectionBounds.width,
|
|
|
|
|
|
height: selectionBounds.height,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-06-23 00:40:45 +08:00
|
|
|
|
// 设置高分辨率倍数
|
2025-06-09 10:25:54 +08:00
|
|
|
|
let highResolutionScale = 1;
|
|
|
|
|
|
if (this.highResolutionEnabled) {
|
|
|
|
|
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
|
|
|
|
highResolutionScale = Math.max(
|
|
|
|
|
|
this.baseResolutionScale,
|
|
|
|
|
|
devicePixelRatio * 2
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建用于导出的高分辨率canvas
|
|
|
|
|
|
const exportCanvas = document.createElement("canvas");
|
|
|
|
|
|
const exportCtx = exportCanvas.getContext("2d", {
|
|
|
|
|
|
alpha: true,
|
|
|
|
|
|
willReadFrequently: false,
|
|
|
|
|
|
colorSpace: "srgb",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const actualWidth = Math.round(
|
|
|
|
|
|
renderBounds.width * highResolutionScale
|
|
|
|
|
|
);
|
|
|
|
|
|
const actualHeight = Math.round(
|
|
|
|
|
|
renderBounds.height * highResolutionScale
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
exportCanvas.width = actualWidth;
|
|
|
|
|
|
exportCanvas.height = actualHeight;
|
|
|
|
|
|
exportCanvas.style.width = renderBounds.width + "px";
|
|
|
|
|
|
exportCanvas.style.height = renderBounds.height + "px";
|
|
|
|
|
|
|
|
|
|
|
|
exportCtx.imageSmoothingEnabled = true;
|
|
|
|
|
|
exportCtx.imageSmoothingQuality = "high";
|
|
|
|
|
|
exportCtx.clearRect(0, 0, actualWidth, actualHeight);
|
|
|
|
|
|
|
|
|
|
|
|
const vpt = this.canvas.viewportTransform;
|
|
|
|
|
|
const zoom = this.canvas.getZoom();
|
|
|
|
|
|
|
|
|
|
|
|
exportCtx.save();
|
|
|
|
|
|
exportCtx.scale(highResolutionScale, highResolutionScale);
|
|
|
|
|
|
exportCtx.translate(-renderBounds.left, -renderBounds.top);
|
|
|
|
|
|
|
|
|
|
|
|
if (zoom !== 1 || vpt[4] !== 0 || vpt[5] !== 0) {
|
|
|
|
|
|
exportCtx.transform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tempGroup.render(exportCtx);
|
|
|
|
|
|
exportCtx.restore();
|
|
|
|
|
|
|
|
|
|
|
|
const dataUrl = exportCanvas.toDataURL("image/png", 1.0);
|
|
|
|
|
|
return dataUrl;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 清理和恢复
|
|
|
|
|
|
if (tempGroup) {
|
|
|
|
|
|
tempGroup.set({ clipPath: null });
|
|
|
|
|
|
if (originalObjects.length > 0) {
|
|
|
|
|
|
this.canvas.remove(tempGroup);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.selection = originalSelection;
|
|
|
|
|
|
if (originalActiveObject) {
|
|
|
|
|
|
this.canvas.setActiveObject(originalActiveObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2025-06-23 00:40:45 +08:00
|
|
|
|
console.error("原始方法执行抠图失败:", error);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-23 00:40:45 +08:00
|
|
|
|
// 使用选区的位置作为图像位置
|
|
|
|
|
|
let targetLeft = selectionBounds.left + selectionBounds.width / 2;
|
|
|
|
|
|
let targetTop = selectionBounds.top + selectionBounds.height / 2;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-23 00:40:45 +08:00
|
|
|
|
// 设置图像属性,保持选区的原始尺寸
|
2025-06-09 10:25:54 +08:00
|
|
|
|
img.set({
|
|
|
|
|
|
left: targetLeft,
|
|
|
|
|
|
top: targetTop,
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
hasControls: true,
|
|
|
|
|
|
hasBorders: true,
|
|
|
|
|
|
cornerStyle: "circle",
|
|
|
|
|
|
cornerColor: "#007aff",
|
|
|
|
|
|
cornerSize: 10,
|
|
|
|
|
|
transparentCorners: false,
|
|
|
|
|
|
borderColor: "#007aff",
|
|
|
|
|
|
borderScaleFactor: 2,
|
|
|
|
|
|
// 优化图像渲染质量
|
|
|
|
|
|
objectCaching: false, // 禁用缓存以确保最佳质量
|
|
|
|
|
|
statefullCache: true,
|
|
|
|
|
|
noScaleCache: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新坐标
|
|
|
|
|
|
img.setCoords();
|
|
|
|
|
|
|
2025-06-23 00:40:45 +08:00
|
|
|
|
console.log(
|
|
|
|
|
|
`图像创建完成,位置: (${targetLeft}, ${targetTop}), 尺寸: ${img.width}x${img.height}`
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-06-29 23:29:47 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 序列化选区对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_serializeSelectionObject() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!this.selectionManager) {
|
|
|
|
|
|
console.warn("选区管理器不存在,无法序列化选区对象");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selectionObject = this.selectionManager.getSelectionObject();
|
|
|
|
|
|
if (!selectionObject) {
|
|
|
|
|
|
console.warn("当前没有选区对象,无法序列化");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将选区对象转换为可序列化的对象
|
|
|
|
|
|
this.serializedSelectionObject = selectionObject.toObject([
|
|
|
|
|
|
"id",
|
|
|
|
|
|
"layerId",
|
|
|
|
|
|
"layerName",
|
|
|
|
|
|
"parentId",
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
console.log("选区对象已序列化保存");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("序列化选区对象失败:", error);
|
|
|
|
|
|
this.serializedSelectionObject = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 反序列化选区对象
|
|
|
|
|
|
* @returns {Promise<Object>} 选区对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _getSelectionObject() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 首先尝试从选区管理器获取当前选区
|
|
|
|
|
|
const currentSelection = this.selectionManager.getSelectionObject();
|
|
|
|
|
|
if (currentSelection) {
|
|
|
|
|
|
console.log("从选区管理器获取到当前选区");
|
|
|
|
|
|
return currentSelection;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果当前没有选区,则从序列化数据恢复
|
|
|
|
|
|
if (!this.serializedSelectionObject) {
|
|
|
|
|
|
console.error("没有序列化的选区对象数据");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log("从序列化数据恢复选区对象");
|
|
|
|
|
|
|
|
|
|
|
|
// 根据选区对象类型进行反序列化
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const objectType = this.serializedSelectionObject.type;
|
|
|
|
|
|
|
|
|
|
|
|
if (objectType === "path") {
|
|
|
|
|
|
// 如果是路径类型(套索选区)
|
|
|
|
|
|
fabric.Path.fromObject(this.serializedSelectionObject, (path) => {
|
|
|
|
|
|
if (path) {
|
|
|
|
|
|
console.log("路径选区对象反序列化成功");
|
|
|
|
|
|
resolve(path);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error("路径选区对象反序列化失败"));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (objectType === "polygon") {
|
|
|
|
|
|
// 如果是多边形类型
|
|
|
|
|
|
fabric.Polygon.fromObject(
|
|
|
|
|
|
this.serializedSelectionObject,
|
|
|
|
|
|
(polygon) => {
|
|
|
|
|
|
if (polygon) {
|
|
|
|
|
|
console.log("多边形选区对象反序列化成功");
|
|
|
|
|
|
resolve(polygon);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error("多边形选区对象反序列化失败"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
} else if (objectType === "rect") {
|
|
|
|
|
|
// 如果是矩形选区
|
|
|
|
|
|
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
|
|
|
|
|
|
if (rect) {
|
|
|
|
|
|
console.log("矩形选区对象反序列化成功");
|
|
|
|
|
|
resolve(rect);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error("矩形选区对象反序列化失败"));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (objectType === "ellipse" || objectType === "circle") {
|
|
|
|
|
|
// 如果是椭圆/圆形选区
|
|
|
|
|
|
fabric.Ellipse.fromObject(
|
|
|
|
|
|
this.serializedSelectionObject,
|
|
|
|
|
|
(ellipse) => {
|
|
|
|
|
|
if (ellipse) {
|
|
|
|
|
|
console.log("椭圆选区对象反序列化成功");
|
|
|
|
|
|
resolve(ellipse);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error("椭圆选区对象反序列化失败"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 通用对象反序列化
|
|
|
|
|
|
fabric.util.enlivenObjects(
|
|
|
|
|
|
[this.serializedSelectionObject],
|
|
|
|
|
|
(objects) => {
|
|
|
|
|
|
if (objects && objects.length > 0) {
|
|
|
|
|
|
console.log("通用选区对象反序列化成功");
|
|
|
|
|
|
resolve(objects[0]);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error("通用选区对象反序列化失败"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取选区对象失败:", error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-07-03 00:04:05 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 剪切选区到新图层命令
|
|
|
|
|
|
* 实现将选区内容剪切到新图层,同时对原图层进行栅格化抠图的功能
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
|
|
|
|
|
constructor(options = {}) {
|
|
|
|
|
|
super([], {
|
|
|
|
|
|
name: "剪切选区到新图层",
|
|
|
|
|
|
description: "将选区剪切到新图层并对原图层进行栅格化",
|
|
|
|
|
|
});
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.selectionManager = options.selectionManager;
|
|
|
|
|
|
this.toolManager = options.toolManager;
|
|
|
|
|
|
this.sourceLayerId = options.sourceLayerId;
|
|
|
|
|
|
this.newLayerName = options.newLayerName || "剪切";
|
|
|
|
|
|
this.newLayerId = null;
|
|
|
|
|
|
this.cutoutImageUrl = null;
|
|
|
|
|
|
this.fabricImage = null;
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 高清截图选项
|
|
|
|
|
|
this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用
|
|
|
|
|
|
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
|
|
|
|
|
|
|
|
|
|
|
|
this.groupId = options.groupId || `cut-group-${Date.now()}`;
|
|
|
|
|
|
this.groupName = options.groupName || `剪切组`;
|
|
|
|
|
|
this.groupLayer = null; // 保存组图层的引用
|
|
|
|
|
|
this.originalLayersLength = 0; // 保存原始图层数量
|
|
|
|
|
|
|
|
|
|
|
|
// 原图层栅格化相关
|
|
|
|
|
|
this.originalLayerRasterized = false;
|
|
|
|
|
|
this.originalLayerBackup = null;
|
|
|
|
|
|
this.rasterizedOriginalImage = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化保存选区对象,用于重做时恢复
|
|
|
|
|
|
this.serializedSelectionObject = null;
|
|
|
|
|
|
this._serializeSelectionObject();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.canvas || !this.layerManager || !this.selectionManager) {
|
|
|
|
|
|
console.error("无法执行剪切选区:参数无效");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 保存原始图层数量,用于撤销时的验证
|
|
|
|
|
|
this.originalLayersLength = this.layerManager.layers.value.length;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取选区
|
|
|
|
|
|
const selectionObject = await this._getSelectionObject();
|
|
|
|
|
|
if (!selectionObject) {
|
|
|
|
|
|
console.error("无法执行剪切选区:当前没有选区");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确定源图层
|
|
|
|
|
|
const sourceLayer = this.layerManager.getActiveLayer();
|
|
|
|
|
|
if (!sourceLayer) {
|
|
|
|
|
|
console.error("无法执行剪切选区:没有活动图层");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 备份原图层状态
|
|
|
|
|
|
this.originalLayerBackup = JSON.parse(JSON.stringify(sourceLayer));
|
|
|
|
|
|
|
|
|
|
|
|
// 获取源图层的所有对象(包括子图层)
|
|
|
|
|
|
const sourceObjects = this._getLayerObjects(sourceLayer);
|
|
|
|
|
|
if (sourceObjects.length === 0) {
|
|
|
|
|
|
console.error("无法执行剪切选区:源图层没有可见对象");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取选区边界信息用于后续定位
|
|
|
|
|
|
const selectionBounds = selectionObject.getBoundingRect(true, true);
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤1: 先创建抠图到新图层(复制选区内容)
|
|
|
|
|
|
this.fabricImage = await this._performCutoutWithRasterized(
|
|
|
|
|
|
sourceObjects,
|
|
|
|
|
|
selectionObject,
|
|
|
|
|
|
selectionBounds
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.fabricImage) {
|
|
|
|
|
|
console.error("抠图失败");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤2: 创建图像图层命令
|
|
|
|
|
|
const createImageLayerCmd = new CreateImageLayerCommand({
|
|
|
|
|
|
layerManager: this.layerManager,
|
|
|
|
|
|
fabricImage: this.fabricImage,
|
|
|
|
|
|
toolManager: this.toolManager,
|
|
|
|
|
|
layerName: this.newLayerName,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行创建图像图层命令
|
|
|
|
|
|
const result = await createImageLayerCmd.execute();
|
|
|
|
|
|
this.newLayerId = createImageLayerCmd.newLayerId;
|
|
|
|
|
|
this.executedCommands.push(createImageLayerCmd);
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤3: 对原图层进行栅格化,移除选区内容
|
|
|
|
|
|
await this._rasterizeOriginalLayerWithCutout(
|
|
|
|
|
|
sourceLayer,
|
|
|
|
|
|
selectionObject,
|
|
|
|
|
|
selectionBounds
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤4: 清除选区命令
|
|
|
|
|
|
const clearSelectionCmd = new ClearSelectionCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
selectionManager: this.selectionManager,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 执行清除选区命令
|
|
|
|
|
|
await clearSelectionCmd.execute();
|
|
|
|
|
|
this.executedCommands.push(clearSelectionCmd);
|
|
|
|
|
|
|
|
|
|
|
|
const topLayerIndex = this.layerManager.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.newLayerId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const selectLayer = this.layerManager.layers.value[topLayerIndex];
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新的组图层
|
|
|
|
|
|
this.groupLayer = createLayer({
|
|
|
|
|
|
id: this.groupId,
|
|
|
|
|
|
name: this.groupName || `剪切组`,
|
|
|
|
|
|
type: LayerType.GROUP,
|
|
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
opacity: 1.0,
|
|
|
|
|
|
fabricObjects: [],
|
|
|
|
|
|
children: [],
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.fabricImage.set({
|
|
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
selectLayer.parentId = this.groupId;
|
|
|
|
|
|
selectLayer.fabricObjects = [
|
|
|
|
|
|
this.fabricImage.toObject("id", "layerId", "layerName", "parentId"),
|
|
|
|
|
|
];
|
|
|
|
|
|
this.groupLayer.clippingMask = this.fabricImage.toObject(
|
|
|
|
|
|
"id",
|
|
|
|
|
|
"layerId",
|
|
|
|
|
|
"layerName",
|
|
|
|
|
|
"parentId"
|
|
|
|
|
|
);
|
|
|
|
|
|
this.groupLayer.children.push(selectLayer);
|
|
|
|
|
|
|
|
|
|
|
|
// 插入新组图层
|
|
|
|
|
|
this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer);
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
this.canvas.setActiveObject(this.fabricImage);
|
|
|
|
|
|
await this.layerManager.updateLayersObjectsInteractivity(true);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`剪切选区完成,新图层ID: ${this.newLayerId}`);
|
|
|
|
|
|
return {
|
|
|
|
|
|
newLayerId: this.newLayerId,
|
|
|
|
|
|
cutoutImageUrl: this.cutoutImageUrl,
|
|
|
|
|
|
groupId: this.groupId,
|
|
|
|
|
|
groupName: this.groupName,
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("剪切选区过程中出错:", error);
|
|
|
|
|
|
|
|
|
|
|
|
// 错误清理和回滚
|
|
|
|
|
|
await this._handleExecutionError();
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log(`↩️ 开始撤销剪切选区操作`);
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 首先移除组图层(如果存在)
|
|
|
|
|
|
if (this.groupId) {
|
|
|
|
|
|
const groupIndex = this.layerManager.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.groupId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (groupIndex !== -1) {
|
|
|
|
|
|
this.layerManager.layers.value.splice(groupIndex, 1);
|
|
|
|
|
|
console.log(`移除了组图层: ${this.groupId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.fabricImage) {
|
|
|
|
|
|
// 从画布移除抠图对象
|
|
|
|
|
|
if (this.canvas.getObjects().includes(this.fabricImage)) {
|
|
|
|
|
|
this.canvas.remove(this.fabricImage);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 恢复原图层的状态(如果进行了栅格化)
|
|
|
|
|
|
if (this.originalLayerRasterized && this.originalLayerBackup) {
|
|
|
|
|
|
await this._restoreOriginalLayer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 逆序撤销所有已执行的子命令
|
|
|
|
|
|
for (let i = this.executedCommands.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const command = this.executedCommands[i];
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (command && typeof command.undo === "function") {
|
|
|
|
|
|
await command.undo();
|
|
|
|
|
|
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
|
|
|
|
|
error
|
|
|
|
|
|
);
|
|
|
|
|
|
// 子命令撤销失败不中断整个撤销过程
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 清理状态
|
|
|
|
|
|
this.executedCommands = [];
|
|
|
|
|
|
this.newLayerId = null;
|
|
|
|
|
|
this.cutoutImageUrl = null;
|
|
|
|
|
|
this.fabricImage = null;
|
|
|
|
|
|
this.groupLayer = null;
|
|
|
|
|
|
this.originalLayerRasterized = false;
|
|
|
|
|
|
this.rasterizedOriginalImage = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 更新画布和图层交互性
|
|
|
|
|
|
await this.layerManager.updateLayersObjectsInteractivity(true);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 剪切选区撤销完成`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("❌ 撤销剪切选区失败:", error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 对原图层进行栅格化,移除选区内容
|
|
|
|
|
|
* @param {Object} sourceLayer 源图层
|
|
|
|
|
|
* @param {Object} selectionObject 选区对象
|
|
|
|
|
|
* @param {Object} selectionBounds 选区边界
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _rasterizeOriginalLayerWithCutout(
|
|
|
|
|
|
sourceLayer,
|
|
|
|
|
|
selectionObject,
|
|
|
|
|
|
selectionBounds
|
|
|
|
|
|
) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log("🔪 开始对原图层进行栅格化剪切");
|
|
|
|
|
|
|
|
|
|
|
|
// 获取源图层的所有对象
|
|
|
|
|
|
const sourceObjects = this._getLayerObjects(sourceLayer);
|
|
|
|
|
|
if (sourceObjects.length === 0) {
|
|
|
|
|
|
console.warn("源图层没有对象需要栅格化");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建反转选区(即选区外的内容)
|
|
|
|
|
|
const invertedSelection = await this._createInvertedSelection(
|
|
|
|
|
|
selectionObject
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!invertedSelection) {
|
|
|
|
|
|
console.error("创建反转选区失败");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用反转选区对原图层进行栅格化(保留选区外的内容)
|
|
|
|
|
|
this.rasterizedOriginalImage = await createRasterizedImage({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
fabricObjects: sourceObjects,
|
|
|
|
|
|
clipPath: invertedSelection,
|
|
|
|
|
|
trimWhitespace: true,
|
|
|
|
|
|
trimPadding: 2,
|
|
|
|
|
|
quality: 1.0,
|
|
|
|
|
|
format: "png",
|
|
|
|
|
|
scaleFactor: this.baseResolutionScale,
|
|
|
|
|
|
preserveOriginalQuality: true,
|
|
|
|
|
|
selectionManager: this.selectionManager,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.rasterizedOriginalImage) {
|
|
|
|
|
|
console.error("原图层栅格化失败");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 移除原图层的所有对象
|
|
|
|
|
|
for (const obj of sourceObjects) {
|
|
|
|
|
|
if (this.canvas.getObjects().includes(obj)) {
|
|
|
|
|
|
this.canvas.remove(obj);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将栅格化后的图像设置到原图层
|
|
|
|
|
|
this.rasterizedOriginalImage.set({
|
|
|
|
|
|
id: generateId("rasterized_"),
|
|
|
|
|
|
layerId: sourceLayer.id,
|
|
|
|
|
|
layerName: sourceLayer.name,
|
|
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加栅格化图像到画布和图层
|
|
|
|
|
|
this.canvas.add(this.rasterizedOriginalImage);
|
|
|
|
|
|
sourceLayer.fabricObjects = [
|
|
|
|
|
|
this.rasterizedOriginalImage.toObject(
|
|
|
|
|
|
"id",
|
|
|
|
|
|
"layerId",
|
|
|
|
|
|
"layerName",
|
|
|
|
|
|
"parentId"
|
|
|
|
|
|
),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
this.originalLayerRasterized = true;
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
|
|
|
|
|
|
console.log("✅ 原图层栅格化剪切完成");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("原图层栅格化剪切失败:", error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建反转选区
|
|
|
|
|
|
* @param {Object} selectionObject 原选区对象
|
|
|
|
|
|
* @returns {Object} 反转选区对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _createInvertedSelection(selectionObject) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取画布范围
|
|
|
|
|
|
const canvasRect = new fabric.Rect({
|
|
|
|
|
|
left: 0,
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
width: this.canvas.width,
|
|
|
|
|
|
height: this.canvas.height,
|
|
|
|
|
|
selectable: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 创建反选路径
|
|
|
|
|
|
let invertedPath;
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 使用 fabric.js 的布尔运算创建反转选区
|
|
|
|
|
|
const canvasPath = canvasRect.toClipPathSVG();
|
|
|
|
|
|
const selectionPath = selectionObject.toClipPathSVG();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建反转选区对象
|
|
|
|
|
|
const pathString = `M 0 0 L ${this.canvas.width} 0 L ${this.canvas.width} ${this.canvas.height} L 0 ${this.canvas.height} Z ${selectionObject.path}`;
|
|
|
|
|
|
|
|
|
|
|
|
invertedPath = new fabric.Path(pathString, {
|
|
|
|
|
|
fillRule: "evenodd",
|
|
|
|
|
|
selectable: false,
|
|
|
|
|
|
evented: false,
|
|
|
|
|
|
id: `inverted_selection_${Date.now()}`,
|
|
|
|
|
|
name: "inverted_selection",
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("无法创建反转选区:", error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return invertedPath;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("创建反转选区失败:", error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 恢复原图层状态
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _restoreOriginalLayer() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!this.originalLayerBackup) {
|
|
|
|
|
|
console.warn("没有原图层备份数据");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 移除栅格化后的图像
|
|
|
|
|
|
if (
|
|
|
|
|
|
this.rasterizedOriginalImage &&
|
|
|
|
|
|
this.canvas.getObjects().includes(this.rasterizedOriginalImage)
|
|
|
|
|
|
) {
|
|
|
|
|
|
this.canvas.remove(this.rasterizedOriginalImage);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原图层的对象
|
|
|
|
|
|
const sourceLayer = this.layerManager.getLayerById(
|
|
|
|
|
|
this.originalLayerBackup.id
|
|
|
|
|
|
);
|
|
|
|
|
|
if (sourceLayer) {
|
|
|
|
|
|
// 恢复图层的fabricObjects
|
|
|
|
|
|
sourceLayer.fabricObjects =
|
|
|
|
|
|
this.originalLayerBackup.fabricObjects || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 重新创建并添加对象到画布
|
|
|
|
|
|
if (sourceLayer.fabricObjects.length > 0) {
|
|
|
|
|
|
await this._restoreLayerObjects(sourceLayer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log("✅ 原图层状态恢复完成");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("恢复原图层状态失败:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 恢复图层对象到画布
|
|
|
|
|
|
* @param {Object} layer 图层对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _restoreLayerObjects(layer) {
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
|
if (!layer.fabricObjects || layer.fabricObjects.length === 0) {
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fabric.util.enlivenObjects(layer.fabricObjects, (objects) => {
|
|
|
|
|
|
objects.forEach((obj) => {
|
|
|
|
|
|
// 确保对象有正确的ID和图层信息
|
|
|
|
|
|
obj.set({
|
|
|
|
|
|
layerId: layer.id,
|
|
|
|
|
|
layerName: layer.name,
|
|
|
|
|
|
selectable: true,
|
|
|
|
|
|
evented: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到画布
|
|
|
|
|
|
this.canvas.add(obj);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理执行错误时的清理
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _handleExecutionError() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 如果已经创建了新图层,需要进行清理
|
|
|
|
|
|
if (this.newLayerId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const layerIndex = this.layerManager.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.newLayerId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (layerIndex !== -1) {
|
|
|
|
|
|
this.layerManager.layers.value.splice(layerIndex, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`清理了异常创建的新图层: ${this.newLayerId}`);
|
|
|
|
|
|
} catch (cleanupError) {
|
|
|
|
|
|
console.warn("清理新图层失败:", cleanupError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理组图层(如果已创建)
|
|
|
|
|
|
if (this.groupLayer && this.groupId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const groupIndex = this.layerManager.layers.value.findIndex(
|
|
|
|
|
|
(layer) => layer.id === this.groupId
|
|
|
|
|
|
);
|
|
|
|
|
|
if (groupIndex !== -1) {
|
|
|
|
|
|
this.layerManager.layers.value.splice(groupIndex, 1);
|
|
|
|
|
|
console.log(`清理了异常创建的组图层: ${this.groupId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
this.groupLayer = null;
|
|
|
|
|
|
} catch (cleanupError) {
|
|
|
|
|
|
console.warn("清理组图层失败:", cleanupError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原图层状态(如果已经栅格化)
|
|
|
|
|
|
if (this.originalLayerRasterized) {
|
|
|
|
|
|
await this._restoreOriginalLayer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试回滚已执行的命令
|
|
|
|
|
|
if (this.executedCommands.length > 0) {
|
|
|
|
|
|
for (let i = this.executedCommands.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const command = this.executedCommands[i];
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (command && typeof command.undo === "function") {
|
|
|
|
|
|
await command.undo();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (rollbackError) {
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
`回滚命令失败: ${command.constructor.name}`,
|
|
|
|
|
|
rollbackError
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("错误清理过程中出现问题:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取命令信息
|
|
|
|
|
|
* @returns {Object} 命令详细信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
getInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: this.name,
|
|
|
|
|
|
description: this.description,
|
|
|
|
|
|
newLayerId: this.newLayerId,
|
|
|
|
|
|
newLayerName: this.newLayerName,
|
|
|
|
|
|
groupId: this.groupId,
|
|
|
|
|
|
groupName: this.groupName,
|
|
|
|
|
|
executedCommandsCount: this.executedCommands.length,
|
|
|
|
|
|
hasGroupLayer: !!this.groupLayer,
|
|
|
|
|
|
sourceLayerId: this.sourceLayerId,
|
|
|
|
|
|
highResolutionEnabled: this.highResolutionEnabled,
|
|
|
|
|
|
baseResolutionScale: this.baseResolutionScale,
|
|
|
|
|
|
hasSerializedSelection: !!this.serializedSelectionObject,
|
|
|
|
|
|
selectionType: this.serializedSelectionObject?.type || null,
|
|
|
|
|
|
originalLayerRasterized: this.originalLayerRasterized,
|
|
|
|
|
|
hasOriginalLayerBackup: !!this.originalLayerBackup,
|
|
|
|
|
|
subCommands: this.executedCommands.map((cmd) => ({
|
|
|
|
|
|
name: cmd.constructor.name,
|
|
|
|
|
|
info: cmd.getInfo ? cmd.getInfo() : {},
|
|
|
|
|
|
})),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 使用createRasterizedImage执行抠图操作
|
|
|
|
|
|
* @param {Array} sourceObjects 源对象数组
|
|
|
|
|
|
* @param {Object} selectionObject 选区对象
|
|
|
|
|
|
* @param {Object} selectionBounds 选区边界
|
|
|
|
|
|
* @returns {fabric.Image} 抠图结果的fabric图像对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _performCutoutWithRasterized(
|
|
|
|
|
|
sourceObjects,
|
|
|
|
|
|
selectionObject,
|
|
|
|
|
|
selectionBounds
|
|
|
|
|
|
) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log("=== 开始使用createRasterizedImage执行剪切抠图 ===");
|
|
|
|
|
|
console.log(`源对象数量: ${sourceObjects.length}`);
|
|
|
|
|
|
console.log(`选区边界:`, selectionBounds);
|
|
|
|
|
|
|
|
|
|
|
|
// 确定缩放因子,确保高质量输出
|
|
|
|
|
|
let scaleFactor = this.baseResolutionScale;
|
|
|
|
|
|
if (this.highResolutionEnabled) {
|
|
|
|
|
|
const currentZoom = this.canvas.getZoom?.() || 1;
|
|
|
|
|
|
scaleFactor = Math.max(
|
|
|
|
|
|
scaleFactor || this.canvas?.getRetinaScaling?.(),
|
|
|
|
|
|
currentZoom
|
|
|
|
|
|
);
|
|
|
|
|
|
scaleFactor = Math.min(scaleFactor, 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用createRasterizedImage生成栅格化图像,将选区作为裁剪路径
|
|
|
|
|
|
const rasterizedImage = await createRasterizedImage({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
fabricObjects: sourceObjects,
|
|
|
|
|
|
clipPath: selectionObject,
|
|
|
|
|
|
trimWhitespace: true,
|
|
|
|
|
|
trimPadding: 2,
|
|
|
|
|
|
quality: 1.0,
|
|
|
|
|
|
format: "png",
|
|
|
|
|
|
scaleFactor: scaleFactor,
|
|
|
|
|
|
preserveOriginalQuality: true,
|
|
|
|
|
|
selectionManager: this.selectionManager,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!rasterizedImage) {
|
|
|
|
|
|
console.error("高质量剪切抠图失败");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 高质量剪切抠图完成,缩放因子: ${scaleFactor}x`);
|
|
|
|
|
|
return rasterizedImage;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("使用createRasterizedImage执行剪切抠图失败:", error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取图层的所有对象(包括子图层,从画布中查找真实对象)
|
|
|
|
|
|
* @param {Object} layer 图层对象
|
|
|
|
|
|
* @returns {Array} 真实的fabric对象数组
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_getLayerObjects(layer) {
|
|
|
|
|
|
const objects = [];
|
|
|
|
|
|
const canvasObjects = this.canvas.getObjects();
|
|
|
|
|
|
|
|
|
|
|
|
// 递归获取图层及其子图层的所有对象
|
|
|
|
|
|
const collectLayerObjects = (currentLayer) => {
|
|
|
|
|
|
// 处理图层的fabricObjects
|
|
|
|
|
|
if (
|
|
|
|
|
|
currentLayer.fabricObjects &&
|
|
|
|
|
|
Array.isArray(currentLayer.fabricObjects)
|
|
|
|
|
|
) {
|
|
|
|
|
|
currentLayer.fabricObjects.forEach((fabricObj) => {
|
|
|
|
|
|
if (fabricObj && fabricObj.id) {
|
|
|
|
|
|
const realObject = canvasObjects.find(
|
|
|
|
|
|
(canvasObj) => canvasObj.id === fabricObj.id
|
|
|
|
|
|
);
|
|
|
|
|
|
if (realObject && realObject.visible !== false) {
|
|
|
|
|
|
objects.push(realObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理单个fabricObject(背景图层等)
|
|
|
|
|
|
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
|
|
|
|
|
|
const realObject = canvasObjects.find(
|
|
|
|
|
|
(canvasObj) => canvasObj.id === currentLayer.fabricObject.id
|
|
|
|
|
|
);
|
|
|
|
|
|
if (realObject && realObject.visible !== false) {
|
|
|
|
|
|
objects.push(realObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 递归处理子图层
|
|
|
|
|
|
if (currentLayer.children && Array.isArray(currentLayer.children)) {
|
|
|
|
|
|
currentLayer.children.forEach((childLayer) => {
|
|
|
|
|
|
collectLayerObjects(childLayer);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
collectLayerObjects(layer);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`从图层 "${layer.name}" 收集到 ${objects.length} 个可见对象`);
|
|
|
|
|
|
return objects;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 序列化选区对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_serializeSelectionObject() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!this.selectionManager) {
|
|
|
|
|
|
console.warn("选区管理器不存在,无法序列化选区对象");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selectionObject = this.selectionManager.getSelectionObject();
|
|
|
|
|
|
if (!selectionObject) {
|
|
|
|
|
|
console.warn("当前没有选区对象,无法序列化");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将选区对象转换为可序列化的对象
|
|
|
|
|
|
this.serializedSelectionObject = selectionObject.toObject([
|
|
|
|
|
|
"id",
|
|
|
|
|
|
"layerId",
|
|
|
|
|
|
"layerName",
|
|
|
|
|
|
"parentId",
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
console.log("选区对象已序列化保存");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("序列化选区对象失败:", error);
|
|
|
|
|
|
this.serializedSelectionObject = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 反序列化选区对象
|
|
|
|
|
|
* @returns {Promise<Object>} 选区对象
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
async _getSelectionObject() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 首先尝试从选区管理器获取当前选区
|
|
|
|
|
|
if (this.selectionManager) {
|
|
|
|
|
|
const currentSelection = this.selectionManager.getSelectionObject();
|
|
|
|
|
|
if (currentSelection) {
|
|
|
|
|
|
return currentSelection;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有当前选区,尝试从序列化数据恢复
|
|
|
|
|
|
if (this.serializedSelectionObject) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
fabric.util.enlivenObjects(
|
|
|
|
|
|
[this.serializedSelectionObject],
|
|
|
|
|
|
(objects) => {
|
|
|
|
|
|
if (objects && objects.length > 0) {
|
|
|
|
|
|
resolve(objects[0]);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error("无法从序列化数据恢复选区对象"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取选区对象失败:", error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清除选区命令
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ClearSelectionCommand extends Command {
|
|
|
|
|
|
constructor(options = {}) {
|
|
|
|
|
|
super({
|
|
|
|
|
|
name: "清除选区",
|
|
|
|
|
|
description: "清除当前选区",
|
|
|
|
|
|
saveState: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.selectionManager = options.selectionManager;
|
|
|
|
|
|
this.originalSelection = options.selectionManager
|
|
|
|
|
|
? options.selectionManager.getSelectionPath()
|
|
|
|
|
|
: null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
if (!this.selectionManager) {
|
|
|
|
|
|
console.error("无法清除选区:参数无效");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存原始选区
|
|
|
|
|
|
if (!this.originalSelection) {
|
|
|
|
|
|
this.originalSelection = this.selectionManager.getSelectionPath();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清除选区
|
|
|
|
|
|
this.selectionManager.clearSelection();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (!this.selectionManager || !this.originalSelection) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原始选区
|
|
|
|
|
|
this.selectionManager.setSelectionFromPath(this.originalSelection);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|