diff --git a/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue b/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue index c5ec0856..658d647f 100644 --- a/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/PartSelectorPanel.vue @@ -63,6 +63,10 @@ $t("Canvas.CreateAndCopy") }} +
+ + 清空当前点位 +
@@ -226,12 +230,16 @@ // 创建 function onCreate() { - + props.partManager.createPart(); } // 复制并创建 function onCopyCreate() { } + // 清空当前点位 + function onReset() { + props.partManager.clearPart(); + } @@ -410,7 +418,7 @@ .tool-actions { display: grid; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 5px; padding: 0 30px; } diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 86b5dda3..237a8512 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -144,6 +144,7 @@ const props = defineProps({ }, }); +const loading = ref(false); // 引用和状态 const canvasRef = ref(null); const canvasContainerRef = shallowRef(null); @@ -277,6 +278,7 @@ onMounted(async () => { canvasManager.canvas.activeLayerId = activeLayerId; canvasManager.activeLayerId = activeLayerId; canvasManager.canvas.activeElementId = activeElementId; + canvasManager.canvas.loading = loading; // 创建命令管理器 commandManager = new CommandManager({ @@ -1392,6 +1394,7 @@ defineExpose({ />
+
@@ -1466,6 +1469,9 @@ defineExpose({ top: 0; bottom: 0; } +.app-container >.loading{ + position: absolute; +} .background-grid { --offsetX: 50%; diff --git a/src/component/Canvas/CanvasEditor/managers/PartManager.js b/src/component/Canvas/CanvasEditor/managers/PartManager.js index aa193b8c..6d593eac 100644 --- a/src/component/Canvas/CanvasEditor/managers/PartManager.js +++ b/src/component/Canvas/CanvasEditor/managers/PartManager.js @@ -1,5 +1,5 @@ import { fabric } from "fabric-with-all"; -import { generateId } from "../utils/helper"; +import { traceImageContour, imageToCanvas } from "../utils/helper"; import { OperationType } from "../utils/layerHelper"; import { CreateSelectionCommand } from "../commands/SelectionCommands"; import { ClearSelectionCommand } from "../commands/LassoCutoutCommand"; @@ -34,15 +34,6 @@ export class PartManager { // 状态 this.isActive = false; - this.partObject = null; // 当前选区对象 - this.partId = "part_selector"; - this.defaultCursor = "default"; - - // 绘制状态 - this.drawingObject = null; - this.startPoint = null; - this.partPath = null; // 存储选区路径数据 - // 不再直接绑定事件处理函数 this._mouseDownHandler = null; @@ -58,10 +49,15 @@ export class PartManager { OperationType.PART_ERASER, ]; - this.pointList = []; // 存储点选坐标 // 当前工具 this.activeTool = this.toolManager.activeTool; + + this.partGroup = null; // 当前选区对象 + this.partId = "part_selector"; + this.partCanvas = null;// 选区画布 + // 点选工具相关 + this.pointList = []; // 存储点选坐标 } /** @@ -82,15 +78,13 @@ export class PartManager { else if (wasActive && !this.isActive) { this.cleanupEvents(); this.clearPartObject(); + this.clearPointData(); } } - /** - * 初始化选区相关事件 - */ + /** 初始化选区相关事件 */ initEvents() { if (!this.canvas || this._mouseDownHandler) return; // 避免重复初始化 - this.defaultCursor = this.canvas.defaultCursor; // 保存实例引用,用于事件处理函数中 const self = this; @@ -187,9 +181,7 @@ export class PartManager { document.addEventListener("keydown", this._keyDownHandler); } - /** - * 清理事件监听 - */ + /** 清理事件监听 */ cleanupEvents() { if (!this.canvas) return; @@ -212,20 +204,19 @@ export class PartManager { } } - // 点选工具模式下点击事件处理 + /** 点选工具模式下点击事件处理 */ _pointDownkHandler(options) { // const button = options.button; // const isLeft = button === 1;// 左键1(添加) 右键3(删除) // const icon = `url("${isLeft ? addIcon : removeIcon}") 16 16, default` // this.canvas.upperCanvasEl.style.cursor = icon; } - // 点选工具模式下移动事件处理 + /** 点选工具模式下移动事件处理 */ _pointMoveHandler(options) { } - // 点选工具模式下抬起事件处理 + /** 点选工具模式下抬起事件处理 */ async _pointUpHandler(options) { const button = options.button; const isLeft = button === 1;// 左键1(添加) 右键3(删除) - this.canvas.upperCanvasEl.style.cursor = this.defaultCursor; const fixedObject = this.canvasManager.getFixedLayerObject(); if (!fixedObject) return console.warn("未找到固定图层"); const { x, y } = options.absolutePointer; @@ -256,10 +247,11 @@ export class PartManager { label: label, }) const image1 = await this.loadImageToObject(url); - const group = this.partObject; - this.removeAllChildren(); + this.resetPartObject(); + const group = this.partGroup; const rgba = { r: 0, g: 255, b: 0, a: 200 } const canvas = getObjectAlphaToCanvas(image1, null, 0, rgba); + this.partCanvas = canvas; const image2 = new fabric.Image(canvas); image2.set({ originX: fixedObject.originX, @@ -279,47 +271,57 @@ export class PartManager { } this.canvas.renderAll(); } - - - // 框选工具模式下点击事件处理 - _rectangleDownHandler(options) { + /** 清空点选数据 */ + clearPointData() { + this.pointList = []; + this.partCanvas = null; } - // 框选工具模式下移动事件处理 + + + /** 框选工具模式下点击事件处理 */ + _rectangleDownHandler(options) { + console.log(options.absolutePointer); + } + /** 框选工具模式下移动事件处理 */ _rectangleMoveHandler(options) { } - // 框选工具模式下抬起事件处理 + /** 框选工具模式下抬起事件处理 */ _rectangleUpHandler(options) { + console.log(options.absolutePointer); } - // 绘制工具模式下点击事件处理 + /** 绘制工具模式下点击事件处理 */ _brushDownHandler(options) { } - // 绘制工具模式下移动事件处理 + /** 绘制工具模式下移动事件处理 */ _brushMoveHandler(options) { } - // 绘制工具模式下抬起事件处理 + /** 绘制工具模式下抬起事件处理 */ _brushUpHandler(options) { } - // 擦除工具模式下抬起事件处理 + /** 擦除工具模式下抬起事件处理 */ _eraseUpHandler(options) { } - // 擦除工具模式下点击事件处理 + /** 擦除工具模式下点击事件处理 */ _eraseDownHandler(options) { } - // 擦除工具模式下移动事件处理 + /** 擦除工具模式下移动事件处理 */ _eraseMoveHandler(options) { } - // 获取分隔后图片 + /** 获取分隔后图片 */ async getSegAnythingImage(obj) { + setTimeout(() => { + this.canvas.loading.value = true; + }); return new Promise((resolve, reject) => { // const user_id = store.state.UserHabit.userDetail.userId; const user_id = 24299; @@ -329,6 +331,7 @@ export class PartManager { } Https.axiosPost(Https.httpUrls.segAnything, data) .then(response => { + this.canvas.loading.value = false; if (response) { resolve(response); } else { @@ -336,12 +339,13 @@ export class PartManager { } }) .catch(error => { + this.canvas.loading.value = false; console.error(error); }); }); } - // 删除指定ID的对象 + /** 删除指定ID的对象 */ removeObjectsById(id) { const objects = this.canvas.getObjects().filter(obj => obj.id === id); this.canvas.remove(...objects); @@ -354,10 +358,11 @@ export class PartManager { }); } - removeAllChildren() { - this.partObject?._objects?.forEach(child => { - group.remove(child); - }); + /** 重置点位对象组 */ + resetPartObject(render = false) { + this.clearPartObject(); + this.createPartObject(); + if (render) this.canvas.renderAll(); } createPartObject() { const fixedObject = this.canvasManager.getFixedLayerObject(); @@ -378,11 +383,53 @@ export class PartManager { evented: false, }) this.canvas.add(group); - this.partObject = group; + this.partGroup = group; } + /** 清空点位对象组 */ clearPartObject() { this.removeObjectsById(this.partId); - this.partObject = null; + this.partGroup = null; + } + + /** 创建当前选区 */ + createPart() { + if (!this.partCanvas) return console.warn("没有点位画布"); + const fixedObject = this.canvasManager.getFixedLayerObject(); + if (!fixedObject) return console.warn("未找到固定图层"); + const scaleY = fixedObject.scaleY + const scaleX = fixedObject.scaleX + const top = fixedObject.top - fixedObject.height * scaleY / 2; + const left = fixedObject.left - fixedObject.width * scaleX / 2; + const arr = traceImageContour(this.partCanvas); + let minX = fixedObject.width; + let minY = fixedObject.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, + fill: "rgba(127, 255, 127, 0.3)", + stroke: "#2AA81B", + strokeWidth: 2, + strokeDashArray: [8, 4], + strokeLineCap: "round",// 折线端点样式 + strokeLineJoin: "bevel", // 折线连接样式 + strokeUniform: true, // 保持描边宽度不随缩放改变 + }); + // this.partGroup.add(path); + this.canvas.add(path); + this.canvas.renderAll(); + } + /** 清空点位 */ + clearPart() { + this.pointList = []; + this.resetPartObject(true); } /** @@ -391,6 +438,8 @@ export class PartManager { dispose() { this.cleanupEvents(); this.clearPartObject(); + this.clearPointData(); + this.canvas = null; this.commandManager = null; this.layerManager = null; diff --git a/src/component/Canvas/CanvasEditor/managers/ToolManager.js b/src/component/Canvas/CanvasEditor/managers/ToolManager.js index 4046d25d..3b34ec84 100644 --- a/src/component/Canvas/CanvasEditor/managers/ToolManager.js +++ b/src/component/Canvas/CanvasEditor/managers/ToolManager.js @@ -429,6 +429,9 @@ export class ToolManager { if (this.canvasManager && this.canvasManager.selectionManager) { this.canvasManager.selectionManager.setCurrentTool(toolId); } + if (this.canvasManager && this.canvasManager.partManager) { + this.canvasManager.partManager.setCurrentTool(toolId); + } // 通知观察者 this.notifyObservers(toolId); diff --git a/src/component/Canvas/CanvasEditor/utils/helper.js b/src/component/Canvas/CanvasEditor/utils/helper.js index f87bcd48..63e2af8d 100644 --- a/src/component/Canvas/CanvasEditor/utils/helper.js +++ b/src/component/Canvas/CanvasEditor/utils/helper.js @@ -995,13 +995,13 @@ export function getTransformScaleAngle(Transform) { } /** - * 图片转换为canvas + * base64转换为canvas * @param {String} base64 - 图片base64编码 * @param {Number} scale - 缩放比例 * @param {Boolean} sr - 缩放反转,默认false * @returns {Promise} canvas元素 */ -export async function base64ToCanvas(base64, scale = 1, sr = false) { +export function base64ToCanvas(base64, scale = 1, sr = false) { return new Promise((resolve, reject) => { const image = new Image(); image.src = base64; @@ -1014,7 +1014,7 @@ export async function base64ToCanvas(base64, scale = 1, sr = false) { const height = sr ? image.height / scale : image.height * scale; canvas.width = width; canvas.height = height; - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d', { willReadFrequently: true }); ctx.clearRect(0, 0, width, height); ctx.drawImage(image, 0, 0, width, height); resolve(canvas); @@ -1022,6 +1022,25 @@ export async function base64ToCanvas(base64, scale = 1, sr = false) { image.onerror = reject; }); } +/** + * image转换为canvas + * @param {HTMLImageElement} image - 图片元素 + * @param {Number} scale - 缩放比例 + * @param {Boolean} sr - 缩放反转,默认false + * @returns {Promise} canvas元素 +*/ +export async function imageToCanvas(image, scale = 1, sr = false) { + await image.decode(); + const canvas = document.createElement('canvas'); + const width = (sr ? image.width / scale : image.width * scale); + const height = sr ? image.height / scale : image.height * scale; + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + ctx.clearRect(0, 0, width, height); + ctx.drawImage(image, 0, 0, width, height); + return resolve(canvas); +} /** * 图片边界跟踪算法(透明底) @@ -1029,7 +1048,7 @@ export async function base64ToCanvas(base64, scale = 1, sr = false) { * @returns {Array} 边界点数组 [{x, y}, ...] */ export function traceImageContour(canvas) { - const ctx = canvas.getContext("2d"); + 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; @@ -1124,7 +1143,7 @@ export function imageAddGapToCanvas(image, gapX, gapY) { const tcanvas = document.createElement('canvas'); tcanvas.width = image.width + gapX; tcanvas.height = image.height + gapY; - const ctx = tcanvas.getContext('2d'); + const ctx = tcanvas.getContext('2d', { willReadFrequently: true }); ctx.clearRect(0, 0, tcanvas.width, tcanvas.height); ctx.drawImage(image, 0, 0); return tcanvas; diff --git a/src/component/Canvas/test.vue b/src/component/Canvas/test.vue index 1433898e..bf297908 100644 --- a/src/component/Canvas/test.vue +++ b/src/component/Canvas/test.vue @@ -1,11 +1,9 @@