This commit is contained in:
李志鹏
2026-01-29 17:16:31 +08:00
parent 33aaf0b600
commit 59422e54d8
10 changed files with 163 additions and 84 deletions

View File

@@ -7,12 +7,11 @@ import {
insertObjectAtZIndex,
removeCanvasObjectByObject,
createPatternTransform,
getTransformScaleAngle,
imageAddGapToCanvas,
} from "../utils/helper";
import { restoreFabricObject } from "../utils/objectHelper";
const scale = 0.3;// 默认缩放比例
export const FillSourceToBase64 = (source) => {
if (source?.toDataURL) {
return source.toDataURL?.();
@@ -39,7 +38,6 @@ export class FillRepeatCommand extends Command {
this.fillRepeat = options.fillRepeat;
this.oldObjects = null;
this.oldLocked = null;
this.oldIsDisableUnlock = null;
}
async execute() {
@@ -64,17 +62,15 @@ export class FillRepeatCommand extends Command {
);
});
image.set({
id: object.id,
layerId: object.layerId,
layerName: object.layerName,
...this.copyObjectProperties(object),
...(fill_.originalInfo || {
top: object.top,
left: object.left,
})
});
layer.fabricObjects = [image.toObject(["id", "layerId", "layerName"])];
this.oldLocked = layer.locked;
layer.locked = false;
// this.oldLocked = layer.locked;
// layer.locked = false;
this.canvas.add(image);
this.canvas.remove(object);
@@ -113,23 +109,25 @@ export class FillRepeatCommand extends Command {
const fdObject = this.canvasManager.getFixedLayerObject();
const bgObject = this.canvasManager.getBackgroundLayerObject();
const tObject = fdObject || bgObject;
// const offsetX = object.fill?.hasOwnProperty("offsetX") ? object.fill.offsetX : tObject.width / 2;
// const offsetY = object.fill?.hasOwnProperty("offsetY") ? object.fill.offsetY : tObject.height / 2;
const patternTransform = object.fill?.hasOwnProperty("patternTransform") ? object.fill.patternTransform : createPatternTransform(0.3, 0);
const scale = getTransformScaleAngle(patternTransform).scale;
const offsetX = tObject.width / 2 - img.width * scale / 2;
const offsetY = tObject.height / 2 - img.height * scale / 2;
const pattern = new fabric.Pattern({
source: img,
repeat: this.fillRepeat,
patternTransform: object.fill?.hasOwnProperty("patternTransform") ? object.fill.patternTransform : createPatternTransform(scale, 0),
offsetX: object.fill?.hasOwnProperty("offsetX") ? object.fill.offsetX : tObject.width / 2, // 水平偏移
offsetY: object.fill?.hasOwnProperty("offsetY") ? object.fill.offsetY : tObject.height / 2, // 垂直偏移
patternTransform,
offsetX, // 水平偏移
offsetY, // 垂直偏移
});
const rect = new fabric.Rect({
id: object.id,
layerId: object.layerId,
layerName: object.layerName,
...this.copyObjectProperties(object),
fill_,
});
layer.fabricObjects = [rect.toObject(["id", "layerId", "layerName"])];
this.oldLocked = layer.locked;
// this.oldIsDisableUnlock = layer.isDisableUnlock;
// layer.isDisableUnlock = true;
// this.oldLocked = layer.locked;
if (this.oldObjects.type === "rect") {
rect.set({
width: object.width,
@@ -155,7 +153,7 @@ export class FillRepeatCommand extends Command {
scaleX,
scaleY,
});
layer.locked = true;
// layer.locked = true;
}
rect.set("fill", pattern);
this.canvas.add(rect);
@@ -184,14 +182,23 @@ export class FillRepeatCommand extends Command {
this.canvas.remove(object);
this.canvas.add(this.oldObjects);
layer.fabricObjects = [this.oldObjects.toObject(["id", "layerId", "layerName"])];
layer.locked = this.oldLocked;
// layer.isDisableUnlock = this.oldIsDisableUnlock;
// layer.locked = this.oldLocked;
await this.layerManager?.updateLayersObjectsInteractivity();
await this.layerManager?.sortLayersWithTool?.();
this.canvas.renderAll();
this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layerId);
return true;
}
// 复制原对象的属性
copyObjectProperties(object) {
return{
id: object.id,
layerId: object.layerId,
layerName: object.layerName,
isPrintTrims: object.isPrintTrims,
}
}
}
@@ -230,6 +237,10 @@ export class FillRepeatChangeCommand extends Command {
...this.newPattern,
});
object.set("fill", pattern);
if (object.globalCompositeOperation_) {
object.globalCompositeOperation = object.globalCompositeOperation_;
object.globalCompositeOperation_ = null;
}
this.canvas.renderAll();
return true;
}
@@ -276,7 +287,7 @@ export class FillRepeatGapChangeCommand extends Command {
this.oldGapY = null;
}
async execute(isUndo = false) {
async execute(isCommand = true, isUndo = false) {
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
console.warn("图层不存在或没有 fabric 对象");
@@ -315,6 +326,10 @@ export class FillRepeatGapChangeCommand extends Command {
const fill = object.get("fill");
fill.source = imageAddGapToCanvas(image, object.fill_.gapX, object.fill_.gapY);
object.set("fill", new fabric.Pattern(fill));
if (isCommand && object.globalCompositeOperation_) {
object.globalCompositeOperation = object.globalCompositeOperation_;
object.globalCompositeOperation_ = null;
}
this.canvas.renderAll();
return true;
}
@@ -324,7 +339,7 @@ export class FillRepeatGapChangeCommand extends Command {
console.warn("没有旧间隙可恢复");
return false;
}
await this.execute(true);
await this.execute(true, true);
return true;
}

View File

@@ -25,7 +25,7 @@ export class TransformCommand extends Command {
this.layerManager = options.layerManager;
this.layers = options.layers || null;
this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID
this.isCommand = options.isCommand == undefined ? true : options.isCommand
const targetObject =
findObjectById(this.canvas, this.objectId)?.object || null;
@@ -189,6 +189,11 @@ export class TransformCommand extends Command {
object.set(key, value);
});
if(this.isCommand && object.globalCompositeOperation_){
object.globalCompositeOperation = object.globalCompositeOperation_;
object.globalCompositeOperation_ = null;
}
// 确保对象更新
object.setCoords();
}

