import { fabric } from 'fabric-with-all' import { createId } from '../../tools/tools' /** 智能框选工具管理器 */ export class AISelectboxToolManager { // 管理器 canvasManager: any stateManager: any layerManager: any isDragging: boolean = false startX: number = 0 startY: number = 0 demoObject: any constructor(options) { this.canvasManager = options.canvasManager this.stateManager = options.stateManager this.layerManager = options.layerManager } mouseDownEvent(e) { this.isDragging = true this.startX = e.absolutePointer.x this.startY = e.absolutePointer.y const rect = new fabric.Rect({ left: this.startX, top: this.startY, width: 0, height: 0, fill: 'transparent', stroke: '#000', strokeWidth: 1, evented: false, }) this.demoObject = rect this.canvasManager.canvas.add(rect) this.canvasManager.canvas.renderAll() } 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.demoObject.set({ width, height, left, top }) this.canvasManager.canvas.renderAll() } mouseUpEvent(e) { if (!this.isDragging) return; this.isDragging = false; const object = this.demoObject.toJSON("evented") if (object.width === 0) object.width = 100 if (object.height === 0) object.height = 100 // console.log(object) this.canvasManager.canvas.remove(this.demoObject) this.canvasManager.canvas.renderAll() this.createSelectbox() } loadImageToObject(url) { return new Promise((resolve, reject) => { fabric.Image.fromURL(url, (img) => { resolve(img); }, { crossOrigin: "anonymous" });// 防止污染 }); } rgba = { r: 0, g: 255, b: 0, a: 200 }; selectionStyle = { stroke: "rgba(255, 77, 71, 1)", strokeWidth: 1.5, strokeDashArray: [4, 4], fill: "rgba(255, 186, 186, 0.5)", strokeUniform: true, // 保持描边宽度不随缩放改变 // strokeLineCap: "round",// 折线端点样式 // strokeLineJoin: "bevel", // 折线连接样式 // selectable: false, // evented: false, excludeFromExport: true, hoverCursor: "default", moveCursor: "default", }; async createSelectbox() { const url = "http://118.31.39.42:3000/falls/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png" const image = await this.loadImageToObject(url) const canvas = getObjectAlphaToCanvas(image, null, 0, this.rgba); const fobject = this.canvasManager.canvas.clipPath // const top = fobject.top - fobject.height * scaleY / 2; // const left = fobject.left - fobject.width * scaleX / 2; 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 * scaleX, top: top + minY * scaleY, scaleX: scaleX, scaleY: scaleY, ...this.selectionStyle, }); const rect1 = new fabric.Rect({ left: 0, top: 0, width: 100, height: 100, fill: '#f00', info: { id: createId("rect"), name: '矩形图层', } }) const rect2 = new fabric.Rect({ left: 200, top: 200, width: 100, height: 100, fill: '#ff0', info: { id: createId("rect"), name: '矩形图层', } }) this.layerManager.createGroupLayer({ child: [rect1, rect2], }) // this.canvasManager.canvas.add(path) // this.canvasManager.canvas.renderAll() } dispose() { } } /** * 获取对象黑白通道画布 * @param {fabric.Object} object - 要处理的 fabric 对象 * @param {ImageData} revData - 相反的ImageData,白通道的相同位置是否为透明,revData为白色为透明,黑色为不透明 * @param {number} diff - 差值,默认 25 * @param {Object} rgba - 自定义 rgba 值,默认 { r: 255, g: 255, b: 255, a: 255 } * @param {boolean} isMerge - 是否合并,true=合并revData,false=反转revData * @returns {HTMLCanvasElement|null} 包含黑白通道的画布,或 null 如果失败 */ export function getObjectAlphaToCanvas(object, revData, diff = 30, rgba = { r: 255, g: 255, b: 255, a: 255 }, isMerge = false) { const image = object.getElement(); if (image.nodeName !== "IMG" && image.nodeName !== "CANVAS") { console.warn("对象不是图片"); return null; } const { width, height } = image; if (!width || !height) { console.warn("对象没有元素"); return null; } const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, width, height); const data = ctx.getImageData(0, 0, width, height); for (let i = 0; i < data.data.length; i += 4) { const r = data.data[i + 0]; const g = data.data[i + 1]; const b = data.data[i + 2]; const a = data.data[i + 3]; const revR = revData?.data[i + 0] || 0; const revG = revData?.data[i + 1] || 0; const revB = revData?.data[i + 2] || 0; const revA = revData?.data[i + 3] || 0; let isHave = false; if (r || g || b || a) { if (revR > diff || revG > diff || revB > diff || revA > diff) { isHave = false; } else { isHave = true; } } if (isMerge && (revR || revG || revB || revA)) isHave = true; if (isHave) { data.data[i + 0] = rgba.r; data.data[i + 1] = rgba.g; data.data[i + 2] = rgba.b; data.data[i + 3] = rgba.a; } else { data.data[i + 0] = 0; data.data[i + 1] = 0; data.data[i + 2] = 0; data.data[i + 3] = 0; } } ctx.clearRect(0, 0, width, height); ctx.putImageData(data, 0, 0); return canvas; } /** * 图片边界跟踪算法(透明底) * @param {HTMLCanvasElement} canvas - canvas元素 * @param {Number} scale - 缩放比例 * @returns {Array} 边界点数组 [{x, y}, ...] */ export function traceImageContour(canvas) { const ctx = canvas.getContext("2d", { willReadFrequently: true }); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; const width = canvas.width; const height = canvas.height; // 查找起始点(第一个不透明像素) let startX = -1; let startY = -1; outer: for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const index = (y * width + x) * 4; if (data[index + 3] > 0) { startX = x; startY = y; break outer; } } } if (startX === -1) return []; // 没有不透明像素 // Moore-Neighbor边界跟踪算法 const contour = []; const visited = new Set(); const directions = [ [-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], ]; let currentX = startX; let currentY = startY; let backtrackDir = 4; // 起始方向:右 do { const pointKey = `${currentX},${currentY}`; if (!visited.has(pointKey)) { contour.push({ x: currentX, y: currentY }); visited.add(pointKey); } // 从右方向开始顺时针查找 let found = false; for (let i = 0; i < 8; i++) { const dir = (backtrackDir + i) % 8; const dx = directions[dir][0]; const dy = directions[dir][1]; const checkX = currentX + dx; const checkY = currentY + dy; if ( checkX >= 0 && checkX < width && checkY >= 0 && checkY < height ) { const index = (checkY * width + checkX) * 4; if (data[index + 3] > 0) { currentX = checkX; currentY = checkY; backtrackDir = (dir + 5) % 8; // 下一个开始查找的方向 found = true; break; } } } if (!found) break; } while ( !(currentX === startX && currentY === startY) && visited.size < width * height ); return contour; }