feat : 显示选区逻辑完成
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
|
||||
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";
|
||||
@@ -52,7 +56,9 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (selectionObject) {
|
||||
try {
|
||||
this._clonedSelectionObject = await this._cloneObject(selectionObject);
|
||||
this._clonedSelectionObject = await this._cloneObject(
|
||||
selectionObject
|
||||
);
|
||||
console.log("套索抠图:选区对象已克隆保存");
|
||||
} catch (error) {
|
||||
console.error("套索抠图:克隆选区对象失败:", error);
|
||||
@@ -195,6 +201,17 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
this.groupLayer.clippingMask = clippingMask.toObject(["id", "layerId"]); // 设置组图层的fabricObject为遮罩图像
|
||||
|
||||
this.groupLayer.children.push(selectLayer);
|
||||
|
||||
selectionObject.set({
|
||||
id: generateId("selectionObject-"),
|
||||
customType: "selectionObject",
|
||||
});
|
||||
|
||||
this.groupLayer.selectObject = selectionObject.toObject([
|
||||
"id",
|
||||
"customType",
|
||||
]);
|
||||
|
||||
// 插入新组图层
|
||||
this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer);
|
||||
|
||||
@@ -288,7 +305,10 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
await command.undo();
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
// 子命令撤销失败不中断整个撤销过程
|
||||
}
|
||||
}
|
||||
@@ -352,11 +372,16 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
// 递归获取图层及其子图层的所有对象
|
||||
const collectLayerObjects = (currentLayer) => {
|
||||
// 处理图层的fabricObjects
|
||||
if (currentLayer.fabricObjects && Array.isArray(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);
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === fabricRef.id
|
||||
);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -366,7 +391,9 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
|
||||
// 处理单个fabricObject(背景图层等)
|
||||
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
|
||||
const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === currentLayer.fabricObject.id
|
||||
);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -397,7 +424,11 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
* @returns {String} 抠图结果的DataURL
|
||||
* @private
|
||||
*/
|
||||
async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
|
||||
async _performCutoutWithRasterized(
|
||||
sourceObjects,
|
||||
selectionObject,
|
||||
selectionBounds
|
||||
) {
|
||||
try {
|
||||
console.log("=== 开始使用createRasterizedImage执行抠图 ===");
|
||||
console.log(`源对象数量: ${sourceObjects.length}`);
|
||||
@@ -436,7 +467,10 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
|
||||
// 如果createRasterizedImage失败,回退到原始方法
|
||||
console.log("⚠️ 回退到原始抠图方法...");
|
||||
return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
|
||||
return await this._performCutout(
|
||||
{ fabricObjects: sourceObjects },
|
||||
selectionObject
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,7 +501,9 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
|
||||
try {
|
||||
// 收集源图层中的可见对象
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter(
|
||||
(obj) => obj.visible
|
||||
);
|
||||
|
||||
if (visibleObjects.length === 0) {
|
||||
throw new Error("源图层没有可见对象");
|
||||
@@ -520,7 +556,10 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
let highResolutionScale = 1;
|
||||
if (this.highResolutionEnabled) {
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
|
||||
highResolutionScale = Math.max(
|
||||
this.baseResolutionScale,
|
||||
devicePixelRatio * 2
|
||||
);
|
||||
}
|
||||
|
||||
// 创建用于导出的高分辨率canvas
|
||||
@@ -531,8 +570,12 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
colorSpace: "srgb",
|
||||
});
|
||||
|
||||
const actualWidth = Math.round(renderBounds.width * highResolutionScale);
|
||||
const actualHeight = Math.round(renderBounds.height * highResolutionScale);
|
||||
const actualWidth = Math.round(
|
||||
renderBounds.width * highResolutionScale
|
||||
);
|
||||
const actualHeight = Math.round(
|
||||
renderBounds.height * highResolutionScale
|
||||
);
|
||||
|
||||
exportCanvas.width = actualWidth;
|
||||
exportCanvas.height = actualHeight;
|
||||
@@ -741,14 +784,17 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "polygon") {
|
||||
fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
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) {
|
||||
@@ -759,24 +805,30 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "ellipse" || objectType === "circle") {
|
||||
fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
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("通用选区对象反序列化失败"));
|
||||
fabric.util.enlivenObjects(
|
||||
[this.serializedSelectionObject],
|
||||
(objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
|
||||
import {
|
||||
createLayer,
|
||||
findInChildLayers,
|
||||
LayerType,
|
||||
} from "../utils/layerHelper.js";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage.js";
|
||||
import { CompositeCommand, Command } from "./Command.js";
|
||||
import { CreateImageLayerCommand, RemoveLayerCommand } from "./LayerCommands.js";
|
||||
import {
|
||||
CreateImageLayerCommand,
|
||||
RemoveLayerCommand,
|
||||
} from "./LayerCommands.js";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { generateId } from "../utils/helper.js";
|
||||
|
||||
@@ -58,7 +65,9 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (selectionObject) {
|
||||
try {
|
||||
this._clonedSelectionObject = await this._cloneObject(selectionObject);
|
||||
this._clonedSelectionObject = await this._cloneObject(
|
||||
selectionObject
|
||||
);
|
||||
console.log("套索抠图:选区对象已克隆保存");
|
||||
} catch (error) {
|
||||
console.error("套索抠图:克隆选区对象失败:", error);
|
||||
@@ -94,7 +103,14 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
const sourceObjects = this._getLayerObjects(activeLayer);
|
||||
this.originalCanvasObjects = sourceObjects; // 保存真实对象引用
|
||||
this.originalFabricObjects = sourceObjects.map((obj) =>
|
||||
obj.toObject(["id", "layerId", "layerName", "parentId", "type", "custom"])
|
||||
obj.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
"type",
|
||||
"custom",
|
||||
])
|
||||
);
|
||||
|
||||
console.log(
|
||||
@@ -196,6 +212,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
layers: this.layerManager.layers,
|
||||
layerId: this.originalLayer.id,
|
||||
activeLayerId: this.layerManager.activeLayerId,
|
||||
layerManager: this.layerManager,
|
||||
});
|
||||
|
||||
// 执行删除原图层命令
|
||||
@@ -337,7 +354,10 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
await command.undo();
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
// 子命令撤销失败不中断整个撤销过程
|
||||
}
|
||||
}
|
||||
@@ -414,11 +434,16 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
// 递归获取图层及其子图层的所有对象
|
||||
const collectLayerObjects = (currentLayer) => {
|
||||
// 处理图层的fabricObjects
|
||||
if (currentLayer.fabricObjects && Array.isArray(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);
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === fabricRef.id
|
||||
);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -428,7 +453,9 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 处理单个fabricObject(背景图层等)
|
||||
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
|
||||
const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === currentLayer.fabricObject.id
|
||||
);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -459,7 +486,11 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
* @returns {String} 抠图结果的DataURL
|
||||
* @private
|
||||
*/
|
||||
async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
|
||||
async _performCutoutWithRasterized(
|
||||
sourceObjects,
|
||||
selectionObject,
|
||||
selectionBounds
|
||||
) {
|
||||
try {
|
||||
console.log("=== 开始使用createRasterizedImage执行抠图 ===");
|
||||
console.log(`源对象数量: ${sourceObjects.length}`);
|
||||
@@ -498,7 +529,10 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 如果createRasterizedImage失败,回退到原始方法
|
||||
console.log("⚠️ 回退到原始抠图方法...");
|
||||
return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
|
||||
return await this._performCutout(
|
||||
{ fabricObjects: sourceObjects },
|
||||
selectionObject
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,7 +563,9 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
try {
|
||||
// 收集源图层中的可见对象
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter(
|
||||
(obj) => obj.visible
|
||||
);
|
||||
|
||||
if (visibleObjects.length === 0) {
|
||||
throw new Error("源图层没有可见对象");
|
||||
@@ -582,7 +618,10 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
let highResolutionScale = 1;
|
||||
if (this.highResolutionEnabled) {
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
|
||||
highResolutionScale = Math.max(
|
||||
this.baseResolutionScale,
|
||||
devicePixelRatio * 2
|
||||
);
|
||||
}
|
||||
|
||||
// 创建用于导出的高分辨率canvas
|
||||
@@ -593,8 +632,12 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
colorSpace: "srgb",
|
||||
});
|
||||
|
||||
const actualWidth = Math.round(renderBounds.width * highResolutionScale);
|
||||
const actualHeight = Math.round(renderBounds.height * highResolutionScale);
|
||||
const actualWidth = Math.round(
|
||||
renderBounds.width * highResolutionScale
|
||||
);
|
||||
const actualHeight = Math.round(
|
||||
renderBounds.height * highResolutionScale
|
||||
);
|
||||
|
||||
exportCanvas.width = actualWidth;
|
||||
exportCanvas.height = actualHeight;
|
||||
@@ -803,14 +846,17 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "polygon") {
|
||||
fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
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) {
|
||||
@@ -821,24 +867,30 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "ellipse" || objectType === "circle") {
|
||||
fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
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("通用选区对象反序列化失败"));
|
||||
fabric.util.enlivenObjects(
|
||||
[this.serializedSelectionObject],
|
||||
(objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -862,7 +914,11 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 1. 恢复图层到原位置
|
||||
if (this.originalLayerIndex !== -1) {
|
||||
this.layerManager.layers.value.splice(this.originalLayerIndex, 0, this.originalLayer);
|
||||
this.layerManager.layers.value.splice(
|
||||
this.originalLayerIndex,
|
||||
0,
|
||||
this.originalLayer
|
||||
);
|
||||
} else {
|
||||
// 如果没有保存索引,添加到末尾
|
||||
this.layerManager.layers.value.push(this.originalLayer);
|
||||
@@ -870,7 +926,9 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 2. 恢复fabric对象到画布
|
||||
if (this.originalFabricObjects.length > 0) {
|
||||
console.log(`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`);
|
||||
console.log(
|
||||
`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`
|
||||
);
|
||||
|
||||
// 使用fabric.util.enlivenObjects批量反序列化对象
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -1019,7 +1077,12 @@ export class ClearSelectionCommand extends Command {
|
||||
// 序列化选区对象和相关状态
|
||||
this.originalSelectionState = {
|
||||
// 选区对象的序列化数据
|
||||
selectionObjectData: selectionObject.toObject(["id", "layerId", "layerName", "parentId"]),
|
||||
selectionObjectData: selectionObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
]),
|
||||
// 选区路径数据
|
||||
selectionPath: this.selectionManager.getSelectionPath(),
|
||||
// 羽化值
|
||||
@@ -1082,8 +1145,13 @@ export class ClearSelectionCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
const { selectionObjectData, featherAmount, selectionStyle, position, managerState } =
|
||||
this.originalSelectionState;
|
||||
const {
|
||||
selectionObjectData,
|
||||
featherAmount,
|
||||
selectionStyle,
|
||||
position,
|
||||
managerState,
|
||||
} = this.originalSelectionState;
|
||||
|
||||
// 根据选区对象类型进行反序列化
|
||||
const objectType = selectionObjectData.type;
|
||||
@@ -1129,7 +1197,9 @@ export class ClearSelectionCommand extends Command {
|
||||
|
||||
// 恢复阴影(羽化效果)
|
||||
if (selectionStyle.shadow) {
|
||||
restoredObject.shadow = new fabric.Shadow(selectionStyle.shadow);
|
||||
restoredObject.shadow = new fabric.Shadow(
|
||||
selectionStyle.shadow
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1214,7 +1284,9 @@ export class ClearSelectionCommand extends Command {
|
||||
// 验证基本属性
|
||||
const originalId = this.originalSelectionState.selectionId;
|
||||
if (currentSelection.id !== originalId) {
|
||||
console.warn(`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`);
|
||||
console.warn(
|
||||
`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1222,7 +1294,9 @@ export class ClearSelectionCommand extends Command {
|
||||
const currentFeather = this.selectionManager.getFeatherAmount();
|
||||
const originalFeather = this.originalSelectionState.featherAmount;
|
||||
if (currentFeather !== originalFeather) {
|
||||
console.warn(`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`);
|
||||
console.warn(
|
||||
`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { createLayer, findInChildLayers, LayerType, OperationType } from "../utils/layerHelper.js";
|
||||
import {
|
||||
createLayer,
|
||||
findInChildLayers,
|
||||
LayerType,
|
||||
OperationType,
|
||||
} from "../utils/layerHelper.js";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage.js";
|
||||
import { CompositeCommand, Command } from "./Command.js";
|
||||
import { CreateImageLayerCommand, RemoveLayerCommand } from "./LayerCommands.js";
|
||||
import {
|
||||
CreateImageLayerCommand,
|
||||
RemoveLayerCommand,
|
||||
} from "./LayerCommands.js";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { generateId } from "../utils/helper.js";
|
||||
import { ToolCommand } from "./ToolCommands.js";
|
||||
@@ -61,7 +69,9 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (selectionObject) {
|
||||
try {
|
||||
this._clonedSelectionObject = await this._cloneObject(selectionObject);
|
||||
this._clonedSelectionObject = await this._cloneObject(
|
||||
selectionObject
|
||||
);
|
||||
console.log("套索抠图:选区对象已克隆保存");
|
||||
} catch (error) {
|
||||
console.error("套索抠图:克隆选区对象失败:", error);
|
||||
@@ -97,7 +107,14 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
const sourceObjects = this._getLayerObjects(activeLayer);
|
||||
this.originalCanvasObjects = sourceObjects; // 保存真实对象引用
|
||||
this.originalFabricObjects = sourceObjects.map((obj) =>
|
||||
obj.toObject(["id", "layerId", "layerName", "parentId", "type", "custom"])
|
||||
obj.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
"type",
|
||||
"custom",
|
||||
])
|
||||
);
|
||||
|
||||
console.log(
|
||||
@@ -245,7 +262,8 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
// canvas: this.canvas,
|
||||
// layers: this.layerManager.layers,
|
||||
// layerId: this.originalLayer.id,
|
||||
// activeLayerId: this.layerManager.activeLayerId,l
|
||||
// activeLayerId: this.layerManager.activeLayerId,
|
||||
// layerManager: this.layerManager,
|
||||
// });
|
||||
|
||||
// // 执行删除原图层命令
|
||||
@@ -254,6 +272,16 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
this.groupLayer.clippingMask = clippingMask.toObject(["id", "layerId"]); // 设置组图层的fabricObject为遮罩图像
|
||||
|
||||
selectionObject.set({
|
||||
id: generateId("selectionObject-"),
|
||||
customType: "selectionObject",
|
||||
});
|
||||
|
||||
this.groupLayer.selectObject = selectionObject.toObject([
|
||||
"id",
|
||||
"customType",
|
||||
]);
|
||||
|
||||
this.groupLayer.children.push(selectLayer);
|
||||
// 插入新组图层
|
||||
this.layerManager.layers.value.splice(topLayerIndex, 0, this.groupLayer);
|
||||
@@ -368,7 +396,10 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
await command.undo();
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
// 子命令撤销失败不中断整个撤销过程
|
||||
}
|
||||
}
|
||||
@@ -445,11 +476,16 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
// 递归获取图层及其子图层的所有对象
|
||||
const collectLayerObjects = (currentLayer) => {
|
||||
// 处理图层的fabricObjects
|
||||
if (currentLayer.fabricObjects && Array.isArray(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);
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === fabricRef.id
|
||||
);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -459,7 +495,9 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 处理单个fabricObject(背景图层等)
|
||||
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
|
||||
const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === currentLayer.fabricObject.id
|
||||
);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -490,7 +528,11 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
* @returns {String} 抠图结果的DataURL
|
||||
* @private
|
||||
*/
|
||||
async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
|
||||
async _performCutoutWithRasterized(
|
||||
sourceObjects,
|
||||
selectionObject,
|
||||
selectionBounds
|
||||
) {
|
||||
try {
|
||||
console.log("=== 开始使用createRasterizedImage执行抠图 ===");
|
||||
console.log(`源对象数量: ${sourceObjects.length}`);
|
||||
@@ -529,7 +571,10 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 如果createRasterizedImage失败,回退到原始方法
|
||||
console.log("⚠️ 回退到原始抠图方法...");
|
||||
return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
|
||||
return await this._performCutout(
|
||||
{ fabricObjects: sourceObjects },
|
||||
selectionObject
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +605,9 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
try {
|
||||
// 收集源图层中的可见对象
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter(
|
||||
(obj) => obj.visible
|
||||
);
|
||||
|
||||
if (visibleObjects.length === 0) {
|
||||
throw new Error("源图层没有可见对象");
|
||||
@@ -613,7 +660,10 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
let highResolutionScale = 1;
|
||||
if (this.highResolutionEnabled) {
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
|
||||
highResolutionScale = Math.max(
|
||||
this.baseResolutionScale,
|
||||
devicePixelRatio * 2
|
||||
);
|
||||
}
|
||||
|
||||
// 创建用于导出的高分辨率canvas
|
||||
@@ -624,8 +674,12 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
colorSpace: "srgb",
|
||||
});
|
||||
|
||||
const actualWidth = Math.round(renderBounds.width * highResolutionScale);
|
||||
const actualHeight = Math.round(renderBounds.height * highResolutionScale);
|
||||
const actualWidth = Math.round(
|
||||
renderBounds.width * highResolutionScale
|
||||
);
|
||||
const actualHeight = Math.round(
|
||||
renderBounds.height * highResolutionScale
|
||||
);
|
||||
|
||||
exportCanvas.width = actualWidth;
|
||||
exportCanvas.height = actualHeight;
|
||||
@@ -834,14 +888,17 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "polygon") {
|
||||
fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
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) {
|
||||
@@ -852,24 +909,30 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "ellipse" || objectType === "circle") {
|
||||
fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
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("通用选区对象反序列化失败"));
|
||||
fabric.util.enlivenObjects(
|
||||
[this.serializedSelectionObject],
|
||||
(objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -893,7 +956,11 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 1. 恢复图层到原位置
|
||||
if (this.originalLayerIndex !== -1) {
|
||||
this.layerManager.layers.value.splice(this.originalLayerIndex, 0, this.originalLayer);
|
||||
this.layerManager.layers.value.splice(
|
||||
this.originalLayerIndex,
|
||||
0,
|
||||
this.originalLayer
|
||||
);
|
||||
} else {
|
||||
// 如果没有保存索引,添加到末尾
|
||||
this.layerManager.layers.value.push(this.originalLayer);
|
||||
@@ -901,7 +968,9 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 2. 恢复fabric对象到画布
|
||||
if (this.originalFabricObjects.length > 0) {
|
||||
console.log(`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`);
|
||||
console.log(
|
||||
`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`
|
||||
);
|
||||
|
||||
// 使用fabric.util.enlivenObjects批量反序列化对象
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -1050,7 +1119,12 @@ export class ClearSelectionCommand extends Command {
|
||||
// 序列化选区对象和相关状态
|
||||
this.originalSelectionState = {
|
||||
// 选区对象的序列化数据
|
||||
selectionObjectData: selectionObject.toObject(["id", "layerId", "layerName", "parentId"]),
|
||||
selectionObjectData: selectionObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
]),
|
||||
// 选区路径数据
|
||||
selectionPath: this.selectionManager.getSelectionPath(),
|
||||
// 羽化值
|
||||
@@ -1113,8 +1187,13 @@ export class ClearSelectionCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
const { selectionObjectData, featherAmount, selectionStyle, position, managerState } =
|
||||
this.originalSelectionState;
|
||||
const {
|
||||
selectionObjectData,
|
||||
featherAmount,
|
||||
selectionStyle,
|
||||
position,
|
||||
managerState,
|
||||
} = this.originalSelectionState;
|
||||
|
||||
// 根据选区对象类型进行反序列化
|
||||
const objectType = selectionObjectData.type;
|
||||
@@ -1160,7 +1239,9 @@ export class ClearSelectionCommand extends Command {
|
||||
|
||||
// 恢复阴影(羽化效果)
|
||||
if (selectionStyle.shadow) {
|
||||
restoredObject.shadow = new fabric.Shadow(selectionStyle.shadow);
|
||||
restoredObject.shadow = new fabric.Shadow(
|
||||
selectionStyle.shadow
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1245,7 +1326,9 @@ export class ClearSelectionCommand extends Command {
|
||||
// 验证基本属性
|
||||
const originalId = this.originalSelectionState.selectionId;
|
||||
if (currentSelection.id !== originalId) {
|
||||
console.warn(`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`);
|
||||
console.warn(
|
||||
`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1253,7 +1336,9 @@ export class ClearSelectionCommand extends Command {
|
||||
const currentFeather = this.selectionManager.getFeatherAmount();
|
||||
const originalFeather = this.originalSelectionState.featherAmount;
|
||||
if (currentFeather !== originalFeather) {
|
||||
console.warn(`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`);
|
||||
console.warn(
|
||||
`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,9 +25,13 @@ export class TransformCommand extends Command {
|
||||
this.layers = options.layers || null;
|
||||
this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID
|
||||
|
||||
const targetObject = findObjectById(this.canvas, this.objectId)?.object || null;
|
||||
const targetObject =
|
||||
findObjectById(this.canvas, this.objectId)?.object || null;
|
||||
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, targetObject?.layerId);
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
targetObject?.layerId
|
||||
);
|
||||
|
||||
this.layer = layer;
|
||||
this.parent = parent;
|
||||
@@ -107,7 +111,7 @@ export class TransformCommand extends Command {
|
||||
if (
|
||||
this.parent &&
|
||||
this.parent?.clippingMask &&
|
||||
this.parent?.children?.length === 1 &&
|
||||
// this.parent?.children?.length === 1 &&
|
||||
this.isSginleObject
|
||||
) {
|
||||
// 计算对象的变换位置
|
||||
@@ -116,6 +120,20 @@ export class TransformCommand extends Command {
|
||||
|
||||
this.parent.clippingMask.left -= moveLeft;
|
||||
this.parent.clippingMask.top -= moveTop;
|
||||
if (this.parent.selectObject) {
|
||||
// 如果有选区 则选区位置也要更新
|
||||
this.parent.selectObject.left = this.parent.clippingMask.left;
|
||||
this.parent.selectObject.top = this.parent.clippingMask.top;
|
||||
const { object } = findObjectById(
|
||||
this.canvas,
|
||||
this.parent.selectObject?.id
|
||||
);
|
||||
object?.set({
|
||||
left: this.parent.clippingMask.left,
|
||||
top: this.parent.clippingMask.top,
|
||||
});
|
||||
object?.setCoords();
|
||||
}
|
||||
|
||||
// 重新创建遮罩对象
|
||||
const clippingMaskFabricObject = await restoreFabricObject(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { findObjectById } from "../utils/helper";
|
||||
import { findLayerRecursively, isGroupLayer } from "../utils/layerHelper";
|
||||
import { restoreFabricObject } from "../utils/objectHelper";
|
||||
import { Command } from "./Command";
|
||||
@@ -54,8 +55,12 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
}) || this.target?.getBoundingRect?.(true, true);
|
||||
|
||||
this.originalSelectionPosition = {
|
||||
left: this.isSginleObject ? this.target.left : this.activeSelection.left || 0,
|
||||
top: this.isSginleObject ? this.target.top : this.activeSelection.top || 0,
|
||||
left: this.isSginleObject
|
||||
? this.target.left
|
||||
: this.activeSelection.left || 0,
|
||||
top: this.isSginleObject
|
||||
? this.target.top
|
||||
: this.activeSelection.top || 0,
|
||||
};
|
||||
|
||||
console.log(
|
||||
@@ -74,6 +79,21 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
this.layer.clippingMask.left = newLeft;
|
||||
this.layer.clippingMask.top = newTop;
|
||||
|
||||
if (this.layer.selectObject) {
|
||||
// 如果有选区 则选区位置也要更新
|
||||
this.layer.selectObject.left = this.layer.clippingMask.left;
|
||||
this.layer.selectObject.top = this.layer.clippingMask.top;
|
||||
const { object } = findObjectById(
|
||||
this.canvas,
|
||||
this.layer.selectObject?.id
|
||||
);
|
||||
object?.set({
|
||||
left: this.layer.clippingMask.left,
|
||||
top: this.layer.clippingMask.top,
|
||||
});
|
||||
object?.setCoords();
|
||||
}
|
||||
|
||||
this.newMaskPosition = {
|
||||
left: newLeft,
|
||||
top: newTop,
|
||||
@@ -112,6 +132,21 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
layer.clippingMask.left = this.originalMaskPosition.left;
|
||||
layer.clippingMask.top = this.originalMaskPosition.top;
|
||||
|
||||
if (this.layer.selectObject) {
|
||||
// 如果有选区 则选区位置也要更新
|
||||
this.layer.selectObject.left = this.originalMaskPosition.left;
|
||||
this.layer.selectObject.top = this.originalMaskPosition.top;
|
||||
const { object } = findObjectById(
|
||||
this.canvas,
|
||||
this.layer.selectObject?.id
|
||||
);
|
||||
object?.set({
|
||||
left: this.originalMaskPosition.left,
|
||||
top: this.originalMaskPosition.top,
|
||||
});
|
||||
object?.setCoords();
|
||||
}
|
||||
|
||||
// 更新所有使用此遮罩的子图层对象
|
||||
await this._updateChildObjectsClipPath(layer, true);
|
||||
// await this.layerManager.updateLayersObjectsInteractivity();
|
||||
@@ -147,6 +182,21 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
layer.clippingMask.left = newLeft;
|
||||
layer.clippingMask.top = newTop;
|
||||
|
||||
if (this.layer.selectObject) {
|
||||
// 如果有选区 则选区位置也要更新
|
||||
this.layer.selectObject.left = this.layer.clippingMask.left;
|
||||
this.layer.selectObject.top = this.layer.clippingMask.top;
|
||||
const { object } = findObjectById(
|
||||
this.canvas,
|
||||
this.layer.selectObject?.id
|
||||
);
|
||||
object?.set({
|
||||
left: this.layer.clippingMask.left,
|
||||
top: this.layer.clippingMask.top,
|
||||
});
|
||||
object?.setCoords();
|
||||
}
|
||||
|
||||
// 更新所有使用此遮罩的子图层对象(不需要等待)
|
||||
this._updateChildObjectsClipPath(layer);
|
||||
|
||||
@@ -173,7 +223,10 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
|
||||
try {
|
||||
// 重新创建遮罩对象
|
||||
const clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas);
|
||||
const clippingMaskFabricObject = await restoreFabricObject(
|
||||
layer.clippingMask,
|
||||
this.canvas
|
||||
);
|
||||
|
||||
if (!clippingMaskFabricObject) {
|
||||
console.warn("无法恢复遮罩对象");
|
||||
@@ -192,7 +245,9 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
layer.children.forEach((childLayer) => {
|
||||
// 更新 fabricObjects 中的对象
|
||||
childLayer.fabricObjects?.forEach((obj) => {
|
||||
const fabricObject = this.canvas.getObjects().find((o) => o.id === obj.id);
|
||||
const fabricObject = this.canvas
|
||||
.getObjects()
|
||||
.find((o) => o.id === obj.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.clipPath = clippingMaskFabricObject;
|
||||
fabricObject.dirty = true;
|
||||
@@ -215,7 +270,9 @@ export class UpdateGroupMaskPositionCommand extends Command {
|
||||
|
||||
if (layer?.fill) {
|
||||
// 更新组图层的填充颜色
|
||||
const fabricObject = this.canvas.getObjects().find((o) => o.id === layer?.fill.id);
|
||||
const fabricObject = this.canvas
|
||||
.getObjects()
|
||||
.find((o) => o.id === layer?.fill.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.clipPath = clippingMaskFabricObject;
|
||||
fabricObject.dirty = true;
|
||||
|
||||
Reference in New Issue
Block a user