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, isRecord = true) { 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, isRecord) } /** 获取填充对象 */ 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) } } /** 修改属性 * @param id 目标对象ID * @param options 参数 * @param isRecord 是否记录 */ async updateProperty(id: string, options: any, isRecord: boolean) { const object = this.canvasManager.getObjectById(id) if (!object) return null object.set(options); this.canvasManager.renderAll() if (isRecord) { this.stateManager.recordState() this.layerManager.updateLayerThumbnailsById(id) } } dispose() { } }