diff --git a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue index 310b6a60..0a8efeb4 100644 --- a/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/BrushControlPanel.vue @@ -154,6 +154,8 @@ const isVisible = computed(() => { OperationType.ERASER, OperationType.RED_BRUSH, OperationType.GREEN_BRUSH, + OperationType.PART_BRUSH, + OperationType.PART_ERASER, ].includes(props.activeTool); }); diff --git a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/RepeatSetting.vue b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/RepeatSetting.vue index fd59a643..3e699e09 100644 --- a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/RepeatSetting.vue +++ b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/RepeatSetting.vue @@ -14,7 +14,7 @@ {{ t("Canvas.scale") }} {{ t("Canvas.offset") }}
@@ -79,29 +79,6 @@ import Slider from "../tools/Slider.vue"; import { useI18n } from "vue-i18n"; const { t } = useI18n(); - - const props = defineProps({ - object: { - required: true, - type: Object, - }, - }); - const angle = computed( - () => getTransformScaleAngle(props.object.fill?.patternTransform).angle - ); - const scale = computed(() => { - const patternTransform = props.object.fill?.patternTransform; - const scaleValue = getTransformScaleAngle(patternTransform).scale * 100; - return Number(Number(scaleValue).toFixed(2)); - }); - const gapX = computed(() => props.object.fill_?.gapX || 0); - const gapY = computed(() => props.object.fill_?.gapY || 0); - const offsetX = computed( - () => (props.object.fill?.offsetX / props.object.width) * 100 - ); - const offsetY = computed( - () => (props.object.fill?.offsetY / props.object.height) * 100 - ); const emit = defineEmits([ "inputFillAngle", "changeFillAngle", @@ -112,13 +89,63 @@ "inputFillGap", "changeFillGap", ]); - const inputFillScale = (e) => { + + const props = defineProps({ + object: { + required: true, + type: Object, + }, + }); + const angle = computed( + () => getTransformScaleAngle(props.object.fill?.patternTransform).angle + ); + const gapX = computed(() => props.object.fill_?.gapX || 0); + const gapY = computed(() => props.object.fill_?.gapY || 0); + + // 缩放比例 + const scale = computed(() => { + const object = props.object; + const patternTransform = object.fill?.patternTransform; + const scaleValue = getTransformScaleAngle(patternTransform).scale; + const scaleX = scaleValue / (object.width / object.fill_.width / 5); + const scaleY = scaleValue / (object.height / object.fill_.height / 5); + const scaleXY = object.width > object.height ? scaleX : scaleY; + return Number(Number(scaleXY * 100).toFixed(2)); + }); + const inputFillScale = (e) => setFillScale(e, true); + const changeFillScale = (e) => setFillScale(e, false); + const setFillScale = (e, isInput) => { + const object = props.object; const scale = e / 100; - emit("inputFillScale", scale); + const scaleX = (object.width / object.fill_.width / 5) * scale; + const scaleY = (object.height / object.fill_.height / 5) * scale; + const scaleXY = object.width > object.height ? scaleX : scaleY; + emit(isInput ? "inputFillScale" : "changeFillScale", scaleXY); }; - const changeFillScale = (e) => { - const scale = e / 100; - emit("changeFillScale", scale); + + // 偏移量 + const offset = computed(() => { + const object = props.object; + const patternTransform = object.fill?.patternTransform; + const scale = getTransformScaleAngle(patternTransform).scale; + const offsetX = object.fill?.offsetX; + const offsetY = object.fill?.offsetY; + const twidth = object.fill_?.width; + const theight = object.fill_?.height; + const x = ((offsetX - (twidth * scale) / 2) * 100) / object.width; + const y = ((offsetY - (theight * scale) / 2) * 100) / object.height; + return { x, y }; + }); + const inputFillOffset = (e) => setFillOffset(e, true); + const changeFillOffset = (e) => setFillOffset(e, false); + const setFillOffset = (e, isInput) => { + const { left, top } = e; + const object = props.object; + const patternTransform = object.fill?.patternTransform; + const scale = getTransformScaleAngle(patternTransform).scale; + const x = (left / 100) * object.width + (object.fill_?.width * scale) / 2; + const y = (top / 100) * object.height + (object.fill_?.height * scale) / 2; + emit(isInput ? "inputFillOffset" : "changeFillOffset", { x, y }); }; @@ -126,6 +153,7 @@ .repeat-setting { user-select: none; width: 228px; + overflow: hidden; > .title { line-height: 35px; font-size: 14px; diff --git a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue index fea8cf41..638f5237 100644 --- a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue +++ b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue @@ -439,16 +439,16 @@ if (!obj.oldPattern) obj.oldPattern = obj.get("fill"); const pattern = new fabric.Pattern({ ...obj.get("fill"), - offsetX: (value.left / 100) * obj.width, - offsetY: (value.top / 100) * obj.height, + offsetX: value.x, + offsetY: value.y, }); obj.set("fill", pattern); props.canvas.renderAll(); }; const changeFillOffset = (value, obj) => { const pattern = new fabric.Pattern({ - offsetX: (value.left / 100) * obj.width, - offsetY: (value.top / 100) * obj.height, + offsetX: value.x, + offsetY: value.y, }); changeFill(obj, pattern); }; diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index 6aa63a6d..fdc87410 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -70,6 +70,7 @@ export class CanvasManager { this.isFixedErasable = options.isFixedErasable || false; // 是否允许擦除固定图层 this.eraserStateManager = null; // 橡皮擦状态管理器引用 this.handleCanvasInit = null; // 画布初始化回调函数 + this.partManager = options.partManager || null; this.props = options.props || {}; this.emit = options.emit || (() => {}); // 初始化画布 @@ -174,7 +175,12 @@ export class CanvasManager { _initCanvasEvents() { // 添加笔刷图像转换处理回调 this.canvas.onBrushImageConverted = async (fabricImage) => { - await this.addImageToLayer({ fabricImage, targetLayerId: null }); + const activeTool = this.toolManager?.activeTool?.value; + if(activeTool === OperationType.PART_BRUSH){ + this.partManager?.addPartImage(fabricImage); + }else{ + await this.addImageToLayer({ fabricImage, targetLayerId: null }); + } // 返回false表示使用默认行为(直接添加到画布) return false; }; @@ -1208,8 +1214,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') let scaleY = scale * 5 * v.fill_.height / flHeight; let scaleXY = flWidth > flHeight ? scaleX : scaleY; - let left = fill.offsetX + v.fill_.width * scale / 2; - let top = fill.offsetY + v.fill_.height * scale / 2; + let left = fill.offsetX - v.fill_.width * scale / 2; + let top = fill.offsetY - v.fill_.height * scale / 2; obj.scale = [scaleXY, scaleXY]; obj.angle = angle; @@ -1707,15 +1713,15 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') resolve(tcanvas); }, { crossOrigin: "anonymous" }); }) - let scaleX_ = fixedLayerObj.width / image.width * (item.scale?.[0] || 1) / 5; - let scaleY_ = fixedLayerObj.height / image.height * (item.scale?.[1] || 1) / 5; - let scale = fixedLayerObj.width > fixedLayerObj.height ? scaleX_ : scaleY_; - let offsetX = (item.location?.[0] || 0) - image.width * scale / 2 - let offsetY = (item.location?.[1] || 0) - image.height * scale / 2 - let top = fixedLayerObj.top - fixedLayerObj.height * fixedLayerObj.scaleY / 2 - let left = fixedLayerObj.left - fixedLayerObj.width * fixedLayerObj.scaleX / 2 - let scaleX = fixedLayerObj.scaleX - let scaleY = fixedLayerObj.scaleY + let scaleX_ = flWidth / image.width * (item.scale?.[0] || 1) / 5; + let scaleY_ = flHeight / image.height * (item.scale?.[1] || 1) / 5; + let scale = flWidth > flHeight ? scaleX_ : scaleY_; + let offsetX = (item.location?.[0] || 0) + image.width * scale / 2 + let offsetY = (item.location?.[1] || 0) + image.height * scale / 2 + let top = flTop - flHeight * flScaleY / 2 + let left = flLeft - flWidth * flScaleX / 2 + let scaleX = flScaleX + let scaleY = flScaleY let opacity = 1 let angle = 0 let gapX = 0 @@ -1725,8 +1731,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') let flipY = false; let blendMode = BlendMode.MULTIPLY; if(item.object){ - top += item.object.top * fixedLayerObj.scaleY - left += item.object.left * fixedLayerObj.scaleX + top += item.object.top * flScaleY + left += item.object.left * flScaleX scaleX *= item.object.scaleX scaleY *= item.object.scaleY opacity = item.object.opacity @@ -1742,8 +1748,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') id: id, layerId: id, layerName: name, - width: fixedLayerObj.width, - height: fixedLayerObj.height, + width: flWidth, + height: flHeight, top: top, left: left, scaleX: scaleX, diff --git a/src/component/Canvas/CanvasEditor/managers/PartManager.js b/src/component/Canvas/CanvasEditor/managers/PartManager.js index b9a1c304..1c6262b3 100644 --- a/src/component/Canvas/CanvasEditor/managers/PartManager.js +++ b/src/component/Canvas/CanvasEditor/managers/PartManager.js @@ -53,11 +53,15 @@ export class PartManager { // 当前工具 this.activeTool = this.toolManager.activeTool; + this.rgba = { r: 0, g: 255, b: 0, a: 200 }; this.partGroup = null; // 当前选区对象 this.partId = "part_selector"; this.partCanvas = null;// 选区画布 - // 点选工具相关 + // 点位列表 this.pointList = []; // 存储点选坐标 + + // 绘制列表 + this.drawList = []; // 存储绘制对象 } /** @@ -69,6 +73,10 @@ export class PartManager { const wasActive = this.isActive; this.isActive = this.tools.includes(toolId); + if (toolId === OperationType.PART_ERASER) { + this.setEraserTool(); + } + // 如果从非选区工具切换到选区工具,初始化事件 if (!wasActive && this.isActive) { this.initEvents(); @@ -79,11 +87,12 @@ export class PartManager { this.cleanupEvents(); this.clearPartObject(); this.clearPointData(); - } else { - this.clearPointData(); - this.resetPartObject(); } - console.log("切换工具", toolId); + // 如果从选区工具切换到选区工具,重置选区 + else if (wasActive && this.isActive) { + // this.clearPointData(); + // this.resetPartObject(); + } } /** 初始化选区相关事件 */ @@ -246,8 +255,7 @@ export class PartManager { const image1 = await this.loadImageToObject(url); this.resetPartObject(); const group = this.partGroup; - const rgba = { r: 0, g: 255, b: 0, a: 200 } - const canvas = getObjectAlphaToCanvas(image1, null, 0, rgba); + const canvas = getObjectAlphaToCanvas(image1, null, 0, this.rgba); this.partCanvas = canvas; const image2 = new fabric.Image(canvas); image2.set({ @@ -300,8 +308,7 @@ export class PartManager { const image1 = await this.loadImageToObject(url); this.resetPartObject(); const group = this.partGroup; - const rgba = { r: 0, g: 255, b: 0, a: 200 } - const canvas = getObjectAlphaToCanvas(image1, null, 0, rgba); + const canvas = getObjectAlphaToCanvas(image1, null, 0, this.rgba); this.partCanvas = canvas; const image2 = new fabric.Image(canvas); image2.set({ @@ -311,45 +318,6 @@ export class PartManager { group.add(image2); this.canvas.renderAll(); } - - - /** 绘制工具模式下点击事件处理 */ - _brushDownHandler(options) { - } - /** 绘制工具模式下移动事件处理 */ - _brushMoveHandler(options) { - - } - /** 绘制工具模式下抬起事件处理 */ - _brushUpHandler(options) { - } - - - /** 擦除工具模式下抬起事件处理 */ - _eraseUpHandler(options) { - } - /** 擦除工具模式下点击事件处理 */ - _eraseDownHandler(options) { - } - /** 擦除工具模式下移动事件处理 */ - _eraseMoveHandler(options) { - - } - - /** 处理鼠标点位 */ - handleMousePosition(options, fixedObject) { - const pos = options.absolutePointer; - const { x, y } = options.absolutePointer; - const width = fixedObject.width * fixedObject.scaleX; - const height = fixedObject.height * fixedObject.scaleY; - const X = (x - (fixedObject.left - width / 2)) / fixedObject.scaleX; - const Y = (y - (fixedObject.top - height / 2)) / fixedObject.scaleY; - return { - x: Math.round(X), - y: Math.round(Y), - } - } - /** 获取分隔后图片 */ async getSegAnythingImage(obj) { setTimeout(() => { @@ -378,6 +346,85 @@ export class PartManager { }); }); } + /** 处理鼠标点位 */ + handleMousePosition(options, fixedObject) { + const pos = options.absolutePointer; + const { x, y } = options.absolutePointer; + const width = fixedObject.width * fixedObject.scaleX; + const height = fixedObject.height * fixedObject.scaleY; + const X = (x - (fixedObject.left - width / 2)) / fixedObject.scaleX; + const Y = (y - (fixedObject.top - height / 2)) / fixedObject.scaleY; + return { + x: Math.round(X), + y: Math.round(Y), + } + } + + async addPartImage(fabricImage) { + const scaleX = fabricImage.scaleX / this.partGroup.scaleX; + const scaleY = fabricImage.scaleY / this.partGroup.scaleY; + const top = (fabricImage.top - this.partGroup.top) / this.partGroup.scaleY; + const left = (fabricImage.left - this.partGroup.left) / this.partGroup.scaleX; + fabricImage.set({ + scaleX, + scaleY, + top: top + this.partGroup.height / 2, + left: left + this.partGroup.width / 2, + }) + this.drawList.push(fabricImage); + const tcanvas = new fabric.StaticCanvas(document.createElement("canvas"), { + width: this.partGroup.width, + height: this.partGroup.height, + enableRetinaScaling: false, + }); + this.drawList.forEach(item => tcanvas.add(item)) + tcanvas.renderAll(); + const canvas = getObjectAlphaToCanvas(tcanvas, null, 0, this.rgba); + this.partCanvas = canvas; + const image = new fabric.Image(canvas); + image.set({ + originX: this.partGroup.originX, + originY: this.partGroup.originY, + erasable: true, + }); + this.resetPartObject(); + this.partGroup.add(image); + this.canvas.renderAll(); + } + + + /** 绘制工具模式下点击事件处理 */ + _brushDownHandler(options) { + } + /** 绘制工具模式下移动事件处理 */ + _brushMoveHandler(options) { + + } + /** 绘制工具模式下抬起事件处理 */ + _brushUpHandler(options) { + } + + + /** 切换到擦除工具 */ + setEraserTool() { + if (!this.canvas) return console.warn("未找到画布"); + const objects = this.canvas.getObjects(); + objects.forEach(obj => { + obj.set({ + erasable: true + }) + }) + } + /** 擦除工具模式下抬起事件处理 */ + _eraseUpHandler(options) { + } + /** 擦除工具模式下点击事件处理 */ + _eraseDownHandler(options) { + } + /** 擦除工具模式下移动事件处理 */ + _eraseMoveHandler(options) { + + } /** 删除指定ID的对象 */ removeObjectsById(id) { @@ -415,6 +462,7 @@ export class PartManager { originY: fixedObject.originY, selectable: false, evented: false, + erasable: false, }) this.canvas.add(group); this.partGroup = group; diff --git a/src/component/Canvas/CanvasEditor/managers/ToolManager.js b/src/component/Canvas/CanvasEditor/managers/ToolManager.js index 69a6dd95..ee5d5759 100644 --- a/src/component/Canvas/CanvasEditor/managers/ToolManager.js +++ b/src/component/Canvas/CanvasEditor/managers/ToolManager.js @@ -736,15 +736,40 @@ export class ToolManager { if (!isExecute && this.canvasManager && this.canvasManager.partManager) { this.canvasManager.partManager.setCurrentTool(OperationType.PART_BRUSH); } + const greenColor = "#0f0"; + // 确保有笔刷管理器 + if (this.brushManager) { + // 设置绿色笔刷 + this.brushManager.setBrushColor(greenColor); // 纯绿色 + this.brushManager.setBrushOpacity(200/255); // 完全不透明 + this.brushManager.setBrushType("pencil"); // 铅笔类型 + + // 更新笔刷大小(使用当前大小) + if (BrushStore && BrushStore.state.size) { + this.brushManager.setBrushSize(BrushStore.state.size); + } + + // 更新应用到画布 + this.brushManager.updateBrush(); + } + + // 启用笔刷指示器并设置绿色 + this._enableBrushIndicator(greenColor); } /** * 设置部件选取工具--橡皮擦 */ setupPartEraserTool(isExecute = false) { if (!this.canvas) return; - this.canvas.isDrawingMode = false; + this.canvas.isDrawingMode = true; this.canvas.selection = false; - if (!isExecute && this.canvasManager && this.canvasManager.partManager) { + if (this.brushManager) { + this.brushManager.createEraser(); + } + // 启用笔刷指示器 + this._enableBrushIndicator(); + + if (!isExecute && this.canvasManager && this.canvasManager.partManager) { this.canvasManager.partManager.setCurrentTool(OperationType.PART_ERASER); } } @@ -1602,7 +1627,9 @@ export class ToolManager { OperationType.ERASER, OperationType.RED_BRUSH, OperationType.GREEN_BRUSH, - OperationType.LIQUIFY, + OperationType.LIQUIFY, + OperationType.PART_BRUSH, + OperationType.PART_ERASER, ]; return brushTools.includes(currentTool); diff --git a/src/component/Canvas/CanvasEditor/utils/objectHelper.js b/src/component/Canvas/CanvasEditor/utils/objectHelper.js index 8cf3b7ed..d52d02fc 100644 --- a/src/component/Canvas/CanvasEditor/utils/objectHelper.js +++ b/src/component/Canvas/CanvasEditor/utils/objectHelper.js @@ -69,6 +69,10 @@ export async function restoreFabricObject(serializedObject, canvas) { */ export function getObjectAlphaToCanvas(object, revData, diff = 30, rgba = { r: 255, g: 255, b: 255, a: 255 }) { const image = object.getElement(); + if (image.nodeName !== "IMG" && image.nodeName !== "CANVAS") { + console.warn("对象不是图片"); + return null; + } const { width, height } = image; if (!width || !height) { console.warn("对象没有元素"); diff --git a/src/component/Canvas/canvasExample.vue b/src/component/Canvas/canvasExample.vue index ce160580..ce7f0a2f 100644 --- a/src/component/Canvas/canvasExample.vue +++ b/src/component/Canvas/canvasExample.vue @@ -417,7 +417,6 @@ }" @change-canvas="changeCanvas" @canvas-init="canvasInit" - isFixedErasable showFixedLayer >