836 lines
27 KiB
JavaScript
836 lines
27 KiB
JavaScript
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;
|
||
}
|
||
}
|
||
}
|