Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts

243 lines
7.4 KiB
TypeScript
Raw Normal View History

2026-03-16 16:51:12 +08:00
import { ref } from 'vue'
import { fabric } from 'fabric-with-all'
import { createId } from '../../tools/tools'
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
import { OperationType } from '../tools/layerHelper'
import { getArrowPath, getLinePath, cloneObjects, getStarArr } from '../tools/canvasMethod'
2026-03-16 16:51:12 +08:00
export const FillSourceToBase64 = (source) => {
if (source?.toDataURL) {
return source.toDataURL?.();
} else if (source?.src) {
return source.src;
}
return source;
}
/**
* gap转换
* @param {HTMLCanvasElement} image - img元素
* @param {Number} gapX - gap
* @param {Number} gapY - gap
* @returns {HTMLCanvasElement} canvas元素
*/
export function imageAddGapToCanvas(image, gapX, gapY) {
// 创建透明 Canvas
const tcanvas = document.createElement('canvas');
tcanvas.width = image.width + gapX;
tcanvas.height = image.height + gapY;
const ctx = tcanvas.getContext('2d', { willReadFrequently: true });
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(image, 0, 0);
return tcanvas;
}
/**
* +
* @param {number} scale -
* @param {number} angle -
* @returns {Array} [a, b, c, d, e, f]
*/
export function createPatternTransform(scale, angle) {
const angle_ = angle * Math.PI / 180;
const cos = Math.cos(angle_);
const sin = Math.sin(angle_);
// 先缩放,后旋转
return [
scale * cos, // a
scale * sin, // b
-scale * sin, // c
scale * cos, // d
0, // e (水平位移)
0 // f (垂直位移)
];
}
/**
*
* @param {Array} Transform -
* @returns {Object} {scale, angle}
*/
export function getTransformScaleAngle(Transform) {
const a = Transform[0];
const b = Transform[1];
const c = Transform[2];
const d = Transform[3];
const scale = Math.sqrt(a * a + b * b);
const angle = Math.round(Math.atan2(b, a) * 180 / Math.PI);
return { scale, angle };
}
export class ObjectManager {
stateManager: any
canvasManager: any
layerManager: any
constructor(options) {
this.stateManager = options.stateManager;
this.canvasManager = options.canvasManager;
this.layerManager = options.layerManager
}
onMounted() { }
2026-03-17 11:35:04 +08:00
/** 设置混合模式 */
setBlendMode(id: string, blendMode: string) {
const object = this.canvasManager.getObjectById(id)
if (!object) return console.warn('设置混合模式失败对象不存在ID:', id)
object.set("globalCompositeOperation", blendMode)
this.canvasManager.renderAll()
}
2026-03-16 16:51:12 +08:00
/** 设置平铺状态 */
2026-03-24 11:49:53 +08:00
setFillRepeat(id: string, isRecord = true) {
2026-03-16 16:51:12 +08:00
const object = this.canvasManager.getObjectById(id)
if (!object) return console.warn('设置平铺状态失败对象不存在ID:', id)
if (object.type !== 'image') return console.warn('设置平铺状态失败,对象不是图片类型:', id)
// 创建透明 Canvas
const imgEl = object.getElement();
const tcanvas = document.createElement('canvas');
tcanvas.width = imgEl.width;
tcanvas.height = imgEl.height;
const ctx = tcanvas.getContext('2d');
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(imgEl, 0, 0);
const fill = {
source: FillSourceToBase64(imgEl),
gapX: 0,
gapY: 0,
}
const pattern = new fabric.Pattern({
source: tcanvas,
repeat: 'repeat',
patternTransform: createPatternTransform(1, 0),
offsetX: 0, // 水平偏移
offsetY: 0, // 垂直偏移
});
const info = {
...object.info,
fill,
}
const rect = new fabric.Rect({
width: object.width,
height: object.height,
top: object.top,
left: object.left,
angle: object.angle,
scaleX: object.scaleX,
scaleY: object.scaleY,
flipX: object.flipX,
flipY: object.flipY,
2026-03-17 11:35:04 +08:00
globalCompositeOperation: object.globalCompositeOperation,
2026-03-16 16:51:12 +08:00
originX: "left",
originY: "top",
info,
});
rect.set("fill", pattern)
this.canvasManager.canvas.remove(object)
2026-03-24 11:49:53 +08:00
this.canvasManager.add(rect, isRecord)
2026-03-16 16:51:12 +08:00
}
/** 获取填充对象 */
getFillRepeatObject(id: string) {
const object = this.canvasManager.getObjectById(id)
if (!object) {
console.warn('getFillRepeatObject 对象不存在ID:', id)
return null
}
if (object.type !== 'rect') {
console.warn('getFillRepeatObject 对象不是矩形类型:', id)
return null
}
if (object.fill?.repeat !== 'repeat') {
console.warn('getFillRepeatObject 对象不是平铺填充类型:', id)
return null
}
return object
}
/**
*
* @param id ID
* @param options
* @param options.scale
* @param options.angle
* @param options.offsetX
* @param options.offsetY
*/
async updateFillRepeatTransform(id: string, options: any, isRecord: boolean) {
const object = this.getFillRepeatObject(id)
if (!object) return null
const fill = object.get("fill");
const scale = options.hasOwnProperty('scale') ? options.scale : getTransformScaleAngle(fill.patternTransform).scale
const angle = options.hasOwnProperty('angle') ? options.angle : getTransformScaleAngle(fill.patternTransform).angle
const offsetX = options.hasOwnProperty('offsetX') ? options.offsetX : fill.offsetX
const offsetY = options.hasOwnProperty('offsetY') ? options.offsetY : fill.offsetY
fill.patternTransform = createPatternTransform(scale, angle)
fill.offsetX = offsetX
fill.offsetY = offsetY
object.set("fill", new fabric.Pattern(fill));
this.canvasManager.renderAll()
2026-03-16 16:54:06 +08:00
if (isRecord) {
this.stateManager.recordState()
this.layerManager.updateLayerThumbnailsById(id)
}
2026-03-16 16:51:12 +08:00
}
/**
* @param id ID
* @param options
* @param options.gapX
* @param options.gapY
*/
async updateFillRepeatGap(id: string, options: any, isRecord: boolean) {
const object = this.getFillRepeatObject(id)
if (!object) return null
const gapX = options.hasOwnProperty('gapX') ? options.gapX : object.info.fill.gapX
const gapY = options.hasOwnProperty('gapY') ? options.gapY : object.info.fill.gapY
const fill = object.get("fill");
const image = new Image();
image.crossOrigin = "anonymous";
image.src = object.info.fill.source;
await image.decode();
fill.source = imageAddGapToCanvas(image, gapX, gapY)
object.info.fill.gapX = gapX
object.info.fill.gapY = gapY
object.set("fill", new fabric.Pattern(fill));
this.canvasManager.renderAll()
2026-03-16 16:54:06 +08:00
if (isRecord) {
this.stateManager.recordState()
this.layerManager.updateLayerThumbnailsById(id)
}
2026-03-16 16:51:12 +08:00
}
2026-03-18 17:25:19 +08:00
/**
2026-03-17 17:17:48 +08:00
* @param id ID
2026-03-18 17:25:19 +08:00
* @param options
2026-03-17 17:17:48 +08:00
* @param isRecord
*/
2026-03-18 17:25:19 +08:00
async updateProperty(id: string, options: any, isRecord: boolean) {
const object = this.canvasManager.getObjectById(id)
2026-03-17 17:17:48 +08:00
if (!object) return null
const type = object.type
const isWidth = object.hasOwnProperty('width')
const isHeight = object.hasOwnProperty('height')
if (isWidth || isHeight) {
let width = options.width
let height = options.height
if (type === "polygon") {
if (object.points.length === 10) {// 五角星
options.points = getStarArr(width, height)
}
} else if (type === "ellipse") {// 椭圆
options.rx = width / 2
options.ry = height / 2
}
}
2026-03-18 17:25:19 +08:00
object.set(options);
2026-03-17 17:17:48 +08:00
this.canvasManager.renderAll()
if (isRecord) {
this.stateManager.recordState()
this.layerManager.updateLayerThumbnailsById(id)
}
}
2026-03-16 16:51:12 +08:00
dispose() { }
}