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