2026-01-02 11:24:11 +08:00
|
|
|
|
import { Command } from "./Command";
|
|
|
|
|
|
import { findLayerRecursively } from "../utils/layerHelper";
|
|
|
|
|
|
import { fabric } from "fabric-with-all";
|
|
|
|
|
|
import {
|
|
|
|
|
|
findObjectById,
|
|
|
|
|
|
generateId,
|
|
|
|
|
|
insertObjectAtZIndex,
|
|
|
|
|
|
removeCanvasObjectByObject,
|
|
|
|
|
|
createPatternTransform,
|
|
|
|
|
|
} from "../utils/helper";
|
|
|
|
|
|
import { restoreFabricObject } from "../utils/objectHelper";
|
|
|
|
|
|
|
|
|
|
|
|
const scale = 0.3;// 默认缩放比例
|
|
|
|
|
|
|
|
|
|
|
|
export const FillSourceToBase64 = (source) => {
|
|
|
|
|
|
if (source?.toDataURL) {
|
|
|
|
|
|
return source.toDataURL?.();
|
|
|
|
|
|
} else if (source?.src) {
|
|
|
|
|
|
return source.src;
|
|
|
|
|
|
}
|
|
|
|
|
|
return source;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 填充图案平铺命令
|
|
|
|
|
|
* 填充重复属性:repeat | repeat-x | repeat-y | no-repeat
|
|
|
|
|
|
* 默认缩放比例:0.3
|
|
|
|
|
|
* 默认偏移量:50%
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class FillRepeatCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({ name: "填充图案平铺", saveState: true });
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.canvasManager = options.canvasManager;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.fillRepeat = options.fillRepeat;
|
|
|
|
|
|
this.oldObjects = null;
|
|
|
|
|
|
this.oldLocked = null;
|
|
|
|
|
|
this.oldIsDisableUnlock = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
|
|
|
|
|
console.warn("图层不存在或没有 fabric 对象");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
|
|
|
|
|
if (!object || (object.type !== "rect" && object.type !== "image")) {
|
|
|
|
|
|
console.warn("当前对象不能平铺", object.type);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-01-05 11:47:36 +08:00
|
|
|
|
console.log("===========", object.toObject(["id", "layerId", "layerName"]))
|
2026-01-02 11:24:11 +08:00
|
|
|
|
this.oldObjects = object;
|
|
|
|
|
|
const img = await new Promise((resolve, reject) => {
|
|
|
|
|
|
if (object.type === "rect") {
|
|
|
|
|
|
let source = object.fill.source;
|
|
|
|
|
|
resolve(source);
|
|
|
|
|
|
} else if (object.type === "image") {
|
|
|
|
|
|
// resolve(object.getElement());
|
|
|
|
|
|
// fabric.Image.fromURL(
|
|
|
|
|
|
// object.src,
|
|
|
|
|
|
// v => resolve(v),
|
|
|
|
|
|
// { crossOrigin: "anonymous" }
|
|
|
|
|
|
// );
|
|
|
|
|
|
const imgElement = object.getElement();
|
|
|
|
|
|
// 创建透明 Canvas
|
|
|
|
|
|
const tcanvas = document.createElement('canvas');
|
|
|
|
|
|
tcanvas.width = imgElement.width;
|
|
|
|
|
|
tcanvas.height = imgElement.height;
|
|
|
|
|
|
const ctx = tcanvas.getContext('2d');
|
|
|
|
|
|
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
|
|
|
|
|
|
ctx.drawImage(imgElement, 0, 0);
|
|
|
|
|
|
resolve(tcanvas);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
const fill_ = {
|
|
|
|
|
|
source: FillSourceToBase64(img),
|
|
|
|
|
|
gapX: 0,
|
|
|
|
|
|
gapY: 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
const bgObject = this.canvasManager.getBackgroundLayerObject();
|
|
|
|
|
|
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 : bgObject.width / 2, // 水平偏移
|
|
|
|
|
|
offsetY: object.fill?.hasOwnProperty("offsetY") ? object.fill.offsetY : bgObject.height / 2, // 垂直偏移
|
|
|
|
|
|
});
|
|
|
|
|
|
const rect = new fabric.Rect({
|
|
|
|
|
|
id: object.id,
|
|
|
|
|
|
layerId: object.layerId,
|
|
|
|
|
|
layerName: object.layerName,
|
|
|
|
|
|
fill_,
|
|
|
|
|
|
});
|
|
|
|
|
|
layer.fabricObjects = [rect.toObject(["id", "layerId", "layerName"])];
|
|
|
|
|
|
this.oldLocked = layer.locked;
|
|
|
|
|
|
// this.oldIsDisableUnlock = layer.isDisableUnlock;
|
|
|
|
|
|
// layer.isDisableUnlock = true;
|
|
|
|
|
|
if (this.oldObjects.type === "rect") {
|
|
|
|
|
|
rect.set({
|
|
|
|
|
|
width: object.width,
|
|
|
|
|
|
height: object.height,
|
|
|
|
|
|
top: object.top,
|
|
|
|
|
|
left: object.left,
|
|
|
|
|
|
originX: object.originX,
|
|
|
|
|
|
originY: object.originY,
|
|
|
|
|
|
angle: object.angle,
|
|
|
|
|
|
scaleX: object.scaleX,
|
|
|
|
|
|
scaleY: object.scaleY,
|
|
|
|
|
|
flipX: object.flipX,
|
|
|
|
|
|
flipY: object.flipY,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
rect.set({
|
|
|
|
|
|
width: bgObject.width,
|
|
|
|
|
|
height: bgObject.height,
|
|
|
|
|
|
top: bgObject.top,
|
|
|
|
|
|
left: bgObject.left,
|
|
|
|
|
|
originX: bgObject.originX,
|
|
|
|
|
|
originY: bgObject.originY,
|
|
|
|
|
|
});
|
|
|
|
|
|
layer.locked = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
rect.set("fill", pattern);
|
|
|
|
|
|
this.canvas.add(rect);
|
|
|
|
|
|
this.canvas.remove(object);
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
|
|
|
|
|
await this.layerManager?.sortLayersWithTool?.();
|
|
|
|
|
|
await this.canvasManager.thumbnailManager?.generateLayerThumbnail(
|
|
|
|
|
|
this.layerId
|
|
|
|
|
|
);
|
|
|
|
|
|
await this.layerManager.selectLayerObjects(this.layerId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (!this.oldObjects) {
|
|
|
|
|
|
console.warn("没有旧对象可恢复");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.oldObjects.layerId);
|
|
|
|
|
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
|
|
|
|
|
console.warn("图层不存在或没有 fabric 对象");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
|
|
|
|
|
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;
|
|
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity();
|
|
|
|
|
|
await this.layerManager?.sortLayersWithTool?.();
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layerId);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 填充图案更改参数
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class FillRepeatChangeCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({ name: "填充图案更改参数", saveState: true });
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.canvasManager = options.canvasManager;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.newPattern = options.newPattern;
|
|
|
|
|
|
this.oldPattern = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute() {
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
|
|
|
|
|
console.warn("图层不存在或没有 fabric 对象");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
|
|
|
|
|
if (!object || object.type !== "rect") {
|
|
|
|
|
|
console.warn("当前对象不是矩形", object);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.oldPattern = object.oldPattern || object.get("fill");
|
|
|
|
|
|
delete object.oldPattern;
|
|
|
|
|
|
const oldPattern = { ...this.oldPattern };
|
|
|
|
|
|
delete oldPattern.id;
|
|
|
|
|
|
const pattern = new fabric.Pattern({
|
|
|
|
|
|
...oldPattern,
|
|
|
|
|
|
...this.newPattern,
|
|
|
|
|
|
});
|
|
|
|
|
|
object.set("fill", pattern);
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (!this.oldPattern) {
|
|
|
|
|
|
console.warn("没有旧图案可恢复");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
|
|
|
|
|
console.warn("图层不存在或没有 fabric 对象");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
|
|
|
|
|
if (!object || object.type !== "rect") {
|
|
|
|
|
|
console.warn("当前对象不是矩形", object);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const pattern = new fabric.Pattern({
|
|
|
|
|
|
...this.oldPattern
|
|
|
|
|
|
});
|
|
|
|
|
|
object.set("fill", pattern);
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 填充图案更改间隙
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class FillRepeatGapChangeCommand extends Command {
|
|
|
|
|
|
constructor(options) {
|
|
|
|
|
|
super({ name: "填充图案更改间隙", saveState: true });
|
|
|
|
|
|
this.canvas = options.canvas;
|
|
|
|
|
|
this.layers = options.layers;
|
|
|
|
|
|
this.canvasManager = options.canvasManager;
|
|
|
|
|
|
this.layerManager = options.layerManager;
|
|
|
|
|
|
this.layerId = options.layerId;
|
|
|
|
|
|
this.newGapX = options.newGapX;
|
|
|
|
|
|
this.newGapY = options.newGapY;
|
|
|
|
|
|
this.record = !!options.record;
|
|
|
|
|
|
this.oldGapX = null;
|
|
|
|
|
|
this.oldGapY = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute(isUndo = false) {
|
|
|
|
|
|
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
|
|
|
|
|
if (!layer || !layer.fabricObjects || layer.fabricObjects.length === 0) {
|
|
|
|
|
|
console.warn("图层不存在或没有 fabric 对象");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { object } = findObjectById(this.canvas, layer?.fabricObjects?.[0]?.id);
|
|
|
|
|
|
if (!object || object.type !== "rect") {
|
|
|
|
|
|
console.warn("当前对象不是矩形", object);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!object.fill_) {
|
|
|
|
|
|
object.fill_ = {
|
|
|
|
|
|
source: FillSourceToBase64(object.fill.source),
|
|
|
|
|
|
gapX: 0,
|
|
|
|
|
|
gapY: 0,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isUndo) {
|
|
|
|
|
|
object.fill_.gapX = this.oldGapX;
|
|
|
|
|
|
object.fill_.gapY = this.oldGapY;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (!object.oldFill_ && this.record) {
|
|
|
|
|
|
object.oldFill_ = { ...object.fill_ };
|
|
|
|
|
|
}
|
|
|
|
|
|
this.oldGapX = object.fill_.gapX;
|
|
|
|
|
|
this.oldGapY = object.fill_.gapY;
|
|
|
|
|
|
object.fill_.gapX = this.newGapX;
|
|
|
|
|
|
object.fill_.gapY = this.newGapY;
|
|
|
|
|
|
}
|
|
|
|
|
|
const image = new Image();
|
|
|
|
|
|
image.src = object.fill_.source;
|
|
|
|
|
|
await image.decode();
|
|
|
|
|
|
// 创建透明 Canvas
|
|
|
|
|
|
const tcanvas = document.createElement('canvas');
|
|
|
|
|
|
tcanvas.width = image.width + object.fill_.gapX;
|
|
|
|
|
|
tcanvas.height = image.height + object.fill_.gapY;
|
|
|
|
|
|
const ctx = tcanvas.getContext('2d');
|
|
|
|
|
|
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
|
|
|
|
|
|
ctx.drawImage(image, 0, 0);
|
|
|
|
|
|
|
|
|
|
|
|
const fill = object.get("fill");
|
|
|
|
|
|
fill.source = tcanvas;
|
|
|
|
|
|
object.set("fill", new fabric.Pattern(fill));
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async undo() {
|
|
|
|
|
|
if (this.oldGapX === null || this.oldGapY === null) {
|
|
|
|
|
|
console.warn("没有旧间隙可恢复");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
await this.execute(true);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|