Files
aida_front/src/component/Canvas/CanvasEditor/commands/CutSelectionToNewLayerCommand_old.js
2025-07-14 01:00:23 +08:00

836 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
import { createRasterizedImage } from "../utils/selectionToImage.js";
import { CompositeCommand, Command } from "./Command.js";
import { CreateImageLayerCommand } from "./LayerCommands.js";
import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper.js";
import { ClearSelectionCommand } from "./LassoCutoutCommand.js";
import { ClearSelectionContentCommand } from "./ClearSelectionContentCommand.js";
/**
* 剪切选区到新图层命令
* 实现将选区内容复制到新图层,并将复制的选区作为组的遮罩
*/
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.clippingMaskObject = null;
this.sourceLayerId = null; // 保存源图层ID
// 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象
this._clonedSelectionObject = null;
this._initializeClonedSelection();
}
/**
* 初始化克隆的选区对象
* @private
*/
async _initializeClonedSelection() {
if (this.selectionManager) {
const selectionObject = this.selectionManager.getSelectionObject();
if (selectionObject) {
try {
this._clonedSelectionObject = await this._cloneObject(selectionObject);
console.log("剪切选区:选区对象已克隆保存");
} catch (error) {
console.error("剪切选区:克隆选区对象失败:", error);
// 备用方案:序列化保存
this.serializedSelectionObject = selectionObject.toObject([
"id",
"layerId",
"layerName",
"parentId",
]);
}
}
}
}
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;
}
// 保存源图层ID
this.sourceLayerId = sourceLayer.id;
// 获取源图层的所有对象(包括子图层)
const sourceObjects = this._getLayerObjects(sourceLayer);
if (sourceObjects.length === 0) {
console.error("无法执行剪切选区:源图层没有可见对象");
return false;
}
// 保存原图层状态用于撤销
await this._backupOriginalLayer(sourceLayer, sourceObjects);
// 获取选区边界信息用于后续定位
const selectionBounds = selectionObject.getBoundingRect(true, true);
// 步骤1: 先创建抠图到新图层(复制选区内容)
this.fabricImage = await this._performCutoutWithRasterized(
sourceObjects,
selectionObject,
selectionBounds
);
if (!this.fabricImage) {
console.error("抠图失败");
return false;
}
// 步骤2: 对原图层进行栅格化处理,移除选区内容
await this._rasterizeOriginalLayerWithCutout(sourceLayer, sourceObjects, selectionObject);
// 步骤3: 创建图像图层命令
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);
// 步骤4: 创建组图层并设置剪切结果为遮罩
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) {
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;
}
}
/**
* 恢复原图层状态 - 关键修复方法
* @private
*/
async _restoreOriginalLayer() {
try {
console.log("🔄 开始恢复原图层状态...");
if (!this.originalLayerBackup || !this.sourceLayerId) {
console.warn("没有原图层备份数据或源图层ID");
return;
}
// 1. 移除栅格化后的图像
if (this.rasterizedOriginalImage) {
if (this.canvas.getObjects().includes(this.rasterizedOriginalImage)) {
this.canvas.remove(this.rasterizedOriginalImage);
console.log("移除了栅格化图像");
}
}
// 2. 获取当前的源图层引用
const sourceLayer = this.layerManager.getLayerById(this.sourceLayerId);
if (!sourceLayer) {
console.error(`找不到源图层: ${this.sourceLayerId}`);
return;
}
// 3. 清空当前图层的对象列表
sourceLayer.fabricObjects = [];
// 4. 恢复原始对象数据到图层
if (this.originalLayerObjectsData.length > 0) {
// 使用备份的序列化数据重建对象
await this._restoreObjectsFromBackup(sourceLayer);
} else {
// 备用方案恢复原始的fabricObjects数组
sourceLayer.fabricObjects = this.originalLayerBackup.fabricObjects || [];
if (sourceLayer.fabricObjects.length > 0) {
await this._restoreLayerObjects(sourceLayer);
}
}
console.log(`✅ 原图层状态恢复完成,恢复了 ${sourceLayer.fabricObjects.length} 个对象`);
} catch (error) {
console.error("恢复原图层状态失败:", error);
throw error;
}
}
/**
* 从备份数据恢复对象 - 修复版本
* @param {Object} layer 目标图层
* @private
*/
async _restoreObjectsFromBackup(layer) {
return new Promise((resolve, reject) => {
if (!this.originalLayerObjectsData || this.originalLayerObjectsData.length === 0) {
console.warn("没有对象备份数据");
resolve();
return;
}
try {
console.log(`开始从备份恢复 ${this.originalLayerObjectsData.length} 个对象...`);
// 使用fabric.util.enlivenObjects重建对象
fabric.util.enlivenObjects(this.originalLayerObjectsData, (restoredObjects) => {
try {
let successCount = 0;
restoredObjects.forEach((obj, index) => {
if (obj) {
// 确保对象有正确的属性
obj.set({
layerId: layer.id,
layerName: layer.name,
selectable: true,
evented: true,
visible: true,
});
// 添加到画布
this.canvas.add(obj);
// 添加到图层的fabricObjects数组
layer.fabricObjects.push(
obj.toObject([
"id",
"layerId",
"layerName",
"parentId",
"selectable",
"evented",
"visible",
])
);
successCount++;
console.log(
`恢复对象 ${index + 1}/${restoredObjects.length}: ${obj.id || obj.type}`
);
} else {
console.warn(`对象 ${index + 1} 恢复失败`);
}
});
// 重新渲染画布
this.canvas.renderAll();
console.log(
`✅ 成功恢复了 ${successCount}/${this.originalLayerObjectsData.length} 个对象`
);
resolve();
} catch (error) {
console.error("处理恢复的对象时出错:", error);
reject(error);
}
});
} catch (error) {
console.error("恢复对象时出错:", error);
reject(error);
}
});
}
/**
* 重做操作
* @returns {Promise<boolean>} 执行结果
*/
async redo() {
try {
console.log(`🔄 开始重做剪切选区操作`);
// 重做时重新执行整个操作
const result = await this.execute();
if (result) {
console.log(`✅ 剪切选区重做完成`);
return true;
} else {
console.log(`❌ 剪切选区重做失败`);
return false;
}
} catch (error) {
console.error("❌ 重做剪切选区失败:", error);
return false;
}
}
/**
* 获取命令信息
* @returns {Object} 命令详细信息
*/
getInfo() {
return {
name: this.name,
description: this.description,
newLayerId: this.newLayerId,
newLayerName: this.newLayerName,
groupId: this.groupId,
groupName: this.groupName,
executedCommandsCount: this.executedCommands.length,
hasGroupLayer: !!this.groupLayer,
sourceLayerId: this.sourceLayerId,
highResolutionEnabled: this.highResolutionEnabled,
baseResolutionScale: this.baseResolutionScale,
hasSerializedSelection: !!this.serializedSelectionObject,
selectionType: this.serializedSelectionObject?.type || null,
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._clonedSelectionObject) {
console.log("使用克隆的选区对象");
return await this._cloneObject(this._clonedSelectionObject);
}
// 尝试从选区管理器获取当前选区
const currentSelection = this.selectionManager.getSelectionObject();
if (currentSelection) {
console.log("从选区管理器获取到当前选区");
return currentSelection;
}
// 最后使用序列化数据恢复(备用方案)
if (!this.serializedSelectionObject) {
console.error("没有可用的选区对象数据");
return null;
}
console.log("从序列化数据恢复选区对象");
// 根据选区对象类型进行反序列化
return new Promise((resolve, reject) => {
const objectType = this.serializedSelectionObject.type;
if (objectType === "path") {
fabric.Path.fromObject(this.serializedSelectionObject, (path) => {
if (path) {
console.log("路径选区对象反序列化成功");
resolve(path);
} else {
reject(new Error("路径选区对象反序列化失败"));
}
});
} else if (objectType === "polygon") {
fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
if (polygon) {
console.log("多边形选区对象反序列化成功");
resolve(polygon);
} else {
reject(new Error("多边形选区对象反序列化失败"));
}
});
} else if (objectType === "rect") {
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
if (rect) {
console.log("矩形选区对象反序列化成功");
resolve(rect);
} else {
reject(new Error("矩形选区对象反序列化失败"));
}
});
} else if (objectType === "ellipse" || objectType === "circle") {
fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
if (ellipse) {
console.log("椭圆选区对象反序列化成功");
resolve(ellipse);
} else {
reject(new Error("椭圆选区对象反序列化失败"));
}
});
} else {
// 通用对象反序列化
fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
if (objects && objects.length > 0) {
console.log("通用选区对象反序列化成功");
resolve(objects[0]);
} else {
reject(new Error("通用选区对象反序列化失败"));
}
});
}
});
} catch (error) {
console.error("获取选区对象失败:", error);
return null;
}
}
/**
* 克隆fabric对象
* @param {Object} obj fabric对象
* @returns {Object} 克隆的对象
* @private
*/
async _cloneObject(obj) {
return new Promise((resolve, reject) => {
if (!obj) {
reject(new Error("对象无效,无法克隆"));
return;
}
try {
obj.clone((cloned) => {
if (cloned) {
resolve(cloned);
} else {
reject(new Error("对象克隆失败"));
}
});
} catch (error) {
reject(error);
}
});
}
/**
* 备份原图层状态
* @param {Object} sourceLayer 源图层
* @param {Array} sourceObjects 源对象数组
* @private
*/
async _backupOriginalLayer(sourceLayer, sourceObjects) {
try {
console.log("🔄 备份原图层状态...");
// 深度复制图层对象
this.originalLayerBackup = JSON.parse(JSON.stringify(sourceLayer));
// 备份图层中的所有对象数据
this.originalLayerObjectsData = [];
for (const obj of sourceObjects) {
if (obj && obj.toObject) {
const objData = obj.toObject([
"id",
"layerId",
"layerName",
"parentId",
"selectable",
"evented",
"visible",
]);
this.originalLayerObjectsData.push(objData);
}
}
this.originalLayerRasterized = false; // 标记原图层是否已栅格化
console.log(`✅ 原图层状态已备份,对象数量: ${this.originalLayerObjectsData.length}`);
} catch (error) {
console.error("备份原图层状态失败:", error);
throw error;
}
}
/**
* 对原图层进行栅格化处理,移除选区内容
* @param {Object} sourceLayer 源图层
* @param {Array} sourceObjects 源对象数组
* @param {Object} selectionObject 选区对象
* @private
*/
async _rasterizeOriginalLayerWithCutout(sourceLayer, sourceObjects, selectionObject) {
try {
console.log("=== 开始对原图层进行栅格化处理,移除选区内容 ===");
// 克隆选区对象,确保不影响原对象
const clonedSelectionObject = await this._cloneObject(selectionObject);
// 直接使用 ClearSelectionContentCommand 的正确实现,传入选区对象
const clearCommand = new ClearSelectionContentCommand({
canvas: this.canvas,
layerManager: this.layerManager,
selectionManager: this.selectionManager,
selectionObject: clonedSelectionObject, // 传入选区对象
highResolutionEnabled: this.highResolutionEnabled,
baseResolutionScale: this.baseResolutionScale,
});
// 临时设置目标图层为当前源图层
clearCommand.targetLayerId = sourceLayer.id;
clearCommand.targetLayer = sourceLayer;
// 执行清除选区内容命令
await clearCommand.execute();
// 获取清除后的栅格化图像
this.rasterizedOriginalImage = clearCommand.rasterizedImage;
this.originalLayerRasterized = true; // 标记原图层已栅格化
console.log("✅ 原图层栅格化处理完成,选区内容已移除");
} catch (error) {
console.error("原图层栅格化处理失败:", error);
throw error;
}
}
/**
* 创建反向剪切路径(选区外的内容)
* @param {Object} selectionObject 选区对象
* @param {Array} sourceObjects 源对象数组
* @returns {Object} 反向剪切路径
* @private
*/
async _createInvertedClipPath(selectionObject, sourceObjects) {
try {
// 计算所有源对象的边界
let minLeft = Infinity,
minTop = Infinity,
maxRight = -Infinity,
maxBottom = -Infinity;
sourceObjects.forEach((obj) => {
const bounds = obj.getBoundingRect(true, true);
minLeft = Math.min(minLeft, bounds.left);
minTop = Math.min(minTop, bounds.top);
maxRight = Math.max(maxRight, bounds.left + bounds.width);
maxBottom = Math.max(maxBottom, bounds.top + bounds.height);
});
// 扩展边界以确保完整覆盖
const padding = 50;
const canvasBounds = {
left: minLeft - padding,
top: minTop - padding,
width: maxRight - minLeft + padding * 2,
height: maxBottom - minTop + padding * 2,
};
// 创建画布大小的矩形
const canvasRect = new fabric.Rect({
left: canvasBounds.left,
top: canvasBounds.top,
width: canvasBounds.width,
height: canvasBounds.height,
fill: "rgba(255,255,255,1)",
stroke: "",
selectable: false,
evented: false,
absolutePositioned: true,
originX: "left",
originY: "top",
});
// 克隆选区对象作为要移除的部分
const cutoutPath = await this._cloneObject(selectionObject);
cutoutPath.set({
fill: "rgba(0,0,0,1)", // 黑色表示要移除的区域
stroke: "",
absolutePositioned: true,
globalCompositeOperation: "destination-out", // 关键使用destination-out混合模式
});
// 创建复合路径:画布矩形 - 选区 = 反向选区
const invertedClipPath = new fabric.Group([canvasRect, cutoutPath], {
selectable: false,
evented: false,
absolutePositioned: true,
});
return invertedClipPath;
} catch (error) {
console.error("创建反向剪切路径失败:", error);
return null;
}
}
}