import { fabric } from 'fabric-with-all' import { OperationType } from '../tools/layerHelper' import { getObjectAlphaToCanvas, traceImageContour, cloneObjects } from '../tools/canvasMethod' /** 智能框选工具管理器 */ export class AISelectboxToolManager { // 管理器 canvasManager: any stateManager: any layerManager: any toolManager: any isDragging: boolean = false startX: number = 0 startY: number = 0 indicatorObject: any// 指示框对象 demoObject: any// 演示框对象 tcanvas: any// 临时画布对象 tools = [ OperationType.AISELECT_ADD, OperationType.AISELECT_REMOVE, OperationType.AISELECT_DRAW, OperationType.AISELECT_ERASER ] constructor(options) { this.canvasManager = options.canvasManager this.stateManager = options.stateManager this.layerManager = options.layerManager this.toolManager = options.toolManager } /** 处理切换工具 */ handleToolChange(oldTool: string, newTool: string) { const oldIsAAA = this.tools.includes(oldTool) const newIsAAA = this.tools.includes(newTool) if (!oldIsAAA && newIsAAA) { // 普通工具切换到智能框选工具 this.init() } else if (oldIsAAA && !newIsAAA) { // 智能框选工具切换到普通工具 this.clear() } } /** 切换到橡皮擦工具 */ changeToolToEraser() { if (!this.demoObject) return; this.demoObject.set({ erasable: true }) } init() { console.log("初始化智能框选工具") this.clear(); this.createDemoObject() this.tcanvas = null; } clear() { console.log("清除智能框选工具") this.clearDemoObject() this.clearIndicatorObject() this.isDragging = false this.canvasManager.canvas.renderAll() } demoObjectDefault = { left: 0, top: 0, evented: false, selectable: false, erasable: false, } createDemoObject() { if (this.demoObject) this.clearDemoObject() const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize(); const canvas = document.createElement('canvas') canvas.width = canvasWidth canvas.height = canvasHeight this.demoObject = new fabric.Image(canvas, this.demoObjectDefault) this.canvasManager.canvas.add(this.demoObject) this.canvasManager.canvas.renderAll() } clearDemoObject() { this.canvasManager.canvas.remove(this.demoObject) this.demoObject = null } createIndicatorObject() { const rect = new fabric.Rect({ left: this.startX, top: this.startY, width: 0, height: 0, fill: 'transparent', stroke: '#000', strokeWidth: 1, evented: false, }) this.indicatorObject = rect this.canvasManager.canvas.add(rect) this.canvasManager.canvas.renderAll() } clearIndicatorObject() { this.canvasManager.canvas.remove(this.indicatorObject) this.indicatorObject = null } resetDemoObject() { this.clearDemoObject() this.createDemoObject() } // 创建临时画布对象 async createStaticCanvas(object: fabric.Object) { if (!this.demoObject) this.createDemoObject() const canvas = new fabric.StaticCanvas(document.createElement("canvas"), { width: this.demoObject.width, height: this.demoObject.height, enableRetinaScaling: false, }); const newObject = await cloneObjects([object]).then(v => v[0]) canvas.add(newObject) canvas.renderAll() return canvas } // 处理绘制图像 async handleDrawImage(object: fabric.Object) { const tcanvas = await this.createStaticCanvas(this.demoObject) tcanvas.add(object) tcanvas.renderAll(); const canvas = getObjectAlphaToCanvas(tcanvas, null, 0, this.rgba); this.replaceDemoObject(canvas) } // 处理图像删除 async handleRemoveImage(object: fabric.Object) { const tcanvas = await this.createStaticCanvas(this.demoObject) const rcanvas = await this.createStaticCanvas(object) const tempCanvas = rcanvas.toCanvasElement(); const tempCtx = tempCanvas.getContext('2d'); const imageData2 = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height); const canvas = getObjectAlphaToCanvas(tcanvas, imageData2, 0, this.rgba); this.replaceDemoObject(canvas) } // 替换框选对象 async replaceDemoObject(canvas) { const image = new fabric.Image(canvas, this.demoObjectDefault); this.canvasManager.canvas.add(image) this.canvasManager.canvas.remove(this.demoObject); this.demoObject = image; this.canvasManager.canvas.renderAll() } mouseDownEvent(e) { const tool = this.toolManager.currentTool.value const tools = [OperationType.AISELECT_ADD, OperationType.AISELECT_REMOVE] if (!tools.includes(tool)) return; this.isDragging = true this.startX = e.absolutePointer.x this.startY = e.absolutePointer.y this.createIndicatorObject() } mouseMoveEvent(e) { if (!this.isDragging) return; var width = e.absolutePointer.x - this.startX var height = e.absolutePointer.y - this.startY var left = this.startX var top = this.startY if (width < 0) { left += width width = -width } if (height < 0) { top += height height = -height } this.indicatorObject.set({ width, height, left, top }) this.canvasManager.canvas.renderAll() } mouseUpEvent(e) { if (!this.isDragging) return; this.isDragging = false; const object = this.indicatorObject.toJSON("evented") if (object.width === 0) object.width = 100 if (object.height === 0) object.height = 100 this.clearIndicatorObject() this.canvasManager.canvas.renderAll() const rect = new fabric.Rect({ left: object.left, top: object.top, width: object.width, height: object.height, fill: '#f00', strokeWidth: 0, }) const currentTool = this.toolManager.currentTool.value if (currentTool === OperationType.AISELECT_ADD) { this.handleDrawImage(rect) } else if (currentTool === OperationType.AISELECT_REMOVE) { this.handleRemoveImage(rect) } } loadImageToObject(url) { return new Promise((resolve, reject) => { fabric.Image.fromURL(url, (img) => { resolve(img); }, { crossOrigin: "anonymous" });// 防止污染 }); } rgba = { r: 255, g: 0, b: 0, a: 127.5 }; selectionStyle = { stroke: "rgba(255, 77, 71, 1)", strokeWidth: 1.5, strokeDashArray: [4, 4], fill: "transparent", strokeUniform: true, // 保持描边宽度不随缩放改变 selectable: false, evented: false, absolutePositioned: true, }; async createSelectbox() { if (!this.demoObject) return const fobject = this.demoObject this.clearDemoObject() const canvas = getObjectAlphaToCanvas(fobject, null, 0, { r: 255, g: 0, b: 0, a: 255 }); const scaleY = fobject.scaleY const scaleX = fobject.scaleX const top = fobject.top const left = fobject.left const arr = traceImageContour(canvas); let minX = fobject.width; let minY = fobject.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, top: top + minY, scaleX: scaleX, scaleY: scaleY, ...this.selectionStyle, }); const group = await this.layerManager.createGroupLayer({ clipPath: path, top: path.top + path.height / 2, left: path.left + path.width / 2, }, false, false) const rect = await this.layerManager.createRectLayer({ width: path.width, height: path.height, left: left + minX, top: top + minY, fill: "rgba(255, 186, 186, 0.5)", info: { parentId: group.info.id }, }, false, true) await this.canvasManager.updateSubLayerClipPath() await this.layerManager.updateLayerThumbnailsById(rect.info.id, "", false) await this.layerManager.updateLayerThumbnailsById(group.info.id, rect.thumbnail) this.stateManager.recordState() this.toolManager.setTool(OperationType.SELECT) } dispose() { this.clear() } }