Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/ObjectManager.ts
2026-03-17 11:35:04 +08:00

211 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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'
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() { }
/** 设置混合模式 */
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()
}
/** 设置平铺状态 */
setFillRepeat(id: string) {
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,
globalCompositeOperation: object.globalCompositeOperation,
originX: "left",
originY: "top",
info,
});
rect.set("fill", pattern)
this.canvasManager.canvas.remove(object)
this.canvasManager.add(rect)
}
/** 获取填充对象 */
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()
if (isRecord) {
this.stateManager.recordState()
this.layerManager.updateLayerThumbnailsById(id)
}
}
/** 修改平铺间隙
* @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()
if (isRecord) {
this.stateManager.recordState()
this.layerManager.updateLayerThumbnailsById(id)
}
}
dispose() { }
}