feat: 优化部分问题,完善选区功能
This commit is contained in:
1
src/assets/icons/CPlusTop.svg
Normal file
1
src/assets/icons/CPlusTop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1751511415858" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9451" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1024 1024H428.8v-70H954V439.8h70z" p-id="9452"></path><path d="M796 70v726H70V70h726m70-70H0v866h866V0z" p-id="9453"></path><path d="M200.2 398h465.6v70H200.2z" p-id="9454"></path><path d="M398 200.2h70v465.6h-70z" p-id="9455"></path></svg>
|
||||
|
After Width: | Height: | Size: 574 B |
@@ -0,0 +1,527 @@
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { Command } from "./Command";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage";
|
||||
import { deepClone, findObjectById, generateId } from "../utils/helper";
|
||||
import { findLayerRecursively } from "../utils/layerHelper";
|
||||
|
||||
/**
|
||||
* 从选区中删除内容命令
|
||||
* 使用栅格化方式清除选区内容,支持复杂选区形状
|
||||
*/
|
||||
export class ClearSelectionContentCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "清除选区内容",
|
||||
description: "使用栅格化方式删除选区中的内容",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layerManager?.layers;
|
||||
this.layerManager = options.layerManager;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.targetLayerId = options.layerManager.getActiveLayerId(); // 默认使用当前活动图层
|
||||
this.removedObjects = [];
|
||||
|
||||
this.oldLayer = [...this.layers.value]; // 获取原图层对象
|
||||
|
||||
const { layer } =
|
||||
findLayerRecursively(this.layers.value, this.targetLayerId) || {};
|
||||
|
||||
// 栅格化相关属性
|
||||
this.originalLayerBackup = null;
|
||||
|
||||
const { object: originalLayerObject } = findObjectById(
|
||||
this.canvas,
|
||||
layer.fabricObjects?.[0]?.id
|
||||
);
|
||||
|
||||
// this.originalLayerObject = fabric.util.object.clone(originalLayerObject); // 获取原图层对象
|
||||
|
||||
this.oldLayerObjects = originalLayerObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
]); // 获取原图层的所有对象
|
||||
|
||||
this.rasterizedImage = null;
|
||||
this.targetLayer = findLayerRecursively(
|
||||
this.canvas,
|
||||
this.targetLayerId
|
||||
)?.layer;
|
||||
|
||||
this.layerRasterized = false;
|
||||
|
||||
// 高清设置
|
||||
this.highResolutionEnabled = options.highResolutionEnabled !== false;
|
||||
this.baseResolutionScale = options.baseResolutionScale || 2;
|
||||
|
||||
// 支持外部传入选区对象,优先使用外部传入的选区对象
|
||||
if (options.selectionObject) {
|
||||
// 外部传入的选区对象,直接使用
|
||||
this.selectionObject = options.selectionObject;
|
||||
console.log("清除选区内容:使用外部传入的选区对象");
|
||||
} else {
|
||||
// 从选区管理器获取选区对象
|
||||
const currentSelection = this.selectionManager?.getSelectionObject();
|
||||
if (currentSelection) {
|
||||
this.selectionObject = fabric.util.object.clone(currentSelection);
|
||||
console.log("清除选区内容:从选区管理器获取选区对象");
|
||||
} else {
|
||||
this.selectionObject = null;
|
||||
console.warn("清除选区内容:没有可用的选区对象");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.layerManager) {
|
||||
console.error("无法清除选区内容:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取选区
|
||||
|
||||
if (!this.selectionObject) {
|
||||
console.error("无法清除选区内容:当前没有选区");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确定目标图层
|
||||
const layerId =
|
||||
this.targetLayerId || this.layerManager.getActiveLayerId();
|
||||
this.targetLayer = this.layerManager.getLayerById(layerId);
|
||||
if (!this.targetLayer) {
|
||||
console.error("无法清除选区内容:目标图层无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 备份原图层状态
|
||||
// this.originalLayerBackup = JSON.parse(JSON.stringify(this.targetLayer));
|
||||
|
||||
// 获取图层的所有对象
|
||||
const layerObjects = this._getLayerObjects(this.targetLayer);
|
||||
if (layerObjects.length === 0) {
|
||||
console.warn("目标图层没有对象,无需清除");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 创建反转选区(保留选区外的内容)
|
||||
const invertedSelection = await this._createInvertedSelection(
|
||||
this.selectionObject
|
||||
);
|
||||
if (!invertedSelection) {
|
||||
console.error("创建反转选区失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用栅格化方式清除选区内容
|
||||
await this._rasterizeLayerWithClearance(layerObjects, invertedSelection);
|
||||
|
||||
// 清除选区
|
||||
this.selectionManager?.clearSelection?.();
|
||||
|
||||
console.log("✅ 选区内容清除完成");
|
||||
return { cleared: true, layerId: this.targetLayer.id };
|
||||
} catch (error) {
|
||||
console.error("清除选区内容失败:", error);
|
||||
// 尝试恢复原状态
|
||||
await this._restoreOriginalState();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async undo() {
|
||||
try {
|
||||
this.layers.value = this.oldLayer; // 恢复原图层对象
|
||||
|
||||
// 恢复原图层状态
|
||||
// 移除栅格化后的图像
|
||||
if (
|
||||
this.rasterizedImage &&
|
||||
this.canvas.getObjects().includes(this.rasterizedImage)
|
||||
) {
|
||||
this.canvas.remove(this.rasterizedImage);
|
||||
}
|
||||
|
||||
// 恢复图层的fabricObjects
|
||||
// 重新创建并添加对象到画布
|
||||
if (this.targetLayer.fabricObjects.length > 0) {
|
||||
await new Promise((resolve) => {
|
||||
fabric.util.enlivenObjects([this.oldLayerObjects], (objects) => {
|
||||
objects.forEach((obj) => {
|
||||
// 确保对象有正确的ID和图层信息
|
||||
obj.set({
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
});
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(obj);
|
||||
|
||||
this.targetLayer.fabricObjects = [
|
||||
obj.toObject(["id", "layerId", "layerName", "type", "custom"]),
|
||||
];
|
||||
});
|
||||
|
||||
this.canvas.renderAll();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.(true);
|
||||
|
||||
this.layerRasterized = false;
|
||||
this.rasterizedImage = null;
|
||||
|
||||
console.log("✅ 清除选区内容撤销完成");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ 撤销清除选区内容失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用栅格化方式清除选区内容
|
||||
* @param {Array} layerObjects 图层对象数组
|
||||
* @param {Object} invertedSelection 反转选区
|
||||
* @private
|
||||
*/
|
||||
async _rasterizeLayerWithClearance(layerObjects, invertedSelection) {
|
||||
try {
|
||||
console.log("🖼️ 开始栅格化图层并清除选区内容");
|
||||
|
||||
// 确定缩放因子
|
||||
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生成栅格化图像,使用反转选区作为裁剪路径
|
||||
this.rasterizedImage = await createRasterizedImage({
|
||||
canvas: this.canvas,
|
||||
fabricObjects: layerObjects,
|
||||
clipPath: invertedSelection, // 使用反转选区,保留选区外的内容
|
||||
trimWhitespace: false, // 保持原始尺寸
|
||||
trimPadding: 0,
|
||||
quality: 1.0,
|
||||
format: "png",
|
||||
scaleFactor: scaleFactor,
|
||||
preserveOriginalQuality: true,
|
||||
selectionManager: this.selectionManager,
|
||||
});
|
||||
|
||||
if (!this.rasterizedImage) {
|
||||
throw new Error("栅格化图层失败");
|
||||
}
|
||||
|
||||
// 移除原图层的所有对象
|
||||
for (const obj of layerObjects) {
|
||||
if (this.canvas.getObjects().includes(obj)) {
|
||||
this.canvas.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置栅格化图像的属性
|
||||
this.rasterizedImage.set({
|
||||
id: generateId("cleared_"),
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
});
|
||||
|
||||
// 添加栅格化图像到画布和图层
|
||||
this.canvas.add(this.rasterizedImage);
|
||||
this.targetLayer.fabricObjects = [
|
||||
this.rasterizedImage.toObject("id", "layerId", "layerName", "parentId"),
|
||||
];
|
||||
|
||||
this.layerRasterized = true;
|
||||
this.canvas.renderAll();
|
||||
|
||||
console.log("✅ 图层栅格化清除完成");
|
||||
} catch (error) {
|
||||
console.error("栅格化图层清除失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建反转选区
|
||||
* @param {Object} selectionObject 原选区对象
|
||||
* @returns {Object} 反转选区对象
|
||||
* @private
|
||||
*/
|
||||
async _createInvertedSelection(selectionObject) {
|
||||
try {
|
||||
// 获取目标图层的所有对象
|
||||
const layerObjects = this._getLayerObjects(this.targetLayer);
|
||||
if (layerObjects.length === 0) {
|
||||
console.warn("目标图层没有对象,无法创建反选区");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 计算图层对象的边界框
|
||||
const layerBounds = this._calculateLayerBounds(layerObjects);
|
||||
if (!layerBounds) {
|
||||
console.warn("无法计算图层边界框");
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log("📐 图层边界框:", layerBounds);
|
||||
console.log("🎯 选区路径:", selectionObject.path);
|
||||
|
||||
// 创建反转选区路径,使用图层边界而不是画布边界
|
||||
const pathString = `M ${layerBounds.left} ${layerBounds.top} L ${
|
||||
layerBounds.left + layerBounds.width
|
||||
} ${layerBounds.top} L ${layerBounds.left + layerBounds.width} ${
|
||||
layerBounds.top + layerBounds.height
|
||||
} L ${layerBounds.left} ${layerBounds.top + layerBounds.height} Z ${
|
||||
selectionObject.path
|
||||
}`;
|
||||
|
||||
const invertedPath = new fabric.Path(pathString, {
|
||||
fillRule: "evenodd",
|
||||
selectable: false,
|
||||
evented: false,
|
||||
fill: "#ffffff", // 确保填充颜色
|
||||
stroke: "",
|
||||
strokeWidth: 0,
|
||||
id: `inverted_selection_${Date.now()}`,
|
||||
name: "inverted_selection",
|
||||
});
|
||||
|
||||
console.log("✅ 反转选区创建完成,使用图层边界框");
|
||||
return invertedPath;
|
||||
} catch (error) {
|
||||
console.error("创建反转选区失败:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算图层对象的边界框
|
||||
* @param {Array} layerObjects 图层对象数组
|
||||
* @returns {Object|null} 边界框 {left, top, width, height}
|
||||
* @private
|
||||
*/
|
||||
_calculateLayerBounds(layerObjects) {
|
||||
if (!layerObjects || layerObjects.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let bounds = null;
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
// 获取对象的边界框(包含变换)
|
||||
const objBounds = obj.getBoundingRect(true, true);
|
||||
|
||||
if (!bounds) {
|
||||
bounds = { ...objBounds };
|
||||
} else {
|
||||
const right = Math.max(
|
||||
bounds.left + bounds.width,
|
||||
objBounds.left + objBounds.width
|
||||
);
|
||||
const bottom = Math.max(
|
||||
bounds.top + bounds.height,
|
||||
objBounds.top + objBounds.height
|
||||
);
|
||||
|
||||
bounds.left = Math.min(bounds.left, objBounds.left);
|
||||
bounds.top = Math.min(bounds.top, objBounds.top);
|
||||
bounds.width = right - bounds.left;
|
||||
bounds.height = bottom - bounds.top;
|
||||
}
|
||||
});
|
||||
|
||||
// 添加一些边距以确保完整覆盖
|
||||
if (bounds) {
|
||||
const padding = 2;
|
||||
bounds.left -= padding;
|
||||
bounds.top -= padding;
|
||||
bounds.width += padding * 2;
|
||||
bounds.height += padding * 2;
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层的所有对象
|
||||
* @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);
|
||||
return objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复原图层状态
|
||||
* @private
|
||||
*/
|
||||
async _restoreOriginalState() {
|
||||
try {
|
||||
if (!this.originalLayerBackup || !this.targetLayer) {
|
||||
console.warn("没有原图层备份数据或目标图层");
|
||||
return;
|
||||
}
|
||||
|
||||
// 移除栅格化后的图像
|
||||
if (
|
||||
this.rasterizedImage &&
|
||||
this.canvas.getObjects().includes(this.rasterizedImage)
|
||||
) {
|
||||
this.canvas.remove(this.rasterizedImage);
|
||||
}
|
||||
|
||||
// 恢复图层的fabricObjects
|
||||
this.targetLayer.fabricObjects =
|
||||
this.originalLayerBackup.fabricObjects || [];
|
||||
|
||||
// 重新创建并添加对象到画布
|
||||
if (this.targetLayer.fabricObjects.length > 0) {
|
||||
await this._restoreLayerObjects(this.targetLayer);
|
||||
}
|
||||
|
||||
this.layerRasterized = false;
|
||||
this.rasterizedImage = null;
|
||||
|
||||
console.log("✅ 原图层状态恢复完成");
|
||||
} catch (error) {
|
||||
console.error("恢复原图层状态失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复图层对象到画布
|
||||
* @param {Object} layer 图层对象
|
||||
* @private
|
||||
*/
|
||||
async _restoreLayerObjects(layer) {
|
||||
return new Promise((resolve) => {
|
||||
if (!layer.fabricObjects || layer.fabricObjects.length === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
fabric.util.enlivenObjects(layer.fabricObjects, (objects) => {
|
||||
objects.forEach((obj) => {
|
||||
// 确保对象有正确的ID和图层信息
|
||||
obj.set({
|
||||
layerId: layer.id,
|
||||
layerName: layer.name,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
});
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(obj);
|
||||
});
|
||||
|
||||
this.canvas.renderAll();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_cloneObjectSync(obj) {
|
||||
// 这是一个简单的深拷贝,不适用于所有场景
|
||||
// 在实际应用中,应该使用fabric.js的clone方法
|
||||
if (!obj) return null;
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
async _cloneObject(obj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!obj) {
|
||||
reject(new Error("对象无效,无法克隆"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof obj.clone === "function") {
|
||||
obj.clone((cloned) => {
|
||||
resolve(cloned);
|
||||
});
|
||||
} else {
|
||||
// 如果对象没有clone方法(可能是因为它已经是序列化后的对象)
|
||||
// 在实际代码中需要适当处理这种情况
|
||||
resolve(Object.assign({}, obj));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令信息
|
||||
* @returns {Object} 命令详细信息
|
||||
*/
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
targetLayerId: this.targetLayer?.id,
|
||||
targetLayerName: this.targetLayer?.name,
|
||||
layerRasterized: this.layerRasterized,
|
||||
hasOriginalBackup: !!this.originalLayerBackup,
|
||||
removedObjectsCount: this.removedObjects.length,
|
||||
highResolutionEnabled: this.highResolutionEnabled,
|
||||
baseResolutionScale: this.baseResolutionScale,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,888 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,13 +34,21 @@ export class AddLayerCommand extends Command {
|
||||
this.insertIndex = options.insertIndex;
|
||||
this.oldActiveLayerId = null;
|
||||
this.beforeLayers = [...this.layers.value]; // 备份原图层列表
|
||||
|
||||
this.options = options.options || {};
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 保存当前活动图层ID
|
||||
this.oldActiveLayerId = this.activeLayerId.value;
|
||||
|
||||
// 执行添加图层操作
|
||||
// 执行添加顶级图层操作
|
||||
if (this.options?.insertTop) {
|
||||
this.layers.value.splice(0, 0, this.newLayer);
|
||||
this.activeLayerId.value = this.newLayer.id;
|
||||
return this.newLayer.id;
|
||||
}
|
||||
|
||||
// 智能插入图层到合适位置
|
||||
this._insertLayerAtCorrectPosition(this.newLayer);
|
||||
// if (this.insertIndex !== undefined && this.insertIndex !== null) {
|
||||
|
||||
@@ -428,379 +428,3 @@ export class CopySelectionToNewLayerCommand extends CompositeCommand {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从选区中删除内容命令
|
||||
* 使用栅格化方式清除选区内容,支持复杂选区形状
|
||||
*/
|
||||
export class ClearSelectionContentCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "清除选区内容",
|
||||
description: "使用栅格化方式删除选区中的内容",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.targetLayerId = options.targetLayerId;
|
||||
this.removedObjects = [];
|
||||
|
||||
// 栅格化相关属性
|
||||
this.originalLayerBackup = null;
|
||||
this.rasterizedImage = null;
|
||||
this.targetLayer = null;
|
||||
this.layerRasterized = false;
|
||||
|
||||
// 高清设置
|
||||
this.highResolutionEnabled = options.highResolutionEnabled !== false;
|
||||
this.baseResolutionScale = options.baseResolutionScale || 2;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.layerManager || !this.selectionManager) {
|
||||
console.error("无法清除选区内容:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取选区
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (!selectionObject) {
|
||||
console.error("无法清除选区内容:当前没有选区");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确定目标图层
|
||||
const layerId =
|
||||
this.targetLayerId || this.layerManager.getActiveLayerId();
|
||||
this.targetLayer = this.layerManager.getLayerById(layerId);
|
||||
if (!this.targetLayer) {
|
||||
console.error("无法清除选区内容:目标图层无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 备份原图层状态
|
||||
this.originalLayerBackup = JSON.parse(JSON.stringify(this.targetLayer));
|
||||
|
||||
// 获取图层的所有对象
|
||||
const layerObjects = this._getLayerObjects(this.targetLayer);
|
||||
if (layerObjects.length === 0) {
|
||||
console.warn("目标图层没有对象,无需清除");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 创建反转选区(保留选区外的内容)
|
||||
const invertedSelection = await this._createInvertedSelection(
|
||||
selectionObject
|
||||
);
|
||||
if (!invertedSelection) {
|
||||
console.error("创建反转选区失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用栅格化方式清除选区内容
|
||||
await this._rasterizeLayerWithClearance(layerObjects, invertedSelection);
|
||||
|
||||
// 清除选区
|
||||
this.selectionManager.clearSelection();
|
||||
|
||||
console.log("✅ 选区内容清除完成");
|
||||
return { cleared: true, layerId: this.targetLayer.id };
|
||||
} catch (error) {
|
||||
console.error("清除选区内容失败:", error);
|
||||
// 尝试恢复原状态
|
||||
await this._restoreOriginalState();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async undo() {
|
||||
try {
|
||||
console.log("↩️ 开始撤销清除选区内容操作");
|
||||
|
||||
if (this.layerRasterized && this.originalLayerBackup) {
|
||||
await this._restoreOriginalState();
|
||||
} else if (this.removedObjects.length > 0) {
|
||||
// 恢复被删除的对象(兼容旧版本)
|
||||
for (const item of this.removedObjects) {
|
||||
if (item.object && item.layerId) {
|
||||
const clonedObj = await this._cloneObject(item.object);
|
||||
this.layerManager.addObjectToLayer(clonedObj, item.layerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("✅ 清除选区内容撤销完成");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ 撤销清除选区内容失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用栅格化方式清除选区内容
|
||||
* @param {Array} layerObjects 图层对象数组
|
||||
* @param {Object} invertedSelection 反转选区
|
||||
* @private
|
||||
*/
|
||||
async _rasterizeLayerWithClearance(layerObjects, invertedSelection) {
|
||||
try {
|
||||
console.log("🖼️ 开始栅格化图层并清除选区内容");
|
||||
|
||||
// 确定缩放因子
|
||||
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生成栅格化图像,使用反转选区作为裁剪路径
|
||||
this.rasterizedImage = await createRasterizedImage({
|
||||
canvas: this.canvas,
|
||||
fabricObjects: layerObjects,
|
||||
clipPath: invertedSelection, // 使用反转选区,保留选区外的内容
|
||||
trimWhitespace: false, // 保持原始尺寸
|
||||
trimPadding: 0,
|
||||
quality: 1.0,
|
||||
format: "png",
|
||||
scaleFactor: scaleFactor,
|
||||
preserveOriginalQuality: true,
|
||||
selectionManager: this.selectionManager,
|
||||
});
|
||||
|
||||
if (!this.rasterizedImage) {
|
||||
throw new Error("栅格化图层失败");
|
||||
}
|
||||
|
||||
// 移除原图层的所有对象
|
||||
for (const obj of layerObjects) {
|
||||
if (this.canvas.getObjects().includes(obj)) {
|
||||
this.canvas.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置栅格化图像的属性
|
||||
this.rasterizedImage.set({
|
||||
id: generateId("cleared_"),
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
});
|
||||
|
||||
// 添加栅格化图像到画布和图层
|
||||
this.canvas.add(this.rasterizedImage);
|
||||
this.targetLayer.fabricObjects = [
|
||||
this.rasterizedImage.toObject("id", "layerId", "layerName", "parentId"),
|
||||
];
|
||||
|
||||
this.layerRasterized = true;
|
||||
this.canvas.renderAll();
|
||||
|
||||
console.log("✅ 图层栅格化清除完成");
|
||||
} catch (error) {
|
||||
console.error("栅格化图层清除失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建反转选区
|
||||
* @param {Object} selectionObject 原选区对象
|
||||
* @returns {Object} 反转选区对象
|
||||
* @private
|
||||
*/
|
||||
async _createInvertedSelection(selectionObject) {
|
||||
try {
|
||||
// 创建反转选区路径
|
||||
const pathString = `M 0 0 L ${this.canvas.width} 0 L ${this.canvas.width} ${this.canvas.height} L 0 ${this.canvas.height} Z ${selectionObject.path}`;
|
||||
|
||||
const invertedPath = new fabric.Path(pathString, {
|
||||
fillRule: "evenodd",
|
||||
selectable: false,
|
||||
evented: false,
|
||||
id: `inverted_selection_${Date.now()}`,
|
||||
name: "inverted_selection",
|
||||
});
|
||||
|
||||
return invertedPath;
|
||||
} catch (error) {
|
||||
console.error("创建反转选区失败:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层的所有对象
|
||||
* @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);
|
||||
return objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复原图层状态
|
||||
* @private
|
||||
*/
|
||||
async _restoreOriginalState() {
|
||||
try {
|
||||
if (!this.originalLayerBackup || !this.targetLayer) {
|
||||
console.warn("没有原图层备份数据或目标图层");
|
||||
return;
|
||||
}
|
||||
|
||||
// 移除栅格化后的图像
|
||||
if (
|
||||
this.rasterizedImage &&
|
||||
this.canvas.getObjects().includes(this.rasterizedImage)
|
||||
) {
|
||||
this.canvas.remove(this.rasterizedImage);
|
||||
}
|
||||
|
||||
// 恢复图层的fabricObjects
|
||||
this.targetLayer.fabricObjects =
|
||||
this.originalLayerBackup.fabricObjects || [];
|
||||
|
||||
// 重新创建并添加对象到画布
|
||||
if (this.targetLayer.fabricObjects.length > 0) {
|
||||
await this._restoreLayerObjects(this.targetLayer);
|
||||
}
|
||||
|
||||
this.layerRasterized = false;
|
||||
this.rasterizedImage = null;
|
||||
|
||||
console.log("✅ 原图层状态恢复完成");
|
||||
} catch (error) {
|
||||
console.error("恢复原图层状态失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复图层对象到画布
|
||||
* @param {Object} layer 图层对象
|
||||
* @private
|
||||
*/
|
||||
async _restoreLayerObjects(layer) {
|
||||
return new Promise((resolve) => {
|
||||
if (!layer.fabricObjects || layer.fabricObjects.length === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
fabric.util.enlivenObjects(layer.fabricObjects, (objects) => {
|
||||
objects.forEach((obj) => {
|
||||
// 确保对象有正确的ID和图层信息
|
||||
obj.set({
|
||||
layerId: layer.id,
|
||||
layerName: layer.name,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
});
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(obj);
|
||||
});
|
||||
|
||||
this.canvas.renderAll();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_cloneObjectSync(obj) {
|
||||
// 这是一个简单的深拷贝,不适用于所有场景
|
||||
// 在实际应用中,应该使用fabric.js的clone方法
|
||||
if (!obj) return null;
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
async _cloneObject(obj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!obj) {
|
||||
reject(new Error("对象无效,无法克隆"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof obj.clone === "function") {
|
||||
obj.clone((cloned) => {
|
||||
resolve(cloned);
|
||||
});
|
||||
} else {
|
||||
// 如果对象没有clone方法(可能是因为它已经是序列化后的对象)
|
||||
// 在实际代码中需要适当处理这种情况
|
||||
resolve(Object.assign({}, obj));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令信息
|
||||
* @returns {Object} 命令详细信息
|
||||
*/
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
targetLayerId: this.targetLayer?.id,
|
||||
targetLayerName: this.targetLayer?.name,
|
||||
layerRasterized: this.layerRasterized,
|
||||
hasOriginalBackup: !!this.originalLayerBackup,
|
||||
removedObjectsCount: this.removedObjects.length,
|
||||
highResolutionEnabled: this.highResolutionEnabled,
|
||||
baseResolutionScale: this.baseResolutionScale,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import LayersList from "./LayersList.vue"; // 引入 LayersList 组件
|
||||
// GroupLayersCommand,
|
||||
// UngroupLayersCommand,
|
||||
// } from "../../commands/LayerCommands";
|
||||
import { generateId } from "../../utils/helper";
|
||||
import { findObjectById, generateId } from "../../utils/helper";
|
||||
|
||||
const props = defineProps({
|
||||
// layers: Array,
|
||||
@@ -26,6 +26,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits([
|
||||
"add-layer",
|
||||
"add-top-layer",
|
||||
"add-group-layer",
|
||||
"set-active-layer",
|
||||
"toggle-layer-visibility",
|
||||
@@ -92,6 +93,13 @@ const selectedLayers = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
// 计算属性:获取当前是否激活子图层
|
||||
const isChildLayerActive = computed(() => {
|
||||
const { parent } = findLayerRecursively(layers.value, props.activeLayerId);
|
||||
if (parent) return true; // 如果有父图层,则表示是子图层
|
||||
return false; // 否则是根图层
|
||||
});
|
||||
|
||||
// 计算属性:检查是否有选中的图层可以分组
|
||||
const canGroupLayers = computed(() => {
|
||||
// 直接基于 selectedLayerIds 和 sortableRootLayers 计算,确保响应式
|
||||
@@ -142,6 +150,9 @@ const canToggleLock = computed(() => {
|
||||
function addLayer() {
|
||||
emit("add-layer");
|
||||
}
|
||||
function addTopLayer() {
|
||||
emit("add-top-layer");
|
||||
}
|
||||
|
||||
function addGroupLayer() {
|
||||
emit("add-group-layer");
|
||||
@@ -590,6 +601,7 @@ function handleLayerClick(layer, event) {
|
||||
) {
|
||||
// 如果是组图层,设置第一个子图层为活动图层
|
||||
setActiveLayer(layer.children[0].id, { parentId: layer.id });
|
||||
layerManager?.setAllActiveGroupLayerCanvasObject?.(layer);
|
||||
} else {
|
||||
// 否则直接设置当前图层为活动图层
|
||||
setActiveLayer(layer.id);
|
||||
@@ -1252,6 +1264,14 @@ const forwardEvent = (eventName, ...args) => {
|
||||
|
||||
<!-- 常规操作按钮 -->
|
||||
<div v-else class="normal-actions">
|
||||
<div
|
||||
v-if="isChildLayerActive"
|
||||
class="add-layer-btn action-btn"
|
||||
@click="addTopLayer"
|
||||
:title="$t('添加顶级图层')"
|
||||
>
|
||||
<SvgIcon name="CPlusTop" size="16"></SvgIcon>
|
||||
</div>
|
||||
<div
|
||||
class="add-layer-btn action-btn"
|
||||
@click="addLayer"
|
||||
|
||||
@@ -188,17 +188,19 @@ import {
|
||||
InvertSelectionCommand,
|
||||
FeatherSelectionCommand,
|
||||
FillSelectionCommand,
|
||||
CopySelectionToNewLayerCommand,
|
||||
ClearSelectionContentCommand,
|
||||
// CopySelectionToNewLayerCommand,
|
||||
// ClearSelectionContentCommand,
|
||||
} from "../commands/SelectionCommands";
|
||||
import { ToolCommand } from "../commands/ToolCommands";
|
||||
import {
|
||||
LassoCutoutCommand,
|
||||
ClearSelectionCommand,
|
||||
CutSelectionToNewLayerCommand,
|
||||
// CutSelectionToNewLayerCommand,
|
||||
} from "../commands/LassoCutoutCommand";
|
||||
|
||||
import { OperationType } from "../utils/layerHelper";
|
||||
import { ClearSelectionContentCommand } from "../commands/ClearSelectionContentCommand";
|
||||
import { CutSelectionToNewLayerCommand } from "../commands/CutSelectionToNewLayerCommand";
|
||||
|
||||
const props = defineProps({
|
||||
canvas: {
|
||||
|
||||
@@ -31,7 +31,7 @@ import BrushControlPanel from "./components/BrushControlPanel.vue";
|
||||
import TextEditorPanel from "./components/TextEditorPanel.vue"; // 引入文本编辑面板
|
||||
import LiquifyPanel from "./components/LiquifyPanel.vue"; // 引入液化编辑面板
|
||||
import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面板
|
||||
import { OperationType } from "./utils/layerHelper.js";
|
||||
import { LayerType, OperationType } from "./utils/layerHelper.js";
|
||||
import { ToolManager } from "./managers/toolManager.js";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import {
|
||||
@@ -521,7 +521,12 @@ function updateCanvasColor() {
|
||||
}
|
||||
|
||||
async function addLayer() {
|
||||
await layerManager.createLayer();
|
||||
await layerManager.createLayer("空图层");
|
||||
}
|
||||
async function addTopLayer() {
|
||||
await layerManager.createLayer("空图层", LayerType.EMPTY, {
|
||||
insertTop: true,
|
||||
});
|
||||
}
|
||||
|
||||
function setActiveLayer(layerId) {
|
||||
@@ -877,6 +882,24 @@ defineExpose({
|
||||
if (!layerManager) return false;
|
||||
return layerManager.batchReorderLayers(reorderOperations);
|
||||
},
|
||||
/**
|
||||
* 切换图层可见性
|
||||
* @param {string} layerId 图层ID
|
||||
* @returns {boolean} 更新后的可见性状态
|
||||
*/
|
||||
toggleLayerVisibility(layerId) {
|
||||
if (!layerManager) return false;
|
||||
return layerManager.toggleLayerVisibility(layerId);
|
||||
},
|
||||
/**
|
||||
* 获取图层可见性状态
|
||||
* @param {string} layerId 图层ID
|
||||
* @returns {boolean} 图层是否可见
|
||||
*/
|
||||
getLayerVisibility(layerId) {
|
||||
if (!layerManager) return false;
|
||||
return layerManager.getLayerVisibility(layerId);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1002,6 +1025,7 @@ defineExpose({
|
||||
:thumbnailManager="canvasManager.thumbnailManager"
|
||||
:showFixedLayer="showFixedLayer"
|
||||
@add-layer="addLayer"
|
||||
@add-top-layer="addTopLayer"
|
||||
@set-active-layer="setActiveLayer"
|
||||
@toggle-layer-visibility="toggleLayerVisibility"
|
||||
@move-layer-up="moveLayerUp"
|
||||
|
||||
@@ -1059,7 +1059,7 @@ export class CanvasManager {
|
||||
|
||||
console.log("画布JSON数据加载完成");
|
||||
// 画布初始化事件
|
||||
this.handleCanvasInit?.(ture);
|
||||
this.handleCanvasInit?.(true);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("恢复图层数据失败:", error);
|
||||
|
||||
@@ -377,24 +377,23 @@ export class LayerManager {
|
||||
*/
|
||||
async createLayer(name = null, type = LayerType.EMPTY, options = {}) {
|
||||
// 生成唯一ID
|
||||
const layerId =
|
||||
options.id ||
|
||||
options.layerId ||
|
||||
generateId("layer_") ||
|
||||
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const layerIndex = this.layers.value.length;
|
||||
const layerId = options.id || options.layerId || generateId("layer_");
|
||||
|
||||
// 计算普通图层数量(非背景、非固定)
|
||||
const normalLayersCount = this.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
).length;
|
||||
// 计算插入位置,如果没有指定insertIndex,则根据当前选中图层决定插入位置
|
||||
// 添加到图层列表
|
||||
let insertIndex = this._getInsertIndexAboveActiveLayer();
|
||||
if (options.insertIndex !== undefined) {
|
||||
insertIndex = options.insertIndex;
|
||||
}
|
||||
// let insertIndex = this._getInsertIndexAboveActiveLayer();
|
||||
// if (options.insertIndex !== undefined) {
|
||||
// insertIndex = options.insertIndex;
|
||||
// }
|
||||
|
||||
// 创建新图层
|
||||
const newLayer = createLayer({
|
||||
id: layerId,
|
||||
name: name || `图层 ${layerIndex + 1}`,
|
||||
name: name || `图层 ${normalLayersCount + 1}`,
|
||||
type: type,
|
||||
visible: true,
|
||||
locked: false,
|
||||
@@ -411,14 +410,9 @@ export class LayerManager {
|
||||
layers: this.layers,
|
||||
newLayer: newLayer,
|
||||
activeLayerId: this.activeLayerId,
|
||||
insertIndex: insertIndex,
|
||||
options,
|
||||
});
|
||||
|
||||
// 计算普通图层数量(非背景、非固定)
|
||||
const normalLayersCount = this.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
).length;
|
||||
|
||||
// 如果有额外选项,设置命令的undoable属性
|
||||
command.undoable = options.undoable;
|
||||
|
||||
@@ -921,6 +915,48 @@ export class LayerManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置激活当前图层下画布中的所有对象,并变成选择组
|
||||
setAllActiveGroupLayerCanvasObject(layer) {
|
||||
// 获取当前图层下所有元素
|
||||
|
||||
// 选择当前组下所有画布元素
|
||||
const allObjects = layer.children.reduce((acc, child) => {
|
||||
// 如果子图层有fabricObjects,则添加到结果数组
|
||||
child?.fabricObjects?.forEach((obj) => {
|
||||
const { object } = findObjectById(this.canvas, obj.id);
|
||||
if (object) {
|
||||
acc.push(object);
|
||||
}
|
||||
});
|
||||
|
||||
if (child?.fabricObject) {
|
||||
const { object } = findObjectById(this.canvas, child?.fabricObject.id);
|
||||
if (object) {
|
||||
acc.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (allObjects.length) {
|
||||
// 切换到选择模式
|
||||
this?.toolManager?.setTool(OperationType.SELECT);
|
||||
|
||||
// 如果有对象,创建选择组
|
||||
this.canvas.discardActiveObject(); // 取消当前活动对象
|
||||
// 选中多个对象,不是创建组
|
||||
// 多个对象时创建活动选择组
|
||||
const activeSelection = new fabric.ActiveSelection(allObjects, {
|
||||
canvas: this.canvas,
|
||||
});
|
||||
|
||||
// 设置活动选择组的属性
|
||||
this.canvas.setActiveObject(activeSelection);
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换图层可见性
|
||||
* @param {string} layerId 图层ID
|
||||
@@ -943,6 +979,22 @@ export class LayerManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询图层可见性
|
||||
* @param {string} layerId 图层ID
|
||||
* @returns {boolean} 图层是否可见
|
||||
*/
|
||||
getLayerVisibility(layerId) {
|
||||
// 查找图层
|
||||
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
||||
if (!layer) {
|
||||
console.error(`图层 ${layerId} 不存在`);
|
||||
return false;
|
||||
}
|
||||
// 返回图层的可见性状态
|
||||
return layer.visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换图层锁定状态
|
||||
* @param {string} layerId 图层ID
|
||||
|
||||
@@ -475,7 +475,10 @@ export function cloneLayer(layer) {
|
||||
export function findLayerRecursively(layers, layerId, parent = null) {
|
||||
try {
|
||||
if (!layers || !Array.isArray(layers) || !layerId) {
|
||||
return null;
|
||||
return {
|
||||
layer: null,
|
||||
parent: null,
|
||||
};
|
||||
}
|
||||
|
||||
// 在当前图层列表中查找
|
||||
@@ -499,11 +502,17 @@ export function findLayerRecursively(layers, layerId, parent = null) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`查找图层 ${layerId} 时出错:`, error);
|
||||
return null;
|
||||
return {
|
||||
layer: null,
|
||||
parent: null,
|
||||
};
|
||||
}
|
||||
|
||||
console.warn(`图层 ${layerId} 未找到`);
|
||||
return null;
|
||||
return {
|
||||
layer: null,
|
||||
parent: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user