import { fabric } from "fabric-with-all"; import { traceImageContour, imageToCanvas } from "../utils/helper"; import { OperationType } from "../utils/layerHelper"; import { CreateSelectionCommand } from "../commands/SelectionCommands"; import { ClearSelectionCommand } from "../commands/LassoCutoutCommand"; import addIcon from "@/assets/images/canvas/add.png"; import removeIcon from "@/assets/images/canvas/remove.png"; import { Https } from "@/tool/https"; import store from "@/store"; import { createStaticCanvas } from "../utils/canvasFactory"; import { getObjectAlphaToCanvas } from "../utils/objectHelper"; /** * 部件选择管理器 */ export class PartManager { /** * 创建部件选择管理器 * @param {Object} options 配置选项 * @param {Object} options.canvas fabric.js画布实例 * @param {Object} options.commandManager 命令管理器实例 * @param {Object} options.canvasManager 画布管理实例 * @param {Object} options.layerManager 图层管理实例 * @param {Object} options.toolManager 工具管理实例 */ constructor(options = {}) { this.canvas = options.canvas; this.commandManager = options.commandManager; this.layerManager = options.layerManager; this.canvasManager = options.canvasManager; this.toolManager = options.toolManager; this.props = options.props; // 状态 this.isActive = false; // 不再直接绑定事件处理函数 this._mouseDownHandler = null; this._mouseMoveHandler = null; this._mouseUpHandler = null; this._keyDownHandler = null; // 选区相关的工具类型 this.tools = [ OperationType.PART, OperationType.PART_RECTANGLE, OperationType.PART_BRUSH, OperationType.PART_ERASER, ]; // 当前工具 this.activeTool = this.toolManager.activeTool; this.partGroup = null; // 当前选区对象 this.partId = "part_selector"; this.partCanvas = null;// 选区画布 // 点选工具相关 this.pointList = []; // 存储点选坐标 } /** * 设置当前工具 * @param {String} toolId 工具ID */ setCurrentTool(toolId) { // 检查是否为选区工具 const wasActive = this.isActive; this.isActive = this.tools.includes(toolId); // 如果从非选区工具切换到选区工具,初始化事件 if (!wasActive && this.isActive) { this.initEvents(); this.createPartObject(); } // 如果从选区工具切换到非选区工具,清理事件和选区 else if (wasActive && !this.isActive) { this.cleanupEvents(); this.clearPartObject(); this.clearPointData(); } else { this.clearPointData(); this.resetPartObject(); } console.log("切换工具", toolId); } /** 初始化选区相关事件 */ initEvents() { if (!this.canvas || this._mouseDownHandler) return; // 避免重复初始化 // 保存实例引用,用于事件处理函数中 const self = this; // 鼠标按下事件处理 this._mouseDownHandler = (options) => { // 如果选区功能未激活,不处理事件 if (!this.isActive) return; // 阻止事件冒泡,避免与 CanvasEventManager 冲突 options.e.stopPropagation(); switch (this.activeTool.value) { case OperationType.PART: this._pointDownkHandler(options); break; case OperationType.PART_RECTANGLE: this._rectangleDownHandler(options); break; case OperationType.PART_BRUSH: this._brushDownHandler(options); break; case OperationType.PART_ERASER: this._eraseDownHandler(options); break; default: break; } }; // 鼠标移动事件处理 this._mouseMoveHandler = (options) => { // 如果选区功能未激活或没有正在绘制的对象,不处理事件 if (!this.isActive) return; // 阻止事件冒泡 options.e.stopPropagation(); switch (this.activeTool.value) { case OperationType.PART: this._pointMoveHandler(options); break; case OperationType.PART_RECTANGLE: this._rectangleMoveHandler(options); break; case OperationType.PART_BRUSH: this._brushMoveHandler(options); break; case OperationType.PART_ERASER: this._eraseMoveHandler(options); break; default: break; } }; // 鼠标抬起事件处理 this._mouseUpHandler = (options) => { // 如果选区功能未激活或没有正在绘制的对象,不处理事件 if (!this.isActive) return; // 阻止事件冒泡 if (options && options.e) { options.e.stopPropagation(); } switch (this.activeTool.value) { case OperationType.PART: this._pointUpHandler(options); break; case OperationType.PART_RECTANGLE: this._rectangleUpHandler(options); break; case OperationType.PART_BRUSH: this._brushUpHandler(options); break; case OperationType.PART_ERASER: this._eraseUpHandler(options); break; default: break; } }; // 键盘事件处理 this._keyDownHandler = (event) => { // 只在选区功能激活时处理键盘事件 if (!this.isActive) return; }; // 添加事件监听 this.canvas.on("mouse:down", this._mouseDownHandler); this.canvas.on("mouse:move", this._mouseMoveHandler); this.canvas.on("mouse:up", this._mouseUpHandler); // 添加键盘事件监听 document.addEventListener("keydown", this._keyDownHandler); } /** 清理事件监听 */ cleanupEvents() { if (!this.canvas) return; // 移除事件监听 if (this._mouseDownHandler) { this.canvas.off("mouse:down", this._mouseDownHandler); this._mouseDownHandler = null; } if (this._mouseMoveHandler) { this.canvas.off("mouse:move", this._mouseMoveHandler); this._mouseMoveHandler = null; } if (this._mouseUpHandler) { this.canvas.off("mouse:up", this._mouseUpHandler); this._mouseUpHandler = null; } if (this._keyDownHandler) { document.removeEventListener("keydown", this._keyDownHandler); this._keyDownHandler = null; } } /** 点选工具模式下点击事件处理 */ _pointDownkHandler(options) { // const button = options.button; // const isLeft = button === 1;// 左键1(添加) 右键3(删除) // const icon = `url("${isLeft ? addIcon : removeIcon}") 16 16, default` // this.canvas.upperCanvasEl.style.cursor = icon; } /** 点选工具模式下移动事件处理 */ _pointMoveHandler(options) { } /** 点选工具模式下抬起事件处理 */ async _pointUpHandler(options) { const button = options.button; const isLeft = button === 1;// 左键1(添加) 右键3(删除) const fixedObject = this.canvasManager.getFixedLayerObject(); if (!fixedObject) return console.warn("未找到固定图层"); const { x, y } = this.handleMousePosition(options, fixedObject); const label = isLeft ? 1 : 0; const points = []; const labels = []; this.pointList.forEach((item) => { points.push([item.x, item.y]); labels.push(item.label); }); points.push([x, y]); labels.push(label); const url = await this.getSegAnythingImage({ type: "point", points, labels, }); this.pointList.push({ x: x, y: y, label: label, }) const image1 = await this.loadImageToObject(url); this.resetPartObject(); const group = this.partGroup; const rgba = { r: 0, g: 255, b: 0, a: 200 } const canvas = getObjectAlphaToCanvas(image1, null, 0, rgba); this.partCanvas = canvas; const image2 = new fabric.Image(canvas); image2.set({ originX: fixedObject.originX, originY: fixedObject.originY, }); group.add(image2); for (let i = 0; i < this.pointList.length; i++) { const item = this.pointList[i]; const icon = await this.loadImageToObject(item.label === 1 ? addIcon : removeIcon); icon.set({ left: item.x - group.width / 2, top: item.y - group.height / 2, originX: fixedObject.originX, originY: fixedObject.originY, }) group.add(icon); } this.canvas.renderAll(); } /** 清空点选数据 */ clearPointData() { this.pointList = []; this.partCanvas = null; } /** 框选工具模式下点击事件处理 */ _rectangleDownHandler(options) { this.clearPointData(); const fixedObject = this.canvasManager.getFixedLayerObject(); if (!fixedObject) return console.warn("未找到固定图层"); const { x, y } = this.handleMousePosition(options, fixedObject); this.pointList.push(x, y); } /** 框选工具模式下移动事件处理 */ _rectangleMoveHandler(options) { } /** 框选工具模式下抬起事件处理 */ async _rectangleUpHandler(options) { const fixedObject = this.canvasManager.getFixedLayerObject(); if (!fixedObject) return console.warn("未找到固定图层"); const { x, y } = this.handleMousePosition(options, fixedObject); this.pointList.push(x, y); if (this.pointList.length !== 4) return console.warn("框选工具选择区域必须是矩形"); const url = await this.getSegAnythingImage({ type: "box", box: [...this.pointList], }); const image1 = await this.loadImageToObject(url); this.resetPartObject(); const group = this.partGroup; const rgba = { r: 0, g: 255, b: 0, a: 200 } const canvas = getObjectAlphaToCanvas(image1, null, 0, rgba); this.partCanvas = canvas; const image2 = new fabric.Image(canvas); image2.set({ originX: fixedObject.originX, originY: fixedObject.originY, }); group.add(image2); this.canvas.renderAll(); } /** 绘制工具模式下点击事件处理 */ _brushDownHandler(options) { } /** 绘制工具模式下移动事件处理 */ _brushMoveHandler(options) { } /** 绘制工具模式下抬起事件处理 */ _brushUpHandler(options) { } /** 擦除工具模式下抬起事件处理 */ _eraseUpHandler(options) { } /** 擦除工具模式下点击事件处理 */ _eraseDownHandler(options) { } /** 擦除工具模式下移动事件处理 */ _eraseMoveHandler(options) { } /** 处理鼠标点位 */ handleMousePosition(options, fixedObject) { const pos = options.absolutePointer; const { x, y } = options.absolutePointer; const width = fixedObject.width * fixedObject.scaleX; const height = fixedObject.height * fixedObject.scaleY; const X = (x - (fixedObject.left - width / 2)) / fixedObject.scaleX; const Y = (y - (fixedObject.top - height / 2)) / fixedObject.scaleY; return { x: Math.round(X), y: Math.round(Y), } } /** 获取分隔后图片 */ async getSegAnythingImage(obj) { setTimeout(() => { this.canvas.loading.value = true; }); return new Promise((resolve, reject) => { // const user_id = store.state.UserHabit.userDetail.userId; const user_id = 24299; const data = { image_path: this.props.clothingMinIOPath, user_id, ...obj, } Https.axiosPost(Https.httpUrls.segAnything, data) .then(response => { this.canvas.loading.value = false; if (response) { resolve(response); } else { console.error("获取分隔后图片失败"); } }) .catch(error => { this.canvas.loading.value = false; console.error(error); }); }); } /** 删除指定ID的对象 */ removeObjectsById(id) { const objects = this.canvas.getObjects().filter(obj => obj.id === id); this.canvas.remove(...objects); } loadImageToObject(url) { return new Promise((resolve, reject) => { fabric.Image.fromURL(url, (img) => { resolve(img); }, { crossOrigin: "anonymous" });// 防止污染 }); } /** 重置点位对象组 */ resetPartObject(render = false) { this.clearPartObject(); this.createPartObject(); if (render) this.canvas.renderAll(); } createPartObject() { const fixedObject = this.canvasManager.getFixedLayerObject(); if (!fixedObject) return console.warn("未找到固定图层"); const group = new fabric.Group(); group.set({ id: this.partId, opacity: 1, left: fixedObject.left, top: fixedObject.top, width: fixedObject.width, height: fixedObject.height, scaleX: fixedObject.scaleX, scaleY: fixedObject.scaleY, originX: fixedObject.originX, originY: fixedObject.originY, selectable: false, evented: false, }) this.canvas.add(group); this.partGroup = group; } /** 清空点位对象组 */ clearPartObject() { this.removeObjectsById(this.partId); this.partGroup = null; } /** 创建当前选区 */ createPart() { if (!this.partCanvas) return console.warn("没有点位画布"); const fixedObject = this.canvasManager.getFixedLayerObject(); if (!fixedObject) return console.warn("未找到固定图层"); const scaleY = fixedObject.scaleY const scaleX = fixedObject.scaleX const top = fixedObject.top - fixedObject.height * scaleY / 2; const left = fixedObject.left - fixedObject.width * scaleX / 2; const arr = traceImageContour(this.partCanvas); let minX = fixedObject.width; let minY = fixedObject.height; const str = arr.map((v) => { if (v.x < minX) minX = v.x; if (v.y < minY) minY = v.y; return `${v.x} ${v.y}` }).join(" L "); const path = new fabric.Path(`M ${str} z`); path.set({ left: left + minX * scaleX, top: top + minY * scaleY, scaleX: scaleX, scaleY: scaleY, fill: "rgba(127, 255, 127, 0.3)", stroke: "#2AA81B", strokeWidth: 2, strokeDashArray: [8, 4], strokeLineCap: "round",// 折线端点样式 strokeLineJoin: "bevel", // 折线连接样式 strokeUniform: true, // 保持描边宽度不随缩放改变 }); // this.partGroup.add(path); this.canvas.add(path); this.canvas.renderAll(); this.clearPart(); } /** 清空点位 */ clearPart() { this.pointList = []; this.resetPartObject(true); } /** * 清理资源 */ dispose() { this.cleanupEvents(); this.clearPartObject(); this.clearPointData(); this.canvas = null; this.commandManager = null; this.layerManager = null; } }