View File

@@ -132,8 +132,8 @@
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;
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);
@@ -143,8 +143,8 @@
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;
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 });
};
</script>

View File

@@ -315,6 +315,7 @@
layerManager: props.layerManager,
layers: layers,
lastSelectLayerId: lastSelectLayerId,
isCommand,
});
if (isCommand) {
props.commandManager.execute(cmd);
@@ -336,6 +337,7 @@
const finalState = computeAngleState(angle, obj, initialState);
transformObject(obj, initialState, finalState, false);
if (!obj.hasOwnProperty("oldState")) obj.oldState = initialState;
props.canvasManager.beforeChangeCanvas([obj]);
};
const changeAngle = (angle, obj) => {
var initialState;
@@ -428,6 +430,7 @@
});
obj.set("fill", pattern);
props.canvas.renderAll();
props.canvasManager.beforeChangeCanvas([obj]);
};
const changeFillAngle = (angle, obj) => {
const fill = obj.get("fill");
@@ -447,6 +450,7 @@
});
obj.set("fill", pattern);
props.canvas.renderAll();
props.canvasManager.beforeChangeCanvas([obj]);
};
const changeFillOffset = (value, obj) => {
const pattern = new fabric.Pattern({
@@ -466,6 +470,7 @@
});
obj.set("fill", pattern);
props.canvas.renderAll();
props.canvasManager.beforeChangeCanvas([obj]);
};
const changeFillScale = (scale, obj) => {
const fill = obj.get("fill");
@@ -498,7 +503,8 @@
newGapY: gapY,
record: true,
});
cmd.execute();
cmd.execute(false);
props.canvasManager.beforeChangeCanvas([obj]);
};
const changeFillGap = (gapX, gapY, obj) => {
if (obj.oldFill_) {

View File

@@ -896,7 +896,7 @@ const changeCanvas = async (command) => {
...command, // 传递完整的命令数据
};
emit("changeCanvas", commandData);
canvasManager.changeCanvas(commandData);
canvasManager.changeCanvas();
if ((command.canUndo || command.canRedo) && props.enabledRedGreenMode) {
setTimeout(async () => {
try {

View File

@@ -74,6 +74,7 @@ export class CanvasManager {
this.props = options.props || {};
this.emit = options.emit || (() => {});
this.awaitCanvasRun = null;
this.canvasChangeing = false;
// 初始化画布
this.initializeCanvas();
}
@@ -338,6 +339,7 @@ export class CanvasManager {
setupCanvasEvents(activeElementId, layerManager) {
// 创建画布事件管理器
this.eventManager = new CanvasEventManager(this.canvas, {
canvasManager: this,
toolManager: this.toolManager,
animationManager: this.animationManager,
thumbnailManager: this.thumbnailManager,
@@ -1220,8 +1222,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;
@@ -1670,7 +1672,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer')
let flipX = false;
let flipY = false;
let blendMode = BlendMode.MULTIPLY;
if(item.type === "trims") blendMode = BlendMode.NORMAL;// 元素正常
// if(item.type === "trims") blendMode = BlendMode.NORMAL;// 元素正常
if(item.object){
opacity = item.object.opacity
flipX = item.object.flipX
@@ -1730,8 +1732,8 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer')
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 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
@@ -1743,7 +1745,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer')
let fillSource = image
let flipX = false;
let flipY = false;
let blendMode = BlendMode.MULTIPLY;
let blendMode = BlendMode.NORMAL;
let fill_repeat = "repeat"
if(item.object){
top += item.object.top * flScaleY
@@ -1754,7 +1756,7 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer')
angle = item.object.angle
flipX = item.object.flipX
flipY = item.object.flipY
blendMode = item.object.blendMode || BlendMode.MULTIPLY;
if(item.object.blendMode) blendMode = item.object.blendMode;
gapX = item.object.gapX
gapY = item.object.gapY
fillSource = imageAddGapToCanvas(image, gapX, gapY);
@@ -1797,10 +1799,10 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer')
name: name,
type: LayerType.BITMAP,
visible: true,
locked: true,
locked: false,
opacity: opacity,
isPrintTrims: true,
blendMode: BlendMode.MULTIPLY,
blendMode: blendMode,
fabricObjects: [rect.toObject(["id", "layerId", "layerName"])],
metadata: {sourceData: item},
})
@@ -1841,12 +1843,13 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer')
/**
* 画布事件变更后
*/
async changeCanvas(){
async changeCanvas(fids = [], isBeforeChange = false){
if(!isBeforeChange) this.canvasChangeing = false;
const fixedLayerObj = this.getFixedLayerObject();
if(!fixedLayerObj) return console.warn("固定图层对象不存在", fixedLayerObj)
const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
if(colorObject){
const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP);
const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP).filter(id => !fids.includes(id));
if(ids.length === 0){
ids.unshift(SpecialLayerId.SPECIAL_GROUP);
await this.setObjecCliptInfo(colorObject);
@@ -1866,6 +1869,24 @@ backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer')
this.canvas.renderAll();
}
}
/** 画布变更之前 */
async beforeChangeCanvas(objects){
if(this.canvasChangeing) return;
const ids = objects.filter(v => {
return v.isPrintTrims && v.globalCompositeOperation && v.globalCompositeOperation !== BlendMode.NORMAL
}).map(v => v.layerId);
if(ids.length > 0){
this.canvasChangeing = true;
this.canvas.getObjects().forEach(v => {
if(ids.includes(v.layerId)){
v.globalCompositeOperation_ = v.globalCompositeOperation;
v.globalCompositeOperation = BlendMode.NORMAL;
}
})
this.canvas.renderAll();
await this.changeCanvas(ids, true);
}
}
/**
* 缩放红绿图模式内容以适应当前画布大小

View File

@@ -6,6 +6,7 @@ import { OperationType, OperationTypes } from "../../utils/layerHelper";
export class CanvasEventManager {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.canvasManager = options.canvasManager;
this.toolManager = options.toolManager || null;
this.animationManager = options.animationManager;
this.thumbnailManager = options.thumbnailManager;
@@ -691,7 +692,9 @@ export class CanvasEventManager {
// 清除临时状态记录
delete activeObj._initialTransformState;
}
}
}else{
this.canvasManager.changeCanvas();
}
if (this.thumbnailManager && e.target) {
if (e.target.id) {
@@ -975,6 +978,13 @@ export class CanvasEventManager {
// 添加调试日志(可选)
// console.log(`捕获对象 ${obj.id} (${obj.type}) 的初始变换状态`);
}
const arrs = [];
if (e.target._objects) {
e.target._objects.forEach((v) => arrs.push(v));
} else {
arrs.push(e.target);
}
this.canvasManager.beforeChangeCanvas(arrs);
}
/**

View File

@@ -203,9 +203,9 @@
let scaleY = ((cheight / image.height) * item.scale[1]) / 5;
let scale = cwidth > cheight ? scaleX : scaleY;
let offsetX =
(item.location[0] * cwidth) / props.width + (image.width * scale) / 2;
(item.location[0] * cwidth) / props.width - (image.width * scale) / 2;
let offsetY =
(item.location[1] * cheight) / props.height +
(item.location[1] * cheight) / props.height -
(image.height * scale) / 2;
let angle = item.angle;
let gapX = item.object.gapX;

View File

@@ -342,16 +342,16 @@
level2Type: "Pattern",
designType: "Library",
path: "/src/assets/images/canvas/yinhua1.jpg",
location: [250, 780],
scale: [1.2, 1.6],
location: [800, 600],
scale: [1, 1],
angle: 0,
object: {
top: 600,
left: 800,
top: 300,
left: 400,
scaleX: 0.5,
scaleY: 0.5,
opacity: 1,
angle: 45,
angle: 0,
flipX: false,
flipY: false,
blendMode: "multiply",
@@ -411,6 +411,7 @@
:clothingMinIOPath="clothingMinIOPath"
:clothingImageUrl="clothingImageUrl"
:clothingImageUrl2="clothingImageUrlInit"
@canvasLoadJsonSuccess="canvasLoadJsonSuccess"
:config="editorConfig"
:clothing-image-opts="{
imageMode: 'contains', // 设置底图包含在画布内