-
-
- Add
-
-
-
-
Remove
+
+
+ {{ item.label }}
+
diff --git a/src/components/Canvas/DepthCanvas/manager/AISelectboxToolManager.ts b/src/components/Canvas/DepthCanvas/manager/AISelectboxToolManager.ts
index 85f7b4b..e6edf5b 100644
--- a/src/components/Canvas/DepthCanvas/manager/AISelectboxToolManager.ts
+++ b/src/components/Canvas/DepthCanvas/manager/AISelectboxToolManager.ts
@@ -1,5 +1,6 @@
import { fabric } from 'fabric-with-all'
-import { createId } from '../../tools/tools'
+import { OperationType, AI_SELECTBOX_TYPE } from '../tools/layerHelper'
+import { getObjectAlphaToCanvas, traceImageContour } from '../tools/canvasMethod'
/** 智能框选工具管理器 */
export class AISelectboxToolManager {
@@ -7,6 +8,7 @@ export class AISelectboxToolManager {
canvasManager: any
stateManager: any
layerManager: any
+ toolManager: any
isDragging: boolean = false
startX: number = 0
@@ -16,7 +18,16 @@ export class AISelectboxToolManager {
this.canvasManager = options.canvasManager
this.stateManager = options.stateManager
this.layerManager = options.layerManager
+ this.toolManager = options.toolManager
+ }
+ /** 处理切换工具 */
+ handleToolChange(oldTool: string, newTool: string) {
+ if (newTool === OperationType.SELECTBOX) {
+ // 切换到智能框选工具
+ } else {
+ // 切换到普通框选工具
+ }
}
mouseDownEvent(e) {
this.isDragging = true
@@ -63,7 +74,7 @@ export class AISelectboxToolManager {
this.canvasManager.canvas.remove(this.demoObject)
this.canvasManager.canvas.renderAll()
- this.createSelectbox()
+ // this.createSelectbox()
}
@@ -81,15 +92,11 @@ export class AISelectboxToolManager {
stroke: "rgba(255, 77, 71, 1)",
strokeWidth: 1.5,
strokeDashArray: [4, 4],
- fill: "rgba(255, 186, 186, 0.5)",
+ fill: "transparent",
strokeUniform: true, // 保持描边宽度不随缩放改变
- // strokeLineCap: "round",// 折线端点样式
- // strokeLineJoin: "bevel", // 折线连接样式
- // selectable: false,
- // evented: false,
- excludeFromExport: true,
- hoverCursor: "default",
- moveCursor: "default",
+ selectable: false,
+ evented: false,
+ absolutePositioned: true,
};
async createSelectbox() {
const url = "http://118.31.39.42:3000/falls/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png"
@@ -112,200 +119,31 @@ export class AISelectboxToolManager {
}).join(" L ");
const path = new fabric.Path(`M ${str} z`);
path.set({
- left: left + minX * scaleX,
- top: top + minY * scaleY,
+ left: left + minX,
+ top: top + minY,
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()
-
+ const group = await this.layerManager.createGroupLayer({
+ clipPath: path,
+ }, 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() { }
}
-
-
-/**
- * 获取对象黑白通道画布
- * @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;
-}
\ No newline at end of file
diff --git a/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts b/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts
index 2f8eb50..4ad79dc 100644
--- a/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts
+++ b/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts
@@ -5,11 +5,12 @@ import { AnimationManager } from './AnimationManager'
import { detectDeviceType } from '../tools/index'
import { CanvasEventManager } from "./events/CanvasEventManager";
import { OperationType } from '../tools/layerHelper'
+import { cloneObjects } from '../tools/canvasMethod'
import { createId } from '../../tools/tools'
import md5 from 'md5'
// 自定义画布转对象属性
-fabric.Object.prototype.customProperties = ["top", "left", "width", "height", "scaleX", "scaleY", "info", "thumbnail"];
+fabric.Object.prototype.customProperties = ["top", "left", "width", "height", "scaleX", "scaleY", "info", "thumbnail", "absolutePositioned"];
fabric.Object.prototype.toObject_ = fabric.Object.prototype.toObject
fabric.Object.prototype.toObject = function () {
const args = [...arguments]
@@ -129,25 +130,28 @@ export class CanvasManager {
/** 测试-开始 */
// this.stateManager.setIsRecord(false)
- // const rect = await this.layerManager.createRectLayer({ left: 200 })
- // await this.layerManager.createStarLayer({ left: 400 })
- // await this.layerManager.createArrowLayer({ left: 600 })
+ // const groupObject = await this.layerManager.createGroupLayer()
+ // const parentId = groupObject.info.id
+ // const rect = await this.layerManager.createRectLayer({ left: 200, info: { parentId } })
+ // const star = await this.layerManager.createStarLayer({ left: 400, info: { parentId } })
+ // const arrow = await this.layerManager.createArrowLayer({ left: 600, info: { parentId } })
+ // await this.layerManager.createGroupLayer()
// this.layerManager.setActiveID(rect.info.id)
// this.stateManager.setIsRecord(true)
/** 测试-结束 */
this.resetZoom(false, true)// 画布居中
+ this.stateManager.toolManager.setTool(OperationType.SELECT)
this.layerManager.updateLayers()
this.stateManager.recordState()
- // this.stateManager.toolManager.setTool(OperationType.RECTANGLE)
}
/** 画布添加对象 */
async add(obj: any, isRecord = true) {
this.canvas.add(obj)
const id = obj?.info?.id || ""
if (id) {
- this.layerManager.updateLayers()
+ await this.layerManager.updateLayers(!!obj.info.parentId)
this.renderAll()
await this.layerManager.updateLayerThumbnailsById(id)
}
@@ -162,6 +166,35 @@ export class CanvasManager {
}
}
+ /** 更新子图层裁剪区域 */
+ async updateSubLayerClipPath() {
+ const objects = this.getObjects().filter((v: any) => v.type !== "group" && !!v.info?.id);
+ for (let i = 0; i < objects.length; i++) {
+ let object = objects[i]
+ if (object.clipPath) object.set({ clipPath: null })
+ let group = this.getObjectById(object.info.parentId)
+ if (!group) continue
+ let path = group.clipPath
+ if (!path) continue
+ let clipPath = await cloneObjects([path]).then((v) => v[0])
+ clipPath.set({
+ absolutePositioned: true,
+ })
+ object.set({ clipPath })
+ }
+ this.renderAll()
+ }
+ /** 排序画布对象 */
+ async sortObjectByIds(ids: string[], isRecord?: boolean) {
+ ids.forEach((id, index) => {
+ this.canvas.moveTo(this.getObjectById(id), index)
+ })
+ await this.updateSubLayerClipPath()
+ this.renderAll()
+ if (isRecord) this.stateManager.recordState()
+ }
+
+
/** 设置画布事件 */
setupCanvasEvents() {
// 创建画布事件管理器
@@ -177,8 +210,18 @@ export class CanvasManager {
}
/** 设置激活对象 */
setActiveObjectById(id: string) {
+ this.discardActiveObject()
const obj = this.getObjectById(id)
- if (obj && obj.evented) this.canvas.setActiveObject(obj)
+ if (!obj) return
+ if (obj.type === "group") {
+ const objects = [];
+ this.getObjects().forEach((item: any) => {
+ if (item?.info?.parentId === id) objects.push(item)
+ })
+ if (objects.length > 0) this.canvas.setActiveObject(new fabric.ActiveSelection(objects, { canvas: this.canvas }));
+ } else {
+ if (obj.evented) this.canvas.setActiveObject(obj)
+ }
this.renderAll()
}
resetZoom(animated = true, adaptive = true) {
@@ -222,11 +265,11 @@ export class CanvasManager {
renderAll() {
this.canvas.renderAll()
}
- deleteObjectById(id: string) {
+ deleteObjectById(id: string, isUpdate = true) {
const object = this.getObjectById(id)
if (object) {
this.canvas.remove(object)
- this.layerManager.updateLayers()
+ if (isUpdate) this.layerManager.updateLayers()
this.renderAll()
}
}
@@ -235,13 +278,6 @@ export class CanvasManager {
this.canvas.discardActiveObject()
this.renderAll()
}
- // 拖拽排序
- dragSort(id, newIndex) {
- this.canvas.moveTo(this.getObjectById(id), newIndex)
- this.layerManager.updateLayers()
- this.renderAll()
- this.stateManager.recordState()
- }
/** 画笔事件 */
setupBrushEvents() {
@@ -254,13 +290,13 @@ export class CanvasManager {
};
}
/** 处理绘制图像 */
- handleDrawImage(fabricImage: fabric.Object) {
+ async handleDrawImage(fabricImage: fabric.Object) {
const activeID = this.stateManager.layerManager.activeID.value
const activeLayer = this.getObjectById(activeID)
- if (activeLayer) {
+ if (activeLayer && activeLayer.fill?.repeat !== "repeat") {
this.layerManager.imageMergeToLayer(activeLayer, fabricImage)
} else {
- const emptyLayer = this.layerManager.createEmptyLayer(false);
+ const emptyLayer = await this.layerManager.createEmptyLayer(false);
this.layerManager.setActiveID(emptyLayer.info.id, false)
this.layerManager.imageMergeToLayer(emptyLayer, fabricImage)
}
diff --git a/src/components/Canvas/DepthCanvas/manager/LayerManager.ts b/src/components/Canvas/DepthCanvas/manager/LayerManager.ts
index aabe849..bce7eac 100644
--- a/src/components/Canvas/DepthCanvas/manager/LayerManager.ts
+++ b/src/components/Canvas/DepthCanvas/manager/LayerManager.ts
@@ -2,7 +2,7 @@ import { ref } from 'vue'
import { fabric } from 'fabric-with-all'
import { createId } from '../../tools/tools'
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
-import { OperationType } from '../tools/layerHelper'
+import { OperationType, BlendMode } from '../tools/layerHelper'
import { getArrowPath, cloneObjects, getStarArr } from '../tools/canvasMethod'
export class LayerManager {
@@ -18,7 +18,12 @@ export class LayerManager {
}
onMounted() { }
setActiveID(id: string, isActive = true) {
- this.activeID.value = id
+ const layer = this.getLayerById(id)
+ if (layer?.type === "group") {
+ this.activeID.value = ""
+ } else {
+ this.activeID.value = id
+ }
if (isActive) {
this.canvasManager.setActiveObjectById(id)
this.stateManager.toolManager.setTool(OperationType.SELECT)
@@ -28,7 +33,18 @@ export class LayerManager {
return this.getLayerById(this.activeID.value)
}
getLayerById(id) {
- return this.layers.value.find((item: any) => item.info.id === id)
+ function call(arr) {
+ for (let i = 0; i < arr.length; i++) {
+ let v = arr[i]
+ if (v.info.id === id) return v
+ if (v.children) {
+ let layer = call(v.children)
+ if (layer) return layer
+ }
+ }
+ return null
+ }
+ return call(this.layers.value)
}
setLayerNameById(id, name: string) {
const layer = this.getLayerById(id)
@@ -82,6 +98,10 @@ export class LayerManager {
/** 删除指定图层 */
deleteLayerById(id, isActive = true) {
+ const layer = this.getLayerById(id)
+ if (layer.children) {
+ layer.children.forEach(v => this.canvasManager.deleteObjectById(v.info.id, false))
+ }
this.canvasManager.deleteObjectById(id)
if (id === this.activeID.value && isActive) {
this.setActiveID(this.layers.value[0]?.info?.id || "")
@@ -106,17 +126,38 @@ export class LayerManager {
this.setActiveID(newObject.info.id)
})
}
- // 拖拽排序
- dragSort(id, newIndex) {
- const index = Math.abs(this.layers.value.length - newIndex - 1)
- this.canvasManager.dragSort(id, index)
+ /** 根据layers排序图层 */
+ async sortLayers(isRecord?: boolean) {
+ const ids = [];
+ call(this.layers.value)
+ await this.canvasManager.sortObjectByIds(ids.reverse(), isRecord)
+ function call(arr) {
+ arr.forEach(v => {
+ ids.push(v.info.id)
+ if (v.children) call(v.children)
+ })
+ }
}
+
// 更新图层列表
- updateLayers() {
- this.layers.value = this.canvasManager.getObjects()
- .filter((v: any) => !!v?.info?.id)
- .reverse()
- .map(v => v.toObject())
+ async updateLayers(isSort = false) {
+ const objects = this.canvasManager.getObjects().map(v => v.toObject()).filter(v => !!v.info?.id).reverse()
+ objects.forEach(v => {
+ if (v.type === "group") {
+ if (!v.children) v.children = []
+ return;
+ }
+ const parentId = v.info?.parentId
+ if (!parentId) return
+ objects.forEach((obj: any) => {
+ if (obj.info?.id !== parentId) return
+ if (!obj.children) obj.children = []
+ obj.children.push(v)
+ })
+ })
+ const layers = objects.filter(v => !v.info?.parentId)
+ this.layers.value = layers
+ if (isSort) await this.sortLayers()
}
/** 设置图层位置-不设置默认居中 */
@@ -136,7 +177,7 @@ export class LayerManager {
}
}
/** 创建空图层 */
- createEmptyLayer(isRecord = true, isActive = false) {
+ async createEmptyLayer(isRecord = true, isActive = false) {
const emptyObject = new fabric.Rect({
width: 0,
height: 0,
@@ -147,37 +188,28 @@ export class LayerManager {
}
})
this.setLayerPosition(emptyObject)
- this.canvasManager.add(emptyObject, isRecord)
+ await this.canvasManager.add(emptyObject, isRecord)
if (isActive) this.setActiveID(emptyObject.info.id, false)
return emptyObject
}
/** 创建组图层 */
- createGroupLayer(options?: any, isRecord = true, isActive = false) {
- const child = options?.child || []
- delete options.child
- const groupObject = new fabric.Group(child, {
- // subTargetCheck: true, // 关键:检测子对象
- // interactive: true, // 启用交互
- // hasControls: true,
- // hasBorders: true,
-
- // // 子对象样式
- // cornerColor: 'blue',
- // cornerSize: 8,
- // borderColor: 'green',
-
- // // 允许子对象独立变换
- // lockScalingX: false,
- // lockScalingY: false,
- // lockRotation: false,
+ async createGroupLayer(options?: any, isRecord = true, isActive = false) {
+ const children = options?.children || []
+ delete options.children
+ const groupObject = new fabric.Group(children, {
+ ...(options || {}),
+ hasControls: false, // 不显示控制点
+ hasBorders: false, // 不显示边框
+ selectable: false, // 不可选中(可选)
info: {
id: createId("group"),
- name: '组图层',
+ name: '智能选区组',
+ showChildren: true,
...(options?.info || {}),
}
})
- // this.setLayerPosition(groupObject)
- this.canvasManager.add(groupObject, isRecord)
+ this.setLayerPosition(groupObject, options)
+ await this.canvasManager.add(groupObject, isRecord)
if (isActive) this.setActiveID(groupObject.info.id, false)
return groupObject
}
@@ -199,7 +231,7 @@ export class LayerManager {
return textObject
}
/** 创建矩形图层 */
- async createRectLayer(options?: any, isActive = false) {
+ async createRectLayer(options?: any, isRecord = true, isActive = true) {
const rectObject = new fabric.Rect({
width: 100,
height: 100,
@@ -213,12 +245,12 @@ export class LayerManager {
}
})
this.setLayerPosition(rectObject, options)
- await this.canvasManager.add(rectObject)
+ await this.canvasManager.add(rectObject, isRecord)
if (isActive) this.setActiveID(rectObject.info.id)
return rectObject
}
/** 创建直线图层 */
- async createLineLayer(options?: any, isActive = false) {
+ async createLineLayer(options?: any, isRecord = true, isActive = true) {
const line = [options?.x1 || 0, options?.y1 || 0, options?.x2 || 100, options?.y2 || 0]
delete options.x1
delete options.y1
@@ -235,14 +267,13 @@ export class LayerManager {
}
})
this.setLayerPosition(lineObject, options)
- await this.canvasManager.add(lineObject)
+ await this.canvasManager.add(lineObject, isRecord)
if (isActive) this.setActiveID(lineObject.info.id)
return lineObject
}
/** 创建椭圆图层 */
- async createEllipseLayer(options?: any, isActive = false) {
+ async createEllipseLayer(options?: any, isRecord = true, isActive = true) {
const ellipseObject = new fabric.Ellipse({
- radius: 50,
fill: '#000',
strokeWidth: 0,
...(options || {}),
@@ -258,7 +289,7 @@ export class LayerManager {
return ellipseObject
}
/** 创建三角形图层 */
- async createTriangleLayer(options?: any, isActive = false) {
+ async createTriangleLayer(options?: any, isRecord = true, isActive = true) {
const triangleObject = new fabric.Triangle({
width: 100,
height: 100,
@@ -272,12 +303,12 @@ export class LayerManager {
}
})
this.setLayerPosition(triangleObject, options)
- await this.canvasManager.add(triangleObject)
+ await this.canvasManager.add(triangleObject, isRecord)
if (isActive) this.setActiveID(triangleObject.info.id)
return triangleObject
}
/** 创建五角星图层 */
- async createStarLayer(options?: any, isActive = false) {
+ async createStarLayer(options?: any, isRecord = true, isActive = true) {
const width = options?.width || 100
const height = options?.height || 100
delete options.points
@@ -292,12 +323,12 @@ export class LayerManager {
}
})
this.setLayerPosition(starObject, options)
- await this.canvasManager.add(starObject)
+ await this.canvasManager.add(starObject, isRecord)
if (isActive) this.setActiveID(starObject.info.id)
return starObject
}
/** 创建箭头图层 */
- async createArrowLayer(options?: any, isActive = false) {
+ async createArrowLayer(options?: any, isRecord = true, isActive = true) {
const width = options?.width || 100
const height = options?.height || 10
delete options.width
@@ -316,7 +347,7 @@ export class LayerManager {
}
});
this.setLayerPosition(arrowObject, options)
- this.canvasManager.add(arrowObject)
+ await this.canvasManager.add(arrowObject, isRecord)
if (isActive) this.setActiveID(arrowObject.info.id)
return arrowObject
}
@@ -324,7 +355,7 @@ export class LayerManager {
/** 创建图片图层 */
- async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) {
+ async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true, isActive = true) {
const { canvasWidth, canvasHeight } = this.canvasManager.getCanvasSize();
const imageObject = await new Promise((resolve) => {
@@ -350,7 +381,7 @@ export class LayerManager {
}) as fabric.Object
this.setLayerPosition(imageObject, options)
await this.canvasManager.add(imageObject, isRecord)
- this.setActiveID(imageObject.info.id)
+ if (isActive) this.setActiveID(imageObject.info.id)
return imageObject
}
@@ -363,6 +394,7 @@ export class LayerManager {
left: info.left,
top: info.top,
info: {
+ ...(targetLayer?.info || {}),
id: createId("image"),
name: targetLayer?.info?.name || "合并图层",
}
@@ -370,12 +402,17 @@ export class LayerManager {
resolve(img)
}, { crossOrigin: 'anonymous' })
})
- // console.log(mergedImage)
const index = this.canvasManager.getObjects().indexOf(targetLayer);
this.deleteLayerById(targetLayer.info.id, false)
- this.setActiveID(mergedImage.info.id, false)
+
+ const nid = mergedImage.info.id
await this.canvasManager.add(mergedImage, false);
+ this.setActiveID(nid, false)
this.canvasManager.canvas.moveTo(mergedImage, index);
+
+ // this.stateManager.objectManager.setBlendMode(nid, BlendMode.MULTIPLY)
+ // this.stateManager.objectManager.setFillRepeat(nid, false)
+
this.canvasManager.renderAll()
this.updateLayers()
this.stateManager.recordState()
@@ -391,12 +428,12 @@ export class LayerManager {
})
}
/** 更新图层缩略图 */
- async updateLayerThumbnailsById(id: string) {
+ async updateLayerThumbnailsById(id: string, thumbnail?: string, isUpdate = true) {
const object = this.canvasManager.getObjectById(id);
if (!object) return;
- const url = await exportObjectToThumbnail(object);
+ const url = thumbnail || await exportObjectToThumbnail(object);
object.thumbnail = url
- this.updateLayers()
+ if (isUpdate) this.updateLayers()
}
dispose() { }
diff --git a/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts b/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts
index 15436a0..5dcc9a2 100644
--- a/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts
+++ b/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts
@@ -87,7 +87,7 @@ export class ObjectManager {
}
/** 设置平铺状态 */
- setFillRepeat(id: string) {
+ setFillRepeat(id: string, isRecord = true) {
const object = this.canvasManager.getObjectById(id)
if (!object) return console.warn('设置平铺状态失败,对象不存在ID:', id)
if (object.type !== 'image') return console.warn('设置平铺状态失败,对象不是图片类型:', id)
@@ -133,7 +133,7 @@ export class ObjectManager {
});
rect.set("fill", pattern)
this.canvasManager.canvas.remove(object)
- this.canvasManager.add(rect)
+ this.canvasManager.add(rect, isRecord)
}
/** 获取填充对象 */
getFillRepeatObject(id: string) {
diff --git a/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts b/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts
index 2699414..c6056cd 100644
--- a/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts
+++ b/src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts
@@ -131,7 +131,7 @@ export class ShapeToolManager {
upRectangle(object) {
if (object.width === 0) object.width = 100
if (object.height === 0) object.height = 100
- this.layerManager.createRectLayer(object, true)
+ this.layerManager.createRectLayer(object)
}
/** 绘制直线 */
@@ -151,7 +151,7 @@ export class ShapeToolManager {
})
}
upLine(object) {
- this.layerManager.createLineLayer(object, true)
+ this.layerManager.createLineLayer(object)
}
/** 绘制椭圆 */
@@ -170,7 +170,7 @@ export class ShapeToolManager {
upEllipse(object) {
if (object.rx === 0) object.rx = 50
if (object.ry === 0) object.ry = 50
- this.layerManager.createEllipseLayer(object, true)
+ this.layerManager.createEllipseLayer(object)
}
@@ -192,7 +192,7 @@ export class ShapeToolManager {
upTriangle(object) {
if (object.width === 0) object.width = 100
if (object.height === 0) object.height = 100
- this.layerManager.createTriangleLayer(object, true)
+ this.layerManager.createTriangleLayer(object)
}
@@ -217,7 +217,7 @@ export class ShapeToolManager {
upStar(object) {
if (object.width === 0) object.width = 100
if (object.height === 0) object.height = 100
- this.layerManager.createStarLayer(object, true)
+ this.layerManager.createStarLayer(object)
}
/** 绘制箭头 */
@@ -249,7 +249,7 @@ export class ShapeToolManager {
top: this.startY,
}, true)
} else {
- this.layerManager.createArrowLayer(object, true)
+ this.layerManager.createArrowLayer(object)
}
}
diff --git a/src/components/Canvas/DepthCanvas/manager/StateManager.ts b/src/components/Canvas/DepthCanvas/manager/StateManager.ts
index b68be75..9b0e59a 100644
--- a/src/components/Canvas/DepthCanvas/manager/StateManager.ts
+++ b/src/components/Canvas/DepthCanvas/manager/StateManager.ts
@@ -38,6 +38,7 @@ export class StateManager {
brushManager: any
keyEventManager: any
objectManager: any
+ aiSelectboxToolManager: any
// 设置管理器
setManager(options) {
options.eventManager && (this.eventManager = options.eventManager)
@@ -47,6 +48,7 @@ export class StateManager {
options.brushManager && (this.brushManager = options.brushManager)
options.keyEventManager && (this.keyEventManager = options.keyEventManager)
options.objectManager && (this.objectManager = options.objectManager)
+ options.aiSelectboxToolManager && (this.aiSelectboxToolManager = options.aiSelectboxToolManager)
}
constructor(options) {
this.mxHistory = ref(50)
@@ -71,6 +73,7 @@ export class StateManager {
/** 记录状态 */
recordState() {
if (this.running.value) return
+ console.log("recordState")
this.running.value = true
if (this.historyIndex.value < this.historyList.value.length - 1) {
this.historyList.value.splice(this.historyIndex.value + 1)
diff --git a/src/components/Canvas/DepthCanvas/manager/ToolManager.ts b/src/components/Canvas/DepthCanvas/manager/ToolManager.ts
index 6a73660..e3d5018 100644
--- a/src/components/Canvas/DepthCanvas/manager/ToolManager.ts
+++ b/src/components/Canvas/DepthCanvas/manager/ToolManager.ts
@@ -101,6 +101,7 @@ export class ToolManager {
setTool(value: string) {
const tool = this.tools.find((t) => t.name === value)
if (!tool) return console.warn(`工具${tool}不存在`)
+ const oldTool = this.currentTool.value
this.currentTool.value = tool.name
this.canvasManager.canvas.defaultCursor = tool.cursor
this.setCanvasEvented(!!tool.selection)
@@ -110,6 +111,7 @@ export class ToolManager {
if (tool.setup) tool.setup()
+ this.stateManager?.aiSelectboxToolManager?.handleToolChange?.(oldTool, tool.name)
setTimeout(() => {
this.canvasManager.renderAll()
});
@@ -142,13 +144,16 @@ export class ToolManager {
const brushStore = this.brushManager?.brushStore
if (brushStore) {
// 同步基本属性
- this.brushManager.setBrushSize(brushStore.state.size);
- this.brushManager.setBrushColor(brushStore.state.color);
- this.brushManager.setBrushOpacity(brushStore.state.opacity);
+ // this.brushManager.setBrushSize(brushStore.state.size);
+ // this.brushManager.setBrushColor(brushStore.state.color);
+ // this.brushManager.setBrushOpacity(brushStore.state.opacity);
// 同步笔刷类型 - 修复方法名,使用正确的setBrushType方法
this.brushManager.setBrushType("pencil");
}
+ this.brushManager.setBrushSize(5);
+ this.brushManager.setBrushColor("#000");
+ this.brushManager.setBrushOpacity(1);
// 更新应用到画布
this.brushManager.updateBrush();
@@ -168,6 +173,7 @@ export class ToolManager {
this.brushManager.createEraser();
}
+ this.brushManager.setBrushSize(5);
this.stateManager.layerManager.setActiveObjectErasable()
// 启用笔刷指示器
this._enableBrushIndicator();
diff --git a/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js b/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js
index 335a056..fda4733 100644
--- a/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js
+++ b/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js
@@ -34,6 +34,7 @@ export class CanvasEventManager {
}
this.shapeToolManager = new ShapeToolManager(managers)
this.aiSelectboxToolManager = new AISelectboxToolManager(managers)
+ this.stateManager.setManager({ aiSelectboxToolManager: this.aiSelectboxToolManager })
// 初始化所有事件
this.initEvents();
@@ -59,9 +60,8 @@ export class CanvasEventManager {
// 共享事件
this.setupSelectionEvents();
this.setupObjectEvents();
- // this.setupDoubleClickEvents();
+ this.setupDoubleClickEvents();
- // this.setupHandlePathCreated();
}
setupZoomEvents() {
@@ -730,8 +730,10 @@ export class CanvasEventManager {
});
this.canvas.on("object:modified", async (e) => {
// updateLayers(e);
- const id = e.target?.info?.id;
+ 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) => {
@@ -752,113 +754,6 @@ export class CanvasEventManager {
});
}
- setupLongPress(callback) {
- this.canvas.on("mouse:down", (opt) => {
- if (!opt.target) return;
-
- this.longPressTimer = setTimeout(() => {
- callback(opt);
- }, this.longPressThreshold);
- });
-
- this.canvas.on("mouse:up", () => {
- clearTimeout(this.longPressTimer);
- });
-
- this.canvas.on("mouse:move", () => {
- clearTimeout(this.longPressTimer);
- });
- }
-
- // 设置路径创建事件
- setupHandlePathCreated() {
- // 在 CanvasEventManager 的构造函数或初始化方法中
- // this.canvas.on("path:created", this._handlePathCreated.bind(this));
- }
-
- _handlePathCreated(e) {
- // // 获取新创建的路径对象
- // const path = e.path;
- // // 设置路径的ID和其他属性
- // path.id = generateId(); // 生成唯一ID
- // // 获取当前活动图层
- // const activeLayer = this.layerManager.getActiveLayer();
- // // 将路径对象绑定到当前活动图层
- // if (activeLayer) {
- // // 设置路径的图层ID
- // path.layerId = activeLayer.id;
- // // 更新图层对象列表
- // if (!activeLayer.fabricObjects) activeLayer.fabricObjects = [];
- // activeLayer.fabricObjects.push(path);
- // // 更新图层缩略图
- // if (this.thumbnailManager) {
- // this.thumbnailManager.generateLayerThumbnail(activeLayer.id);
- // }
- // }
- }
-
- /**
- * 合并图层中的对象为组以提高性能
- * @param {Object} options 合并选项
- * @param {fabric.Image} options.fabricImage 新的图像对象
- * @param {Object} options.activeLayer 当前活动图层
- * @private
- */
- async mergeLayerObjectsForPerformance({ fabricImage, activeLayer, options }) {
- // 确保有命令管理器
- if (!this.layerManager || !this.layerManager.commandManager) {
- console.warn("合并对象失败:没有命令管理器");
- return;
- }
-
- // 确保有活动图层
- if (!activeLayer) {
- console.warn("合并对象失败:没有活动图层");
- return;
- }
- // 验证是否需要合并
- const hasExistingObjects =
- Array.isArray(activeLayer.fabricObjects) &&
- activeLayer.fabricObjects.length > 0;
- const hasNewImage = !!fabricImage;
-
- if (!hasExistingObjects && !hasNewImage) {
- // console.log("没有对象需要合并");
- return;
- }
-
- // 如果只有一个新图像且图层为空,直接添加到图层
- if (hasNewImage && !hasExistingObjects) {
- this.layerManager.addObjectToLayer(fabricImage, activeLayer.id, options);
- return;
- }
-
- // 执行高保真合并操作
- try {
- // console.log(`开始合并图层 ${activeLayer.name} 中的对象为组...`);
-
- const command = await this.layerManager.LayerObjectsToGroup(
- activeLayer,
- fabricImage
- );
-
- // 设置命令的撤销状态
- if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销
-
- this.layerManager?.commandManager?.execute?.(command, {
- name: `合并图层 ${activeLayer.name} 中的对象为组`,
- });
- } catch (error) {
- console.error("合并图层对象时发生错误:", error);
-
- // 降级处理:如果合并失败,至少保证新图像能添加到图层
- if (fabricImage && this.layerManager) {
- // console.log("执行降级处理:直接添加图像到图层");
- this.layerManager.addObjectToLayer(fabricImage, activeLayer.id);
- }
- }
- }
-
updateSelectedLayer(opt) {
const selected = opt.selected[0];
if (selected && opt.selected.length === 1) {
@@ -866,31 +761,6 @@ export class CanvasEventManager {
}
}
- // 更新图层缩略图
- updateLayerThumbnail(layerId) {
- if (!this.thumbnailManager || !layerId || !this.layers) return;
-
- const layer = this.layers.value.find((l) => l.id === layerId);
- if (layer) {
- this.thumbnailManager.generateLayerThumbnail(layer);
- }
- }
-
- // 更新子元素组合缩略图
- updateLayerChidrenThumbnail(layerId, fabricObject) {
- if (!this.thumbnailManager || !fabricObject || !this.layers) return;
-
- // 查找对应的图层(现在元素就是图层)
- const layer = this.layers.value.find(
- (l) => l.fabricObjects && l.fabricObjects?.[0]?.id === layerId
- );
-
- if (layer) {
- // 生成图层缩略图
- this.thumbnailManager.generateLayerThumbnail(layer);
- }
- }
-
/**
* 精确检测设备类型,区分 PC、Mac、平板和移动设备
diff --git a/src/components/Canvas/DepthCanvas/tools/canvasMethod.js b/src/components/Canvas/DepthCanvas/tools/canvasMethod.js
index a506e8d..3792222 100644
--- a/src/components/Canvas/DepthCanvas/tools/canvasMethod.js
+++ b/src/components/Canvas/DepthCanvas/tools/canvasMethod.js
@@ -93,3 +93,157 @@ export function angleBetweenPointsDegrees(x1, y1, x2, y2) {
return deg;
}
+
+
+
+/**
+ * 获取对象黑白通道画布
+ * @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", { willReadFrequently: true });
+ 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;
+}
\ No newline at end of file
diff --git a/src/components/Canvas/DepthCanvas/tools/exportMethod.js b/src/components/Canvas/DepthCanvas/tools/exportMethod.js
index dbf897b..01cd8fa 100644
--- a/src/components/Canvas/DepthCanvas/tools/exportMethod.js
+++ b/src/components/Canvas/DepthCanvas/tools/exportMethod.js
@@ -29,6 +29,12 @@ export async function exportObjectsToImage(objects = [], isDetails = false) {
left: obj.left - boundingBox.left,
top: obj.top - boundingBox.top,
})
+ if (obj.clipPath && obj.clipPath.absolutePositioned) {
+ obj.clipPath.set({
+ left: obj.clipPath.left - boundingBox.left,
+ top: obj.clipPath.top - boundingBox.top,
+ })
+ }
staticCanvas.add(obj)
})
// 导出图片
diff --git a/src/components/Canvas/DepthCanvas/tools/layerHelper.js b/src/components/Canvas/DepthCanvas/tools/layerHelper.js
index acdb191..0f9c2f1 100644
--- a/src/components/Canvas/DepthCanvas/tools/layerHelper.js
+++ b/src/components/Canvas/DepthCanvas/tools/layerHelper.js
@@ -68,3 +68,11 @@ export const BlendMode = {
DESTINATION_IN: "destination-in", // 目标内
DESTINATION_OUT: "destination-out", // 目标外
};
+
+/** 智能框选工具类型枚举 */
+export const AI_SELECTBOX_TYPE = {
+ ADD: "add", // 添加模式
+ REMOVE: "remove", // 删除模式
+ DRAW: "draw", // 绘画模式
+ ERASER: "eraser", // 橡皮擦模式
+}
diff --git a/src/components/Canvas/FlowCanvas/components/node-el.vue b/src/components/Canvas/FlowCanvas/components/node-el.vue
index 16d7155..76f85c9 100644
--- a/src/components/Canvas/FlowCanvas/components/node-el.vue
+++ b/src/components/Canvas/FlowCanvas/components/node-el.vue
@@ -11,15 +11,12 @@
stateManager.setActiveNodeID(node.id)">
-
-
-