Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/AISelectboxToolManager.ts
2026-03-31 15:45:15 +08:00

344 lines
9.6 KiB
TypeScript

import { fabric } from 'fabric-with-all'
import { OperationType } from '../tools/layerHelper'
import { getObjectAlphaToCanvas, traceImageContour, clipCanvasTransparent, cloneObjects } from '../tools/canvasMethod'
import { getSegAnythingImage } from '@/api/depth-canvas'
import { useGlobalStore, useUserInfoStore } from '@/stores'
/** 智能框选工具管理器 */
export class AISelectboxToolManager {
// 管理器
canvasManager: any
stateManager: any
layerManager: any
toolManager: any
targetObject: 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()
this.canvasManager.discardActiveObject()
} 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;
this.targetObject = null;
this.canvasManager.getObjects().forEach((obj) => {
if (obj?.info?.isOriginal) {
this.targetObject = obj.toObject();
}
})
// const image = this.layerManager.getActiveLayer()
// if (image && image.info.isAiSelect) {
// this.targetObject = image;
// } else {
// this.targetObject = 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() {
this.clearIndicatorObject()
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) {
if (!this.targetObject) return;
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) {
if (!this.targetObject) return;
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) {
if (!this.targetObject) return;
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.targetObject) return;
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()
}
async mouseUpEvent(e) {
if (!this.targetObject) return;
if (!this.isDragging) return;
this.isDragging = false;
const object = this.indicatorObject.toJSON("evented")
if (object.width === 0 || object.height === 0) return
this.clearIndicatorObject()
this.canvasManager.canvas.renderAll()
useGlobalStore().setLoading(true)
const x1 = Math.round(object.left)
const y1 = Math.round(object.top)
const x2 = Math.round(object.left + object.width)
const y2 = Math.round(object.top + object.height)
const data = {
type: "box",
box: [x1, y1, x2, y2],
}
const url = await this.getSegAnythingImage(data)
const image = await this.loadImageToObject(url)
// 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) {
await this.handleDrawImage(image)
} else if (currentTool === OperationType.AISELECT_REMOVE) {
await this.handleRemoveImage(image)
}
useGlobalStore().setLoading(false)
}
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.targetObject) return;
if (!this.demoObject) return
const fobject = this.demoObject
this.clearDemoObject()
const scaleY = fobject.scaleY
const scaleX = fobject.scaleX
const top = fobject.top
const left = fobject.left
let minX = fobject.width;
let minY = fobject.height;
const tcanvas = await this.createStaticCanvas(fobject)
const canvas = getObjectAlphaToCanvas(tcanvas, null, 0, { r: 255, g: 255, b: 255, a: 255 });
/** 路径裁剪法 */
// const arr = traceImageContour(canvas);
// 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 info = clipCanvasTransparent(canvas)
minX = info.minX
minY = info.minY
const path = new fabric.Image(info.canvas)
path.set({
left: left + minX,
top: top + minY,
scaleX: scaleX,
scaleY: scaleY,
absolutePositioned: true,
})
// 创建分组层
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)
}
/** 获取分隔后图片 */
async getSegAnythingImage(obj) {
return new Promise((resolve, reject) => {
if (!this.targetObject) return;
const user_id = useUserInfoStore().state.userInfo.id;
const image_path = this.targetObject.src;
const data = {
image_path,
user_id,
...obj,
}
getSegAnythingImage(data).then((res) => {
console.log(res)
resolve(res)
}).catch((error) => {
console.error(error);
useGlobalStore().setLoading(false)
})
});
}
dispose() {
this.clear()
}
}