feat: 优化选区功能,修复部分bug
This commit is contained in:
@@ -35,11 +35,13 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
|||||||
this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用
|
this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用
|
||||||
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
|
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
|
||||||
|
|
||||||
this.groupId = options.groupId || `cutout-group-${Date.now()}`;
|
this.groupId = options.groupId || generateId("lasso-copy-group-");
|
||||||
this.groupName = options.groupName || `选区组`;
|
this.groupName = options.groupName || `选区组`;
|
||||||
this.groupLayer = null; // 新增:保存组图层的引用
|
this.groupLayer = null; // 新增:保存组图层的引用
|
||||||
this.originalLayersLength = 0; // 新增:保存原始图层数量
|
this.originalLayersLength = 0; // 新增:保存原始图层数量
|
||||||
|
|
||||||
|
this.clippingMaskId = generateId("clipping-mask-");
|
||||||
|
|
||||||
// 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象
|
// 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象
|
||||||
this._clonedSelectionObject = null;
|
this._clonedSelectionObject = null;
|
||||||
this._initializeClonedSelection();
|
this._initializeClonedSelection();
|
||||||
@@ -105,6 +107,16 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clippingMask = fabric.util.object.clone(selectionObject);
|
||||||
|
clippingMask.set({
|
||||||
|
id: this.clippingMaskId,
|
||||||
|
selectable: false,
|
||||||
|
evented: false,
|
||||||
|
hasControls: false,
|
||||||
|
// layerId: this.groupId,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
|
||||||
// 获取选区边界信息用于后续定位
|
// 获取选区边界信息用于后续定位
|
||||||
const selectionBounds = selectionObject.getBoundingRect(true, true);
|
const selectionBounds = selectionObject.getBoundingRect(true, true);
|
||||||
|
|
||||||
@@ -185,12 +197,8 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
|||||||
selectLayer.fabricObjects = [
|
selectLayer.fabricObjects = [
|
||||||
this.fabricImage.toObject("id", "layerId", "layerName", "parentId"),
|
this.fabricImage.toObject("id", "layerId", "layerName", "parentId"),
|
||||||
];
|
];
|
||||||
this.groupLayer.clippingMask = this.fabricImage.toObject(
|
|
||||||
"id",
|
this.groupLayer.clippingMask = clippingMask.toObject(["id", "layerId"]); // 设置组图层的fabricObject为遮罩图像
|
||||||
"layerId",
|
|
||||||
"layerName",
|
|
||||||
"parentId"
|
|
||||||
); // 设置组图层的fabricObject为遮罩图像
|
|
||||||
|
|
||||||
this.groupLayer.children.push(selectLayer);
|
this.groupLayer.children.push(selectLayer);
|
||||||
// 插入新组图层
|
// 插入新组图层
|
||||||
|
|||||||
1323
src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js
Normal file
1323
src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.bak.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ import { generateId } from "../utils/helper.js";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 套索抠图命令
|
* 套索抠图命令
|
||||||
* 实现将选区内容抠图到新图层的功能
|
* 实现将选区内容到新图层遮罩
|
||||||
*/
|
*/
|
||||||
export class LassoCutoutCommand extends CompositeCommand {
|
export class LassoCutoutCommand extends CompositeCommand {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
@@ -36,8 +36,11 @@ export class LassoCutoutCommand extends CompositeCommand {
|
|||||||
this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用
|
this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用
|
||||||
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
|
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
|
||||||
|
|
||||||
this.groupId = options.groupId || `cutout-group-${Date.now()}`;
|
this.groupId = options.groupId || generateId("lasso-group-");
|
||||||
this.groupName = options.groupName || `选区组`;
|
this.groupName = options.groupName || `选区组`;
|
||||||
|
|
||||||
|
this.clippingMaskId = generateId("clipping-mask-");
|
||||||
|
|
||||||
this.groupLayer = null; // 新增:保存组图层的引用
|
this.groupLayer = null; // 新增:保存组图层的引用
|
||||||
this.originalLayersLength = 0; // 新增:保存原始图层数量
|
this.originalLayersLength = 0; // 新增:保存原始图层数量
|
||||||
|
|
||||||
@@ -51,6 +54,8 @@ export class LassoCutoutCommand extends CompositeCommand {
|
|||||||
this.originalFabricObjects = []; // 保存原图层的所有fabric对象(序列化)
|
this.originalFabricObjects = []; // 保存原图层的所有fabric对象(序列化)
|
||||||
this.originalCanvasObjects = []; // 保存从画布中获取的真实fabric对象
|
this.originalCanvasObjects = []; // 保存从画布中获取的真实fabric对象
|
||||||
this._initializeOriginalLayerInfo();
|
this._initializeOriginalLayerInfo();
|
||||||
|
|
||||||
|
this.oldActiveLayerId = this.layerManager.activeLayerId.value; // 保存旧的活动图层ID
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,14 +149,24 @@ export class LassoCutoutCommand extends CompositeCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取源图层的所有对象(包括子图层)
|
// 获取源图层的所有对象(包括子图层)
|
||||||
const sourceObjects = this._getLayerObjects(sourceLayer);
|
// const sourceObjects = this._getLayerObjects(sourceLayer);
|
||||||
if (sourceObjects.length === 0) {
|
// if (sourceObjects.length === 0) {
|
||||||
console.error("无法执行套索抠图:源图层没有可见对象");
|
// console.error("无法执行套索抠图:源图层没有可见对象");
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
const clippingMask = fabric.util.object.clone(selectionObject);
|
||||||
|
clippingMask.set({
|
||||||
|
id: this.clippingMaskId,
|
||||||
|
selectable: false,
|
||||||
|
evented: false,
|
||||||
|
hasControls: false,
|
||||||
|
// layerId: this.groupId,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
|
||||||
// 获取选区边界信息用于后续定位
|
// 获取选区边界信息用于后续定位
|
||||||
const selectionBounds = selectionObject.getBoundingRect(true, true);
|
// const selectionBounds = selectionObject.getBoundingRect(true, true);
|
||||||
|
|
||||||
// 使用createRasterizedImage执行抠图操作
|
// 使用createRasterizedImage执行抠图操作
|
||||||
// this.cutoutImageUrl = await this._performCutoutWithRasterized(
|
// this.cutoutImageUrl = await this._performCutoutWithRasterized(
|
||||||
@@ -164,46 +179,34 @@ export class LassoCutoutCommand extends CompositeCommand {
|
|||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
this.fabricImage = await this._performCutoutWithRasterized(
|
// this.fabricImage = await this._performCutoutWithRasterized(
|
||||||
sourceObjects,
|
// sourceObjects,
|
||||||
selectionObject,
|
// selectionObject,
|
||||||
selectionBounds
|
// selectionBounds
|
||||||
);
|
// );
|
||||||
|
|
||||||
// // 创建fabric图像对象,传递选区边界信息
|
// // 创建fabric图像对象,传递选区边界信息
|
||||||
// this.fabricImage = await this._createFabricImage(
|
// this.fabricImage = await this._createFabricImage(
|
||||||
// this.cutoutImageUrl,
|
// this.cutoutImageUrl,
|
||||||
// selectionBounds
|
// selectionBounds
|
||||||
// );
|
// );
|
||||||
if (!this.fabricImage) {
|
// if (!this.fabricImage) {
|
||||||
console.error("创建图像对象失败");
|
// console.error("创建图像对象失败");
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 1. 创建图像图层命令
|
// 1. 创建图像图层命令
|
||||||
const createImageLayerCmd = new CreateImageLayerCommand({
|
// const createImageLayerCmd = new CreateImageLayerCommand({
|
||||||
layerManager: this.layerManager,
|
// layerManager: this.layerManager,
|
||||||
fabricImage: this.fabricImage,
|
// fabricImage: this.fabricImage,
|
||||||
toolManager: this.toolManager,
|
// toolManager: this.toolManager,
|
||||||
layerName: this.newLayerName,
|
// layerName: this.newLayerName,
|
||||||
});
|
// });
|
||||||
|
|
||||||
// 执行创建图像图层命令
|
// // 执行创建图像图层命令
|
||||||
const result = await createImageLayerCmd.execute();
|
// const result = await createImageLayerCmd.execute();
|
||||||
this.newLayerId = createImageLayerCmd.newLayerId;
|
// this.newLayerId = createImageLayerCmd.newLayerId;
|
||||||
this.executedCommands.push(createImageLayerCmd);
|
// this.executedCommands.push(createImageLayerCmd);
|
||||||
|
|
||||||
// 2. 删除原图层命令
|
|
||||||
const removeOriginalLayerCmd = new RemoveLayerCommand({
|
|
||||||
canvas: this.canvas,
|
|
||||||
layers: this.layerManager.layers,
|
|
||||||
layerId: this.originalLayer.id,
|
|
||||||
activeLayerId: this.layerManager.activeLayerId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 执行删除原图层命令
|
|
||||||
await removeOriginalLayerCmd.execute();
|
|
||||||
this.executedCommands.push(removeOriginalLayerCmd);
|
|
||||||
|
|
||||||
// 3. 清除选区命令
|
// 3. 清除选区命令
|
||||||
const clearSelectionCmd = new ClearSelectionCommand({
|
const clearSelectionCmd = new ClearSelectionCommand({
|
||||||
@@ -216,10 +219,10 @@ export class LassoCutoutCommand extends CompositeCommand {
|
|||||||
this.executedCommands.push(clearSelectionCmd);
|
this.executedCommands.push(clearSelectionCmd);
|
||||||
|
|
||||||
const topLayerIndex = this.layerManager.layers.value.findIndex(
|
const topLayerIndex = this.layerManager.layers.value.findIndex(
|
||||||
(layer) => layer.id === this.newLayerId
|
(layer) => layer.id === this.originalLayer.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectLayer = this.layerManager.layers.value[topLayerIndex];
|
// const selectLayer = this.layerManager.layers.value[topLayerIndex];
|
||||||
|
|
||||||
// 创建新的组图层
|
// 创建新的组图层
|
||||||
this.groupLayer = createLayer({
|
this.groupLayer = createLayer({
|
||||||
@@ -233,28 +236,47 @@ export class LassoCutoutCommand extends CompositeCommand {
|
|||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fabricImage.set({
|
// this.fabricImage.set({
|
||||||
selectable: true,
|
// selectable: true,
|
||||||
evented: true,
|
// evented: true,
|
||||||
|
// });
|
||||||
|
|
||||||
|
const selectLayer = createLayer({
|
||||||
|
name: `选区空图层`,
|
||||||
|
type: LayerType.EMPTY,
|
||||||
|
visible: true,
|
||||||
|
locked: false,
|
||||||
|
opacity: 1.0,
|
||||||
|
fabricObjects: [],
|
||||||
|
children: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
selectLayer.parentId = this.groupId; // 设置新图层的parentId为组图层ID
|
selectLayer.parentId = this.groupId; // 设置新图层的parentId为组图层ID
|
||||||
selectLayer.fabricObjects = [
|
// selectLayer.fabricObjects = [
|
||||||
this.fabricImage.toObject("id", "layerId", "layerName", "parentId"),
|
// this.fabricImage.toObject("id", "layerId", "layerName", "parentId"),
|
||||||
];
|
// ];
|
||||||
this.groupLayer.clippingMask = this.fabricImage.toObject(
|
// 2. 删除原图层命令
|
||||||
"id",
|
const removeOriginalLayerCmd = new RemoveLayerCommand({
|
||||||
"layerId",
|
canvas: this.canvas,
|
||||||
"layerName",
|
layers: this.layerManager.layers,
|
||||||
"parentId"
|
layerId: this.originalLayer.id,
|
||||||
); // 设置组图层的fabricObject为遮罩图像
|
activeLayerId: this.layerManager.activeLayerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行删除原图层命令
|
||||||
|
await removeOriginalLayerCmd.execute();
|
||||||
|
this.executedCommands.push(removeOriginalLayerCmd);
|
||||||
|
|
||||||
|
this.groupLayer.clippingMask = clippingMask.toObject(["id", "layerId"]); // 设置组图层的fabricObject为遮罩图像
|
||||||
|
|
||||||
this.groupLayer.children.push(selectLayer);
|
this.groupLayer.children.push(selectLayer);
|
||||||
// 插入新组图层
|
// 插入新组图层
|
||||||
this.layerManager.layers.value.splice(topLayerIndex, 1, this.groupLayer);
|
this.layerManager.layers.value.splice(topLayerIndex, 0, this.groupLayer);
|
||||||
|
|
||||||
|
this.layerManager.activeLayerId.value = selectLayer.id; // 设置新组图层为活动图层
|
||||||
|
|
||||||
this.canvas.discardActiveObject();
|
this.canvas.discardActiveObject();
|
||||||
this.canvas.setActiveObject(this.fabricImage);
|
// this.canvas.setActiveObject(this.fabricImage);
|
||||||
await this.layerManager.updateLayersObjectsInteractivity(true);
|
await this.layerManager.updateLayersObjectsInteractivity(true);
|
||||||
|
|
||||||
console.log(`套索抠图完成,新图层ID: ${this.newLayerId}`);
|
console.log(`套索抠图完成,新图层ID: ${this.newLayerId}`);
|
||||||
@@ -328,6 +350,8 @@ export class LassoCutoutCommand extends CompositeCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.layerManager.activeLayerId.value = this.oldActiveLayerId; // 恢复旧的活动图层ID
|
||||||
|
|
||||||
if (this.fabricImage) {
|
if (this.fabricImage) {
|
||||||
console.log(`↩️ 移除抠图图像: ${this.fabricImage.id}`);
|
console.log(`↩️ 移除抠图图像: ${this.fabricImage.id}`);
|
||||||
// 从画布中移除抠图图像
|
// 从画布中移除抠图图像
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "../utils/helper";
|
} from "../utils/helper";
|
||||||
import { createRasterizedImage } from "../utils/rasterizedImage";
|
import { createRasterizedImage } from "../utils/rasterizedImage";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
|
import { restoreFabricObject } from "../utils/objectHelper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 栅格化图层命令
|
* 栅格化图层命令
|
||||||
@@ -82,7 +83,7 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
this.canvas.renderAll();
|
this.canvas.renderAll();
|
||||||
|
|
||||||
// 检查是否有遮罩对象
|
// 检查是否有遮罩对象
|
||||||
const maskObject = this._getMaskObject();
|
const maskObject = await this._getMaskObject();
|
||||||
|
|
||||||
// 创建栅格化图像
|
// 创建栅格化图像
|
||||||
const rasterizedImage = await createRasterizedImage({
|
const rasterizedImage = await createRasterizedImage({
|
||||||
@@ -392,9 +393,9 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
* @returns {Object|null} 遮罩对象或null
|
* @returns {Object|null} 遮罩对象或null
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_getMaskObject() {
|
async _getMaskObject() {
|
||||||
// 如果图层有clippingMask,获取对应的fabric对象
|
// 如果图层有clippingMask,获取对应的fabric对象
|
||||||
if (this.layer?.clippingMask?.id) {
|
if (this.layer?.clippingMask) {
|
||||||
const { object: maskObject } = findObjectById(
|
const { object: maskObject } = findObjectById(
|
||||||
this.canvas,
|
this.canvas,
|
||||||
this.layer.clippingMask.id
|
this.layer.clippingMask.id
|
||||||
@@ -405,6 +406,7 @@ export class RasterizeLayerCommand extends Command {
|
|||||||
);
|
);
|
||||||
return maskObject;
|
return maskObject;
|
||||||
}
|
}
|
||||||
|
return await restoreFabricObject(this.layer.clippingMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("📎 未找到遮罩对象");
|
console.log("📎 未找到遮罩对象");
|
||||||
|
|||||||
@@ -601,8 +601,8 @@ function handleLayerClick(layer, event) {
|
|||||||
layer.children.length > 0
|
layer.children.length > 0
|
||||||
) {
|
) {
|
||||||
// 如果是组图层,设置第一个子图层为活动图层
|
// 如果是组图层,设置第一个子图层为活动图层
|
||||||
setActiveLayer(layer.children[0].id, { parentId: layer.id });
|
|
||||||
layerManager?.setAllActiveGroupLayerCanvasObject?.(layer);
|
layerManager?.setAllActiveGroupLayerCanvasObject?.(layer);
|
||||||
|
setActiveLayer(layer.children[0].id, { parentId: layer.id });
|
||||||
} else {
|
} else {
|
||||||
// 否则直接设置当前图层为活动图层
|
// 否则直接设置当前图层为活动图层
|
||||||
setActiveLayer(layer.id);
|
setActiveLayer(layer.id);
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import {
|
|||||||
loadImageUrlToLayer,
|
loadImageUrlToLayer,
|
||||||
loadImage,
|
loadImage,
|
||||||
} from "./utils/imageHelper.js";
|
} from "./utils/imageHelper.js";
|
||||||
import { next } from "lodash-es";
|
|
||||||
// import MinimapPanel from "./components/MinimapPanel.vue";
|
// import MinimapPanel from "./components/MinimapPanel.vue";
|
||||||
const KeyboardShortcutHelp = defineAsyncComponent(() =>
|
const KeyboardShortcutHelp = defineAsyncComponent(() =>
|
||||||
import("./components/KeyboardShortcutHelp.vue")
|
import("./components/KeyboardShortcutHelp.vue")
|
||||||
|
|||||||
@@ -407,8 +407,8 @@ export class CanvasManager {
|
|||||||
// 居中所有画布元素,包括背景层和其他元素
|
// 居中所有画布元素,包括背景层和其他元素
|
||||||
this.centerAllObjects();
|
this.centerAllObjects();
|
||||||
|
|
||||||
// 重新渲染画布使变更生效
|
// // 重新渲染画布使变更生效
|
||||||
this.canvas.renderAll();
|
// this.canvas.renderAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -510,7 +510,7 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重新渲染画布
|
// 重新渲染画布
|
||||||
this.canvas.renderAll();
|
// this.canvas.renderAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -958,6 +958,9 @@ export class CanvasManager {
|
|||||||
// 解析JSON字符串
|
// 解析JSON字符串
|
||||||
try {
|
try {
|
||||||
const parsedJson = JSON.parse(json);
|
const parsedJson = JSON.parse(json);
|
||||||
|
this.canvasWidth.value = parsedJson.canvasWidth || this.width;
|
||||||
|
this.canvasHeight.value = parsedJson.canvasHeight || this.height;
|
||||||
|
this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const tempLayers = JSON.parse(parsedJson?.layers) || [];
|
const tempLayers = JSON.parse(parsedJson?.layers) || [];
|
||||||
@@ -974,6 +977,7 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.layers.value = tempLayers;
|
this.layers.value = tempLayers;
|
||||||
|
debugger;
|
||||||
|
|
||||||
// this.canvasWidth.value = parsedJson.canvasWidth || this.width;
|
// this.canvasWidth.value = parsedJson.canvasWidth || this.width;
|
||||||
// this.canvasHeight.value = parsedJson.canvasHeight || this.height;
|
// this.canvasHeight.value = parsedJson.canvasHeight || this.height;
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ import {
|
|||||||
} from "../utils/helper";
|
} from "../utils/helper";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
import { fabric } from "fabric-with-all";
|
import { fabric } from "fabric-with-all";
|
||||||
|
import { getOriginObjectInfo } from "../utils/layerUtils";
|
||||||
|
import { restoreFabricObject } from "../utils/objectHelper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图层管理器 - 负责管理画布上的所有图层
|
* 图层管理器 - 负责管理画布上的所有图层
|
||||||
@@ -333,52 +335,94 @@ export class LayerManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 设置裁剪对象
|
// 设置裁剪对象
|
||||||
layers.forEach((layer) => {
|
layers.forEach(async (layer) => {
|
||||||
if (layer.clippingMask) {
|
if (layer.clippingMask) {
|
||||||
const activeObject = this.canvas.getActiveObject();
|
// 反序列化 clippingMask
|
||||||
if (activeObject?._objects?.length > 1) {
|
const clippingMaskFabricObject = await restoreFabricObject(
|
||||||
console.log(activeObject?._objects?.length);
|
layer.clippingMask,
|
||||||
return false; // 如果是多选对象,则不设置裁剪路径
|
this.canvas
|
||||||
}
|
);
|
||||||
|
clippingMaskFabricObject.clipPath = null;
|
||||||
|
|
||||||
|
clippingMaskFabricObject.set({
|
||||||
|
// 设置绝对定位
|
||||||
|
// ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位
|
||||||
|
absolutePositioned: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// const activeObject = this.canvas.getActiveObject();
|
||||||
|
// if (activeObject?._objects?.length > 1) {
|
||||||
|
// const { object } = findObjectById(
|
||||||
|
// this.canvas,
|
||||||
|
// layer.clippingMask?.id
|
||||||
|
// );
|
||||||
|
// if (!object) return;
|
||||||
|
// const tempClipPath = fabric.util.object.clone(object);
|
||||||
|
// tempClipPath.clipPath = null;
|
||||||
|
// tempClipPath.set({
|
||||||
|
// // 设置绝对定位
|
||||||
|
// ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位
|
||||||
|
// absolutePositioned: true,
|
||||||
|
// });
|
||||||
|
// activeObject.clipPath = tempClipPath;
|
||||||
|
// // 确保选择组正确渲染
|
||||||
|
// // activeObject.setCoords();
|
||||||
|
// console.log(activeObject?._objects?.length);
|
||||||
|
// return; // 如果是多选对象,则不设置裁剪路径
|
||||||
|
// }
|
||||||
// 如果是组图层 则给所有子对象设置裁剪对象
|
// 如果是组图层 则给所有子对象设置裁剪对象
|
||||||
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
||||||
layer.children.forEach((childLayer) => {
|
layer.children.forEach((childLayer) => {
|
||||||
const { object } = findObjectById(
|
if (clippingMaskFabricObject) {
|
||||||
this.canvas,
|
|
||||||
layer.clippingMask?.id
|
|
||||||
);
|
|
||||||
if (object) {
|
|
||||||
const tempClipPath = fabric.util.object.clone(object);
|
|
||||||
tempClipPath.clipPath = null;
|
|
||||||
tempClipPath.set({
|
|
||||||
// 设置绝对定位
|
|
||||||
// ...layer.clippingMask, // 恢复原定位
|
|
||||||
absolutePositioned: true,
|
|
||||||
});
|
|
||||||
const childObj = this.canvas
|
const childObj = this.canvas
|
||||||
.getObjects()
|
.getObjects()
|
||||||
.find((o) => o.layerId === childLayer.id);
|
.find((o) => o.layerId === childLayer.id);
|
||||||
if (childObj) {
|
if (childObj) {
|
||||||
childObj.clipPath = tempClipPath;
|
childObj.clipPath = clippingMaskFabricObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// const { object } = findObjectById(
|
||||||
|
// this.canvas,
|
||||||
|
// layer.clippingMask?.id
|
||||||
|
// );
|
||||||
|
// if (object) {
|
||||||
|
// const tempClipPath = fabric.util.object.clone(object);
|
||||||
|
// tempClipPath.clipPath = null;
|
||||||
|
// tempClipPath.set({
|
||||||
|
// // 设置绝对定位
|
||||||
|
// // ...layer.clippingMask, // 恢复原定位
|
||||||
|
// ...getOriginObjectInfo(layer.clippingMask),
|
||||||
|
// absolutePositioned: true,
|
||||||
|
// });
|
||||||
|
// const childObj = this.canvas
|
||||||
|
// .getObjects()
|
||||||
|
// .find((o) => o.layerId === childLayer.id);
|
||||||
|
// if (childObj) {
|
||||||
|
// childObj.clipPath = tempClipPath;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
} else {
|
} else if (clippingMaskFabricObject) {
|
||||||
const { object } = findObjectById(
|
obj.clipPath = clippingMaskFabricObject;
|
||||||
this.canvas,
|
|
||||||
layer.clippingMask?.id
|
|
||||||
);
|
|
||||||
if (object) {
|
|
||||||
const tempClipPath = fabric.util.object.clone(object);
|
|
||||||
tempClipPath.clipPath = null; // 确保克隆的遮罩没有clipPath
|
|
||||||
tempClipPath.set({
|
|
||||||
// 设置绝对定位
|
|
||||||
// ...layer.clippingMask, // 恢复原定位
|
|
||||||
absolutePositioned: true,
|
|
||||||
});
|
|
||||||
obj.clipPath = tempClipPath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// {
|
||||||
|
// // const { object } = findObjectById(
|
||||||
|
// // this.canvas,
|
||||||
|
// // layer.clippingMask?.id
|
||||||
|
// // );
|
||||||
|
// // if (object) {
|
||||||
|
// // const tempClipPath = fabric.util.object.clone(object);
|
||||||
|
// // tempClipPath.clipPath = null; // 确保克隆的遮罩没有clipPath
|
||||||
|
// // tempClipPath.set({
|
||||||
|
// // // 设置绝对定位
|
||||||
|
// // // ...layer.clippingMask, // 恢复原定位
|
||||||
|
// // ...getOriginObjectInfo(layer.clippingMask),
|
||||||
|
// // absolutePositioned: true,
|
||||||
|
// // });
|
||||||
|
// // obj.clipPath = tempClipPath;
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -932,17 +976,16 @@ export class LayerManager {
|
|||||||
// 设置激活当前图层下画布中的所有对象,并变成选择组
|
// 设置激活当前图层下画布中的所有对象,并变成选择组
|
||||||
setAllActiveGroupLayerCanvasObject(layer) {
|
setAllActiveGroupLayerCanvasObject(layer) {
|
||||||
// 获取当前图层下所有元素
|
// 获取当前图层下所有元素
|
||||||
let layerMask = null;
|
|
||||||
// 选择当前组下所有画布元素
|
// 选择当前组下所有画布元素
|
||||||
const allObjects = layer.children.reduce((acc, child) => {
|
const allObjects = layer.children.reduce((acc, child) => {
|
||||||
// 如果子图层有fabricObjects,则添加到结果数组
|
// 如果子图层有fabricObjects,则添加到结果数组
|
||||||
child?.fabricObjects?.forEach((obj) => {
|
child?.fabricObjects?.forEach((obj) => {
|
||||||
const { object } = findObjectById(this.canvas, obj.id);
|
const { object } = findObjectById(this.canvas, obj.id);
|
||||||
if (object) {
|
if (object) {
|
||||||
if (!layerMask) {
|
// if (!layerMask) {
|
||||||
layerMask = fabric.util.object.clone(object.clipPath);
|
// layerMask = fabric.util.object.clone(object.clipPath);
|
||||||
}
|
// }
|
||||||
object.clipPath = null; // 确保克隆的遮罩没有clipPath
|
// object.clipPath = null; // 确保克隆的遮罩没有clipPath
|
||||||
acc.push(object);
|
acc.push(object);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -950,10 +993,10 @@ export class LayerManager {
|
|||||||
if (child?.fabricObject) {
|
if (child?.fabricObject) {
|
||||||
const { object } = findObjectById(this.canvas, child?.fabricObject.id);
|
const { object } = findObjectById(this.canvas, child?.fabricObject.id);
|
||||||
if (object) {
|
if (object) {
|
||||||
if (!layerMask) {
|
// if (!layerMask) {
|
||||||
layerMask = fabric.util.object.clone(object.clipPath);
|
// layerMask = fabric.util.object.clone(object.clipPath);
|
||||||
}
|
// }
|
||||||
object.clipPath = null; // 确保克隆的遮罩没有clipPath
|
// object.clipPath = null; // 确保克隆的遮罩没有clipPath
|
||||||
acc.push(object);
|
acc.push(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -967,17 +1010,48 @@ export class LayerManager {
|
|||||||
|
|
||||||
// 如果有对象,创建选择组
|
// 如果有对象,创建选择组
|
||||||
this.canvas.discardActiveObject(); // 取消当前活动对象
|
this.canvas.discardActiveObject(); // 取消当前活动对象
|
||||||
|
this.canvas.renderAll(); // 确保画布渲染
|
||||||
|
|
||||||
|
// const { object } = findObjectById(this.canvas, layer.clippingMask?.id);
|
||||||
|
|
||||||
// 选中多个对象,不是创建组
|
// 选中多个对象,不是创建组
|
||||||
// 多个对象时创建活动选择组
|
// 多个对象时创建活动选择组
|
||||||
const activeSelection = new fabric.ActiveSelection(allObjects, {
|
let activeSelection = new fabric.ActiveSelection(allObjects, {
|
||||||
canvas: this.canvas,
|
canvas: this.canvas,
|
||||||
});
|
});
|
||||||
|
|
||||||
activeSelection.clipPath = layerMask; // 保留第一个对象的裁剪路径
|
// if (object) {
|
||||||
|
// const tempClipPath = fabric.util.object.clone(object);
|
||||||
|
// tempClipPath.clipPath = null;
|
||||||
|
// tempClipPath.set({
|
||||||
|
// // 设置绝对定位
|
||||||
|
// // ...layer.clippingMask, // 恢复原定位
|
||||||
|
// ...getOriginObjectInfo(layer.clippingMask),
|
||||||
|
// absolutePositioned: true,
|
||||||
|
// });
|
||||||
|
// activeSelection.clipPath = tempClipPath; // 保留第一个对象的裁剪路径
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 监听选择取消事件,恢复原始裁剪路径
|
||||||
|
// const restoreClipPaths = () => {
|
||||||
|
// allObjects.forEach((obj) => {
|
||||||
|
// if (obj._originalClipPath !== undefined) {
|
||||||
|
// obj.clipPath = obj._originalClipPath;
|
||||||
|
// delete obj._originalClipPath;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// this.canvas.off("selection:cleared", restoreClipPaths);
|
||||||
|
// this.canvas.off("selection:updated", restoreClipPaths);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// this.canvas.on("selection:cleared", restoreClipPaths);
|
||||||
|
// this.canvas.on("selection:updated", restoreClipPaths);
|
||||||
|
|
||||||
// 设置活动选择组的属性
|
// 设置活动选择组的属性
|
||||||
this.canvas.setActiveObject(activeSelection);
|
this.canvas.setActiveObject(activeSelection);
|
||||||
this.canvas.renderAll();
|
activeSelection = null; // 清理引用,避免内存泄漏
|
||||||
|
// 确保选择组正确渲染
|
||||||
|
// activeSelection.setCoords();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { generateId } from "./helper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图层类型枚举
|
* 图层类型枚举
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ export function buildLayerAssociations(layer, canvasObjects) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (layer.clippingMask) {
|
if (layer.clippingMask) {
|
||||||
// clippingMask 可能是一个fabricObject或组
|
// clippingMask 可能是一个fabricObject或组 也可能是一个简单对象
|
||||||
const clippingMaskObj = canvasObjects.find(
|
const clippingMaskObj = canvasObjects.find(
|
||||||
(obj) => obj.id === layer.clippingMask.id
|
(obj) => obj.id === layer.clippingMask.id
|
||||||
);
|
);
|
||||||
layer.clippingMask = clippingMaskObj?.toObject?.(["id"]) || null;
|
layer.clippingMask = clippingMaskObj
|
||||||
|
? clippingMaskObj?.toObject?.(["id"])
|
||||||
|
: layer.clippingMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理多个fabricObjects关联
|
// 处理多个fabricObjects关联
|
||||||
@@ -170,12 +172,15 @@ export function simplifyLayers(layers) {
|
|||||||
opacity: layer.opacity,
|
opacity: layer.opacity,
|
||||||
isBackground: layer.isBackground || false,
|
isBackground: layer.isBackground || false,
|
||||||
isFixed: layer.isFixed || false,
|
isFixed: layer.isFixed || false,
|
||||||
clippingMask: layer.clippingMask
|
clippingMask:
|
||||||
? {
|
layer.clippingMask?.toObject?.(["id", "layerId"]) ||
|
||||||
id: layer.clippingMask.id,
|
layer.clippingMask ||
|
||||||
type: layer.clippingMask.type,
|
null, // 可能是一个fabricObject或组 也可能是一个简单对象
|
||||||
}
|
// ? {
|
||||||
: null,
|
// id: layer.clippingMask.id,
|
||||||
|
// type: layer.clippingMask.type,
|
||||||
|
// }
|
||||||
|
// : null,
|
||||||
fabricObject: layer.fabricObject
|
fabricObject: layer.fabricObject
|
||||||
? {
|
? {
|
||||||
id: layer.fabricObject.id,
|
id: layer.fabricObject.id,
|
||||||
@@ -226,11 +231,21 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
|
|||||||
const restoredLayer = { ...layer };
|
const restoredLayer = { ...layer };
|
||||||
|
|
||||||
// 恢复clippingMask关联
|
// 恢复clippingMask关联
|
||||||
if (layer.clippingMask?.id) {
|
// if (layer.clippingMask?.id) {
|
||||||
|
// const clippingMaskObj = canvasObjects.find(
|
||||||
|
// (obj) => obj.id === layer.clippingMask.id
|
||||||
|
// );
|
||||||
|
// restoredLayer.clippingMask = clippingMaskObj || null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (layer.clippingMask) {
|
||||||
|
// clippingMask 可能是一个fabricObject或组 也可能是一个简单对象
|
||||||
const clippingMaskObj = canvasObjects.find(
|
const clippingMaskObj = canvasObjects.find(
|
||||||
(obj) => obj.id === layer.clippingMask.id
|
(obj) => obj.id === layer.clippingMask.id
|
||||||
);
|
);
|
||||||
restoredLayer.clippingMask = clippingMaskObj || null;
|
restoredLayer.clippingMask = clippingMaskObj
|
||||||
|
? clippingMaskObj?.toObject?.(["id"])
|
||||||
|
: layer.clippingMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复单个fabricObject关联
|
// 恢复单个fabricObject关联
|
||||||
@@ -334,3 +349,30 @@ export function restoreFromSnapshot(snapshot, canvasObjects) {
|
|||||||
|
|
||||||
return restoreLayers(snapshot.data, canvasObjects);
|
return restoreLayers(snapshot.data, canvasObjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对象的定位信息
|
||||||
|
* @param {Object} object 对象
|
||||||
|
* @returns {Object} 对象的定位信息
|
||||||
|
*/
|
||||||
|
export function getOriginObjectInfo(object) {
|
||||||
|
if (!object) {
|
||||||
|
console.warn("getOriginObjectInfo 请传入有效的fabric对象:", object);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取对象的原始信息
|
||||||
|
const originInfo = {
|
||||||
|
left: object.left || 0,
|
||||||
|
top: object.top || 0,
|
||||||
|
scaleX: object.scaleX || 1,
|
||||||
|
scaleY: object.scaleY || 1,
|
||||||
|
angle: object.angle || 0,
|
||||||
|
flipX: object.flipX || false,
|
||||||
|
flipY: object.flipY || false,
|
||||||
|
skewX: object.skewX || 0,
|
||||||
|
skewY: object.skewY || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return originInfo;
|
||||||
|
}
|
||||||
|
|||||||
63
src/component/Canvas/CanvasEditor/utils/objectHelper.js
Normal file
63
src/component/Canvas/CanvasEditor/utils/objectHelper.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { fabric } from "fabric-with-all";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将序列化对象恢复为 fabric 对象
|
||||||
|
* @param {Object} serializedObject - toObject() 生成的对象
|
||||||
|
* @param {fabric.Canvas} canvas - 目标画布
|
||||||
|
* @returns {Promise<fabric.Object>} 恢复的 fabric 对象
|
||||||
|
*/
|
||||||
|
export async function restoreFabricObject(serializedObject, canvas) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const objectType = serializedObject.type;
|
||||||
|
// 定义恢复后的处理函数
|
||||||
|
const handleRestoredObject = (fabricObject) => {
|
||||||
|
if (!fabricObject) {
|
||||||
|
reject(new Error(`无法恢复 ${objectType} 类型的对象`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复自定义属性
|
||||||
|
if (serializedObject.id) fabricObject.id = serializedObject.id;
|
||||||
|
if (serializedObject.layerId)
|
||||||
|
fabricObject.layerId = serializedObject.layerId;
|
||||||
|
if (serializedObject.layerName)
|
||||||
|
fabricObject.layerName = serializedObject.layerName;
|
||||||
|
|
||||||
|
// 更新坐标
|
||||||
|
fabricObject.setCoords();
|
||||||
|
|
||||||
|
// 添加到画布
|
||||||
|
// canvas.add(fabricObject);
|
||||||
|
|
||||||
|
resolve(fabricObject);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据类型选择恢复方法
|
||||||
|
switch (objectType) {
|
||||||
|
case "rect":
|
||||||
|
fabric.Rect.fromObject(serializedObject, handleRestoredObject);
|
||||||
|
break;
|
||||||
|
case "circle":
|
||||||
|
fabric.Circle.fromObject(serializedObject, handleRestoredObject);
|
||||||
|
break;
|
||||||
|
case "path":
|
||||||
|
fabric.Path.fromObject(serializedObject, handleRestoredObject);
|
||||||
|
break;
|
||||||
|
case "image":
|
||||||
|
fabric.Image.fromObject(serializedObject, handleRestoredObject);
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
fabric.Group.fromObject(serializedObject, handleRestoredObject);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// 使用通用方法
|
||||||
|
fabric.util.enlivenObjects([serializedObject], (objects) => {
|
||||||
|
if (objects && objects[0]) {
|
||||||
|
handleRestoredObject(objects[0]);
|
||||||
|
} else {
|
||||||
|
reject(new Error("对象恢复失败"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user