Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/AISelectboxToolManager.ts

311 lines
7.8 KiB
TypeScript
Raw Normal View History

2026-03-13 11:18:36 +08:00
import { fabric } from 'fabric-with-all'
2026-03-20 13:23:00 +08:00
import { createId } from '../../tools/tools'
2026-03-13 11:18:36 +08:00
/** 智能框选工具管理器 */
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
2026-03-20 13:23:00 +08:00
2026-03-13 11:18:36 +08:00
}
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
2026-03-20 13:23:00 +08:00
if (width < 0) {
2026-03-13 11:18:36 +08:00
left += width
width = -width
}
2026-03-20 13:23:00 +08:00
if (height < 0) {
2026-03-13 11:18:36 +08:00
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()
2026-03-20 13:23:00 +08:00
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()
2026-03-13 11:18:36 +08:00
}
2026-03-20 13:23:00 +08:00
2026-03-13 11:18:36 +08:00
dispose() { }
}
2026-03-20 13:23:00 +08:00
/**
*
* @param {fabric.Object} object - fabric
* @param {ImageData} revData - ImageDatarevData为白色为透明
* @param {number} diff - 25
* @param {Object} rgba - rgba { r: 255, g: 255, b: 255, a: 255 }
* @param {boolean} isMerge - true=revDatafalse=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;
}