import { CompositeCommand } from "./Command.js"; import { AddLayerCommand, CreateImageLayerCommand } from "./LayerCommands.js"; import { ToolCommand } from "./ToolCommands.js"; import { ClearSelectionCommand } from "./SelectionCommands.js"; import { createLayer, LayerType, OperationType } from "../utils/layerHelper.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 || 4; // 基础分辨率倍数,提高到4倍获得更清晰的图像 } 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 || sourceLayer.fabricObjects.length === 0) { console.error("无法执行套索抠图:源图层无效"); return false; } // 获取选区边界信息用于后续定位 const selectionBounds = selectionObject.getBoundingRect(true, true); // 执行在当前画布上的抠图操作 this.cutoutImageUrl = await this._performCutout( sourceLayer, selectionObject ); if (!this.cutoutImageUrl) { console.error("抠图失败"); return false; } // 创建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} sourceLayer 源图层 * @param {Object} selectionObject 选区对象 * @returns {String} 抠图结果的DataURL * @private */ async _performCutout(sourceLayer, selectionObject) { try { console.log("=== 开始在当前画布执行抠图 ==="); // 获取选区边界 const selectionBounds = selectionObject.getBoundingRect(true, true); console.log( `选区边界: left=${selectionBounds.left}, top=${selectionBounds.top}, width=${selectionBounds.width}, height=${selectionBounds.height}` ); // 保存画布当前状态 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 ); console.log(`源图层可见对象数量: ${visibleObjects.length}`); if (visibleObjects.length === 0) { throw new Error("源图层没有可见对象"); } // 如果只有一个对象且已经是组,直接使用 if (visibleObjects.length === 1 && visibleObjects[0].type === "group") { tempGroup = visibleObjects[0]; console.log("使用现有组对象"); } else { // 创建临时组 console.log("创建临时组..."); // 记录原始对象的位置和状态,用于后续恢复 originalObjects = visibleObjects.map((obj) => ({ object: obj, originalLeft: obj.left, originalTop: obj.top, originalAngle: obj.angle, originalScaleX: obj.scaleX, originalScaleY: obj.scaleY, })); // 不需要从画布移除原对象,直接创建组 // 克隆对象来创建组,避免影响原对象 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 ); console.log( `设备像素比: ${devicePixelRatio}, 基础倍数: ${this.baseResolutionScale}, 最终放大倍数: ${highResolutionScale}` ); } else { console.log("高分辨率渲染已禁用,使用1x倍数"); } // 创建用于导出的高分辨率canvas const exportCanvas = document.createElement("canvas"); const exportCtx = exportCanvas.getContext("2d", { alpha: true, willReadFrequently: false, colorSpace: "srgb", }); // 设置canvas的实际像素尺寸(放大倍数) const actualWidth = Math.round( renderBounds.width * highResolutionScale ); const actualHeight = Math.round( renderBounds.height * highResolutionScale ); exportCanvas.width = actualWidth; exportCanvas.height = actualHeight; // 设置canvas的显示尺寸(CSS尺寸,保持与选区一致) exportCanvas.style.width = renderBounds.width + "px"; exportCanvas.style.height = renderBounds.height + "px"; // 启用最高质量渲染设置 exportCtx.imageSmoothingEnabled = true; exportCtx.imageSmoothingQuality = "high"; // 设置文本渲染质量 if (exportCtx.textRenderingOptimization) { exportCtx.textRenderingOptimization = "optimizeQuality"; } // 设置线条和图形的渲染质量 exportCtx.lineCap = "round"; exportCtx.lineJoin = "round"; exportCtx.miterLimit = 10; // 设置画布背景为透明 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); console.log( `抠图完成,选区尺寸: ${renderBounds.width}x${renderBounds.height}, 实际渲染尺寸: ${actualWidth}x${actualHeight}, 放大倍数: ${highResolutionScale}x` ); return dataUrl; } finally { // 清理和恢复 if (tempGroup) { // 移除裁剪路径 tempGroup.set({ clipPath: null }); // 如果是我们创建的临时组,需要移除它 if (originalObjects.length > 0) { this.canvas.remove(tempGroup); // 恢复原始对象的状态(位置等信息保持不变) originalObjects.forEach( ({ object, originalLeft, originalTop, originalAngle, originalScaleX, originalScaleY, }) => { // 确保对象仍然在画布上且状态正确 if (!this.canvas.getObjects().includes(object)) { this.canvas.add(object); } // 恢复原始变换状态(如果需要的话) object.set({ left: originalLeft, top: originalTop, angle: originalAngle, scaleX: originalScaleX, scaleY: originalScaleY, }); object.setCoords(); } ); } } // 恢复画布状态 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; } // 计算画布中心位置 const canvasCenter = this.canvas.getCenter(); // 如果有选区边界信息,使用选区的原始位置和尺寸 let targetLeft = canvasCenter.left; let targetTop = canvasCenter.top; let targetWidth = img.width; let targetHeight = img.height; if (selectionBounds) { // 使用选区的原始位置 targetLeft = selectionBounds.left + selectionBounds.width / 2; targetTop = selectionBounds.top + selectionBounds.height / 2; // 确保图像显示尺寸与选区尺寸一致 targetWidth = selectionBounds.width; targetHeight = selectionBounds.height; console.log( `设置图像位置: left=${targetLeft}, top=${targetTop}, 尺寸: ${targetWidth}x${targetHeight}` ); } // 计算缩放比例以匹配目标尺寸 const scaleX = targetWidth / img.width; const scaleY = targetHeight / img.height; // 设置图像属性 img.set({ left: targetLeft, top: targetTop, scaleX: scaleX, scaleY: scaleY, 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(); 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); } }); } }