深度画布智能选区

This commit is contained in:
lzp
2026-03-25 11:27:24 +08:00
parent d657ca30ee
commit 2d5c02e191
10 changed files with 371 additions and 163 deletions

View File

@@ -4,51 +4,63 @@
<div
v-for="item in list"
:key="item.type"
:class="{ active: item.name === props.currentTool }"
:class="{ active: item.type === currentTool }"
@click="onClickItem(item.type)"
>
<span class="icon"><svg-icon :name="item.name" size="16" /></span>
<span class="label">{{ item.label }}</span>
</div>
<button>创建</button>
<button @click="onCreate">创建</button>
</div>
</transition>
<brush-control-panel :currentTool="show ? 'draw' : ''" style="top: 14rem" />
<brush-control-panel v-if="show" :currentTool="currentTool2" style="top: 14rem" />
</template>
<script setup lang="ts">
import { ref, inject, computed, watch } from 'vue'
import depthSlider from './tools/depth-slider.vue'
import { OperationType, AI_SELECTBOX_TYPE } from '../tools/layerHelper'
import { OperationType } from '../tools/layerHelper'
import brushControlPanel from './brush-control-panel.vue'
const props = defineProps({
currentTool: { required: true, type: [String, null] }
})
const stateManager = inject('stateManager') as any
const toolManager = inject('toolManager') as any
const showTools = [OperationType.SELECTBOX]
const show = computed(() => showTools.includes(props.currentTool))
const tool2 = {
[OperationType.AISELECT_DRAW]: OperationType.ERASER,
[OperationType.AISELECT_ERASER]: OperationType.ERASER
}
const currentTool2 = computed(() => tool2[props.currentTool] || props.currentTool)
const show = computed(() => stateManager.aiSelectboxToolManager.tools.includes(props.currentTool))
const list = ref([
{
type: AI_SELECTBOX_TYPE.ADD,
type: OperationType.AISELECT_ADD,
name: 'dc-add_sb',
label: 'Add'
},
{
type: AI_SELECTBOX_TYPE.REMOVE,
type: OperationType.AISELECT_REMOVE,
name: 'dc-remove_sb',
label: 'Remove'
},
{
type: AI_SELECTBOX_TYPE.DRAW,
type: OperationType.AISELECT_DRAW,
name: 'dc-brush_sb',
label: 'Brush'
},
{
type: AI_SELECTBOX_TYPE.ERASER,
type: OperationType.AISELECT_ERASER,
name: 'dc-erase_sb',
label: 'Erase'
}
])
const onClickItem = (type: string) => {
toolManager.setTool(type)
}
const onCreate = () => {
stateManager.aiSelectboxToolManager.createSelectbox()
}
</script>
<style lang="less" scoped>
// 淡入淡出动画

View File

@@ -39,20 +39,22 @@
currentTool: { required: true, type: [String, null] }
})
const stateManager = inject('stateManager') as any
const toolManager = inject('toolManager') as any
const brushState = computed(() => stateManager.brushManager.brushStore.state)
const showTools = [OperationType.DRAW, OperationType.ERASER]
const show = computed(() => showTools.includes(props.currentTool))
watch(brushState, (value) => {
if (value) updateBrushState()
})
watch(
() => props.currentTool,
(value) => updateBrushState()
)
const brushSize = ref(40)
const brushOpacity = ref(100)
const brushColor = ref('#000000')
const updateBrushState = () => {
brushSize.value = brushState.value.size
brushOpacity.value = brushState.value.opacity
brushColor.value = brushState.value.color
}
updateBrushState()
watch(() => brushState.value.size, updateBrushState)
watch(() => brushState.value.opacity, updateBrushState)
watch(() => brushState.value.color, updateBrushState)
const onInputSize = (value: number) => {
stateManager.brushManager.setBrushSize(value)
}
@@ -62,12 +64,6 @@
const onInputColor = () => {
stateManager.brushManager.setBrushColor(brushColor.value)
}
const updateBrushState = () => {
brushSize.value = brushState.value.size
brushOpacity.value = brushState.value.opacity
brushColor.value = brushState.value.color
}
updateBrushState()
</script>
<style lang="less" scoped>
// 淡入淡出动画

