Files
aida_front/src/component/Canvas/CanvasEditor/commands/LassoCutoutCommand.js

504 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createRasterizedImage } from "../utils/selectionToImage.js";
import { CompositeCommand } from "./Command.js";
import { CreateImageLayerCommand } from "./LayerCommands.js";
import { ClearSelectionCommand } from "./SelectionCommands.js";
import { fabric } from "fabric-with-all";
/**
* 套索抠图命令
* 实现将选区内容抠图到新图层的功能
*/
export class LassoCutoutCommand 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; // 基础分辨率倍数
}
async execute() {
if (!this.canvas || !this.layerManager || !this.selectionManager) {
console.error("无法执行套索抠图:参数无效");
return false;
}
try {
this.executedCommands = [];
// 获取选区
const selectionObject = this.selectionManager.getSelectionObject();
if (!selectionObject) {
console.error("无法执行套索抠图:当前没有选区");
return false;
}
// 确定源图层
const sourceLayer = this.layerManager.getActiveLayer();
if (!sourceLayer) {
console.error("无法执行套索抠图:源图层无效");
return false;
}
// 获取源图层的所有对象(包括子图层)
const sourceObjects = this._getLayerObjects(sourceLayer);
if (sourceObjects.length === 0) {
console.error("无法执行套索抠图:源图层没有可见对象");
return false;
}
// 获取选区边界信息用于后续定位
const selectionBounds = selectionObject.getBoundingRect(true, true);
// 使用createRasterizedImage执行抠图操作
// this.cutoutImageUrl = await this._performCutoutWithRasterized(
// sourceObjects,
// selectionObject,
// selectionBounds
// );
// if (!this.cutoutImageUrl) {
// console.error("抠图失败");
// return false;
// }
this.fabricImage = await this._performCutoutWithRasterized(
sourceObjects,
selectionObject,
selectionBounds
);
// // 创建fabric图像对象传递选区边界信息
// this.fabricImage = await this._createFabricImage(
// this.cutoutImageUrl,
// selectionBounds
// );
if (!this.fabricImage) {
console.error("创建图像对象失败");
return false;
}
// 1. 创建图像图层命令
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);
// 2. 清除选区命令
const clearSelectionCmd = new ClearSelectionCommand({
canvas: this.canvas,
selectionManager: this.selectionManager,
});
// 执行清除选区命令
await clearSelectionCmd.execute();
this.executedCommands.push(clearSelectionCmd);
console.log(`套索抠图完成新图层ID: ${this.newLayerId}`);
return {
newLayerId: this.newLayerId,
cutoutImageUrl: this.cutoutImageUrl,
};
} catch (error) {
console.error("套索抠图过程中出错:", error);
// 如果已经创建了新图层,需要进行清理
if (this.newLayerId) {
try {
await this.layerManager.removeLayer(this.newLayerId);
} catch (cleanupError) {
console.warn("清理新图层失败:", cleanupError);
}
}
throw error;
}
}
async undo() {
try {
// 逆序撤销所有已执行的命令
for (let i = this.executedCommands.length - 1; i >= 0; i--) {
const command = this.executedCommands[i];
if (command && typeof command.undo === "function") {
await command.undo();
}
}
this.executedCommands = [];
this.newLayerId = null;
this.cutoutImageUrl = null;
this.fabricImage = null;
return true;
} catch (error) {
console.error("撤销套索抠图失败:", error);
return false;
}
}
/**
* 获取图层的所有对象(包括子图层,从画布中查找真实对象)
* @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((fabricRef) => {
if (fabricRef && fabricRef.id) {
// 从画布中查找真实的对象
const realObject = canvasObjects.find(
(obj) => obj.id === fabricRef.id
);
if (realObject && realObject.visible) {
objects.push(realObject);
}
}
});
}
// 处理单个fabricObject背景图层等
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
const realObject = canvasObjects.find(
(obj) => obj.id === currentLayer.fabricObject.id
);
if (realObject && realObject.visible) {
objects.push(realObject);
}
}
// 递归处理子图层
if (currentLayer.children && Array.isArray(currentLayer.children)) {
currentLayer.children.forEach((childLayer) => {
if (childLayer.visible !== false) {
// 只处理可见的子图层
collectLayerObjects(childLayer);
}
});
}
};
collectLayerObjects(layer);
console.log(`从图层 "${layer.name}" 收集到 ${objects.length} 个可见对象`);
return objects;
}
/**
* 使用createRasterizedImage执行抠图操作
* @param {Array} sourceObjects 源对象数组
* @param {Object} selectionObject 选区对象
* @param {Object} selectionBounds 选区边界
* @returns {String} 抠图结果的DataURL
* @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 devicePixelRatio = window.devicePixelRatio || 1;
scaleFactor = Math.max(scaleFactor, devicePixelRatio * 1.5);
}
// 使用createRasterizedImage生成栅格化图像将选区作为裁剪路径
const rasterizedDataURL = await createRasterizedImage({
canvas: this.canvas,
fabricObjects: sourceObjects,
clipPath: selectionObject, // 使用选区作为裁剪路径
trimWhitespace: true,
trimPadding: 2,
quality: 1.0,
format: "png",
scaleFactor: scaleFactor,
// isReturenDataURL: true, // 返回DataURL
preserveOriginalQuality: true, // 启用高质量模式
});
if (!rasterizedDataURL) {
throw new Error("栅格化生成图像失败");
}
console.log(`✅ 高质量抠图完成,缩放因子: ${scaleFactor}x`);
return rasterizedDataURL;
} catch (error) {
console.error("使用createRasterizedImage执行抠图失败:", error);
// 如果createRasterizedImage失败回退到原始方法
console.log("⚠️ 回退到原始抠图方法...");
return await this._performCutout(
{ fabricObjects: sourceObjects },
selectionObject
);
}
}
/**
* 原始抠图方法(作为备用方案)
* @param {Object} sourceLayer 源图层
* @param {Object} selectionObject 选区对象
* @returns {String} 抠图结果的DataURL
* @private
*/
async _performCutout(sourceLayer, selectionObject) {
try {
console.log("=== 使用原始方法执行抠图 ===");
// 获取选区边界
const selectionBounds = selectionObject.getBoundingRect(true, true);
// 保存画布当前状态
const originalActiveObject = this.canvas.getActiveObject();
const originalSelection = this.canvas.selection;
// 临时禁用画布选择
this.canvas.selection = false;
this.canvas.discardActiveObject();
let tempGroup = null;
let originalObjects = [];
try {
// 收集源图层中的可见对象
const visibleObjects = sourceLayer.fabricObjects.filter(
(obj) => obj.visible
);
if (visibleObjects.length === 0) {
throw new Error("源图层没有可见对象");
}
// 如果只有一个对象且已经是组,直接使用
if (visibleObjects.length === 1 && visibleObjects[0].type === "group") {
tempGroup = visibleObjects[0];
} else {
// 创建临时组
const clonedObjects = [];
for (const obj of visibleObjects) {
const cloned = await this._cloneObject(obj);
clonedObjects.push(cloned);
}
tempGroup = new fabric.Group(clonedObjects, {
selectable: false,
evented: false,
});
this.canvas.add(tempGroup);
}
// 设置选区为裁剪路径
const clipPath = await this._cloneObject(selectionObject);
clipPath.set({
fill: "",
stroke: "",
absolutePositioned: true,
originX: "left",
originY: "top",
});
tempGroup.set({
clipPath: clipPath,
});
this.canvas.renderAll();
// 计算渲染区域
const renderBounds = {
left: selectionBounds.left,
top: selectionBounds.top,
width: selectionBounds.width,
height: selectionBounds.height,
};
// 设置高分辨率倍数
let highResolutionScale = 1;
if (this.highResolutionEnabled) {
const devicePixelRatio = window.devicePixelRatio || 1;
highResolutionScale = Math.max(
this.baseResolutionScale,
devicePixelRatio * 2
);
}
// 创建用于导出的高分辨率canvas
const exportCanvas = document.createElement("canvas");
const exportCtx = exportCanvas.getContext("2d", {
alpha: true,
willReadFrequently: false,
colorSpace: "srgb",
});
const actualWidth = Math.round(
renderBounds.width * highResolutionScale
);
const actualHeight = Math.round(
renderBounds.height * highResolutionScale
);
exportCanvas.width = actualWidth;
exportCanvas.height = actualHeight;
exportCanvas.style.width = renderBounds.width + "px";
exportCanvas.style.height = renderBounds.height + "px";
exportCtx.imageSmoothingEnabled = true;
exportCtx.imageSmoothingQuality = "high";
exportCtx.clearRect(0, 0, actualWidth, actualHeight);
const vpt = this.canvas.viewportTransform;
const zoom = this.canvas.getZoom();
exportCtx.save();
exportCtx.scale(highResolutionScale, highResolutionScale);
exportCtx.translate(-renderBounds.left, -renderBounds.top);
if (zoom !== 1 || vpt[4] !== 0 || vpt[5] !== 0) {
exportCtx.transform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]);
}
tempGroup.render(exportCtx);
exportCtx.restore();
const dataUrl = exportCanvas.toDataURL("image/png", 1.0);
return dataUrl;
} finally {
// 清理和恢复
if (tempGroup) {
tempGroup.set({ clipPath: null });
if (originalObjects.length > 0) {
this.canvas.remove(tempGroup);
}
}
this.canvas.selection = originalSelection;
if (originalActiveObject) {
this.canvas.setActiveObject(originalActiveObject);
}
this.canvas.renderAll();
}
} catch (error) {
console.error("原始方法执行抠图失败:", error);
return null;
}
}
/**
* 从DataURL创建fabric图像对象
* @param {String} dataUrl 图像DataURL
* @param {Object} selectionBounds 选区边界信息
* @returns {fabric.Image} fabric图像对象
* @private
*/
async _createFabricImage(dataUrl, selectionBounds) {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
dataUrl,
(img) => {
if (!img) {
reject(new Error("无法从DataURL创建图像"));
return;
}
// 使用选区的位置作为图像位置
let targetLeft = selectionBounds.left + selectionBounds.width / 2;
let targetTop = selectionBounds.top + selectionBounds.height / 2;
// 设置图像属性,保持选区的原始尺寸
img.set({
left: targetLeft,
top: targetTop,
originX: "center",
originY: "center",
selectable: true,
evented: true,
hasControls: true,
hasBorders: true,
cornerStyle: "circle",
cornerColor: "#007aff",
cornerSize: 10,
transparentCorners: false,
borderColor: "#007aff",
borderScaleFactor: 2,
// 优化图像渲染质量
objectCaching: false, // 禁用缓存以确保最佳质量
statefullCache: true,
noScaleCache: false,
});
// 更新坐标
img.setCoords();
console.log(
`图像创建完成,位置: (${targetLeft}, ${targetTop}), 尺寸: ${img.width}x${img.height}`
);
resolve(img);
},
{
crossOrigin: "anonymous",
// 确保图像以最高质量加载
quality: 1.0,
}
);
});
}
/**
* 克隆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) => {
resolve(cloned);
});
} catch (error) {
reject(error);
}
});
}
}