emit('inputFillOffset', e)"
- @change="(e) => emit('changeFillOffset', e)"
+ :left="offset.x"
+ :top="offset.y"
+ @input="inputFillOffset"
+ @change="changeFillOffset"
:show-dish="false"
/>
emit('inputFillOffset', e)"
- @change="(e) => emit('changeFillOffset', e)"
+ :left="offset.x"
+ :top="offset.y"
+ @input="inputFillOffset"
+ @change="changeFillOffset"
:show-input="false"
/>
@@ -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
>
diff --git a/src/component/Detail/detailRight/overallSetting/RepeatSetting.vue b/src/component/Detail/detailRight/overallSetting/RepeatSetting.vue
index 2f5418db..9de7bd8c 100644
--- a/src/component/Detail/detailRight/overallSetting/RepeatSetting.vue
+++ b/src/component/Detail/detailRight/overallSetting/RepeatSetting.vue
@@ -133,6 +133,7 @@
.repeat-setting {
user-select: none;
width: 228px;
+ overflow: hidden;
> .title {
line-height: 35px;
font-size: 14px;