feat: 液化撤销问题修复+选取更改逻辑+右键删除组图层问题修复

This commit is contained in:
bighuixiang
2025-07-09 00:22:03 +08:00
parent 5cc93aeba4
commit 943b49c1d7
9 changed files with 1668 additions and 545 deletions

View File

@@ -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;
}
}
}

View File

@@ -5,7 +5,10 @@ import {
} from "../utils/layerHelper.js";
import { createRasterizedImage } from "../utils/selectionToImage.js";
import { CompositeCommand, Command } from "./Command.js";
import { CreateImageLayerCommand } from "./LayerCommands.js";
import {
CreateImageLayerCommand,
RemoveLayerCommand,
} from "./LayerCommands.js";
import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper.js";
@@ -41,6 +44,13 @@ export class LassoCutoutCommand extends CompositeCommand {
// 在初始化时克隆保存选区对象,避免撤销后重做时获取不到选区对象
this._clonedSelectionObject = null;
this._initializeClonedSelection();
// 新增:保存原图层信息用于撤销恢复
this.originalLayer = null; // 保存原图层的完整信息
this.originalLayerIndex = -1; // 保存原图层在layers数组中的索引
this.originalFabricObjects = []; // 保存原图层的所有fabric对象序列化
this.originalCanvasObjects = []; // 保存从画布中获取的真实fabric对象
this._initializeOriginalLayerInfo();
}
/**
@@ -70,6 +80,43 @@ export class LassoCutoutCommand extends CompositeCommand {
}
}
/**
* 初始化原图层信息
* @private
*/
_initializeOriginalLayerInfo() {
if (this.layerManager) {
const activeLayer = this.layerManager.getActiveLayer();
if (activeLayer) {
// 保存原图层的完整信息
this.originalLayer = JSON.parse(JSON.stringify(activeLayer)); // 深拷贝
// 保存原图层在layers数组中的索引
this.originalLayerIndex = this.layerManager.layers.value.findIndex(
(layer) => layer.id === activeLayer.id
);
// 获取并序列化原图层的所有fabric对象
const sourceObjects = this._getLayerObjects(activeLayer);
this.originalCanvasObjects = sourceObjects; // 保存真实对象引用
this.originalFabricObjects = sourceObjects.map((obj) =>
obj.toObject([
"id",
"layerId",
"layerName",
"parentId",
"type",
"custom",
])
);
console.log(
`套索抠图:已保存原图层信息,图层名: ${activeLayer.name},对象数量: ${sourceObjects.length}`
);
}
}
}
async execute() {
if (!this.canvas || !this.layerManager || !this.selectionManager) {
console.error("无法执行套索抠图:参数无效");
@@ -146,7 +193,19 @@ export class LassoCutoutCommand extends CompositeCommand {
this.newLayerId = createImageLayerCmd.newLayerId;
this.executedCommands.push(createImageLayerCmd);
// 2. 清除选区命令
// 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. 清除选区命令
const clearSelectionCmd = new ClearSelectionCommand({
canvas: this.canvas,
selectionManager: this.selectionManager,
@@ -276,6 +335,7 @@ export class LassoCutoutCommand extends CompositeCommand {
}
// 2. 逆序撤销所有已执行的子命令
// RemoveLayerCommand的undo会自动恢复原图层所以不需要再调用_restoreOriginalLayer
for (let i = this.executedCommands.length - 1; i >= 0; i--) {
const command = this.executedCommands[i];
if (command && typeof command.undo === "function") {
@@ -293,6 +353,12 @@ export class LassoCutoutCommand extends CompositeCommand {
}
}
// 注意不需要调用_restoreOriginalLayer因为RemoveLayerCommand的undo已经恢复了原图层
// 但是需要确保画布状态正确,所以手动触发一次渲染
if (this.canvas) {
this.canvas.renderAll();
}
// 3. 清理状态
this.executedCommands = [];
this.newLayerId = null;
@@ -331,6 +397,13 @@ export class LassoCutoutCommand extends CompositeCommand {
baseResolutionScale: this.baseResolutionScale,
hasSerializedSelection: !!this.serializedSelectionObject,
selectionType: this.serializedSelectionObject?.type || null,
// 新增:原图层信息
hasOriginalLayer: !!this.originalLayer,
originalLayerName: this.originalLayer?.name || null,
originalLayerId: this.originalLayer?.id || null,
originalLayerIndex: this.originalLayerIndex,
originalFabricObjectsCount: this.originalFabricObjects.length,
originalCanvasObjectsCount: this.originalCanvasObjects.length,
subCommands: this.executedCommands.map((cmd) => ({
name: cmd.constructor.name,
info: cmd.getInfo ? cmd.getInfo() : {},
@@ -815,6 +888,109 @@ export class LassoCutoutCommand extends CompositeCommand {
return null;
}
}
/**
* 恢复原图层和其fabric对象
* @private
*/
async _restoreOriginalLayer() {
if (!this.originalLayer) {
console.warn("没有保存的原图层信息,无法恢复");
return false;
}
try {
console.log(`↩️ 开始恢复原图层: ${this.originalLayer.name}`);
// 1. 恢复图层到原位置
if (this.originalLayerIndex !== -1) {
this.layerManager.layers.value.splice(
this.originalLayerIndex,
0,
this.originalLayer
);
} else {
// 如果没有保存索引,添加到末尾
this.layerManager.layers.value.push(this.originalLayer);
}
// 2. 恢复fabric对象到画布
if (this.originalFabricObjects.length > 0) {
console.log(
`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`
);
// 使用fabric.util.enlivenObjects批量反序列化对象
await new Promise((resolve, reject) => {
fabric.util.enlivenObjects(
this.originalFabricObjects,
(restoredObjects) => {
if (!restoredObjects || restoredObjects.length === 0) {
console.warn("没有成功反序列化任何对象");
resolve();
return;
}
// 将恢复的对象添加到画布
restoredObjects.forEach((obj) => {
if (obj) {
// 确保对象的图层信息正确
obj.layerId = this.originalLayer.id;
obj.layerName = this.originalLayer.name;
obj.setCoords();
this.canvas.add(obj);
}
});
console.log(`✅ 成功恢复 ${restoredObjects.length} 个fabric对象`);
resolve();
},
// 命名空间,用于自定义对象类型
""
);
});
}
// 3. 如果恢复的是活动图层,设置为当前活动图层
if (this.originalLayer.id === this.layerManager.activeLayerId.value) {
this.layerManager.activeLayerId.value = this.originalLayer.id;
} else {
// 如果当前没有活动图层,将恢复的图层设为活动图层
if (!this.layerManager.activeLayerId.value) {
this.layerManager.activeLayerId.value = this.originalLayer.id;
}
}
// 4. 重新渲染画布
this.canvas.renderAll();
console.log(`✅ 原图层恢复完成: ${this.originalLayer.name}`);
return true;
} catch (error) {
console.error("❌ 恢复原图层失败:", error);
return false;
}
}
/**
* 重做操作
* @returns {Promise<Object|boolean>} 执行结果
*/
async redo() {
console.log(`🔄 开始重做套索抠图操作`);
// 重做操作等同于重新执行,但需要先清理一些状态
this.executedCommands = [];
this.newLayerId = null;
this.cutoutImageUrl = null;
this.fabricImage = null;
this.groupLayer = null;
// 重新生成groupId确保唯一性可选也可以复用原来的
// this.groupId = `cutout-group-${Date.now()}`;
return await this.execute();
}
}
/**

View File

@@ -411,43 +411,63 @@ export class RemoveLayerCommand extends Command {
this.removedLayer = this.layers.value[this.layerIndex];
this.isActiveLayer = this.layerId === this.activeLayerId.value;
// 从Canvas中找到真实对象并备份确保撤销和重做时对象的一致性
// 从Canvas中找到真实对象并备份包括当前图层及其所有子图层的对象
this.originalObjects = [];
if (this.removedLayer) {
// 从画布中获取真实的对象引用
this.originalObjects = this.canvas.getObjects().filter((obj) => {
return obj.layerId === this.layerId;
});
this.originalObjects = this._collectAllLayerObjects(this.removedLayer);
}
// 备份原活动图层ID
this.originalActiveLayerId = this.activeLayerId.value;
}
/**
* 收集图层及其所有子图层的画布对象
* @param {Object} layer 要收集对象的图层
* @returns {Array} 所有相关的画布对象
* @private
*/
_collectAllLayerObjects(layer) {
const allObjects = [];
// 收集当前图层的对象
const layerObjects = this.canvas.getObjects().filter((obj) => {
return obj.layerId === layer.id;
});
allObjects.push(...layerObjects);
// 如果图层有特殊的fabricObject属性如背景图层
if (layer.fabricObject) {
const { object } = findObjectById(this.canvas, layer.fabricObject.id);
if (object && !allObjects.includes(object)) {
allObjects.push(object);
}
}
// 递归收集子图层的对象
if (layer.children && Array.isArray(layer.children)) {
layer.children.forEach((childLayer) => {
const childObjects = this._collectAllLayerObjects(childLayer);
allObjects.push(...childObjects);
});
}
return allObjects;
}
execute() {
if (this.layerIndex === -1 || !this.removedLayer) {
console.error(`图层 ${this.layerId} 不存在`);
return false;
}
// 从画布中移除图层中的所有真实对象
// 从画布中移除图层及其所有子图层中的真实对象
this.originalObjects.forEach((obj) => {
if (this.canvas.getObjects().includes(obj)) {
this.canvas.remove(obj);
}
});
// 如果是背景图层,移除特殊对象
if (this.removedLayer.isBackground && this.removedLayer.fabricObject) {
const { object } = findObjectById(
this.canvas,
this.removedLayer.fabricObject.id
);
if (object) {
this.canvas.remove(object);
}
}
// 从图层列表中删除
this.layers.value.splice(this.layerIndex, 1);
@@ -470,7 +490,7 @@ export class RemoveLayerCommand extends Command {
}
console.log(
`✅ 已移除图层: ${this.removedLayer.name} (ID: ${this.layerId})`
`✅ 已移除图层: ${this.removedLayer.name} (ID: ${this.layerId}),包含 ${this.originalObjects.length} 个对象`
);
return true;
}
@@ -482,26 +502,18 @@ export class RemoveLayerCommand extends Command {
// 使用优化渲染批处理恢复真实对象到画布
optimizeCanvasRendering(this.canvas, () => {
this.originalObjects.forEach((obj) => {
// 恢复对象到画布
this.canvas.add(obj);
// 确保对象的图层信息正确
obj.layerId = this.layerId;
obj.layerName = this.removedLayer.name;
obj.setCoords(); // 更新坐标
});
// 如果是背景图层,恢复特殊对象
if (this.removedLayer.isBackground && this.removedLayer.fabricObject) {
// 检查对象是否已在画布中
const { object } = findObjectById(
this.canvas,
this.removedLayer.fabricObject.id
);
if (!object) {
this.canvas.add(this.removedLayer.fabricObject);
}
}
// 倒序添加对象,确保下标越小的子图层在画布中越靠前
this.originalObjects
.slice()
.reverse()
.forEach((obj) => {
// 确保对象不在画布中才添加,避免重复添加
if (!this.canvas.getObjects().includes(obj)) {
this.canvas.add(obj);
// 确保对象的坐标正确
obj.setCoords();
}
});
});
// 如果删除的是当前活动图层,恢复活动图层
@@ -510,7 +522,7 @@ export class RemoveLayerCommand extends Command {
}
console.log(
`↩️ 已恢复图层: ${this.removedLayer.name} (ID: ${this.layerId})`
`↩️ 已恢复图层: ${this.removedLayer.name} (ID: ${this.layerId}),包含 ${this.originalObjects.length} 个对象`
);
}
}

View File

@@ -508,6 +508,9 @@ export class LiquifyStateCommand extends Command {
// 获取引用管理器实例
this.refManager = getLiquifyReferenceManager();
// 实时更新实例
this.realtimeUpdater = options.realtimeUpdater || null;
// 注册对象到引用管理器
this.objectRefId = this.refManager.registerObject(
this.targetObject,
@@ -587,17 +590,49 @@ export class LiquifyStateCommand extends Command {
this.initialSnapshotId
);
// 恢复液化管理器到初始状态
// 恢复液化管理器到初始状态 - 关键修复
if (this.liquifyManager) {
if (this.initialLiquifyState) {
this._restoreLiquifyManagerState(this.initialLiquifyState);
} else {
// 如果没有初始状态,重置液化管理器
this.liquifyManager.reset();
// 重新准备液化环境
// if (this.initialLiquifyState) {
// this._restoreLiquifyManagerState(this.initialLiquifyState);
// } else {
// 如果没有初始状态,重置液化管理器并重新初始化
this.liquifyManager.reset();
const currentTarget = this.refManager.getObjectRef(this.objectRefId);
if (currentTarget) {
// 重新准备液化环境,使用初始图像数据
await this.liquifyManager.prepareForLiquify(currentTarget);
// 强制设置原始图像数据为初始状态
if (this.liquifyManager.enhancedManager) {
this.liquifyManager.enhancedManager.originalImageData =
this.initialImageData;
this.liquifyManager.enhancedManager.currentImageData =
this._cloneImageData(this.initialImageData);
// 重置激活渲染器的数据
if (this.liquifyManager.enhancedManager.activeRenderer) {
const renderer = this.liquifyManager.enhancedManager.activeRenderer;
renderer.originalImageData = this.initialImageData;
renderer.currentImageData = this._cloneImageData(
this.initialImageData
);
// 重置CPU渲染器的网格和变形历史
if (renderer.reset) {
renderer.reset();
}
// }
}
}
}
// 确保实时更新器使用正确的目标对象和初始数据
if (this.realtimeUpdater) {
const currentTarget = this.refManager.getObjectRef(this.objectRefId);
if (currentTarget) {
await this.liquifyManager.prepareForLiquify(currentTarget);
this.realtimeUpdater.targetObject = currentTarget;
// 重置实时更新器的内部状态
if (this.realtimeUpdater.reset) {
this.realtimeUpdater.reset();
}
}
}
@@ -605,7 +640,7 @@ export class LiquifyStateCommand extends Command {
this.currentState = "initial";
this.canvas.renderAll();
console.log("🔄 液化命令撤销完成,恢复初始状态");
console.log("🔄 液化命令撤销完成,恢复初始状态(已重置所有内部状态)");
return true;
}
@@ -761,20 +796,22 @@ export class LiquifyStateCommand extends Command {
if (!this.liquifyManager) return null;
try {
const enhancedManager = this.liquifyManager.enhancedManager;
const state = {
// 捕获增强管理器状态
enhancedManagerState: null,
// 捕获当前渲染器状态
activeRendererState: null,
// 捕获目标对象引用
targetObjectRef: this.liquifyManager.targetObject,
targetObjectRef:
this.liquifyManager.targetObject ?? enhancedManager.targetObject,
// 捕获初始化状态
initialized: this.liquifyManager.initialized || false,
};
// 如果有增强管理器,捕获其状态
if (this.liquifyManager.enhancedManager) {
const enhancedManager = this.liquifyManager.enhancedManager;
state.enhancedManagerState = {
initialized: enhancedManager.initialized,
renderMode: enhancedManager.renderMode,
@@ -829,6 +866,17 @@ export class LiquifyStateCommand extends Command {
this.liquifyManager.targetObject = state.targetObjectRef;
}
// 确保实时更新器使用正确的目标对象
if (this.realtimeUpdater) {
this.realtimeUpdater.targetObject = this.liquifyManager.targetObject;
// 如果有setTargetObject方法调用它
if (typeof this.realtimeUpdater.setTargetObject === "function") {
this.realtimeUpdater.setTargetObject(
this.liquifyManager.targetObject
);
}
}
// 恢复增强管理器状态
if (state.enhancedManagerState && this.liquifyManager.enhancedManager) {
const enhancedManager = this.liquifyManager.enhancedManager;
@@ -837,6 +885,8 @@ export class LiquifyStateCommand extends Command {
enhancedManager.initialized = enhancedState.initialized;
enhancedManager.renderMode = enhancedState.renderMode;
enhancedManager.targetObject = enhancedState.targetObject;
// 关键修复:确保图像数据被正确恢复
enhancedManager.originalImageData = enhancedState.originalImageData;
enhancedManager.currentImageData = enhancedState.currentImageData;
enhancedManager.params = { ...enhancedState.params };
@@ -848,6 +898,8 @@ export class LiquifyStateCommand extends Command {
const rendererState = state.activeRendererState;
renderer.initialized = rendererState.initialized;
// 关键修复:强制重置渲染器的图像数据
renderer.originalImageData = rendererState.originalImageData;
renderer.currentImageData = rendererState.currentImageData;
renderer.params = { ...rendererState.params };
@@ -856,16 +908,45 @@ export class LiquifyStateCommand extends Command {
// 恢复网格状态如果是CPU渲染器
if (rendererState.meshState && renderer.mesh) {
this._restoreMeshState(renderer.mesh, rendererState.meshState);
} else if (renderer.mesh && renderer._initMesh) {
// 如果没有保存的网格状态,重新初始化网格
renderer._initMesh(
rendererState.originalImageData?.width ||
renderer.originalImageData?.width,
rendererState.originalImageData?.height ||
renderer.originalImageData?.height
);
}
// 恢复变形历史
// 重置变形历史
if (rendererState.deformHistory) {
renderer.deformHistory = [...rendererState.deformHistory];
} else {
// 如果没有保存的历史,清空变形历史
renderer.deformHistory = [];
}
// 重置持续按压相关状态(如果存在)
if (renderer.isHolding !== undefined) {
renderer.isHolding = false;
renderer.pressStartTime = 0;
renderer.pressDuration = 0;
renderer.accumulatedRotation = 0;
renderer.accumulatedScale = 0;
renderer.lastApplyTime = 0;
}
// 重置拖拽状态(如果存在)
if (renderer.isDragging !== undefined) {
renderer.isDragging = false;
renderer.dragDistance = 0;
renderer.dragAngle = 0;
renderer.isFirstApply = true;
}
}
}
console.log(`🔄 液化管理器状态已恢复`);
console.log(`🔄 液化管理器状态已恢复(包含完整的数据重置)`);
} catch (error) {
console.error("恢复液化管理器状态失败:", error);
}
@@ -917,6 +998,36 @@ export class LiquifyStateCommand extends Command {
}
}
}
/**
* 克隆图像数据
* @param {ImageData} imageData 原始图像数据
* @returns {ImageData} 克隆的图像数据
* @private
*/
_cloneImageData(imageData) {
if (!imageData) return null;
try {
// 使用新的浏览器API直接复制
return new ImageData(
new Uint8ClampedArray(imageData.data),
imageData.width,
imageData.height
);
} catch (e) {
console.warn("使用备选方法克隆ImageData");
// 备选方法
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = imageData.width;
canvas.height = imageData.height;
ctx.putImageData(imageData, 0, 0);
return ctx.getImageData(0, 0, imageData.width, imageData.height);
}
}
}
/**

View File

@@ -1036,6 +1036,7 @@ async function handleMouseUp() {
targetObjectId: targetObjectId.value,
initialImageData: initialImageData.value,
finalImageData: finalImageData,
realtimeUpdater: realtimeUpdater.value,
name: `液化操作 - ${currentMode.value}`,
description: `应用${currentMode.value}模式的液化变形操作`,
});

View File

@@ -48,11 +48,11 @@
<div class="tool-actions">
<div class="action-btn" @click="copySelectionToNewLayer">
<svg-icon name="CPaste" size="20" />
<span class="btn-text">{{ $t("拷贝并粘贴") }}</span>
<span class="btn-text">{{ $t("创建") }}</span>
</div>
<div class="action-btn" @click="cutSelectionToNewLayer">
<svg-icon name="CCut" size="30" />
<span class="btn-text">{{ $t("剪切并粘贴") }}</span>
<span class="btn-text">{{ $t("创建并拷贝") }}</span>
</div>
<div class="action-btn" @click="clearSelectionContent">
<svg-icon name="CClear" size="22" />

View File

@@ -161,7 +161,7 @@ export class EnhancedLiquifyManager {
// 传入的是对象
targetObject = target;
const { layer } = findInChildLayers(
this.layerManager.layers.value,
this.layerManager.layers?.value ?? this.layerManager.layers,
targetObject.layerId
);
if (layer) {

View File

@@ -524,7 +524,10 @@ export function findLayerRecursively(layers, layerId, parent = null) {
*/
export function findInChildLayers(children, layerId, parent) {
if (!children || !Array.isArray(children) || !layerId) {
return null;
return {
layer: null,
parent: null,
};
}
for (const child of children) {
@@ -540,7 +543,7 @@ export function findInChildLayers(children, layerId, parent) {
Array.isArray(child.children)
) {
const result = findInChildLayers(child.children, layerId, child);
if (result) {
if (result && result.layer) {
return result;
}
}