View File

@@ -7,7 +7,7 @@
class="icon"
@click="onClickTool(v)"
:class="{
active: v.name === tool,
active: v.name === tool || v.tools?.includes(tool),
disabled: v.disabled
}"
>
@@ -72,7 +72,12 @@
{ name: OperationType.DRAW, icon: 'dc-brush', iconSize: 18 },
{ name: OperationType.ERASER, icon: 'dc-eraser', iconSize: 18 },
{ icon: 'dc-image', iconSize: 17, on: () => onImageClick() },
{ name: OperationType.SELECTBOX, icon: 'dc-selectbox', iconSize: 16 },
{
name: OperationType.AISELECT_ADD,
icon: 'dc-selectbox',
iconSize: 16,
tools: stateManager.aiSelectboxToolManager.tools
},
{
name: OperationType.RECTANGLE,
icon: 'dc-rectangle',

View File

@@ -1,6 +1,6 @@
import { fabric } from 'fabric-with-all'
import { OperationType, AI_SELECTBOX_TYPE } from '../tools/layerHelper'
import { getObjectAlphaToCanvas, traceImageContour } from '../tools/canvasMethod'
import { OperationType } from '../tools/layerHelper'
import { getObjectAlphaToCanvas, traceImageContour, cloneObjects } from '../tools/canvasMethod'
/** 智能框选工具管理器 */
export class AISelectboxToolManager {
@@ -13,7 +13,17 @@ export class AISelectboxToolManager {
isDragging: boolean = false
startX: number = 0
startY: number = 0
demoObject: any
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
@@ -22,17 +32,56 @@ export class AISelectboxToolManager {
}
/** 处理切换工具 */
handleToolChange(oldTool: string, newTool: string) {
if (newTool === OperationType.SELECTBOX) {
// 切换到智能框选工具
} else {
// 切换到普通框选工具
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()
}
mouseDownEvent(e) {
this.isDragging = true
this.startX = e.absolutePointer.x
this.startY = e.absolutePointer.y
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, {
left: 0,
top: 0,
evented: false,
selectable: false,
erasable: false,
})
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,
@@ -43,10 +92,44 @@ export class AISelectboxToolManager {
strokeWidth: 1,
evented: false,
})
this.demoObject = rect
this.indicatorObject = rect
this.canvasManager.canvas.add(rect)
this.canvasManager.canvas.renderAll()
}
clearIndicatorObject() {
this.canvasManager.canvas.remove(this.indicatorObject)
this.indicatorObject = null
}
// 处理画笔绘制图像
async handleBrushDrawImage(fabricImage: fabric.Object) {
if (!this.demoObject) return;
const tcanvas = new fabric.StaticCanvas(document.createElement("canvas"), {
width: this.demoObject.width,
height: this.demoObject.height,
enableRetinaScaling: false,
});
const demoObject = await cloneObjects([this.demoObject]).then(v => v[0])
tcanvas.add(demoObject)
tcanvas.add(fabricImage)
tcanvas.renderAll();
const canvas = getObjectAlphaToCanvas(tcanvas, null, 0, this.rgba);
const image = new fabric.Image(canvas);
this.canvasManager.canvas.add(image)
this.canvasManager.canvas.remove(this.demoObject);
this.demoObject = image;
this.canvasManager.canvas.renderAll()
}
mouseDownEvent(e) {
// if (true) 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.isDragging) return;
var width = e.absolutePointer.x - this.startX
@@ -61,25 +144,23 @@ export class AISelectboxToolManager {
top += height
height = -height
}
this.demoObject.set({ width, height, left, top })
this.indicatorObject.set({ width, height, left, top })
this.canvasManager.canvas.renderAll()
}
mouseUpEvent(e) {
if (!this.isDragging) return;
this.isDragging = false;
const object = this.demoObject.toJSON("evented")
const object = this.indicatorObject.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)
console.log(object.top, object.left, object.width, object.height)
this.clearIndicatorObject()
this.canvasManager.canvas.renderAll()
// this.createSelectbox()
}
loadImageToObject(url) {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(url, (img) => {
@@ -87,7 +168,7 @@ export class AISelectboxToolManager {
}, { crossOrigin: "anonymous" });// 防止污染
});
}
rgba = { r: 0, g: 255, b: 0, a: 200 };
rgba = { r: 255, g: 0, b: 0, a: 127.5 };
selectionStyle = {
stroke: "rgba(255, 77, 71, 1)",
strokeWidth: 1.5,
@@ -99,12 +180,12 @@ export class AISelectboxToolManager {
absolutePositioned: true,
};
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 url = "http://118.31.39.42:3000/falls/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png"
// const image = await this.loadImageToObject(url)
// const fobject = this.canvasManager.canvas.clipPath
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
@@ -127,6 +208,8 @@ export class AISelectboxToolManager {
});
const group = await this.layerManager.createGroupLayer({
clipPath: path,
top: path.top,
left: path.left,
}, false, false)
const rect = await this.layerManager.createRectLayer({
width: path.width,
@@ -145,5 +228,7 @@ export class AISelectboxToolManager {
dispose() { }
dispose() {
this.clear()
}
}

View File

@@ -25,6 +25,7 @@ fabric.Object.prototype.toObject = function () {
const object = this.toObject_(arr)
if (object.info) {
let lock = !!object.info.lock
if (object.type === "group") lock = true
object.evented = !lock
object.selectable = !lock
}
@@ -214,7 +215,13 @@ export class CanvasManager {
const obj = this.getObjectById(id)
if (!obj) return
if (obj.type === "group") {
const objects = [];
obj._originPosition = {
top: obj.top,
left: obj.left,
cpTop: obj.clipPath.top,
cpLeft: obj.clipPath.left,
}
const objects = [obj];
this.getObjects().forEach((item: any) => {
if (item?.info?.parentId === id) objects.push(item)
})
@@ -258,6 +265,9 @@ export class CanvasManager {
getObjectById(id: string) {
return this.getObjects().find((item: any) => item?.info?.id === id)
}
getChildObjectsById(id: string) {
return this.getObjects().filter((item: any) => item?.info?.parentId === id)
}
/** 获取选中对象 */
getSelectedObject() {
return this.canvas.getActiveObject()
@@ -285,6 +295,8 @@ export class CanvasManager {
const currentTool = this.stateManager.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
this.handleDrawImage(fabricImage)
} else if (currentTool === OperationType.AISELECT_DRAW) {
this.stateManager.aiSelectboxToolManager.handleBrushDrawImage(fabricImage)
}
return true
};
@@ -293,7 +305,7 @@ export class CanvasManager {
async handleDrawImage(fabricImage: fabric.Object) {
const activeID = this.stateManager.layerManager.activeID.value
const activeLayer = this.getObjectById(activeID)
if (activeLayer && activeLayer.fill?.repeat !== "repeat") {
if (activeLayer && activeLayer.fill?.repeat !== "repeat" && activeLayer.type !== "group") {
this.layerManager.imageMergeToLayer(activeLayer, fabricImage)
} else {
const emptyLayer = await this.layerManager.createEmptyLayer(false);

View File

@@ -19,11 +19,11 @@ export class LayerManager {
onMounted() { }
setActiveID(id: string, isActive = true) {
const layer = this.getLayerById(id)
if (layer?.type === "group") {
this.activeID.value = ""
} else {
// if (layer?.type === "group") {
// this.activeID.value = ""
// } else {
this.activeID.value = id
}
// }
if (isActive) {
this.canvasManager.setActiveObjectById(id)
this.stateManager.toolManager.setTool(OperationType.SELECT)
@@ -62,11 +62,21 @@ export class LayerManager {
setLayerVisibleById(id, visible: boolean) {
const layer = this.getLayerById(id)
const object = this.canvasManager.getObjectById(id)
const call = (layer, object) => {
if (!layer || !object) return
layer.visible = visible
object.set({
visible: visible
})
}
call(layer, object)
if (object.type === "group") {
const children = this.canvasManager.getChildObjectsById(id)
children.forEach(v => {
const layer = this.getLayerById(v.info.id)
call(layer, v)
})
}
this.canvasManager.renderAll()
this.stateManager.recordState()
}
@@ -74,14 +84,26 @@ export class LayerManager {
setLayerLockById(id, lock: boolean) {
const layer = this.getLayerById(id)
const object = this.canvasManager.getObjectById(id)
const call = (layer, object) => {
if (!layer || !object) return
layer.info.lock = !!lock
layer.evented = !lock
object.info.lock = !!lock
if (object.type !== "group") {
object.set({
evented: !lock,
selectable: !lock,
})
}
}
call(layer, object)
if (object.type === "group") {
const children = this.canvasManager.getChildObjectsById(id)
children.forEach(v => {
const layer = this.getLayerById(v.info.id)
call(layer, v)
})
}
if (lock) {
// 取消选中对象
const e = this.canvasManager.getSelectedObject()
@@ -194,13 +216,14 @@ export class LayerManager {
}
/** 创建组图层 */
async createGroupLayer(options?: any, isRecord = true, isActive = false) {
const children = options?.children || []
delete options.children
const groupObject = new fabric.Group(children, {
const groupObject = new fabric.Group([], {
...(options || {}),
width: 1,
height: 1,
hasControls: false, // 不显示控制点
hasBorders: false, // 不显示边框
selectable: false, // 不可选中(可选)
evented: false,
info: {
id: createId("group"),
name: '智能选区组',
@@ -423,10 +446,18 @@ export class LayerManager {
const objects = this.canvasManager.getObjects()
objects.forEach((item: any) => {
item.set({
erasable: item.info.id === this.activeID.value
erasable: (item.info.id === this.activeID.value && item.type !== "group")
})
})
}
/** 设置所有对象擦除属性 */
setAllObjectsErasable(isErasable = false) {
const objects = this.canvasManager.getObjects()
objects.forEach((item: any) => {
item.set({ erasable: isErasable })
})
}
/** 更新图层缩略图 */
async updateLayerThumbnailsById(id: string, thumbnail?: string, isUpdate = true) {
const object = this.canvasManager.getObjectById(id);

View File

@@ -46,9 +46,25 @@ export class ToolManager {
},
/** 智能选框工具 */
{
name: OperationType.SELECTBOX,
name: OperationType.AISELECT_ADD,
cursor: "crosshair",
},
{
name: OperationType.AISELECT_REMOVE,
cursor: "crosshair",
},
{
name: OperationType.AISELECT_DRAW,
cursor: "crosshair",
setup: this.setupAISelectBrushTool.bind(this),
isDrawingMode: true,
},
{
name: OperationType.AISELECT_ERASER,
cursor: "crosshair",
setup: this.setupAISelectEraserTool.bind(this),
isDrawingMode: true,
},
/** 矩形工具 */
{
name: OperationType.RECTANGLE,
@@ -120,7 +136,7 @@ export class ToolManager {
setCanvasEvented(value: boolean) {
this.canvasManager.canvas.selection = value
this.canvasManager.canvas.getObjects().forEach((v) => {
if (v.info?.lock) return
if (v.info?.lock || v.type === "group") return
v.evented = value
})
}
@@ -179,6 +195,46 @@ export class ToolManager {
this._enableBrushIndicator();
}
/** 智能选区画笔工具 */
setupAISelectBrushTool() {
if (!this.canvasManager.canvas) return;
// 确保有笔刷管理器
if (this.brushManager) {
// 检查画笔是否正在更新中
if (this.brushManager.isUpdatingBrush) {
console.warn("画笔正在更新中,请稍候...");
return;
}
this.brushManager.setBrushSize(5);
this.brushManager.setBrushColor("rgb(255, 0, 0)");
this.brushManager.setBrushOpacity(0.5);
this.brushManager.setBrushType("pencil");
// 更新应用到画布
this.brushManager.updateBrush();
}
// 启用笔刷指示器并同步颜色
this._enableBrushIndicator();
}
/** 智能选区橡皮擦工具 */
setupAISelectEraserTool() {
if (!this.canvasManager.canvas) return;
// 确保有笔刷管理器
if (this.brushManager) {
this.brushManager.createEraser();
this.brushManager.setBrushSize(5);
}
this.stateManager.layerManager.setAllObjectsErasable(false)
// 启用笔刷指示器
this._enableBrushIndicator();
this.stateManager.aiSelectboxToolManager.changeToolToEraser()
}
/**
* 启用笔刷指示器
* @param {String} color 笔刷颜色(可选)

View File

@@ -187,6 +187,58 @@ export class CanvasEventManager {
}
}
_handleMouseDown(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseDownEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseDownEvent(opt);
} else {
return false
}
return true
}
_handleMouseMove(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseMoveEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseMoveEvent(opt);
} else {
return false
}
return true
}
_handleMouseUp(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseUpEvent(opt);
} else {
return false
}
return true
}
/**
* 设置鼠标事件处理
*/
@@ -202,16 +254,7 @@ export class CanvasEventManager {
// } else
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseDownEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseDownEvent(opt);
if (this._handleMouseDown(opt)) {
} else if (opt.e.altKey || opt.e.which === 2 || currentTool === OperationType.PAN) {
this.canvas.isDragging = true;
this.canvas.lastPosX = opt.e.clientX;
@@ -230,16 +273,7 @@ export class CanvasEventManager {
// 鼠标移动事件
this.canvas.on("mouse:move", (opt) => {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseMoveEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseMoveEvent(opt);
if (this._handleMouseMove(opt)) {
} else if (this.canvas.isDragging) {
const vpt = this.canvas.viewportTransform;
vpt[4] += opt.e.clientX - this.canvas.lastPosX;
@@ -314,16 +348,7 @@ export class CanvasEventManager {
this.canvas.on("mouse:down", (opt) => {
// 只在PAN模式下处理触摸事件
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseDownEvent(opt);
if (this._handleMouseDown(opt)) {
} else if (currentTool === OperationType.PAN) {
// 平滑停止任何正在进行的惯性动画
@@ -379,16 +404,7 @@ export class CanvasEventManager {
this.canvas.on("mouse:move", (opt) => {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseMoveEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseMoveEvent(opt);
if (this._handleMouseMove(opt)) {
} else if (currentTool === OperationType.PAN) {
// 检查是否是触摸事件
@@ -489,16 +505,7 @@ export class CanvasEventManager {
// 触摸结束事件
this.canvas.on("mouse:up", (opt) => {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseUpEvent(opt);
if (this._handleMouseUp(opt)) {
} else if (currentTool === OperationType.PAN) {
// 重置触摸状态
@@ -661,16 +668,7 @@ export class CanvasEventManager {
*/
handleDragEnd(opt, isTouch = false) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (currentTool === OperationType.SELECTBOX) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseUpEvent(opt);
if (this._handleMouseUp(opt)) {
} else if (this.canvas.isDragging) {
// if (this.lastMousePositions.length > 1 && opt && opt.e) {
// this.animationManager.applyInertiaEffect(
@@ -691,9 +689,16 @@ export class CanvasEventManager {
this.canvas.on("selection:updated", (opt) => this.updateSelectedLayer(opt));
// this.canvas.on("selection:cleared", () => this.clearSelectedElements());
this.canvas.on("selection:cleared", (opt) => this.clearSelectedElements(opt));
}
clearSelectedElements(opt) {
if (opt.deselected && opt.deselected.length > 0) {
opt.deselected.forEach((object) => {
if (object.type !== "group") return;
if (object._originPosition) delete object._originPosition
})
}
}
setupObjectEvents() {
// 监听对象变化事件,用于更新缩略图
// this.canvas.on("object:added", (e) => {
@@ -706,34 +711,43 @@ export class CanvasEventManager {
// }
// });
const updateLayers = (e) => {
if (e.target._objects) return;
// this.layerManager.updateLayers();// 先不用数据大了非常卡
};
// 添加对象开始变换时的状态捕获
this.canvas.on("object:moving", (e) => {
// console.log("object:moving", e);
// updateLayers(e);
console.log("object:moving", e);
const target = e.target;
if (target._objects && target._objects.length > 0) {
target._objects.forEach((object) => {
if (object.type !== "group") return;
if (!object._originPosition) return
const originTop = object._originPosition.top
const originLeft = object._originPosition.left
const originCpTop = object._originPosition.cpTop
const originCpLeft = object._originPosition.cpLeft
const top = object.top + target.top + target.height / 2;
const left = object.left + target.left + target.width / 2;
object.clipPath.set({
top: originCpTop + (top - originTop),
left: originCpLeft + (left - originLeft),
})
})
this.canvasManager.updateSubLayerClipPath()
}
});
this.canvas.on("object:scaling", (e) => {
// console.log("object:scaling", e);
// updateLayers(e);
});
this.canvas.on("object:rotating", (e) => {
// console.log("object:rotating", e);
// updateLayers(e);
});
this.canvas.on("object:skewing", (e) => {
// console.log("object:skewing", e);
// updateLayers(e);
});
this.canvas.on("object:modified", async (e) => {
// updateLayers(e);
// console.log("object:modified", e);
const target = e.target;
const id = target?.info?.id;
if (id) await this.layerManager.updateLayerThumbnailsById(id)
if (target.type === "group") await this.canvasManager.updateSubLayerClipPath()
this.stateManager.recordState();
});
this.canvas.on("object:removed", (e) => {

View File

@@ -29,7 +29,11 @@ export const OperationType = {
PAN: "pan", // 拖拽模式
DRAW: "draw", // 绘画模式
ERASER: "eraser", // 橡皮擦模式
SELECTBOX: "selectbox",// 选择框工具模式
AISELECT_ADD: "aiSelectAdd",// 智能框选添加模式
AISELECT_REMOVE: "aiSelectRemove",// 智能框选删除模式
AISELECT_DRAW: "aiSelectDraw",// 智能框选绘制模式
AISELECT_ERASER: "aiSelectEraser",// 智能框选橡皮擦模式
RECTANGLE: "rectangle",// 矩形工具模式
LINE: "line",// 直线工具模式
@@ -68,11 +72,3 @@ export const BlendMode = {
DESTINATION_IN: "destination-in", // 目标内
DESTINATION_OUT: "destination-out", // 目标外
};
/** 智能框选工具类型枚举 */
export const AI_SELECTBOX_TYPE = {
ADD: "add", // 添加模式
REMOVE: "remove", // 删除模式
DRAW: "draw", // 绘画模式
ERASER: "eraser", // 橡皮擦模式
}

View File

@@ -10,12 +10,13 @@
import { computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const url = 'https://www.minio-api.aida.com.hk/fida-test/furniture/sketches/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20260320%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260320T020948Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7dc192bac887bce7b02c99d7037c08d9d684310f00add9b0e63b74b36ee63d37'
const url =
'https://www.minio-api.aida.com.hk/fida-test/furniture/sketches/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20260320%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260320T020948Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7dc192bac887bce7b02c99d7037c08d9d684310f00add9b0e63b74b36ee63d37'
const openCanvas = () => {
myEvent.emit('openFlowCanvas', { url })
}
const openDepthCanvas = () => {
myEvent.emit('openDepthCanvas', { url })
myEvent.emit('openDepthCanvas', { url, canvasId: '69c34539ce996b52f07e625f' })
}
onMounted(() => {
if (route.query.depth) {