feat: 实时更新背景色+选取剪切+选取删除初步开发完成
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Command, CompositeCommand } from "./Command.js";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { createLayer, LayerType } from "../utils/layerHelper.js";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage.js";
|
||||
import { generateId } from "../utils/helper.js";
|
||||
|
||||
/**
|
||||
* 创建选区命令
|
||||
@@ -168,47 +170,6 @@ export class RemoveFromSelectionCommand extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除选区命令
|
||||
*/
|
||||
export class ClearSelectionCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "清除选区",
|
||||
description: "清除当前选区",
|
||||
saveState: false,
|
||||
});
|
||||
this.selectionManager = options.selectionManager;
|
||||
this.originalSelection = options.selectionManager
|
||||
? options.selectionManager.getSelectionPath()
|
||||
: null;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.selectionManager) {
|
||||
console.error("无法清除选区:参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存原始选区
|
||||
if (!this.originalSelection) {
|
||||
this.originalSelection = this.selectionManager.getSelectionPath();
|
||||
}
|
||||
|
||||
// 清除选区
|
||||
this.selectionManager.clearSelection();
|
||||
return true;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.selectionManager || !this.originalSelection) return false;
|
||||
|
||||
// 恢复原始选区
|
||||
this.selectionManager.setSelectionFromPath(this.originalSelection);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 羽化选区命令
|
||||
*/
|
||||
@@ -470,19 +431,30 @@ export class CopySelectionToNewLayerCommand extends CompositeCommand {
|
||||
|
||||
/**
|
||||
* 从选区中删除内容命令
|
||||
* 使用栅格化方式清除选区内容,支持复杂选区形状
|
||||
*/
|
||||
export class ClearSelectionContentCommand extends Command {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
name: "清除选区内容",
|
||||
description: "删除选区中的内容",
|
||||
saveState: false,
|
||||
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() {
|
||||
@@ -491,56 +463,297 @@ export class ClearSelectionContentCommand extends Command {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取选区
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (!selectionObject) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 确定目标图层
|
||||
const layerId = this.targetLayerId || this.layerManager.getActiveLayerId();
|
||||
const layer = this.layerManager.getLayerById(layerId);
|
||||
if (!layer || !layer.fabricObjects) {
|
||||
console.error("无法清除选区内容:目标图层无效或为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 找到选区内的对象
|
||||
const objectsToRemove = layer.fabricObjects.filter((obj) => {
|
||||
return this.selectionManager.isObjectInSelection(obj);
|
||||
});
|
||||
|
||||
if (objectsToRemove.length === 0) {
|
||||
console.warn("选区内没有对象需要清除");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 备份被删除的对象
|
||||
this.removedObjects = objectsToRemove.map((obj) => ({
|
||||
object: this._cloneObjectSync(obj),
|
||||
layerId: layerId,
|
||||
}));
|
||||
|
||||
// 从图层中移除对象
|
||||
for (const obj of objectsToRemove) {
|
||||
this.layerManager.removeObjectFromLayer(layerId, obj.id);
|
||||
}
|
||||
|
||||
return { removedCount: objectsToRemove.length };
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.layerManager || this.removedObjects.length === 0) return false;
|
||||
try {
|
||||
console.log("↩️ 开始撤销清除选区内容操作");
|
||||
|
||||
// 恢复被删除的对象
|
||||
for (const item of this.removedObjects) {
|
||||
if (item.object && item.layerId) {
|
||||
const clonedObj = await this._cloneObject(item.object);
|
||||
this.layerManager.addObjectToLayer(item.layerId, clonedObj);
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用栅格化方式清除选区内容
|
||||
* @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) {
|
||||
@@ -572,7 +785,22 @@ export class ClearSelectionContentCommand extends Command {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 导入套索抠图命令
|
||||
export { LassoCutoutCommand } from "./LassoCutoutCommand.js";
|
||||
/**
|
||||
* 获取命令信息
|
||||
* @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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user