import { fabric } from "fabric-with-all"; import initAligningGuidelines, { initCenteringGuidelines, } from "../utils/helperLine"; import { ThumbnailManager } from "./ThumbnailManager"; import { ExportManager } from "./ExportManager"; import { findLayerRecursively, isGroupLayer, OperationType, OperationTypes, findLayer, createLayer, LayerType, SpecialLayerId, BlendMode, } from "../utils/layerHelper"; import { ObjectMoveCommand } from "../commands/ObjectCommands"; import { AnimationManager } from "./animation/AnimationManager"; import { createCanvas } from "../utils/canvasFactory"; import { CanvasEventManager } from "./events/CanvasEventManager"; import CanvasConfig from "../config/canvasConfig"; import { RedGreenModeManager } from "./RedGreenModeManager"; import { EraserStateManager } from "./EraserStateManager"; import { deepClone, findObjectById, generateId, optimizeCanvasRendering, palletToFill, fillToCssStyle, calculateRotatedTopLeftDeg, calculateCenterPoint, calculateTopLeftPoint, createPatternTransform, getTransformScaleAngle, base64ToCanvas, imageAddGapToCanvas, } from "../utils/helper"; import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands"; import { isFunction } from "lodash-es"; import { restoreObjectLayerAssociations, simplifyLayers, validateLayerAssociations, } from "../utils/layerUtils"; import { imageModeHandler } from "../utils/imageHelper"; import { getObjectAlphaToCanvas } from "../utils/objectHelper"; import { AddLayerCommand, RemoveLayerCommand, ToggleChildLayerVisibilityCommand } from "../commands/LayerCommands"; import { fa, id } from "element-plus/es/locales.mjs"; import i18n from "@/lang/index.ts"; const {t} = i18n.global; export class CanvasManager { constructor(canvasElement, options) { this.canvasElement = canvasElement; this.width = options.width || 1024; this.height = options.height || 768; this.backgroundColor = options.backgroundColor || "#ffffff"; this.toolManager = options.toolManager || null; // 工具管理器引用 this.currentZoom = options.currentZoom || { value: 100 }; this.maskLayer = null; // 添加蒙层引用 this.editorMode = CanvasConfig.defaultTool; // 默认编辑器模式 this.layers = options.layers || null; // 图层引用 this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID this.canvasWidth = options.canvasWidth || this.width; // 画布宽度 this.canvasHeight = options.canvasHeight || this.height; // 画布高度 this.canvasColor = options.canvasColor || "#ffffff"; // 画布背景颜色 this.enabledRedGreenMode = options.enabledRedGreenMode || false; // 是否启用红绿图模式 this.isFixedErasable = options.isFixedErasable || false; // 是否允许擦除固定图层 this.eraserStateManager = null; // 橡皮擦状态管理器引用 this.handleCanvasInit = null; // 画布初始化回调函数 this.partManager = options.partManager || null; this.props = options.props || {}; this.emit = options.emit || (() => {}); // 初始化画布 this.initializeCanvas(); } initializeCanvas() { console.log("fabric.version:", fabric.version); this.canvas = createCanvas(this.canvasElement, { width: this.width, height: this.height, preserveObjectStacking: true, enableRetinaScaling: true, stopContextMenu: true, fireRightClick: true, }); // 初始化动画管理器 this.animationManager = new AnimationManager(this.canvas, { currentZoom: this.currentZoom, wheelThrottleTime: 15, // 降低滚轮事件节流时间,提高响应性 defaultEase: "power2.lin", defaultDuration: 0.3, // 缩短默认动画时间 }); // 初始化缩略图管理器 this.thumbnailManager = new ThumbnailManager(this.canvas, { // 可以根据需求自定义选项 // layerThumbSize: { width: 32, height: 32 }, // elementThumbSize: { width: 32, height: 24 }, layers: this.layers, }); this.canvas.thumbnailManager = this.thumbnailManager; // 将缩略图管理器绑定到画布 // 设置画布辅助线 initAligningGuidelines(this.canvas); // 设置画布中心线 // initCenteringGuidelines(this.canvas); // 初始化画布事件监听器 this._initCanvasEvents(); } // 添加图像到指定图层 async addImageToLayer({ targetLayerId, fabricImage, ...options }) { // 如果图层管理器存在,将图像合并到当前活动图层 if (this.layerManager) { // 获取当前活动图层 let activeLayer = targetLayerId ? findLayerRecursively(this.layers.value, targetLayerId)?.layer : this.layerManager.getActiveLayer(); if (activeLayer) { // 确保新图像具有正确的图层信息 fabricImage.set({ layerId: activeLayer.id, layerName: activeLayer.name, id: fabricImage.id || generateId("brush_img_"), }); if (options.imageMode) { imageModeHandler({ imageMode: options.imageMode, newImage: fabricImage, canvasWidth: this.canvasWidth.value, canvasHeight: this.canvasHeight.value, }); // 默认居中 fabricImage.set({ originX: "center", originY: "center", left: this.canvas.width / 2, top: this.canvas.height / 2, }); } // 执行高保真合并操作 await this.eventManager?.mergeLayerObjectsForPerformance?.({ fabricImage, activeLayer, options, }); this.thumbnailManager?.generateLayerThumbnail(activeLayer.id); // 返回true表示不要自动添加到画布,因为我们已经通过图层管理器处理了 return true; } else { console.warn("没有活动图层,无法添加图像"); } } } /** * 初始化画布事件监听器 * 设置鼠标事件、键盘事件等 * @private */ _initCanvasEvents() { // 添加笔刷图像转换处理回调 this.canvas.onBrushImageConverted = async (fabricImage) => { const activeTool = this.toolManager?.activeTool?.value; if(activeTool === OperationType.PART_BRUSH){ this.partManager?.addPartImage(fabricImage); }else{ await this.addImageToLayer({ fabricImage, targetLayerId: null }); } // 返回false表示使用默认行为(直接添加到画布) return false; }; this.eraserStateManager = new EraserStateManager( this.canvas, this.layerManager ); // 监听擦除开始事件 this.canvas.on("erasing:start", () => { console.log("开始擦除"); this.eraserStateManager.startErasing(); }); // 监听擦除结束事件 this.canvas.on("erasing:end", async (e) => { console.log("擦除完成", e.targets); // 可以在这里保存状态到命令管理器 const affectedObjects = e.targets || []; const command = this.eraserStateManager.endErasing(affectedObjects); if (command && this.commandManager) { await this.commandManager?.executeCommand?.(command); } else { await command?.execute?.(); // 如果没有命令管理器,直接执行命令 } // 更新交互性 command && (await this.layerManager?.updateLayersObjectsInteractivity?.()); this.thumbnailManager?.generateLayerThumbnail( this.layerManager?.activeLayerId?.value ); // 固定图层 的擦除也需要重新生成缩略图 要判断 当前固定图层是否锁定 const fixedLayer = this.layers?.value?.find( (layer) => layer.isFixed && !layer.locked ); // 如果有固定图层且未锁定,则生成缩略图 fixedLayer && this.isFixedErasable && this.thumbnailManager?.generateLayerThumbnail(fixedLayer?.id); }); } /** * 设置编辑器模式 * @param {string} mode 'draw'、'select'或'pan' */ toolChanged(mode) { if (!OperationTypes.includes(mode)) { console.warn(`不支持的编辑器模式: ${mode}`); return; } this.editorMode = mode; // 如果已创建事件管理器,更新它的编辑器模式 if (this.eventManager) { this.eventManager.setEditorMode(mode); } } setToolManager(toolManager) { this.toolManager = toolManager || null; // 工具管理器引用 // 更新红绿图模式管理器的工具管理器引用 if (this.redGreenModeManager) { this.redGreenModeManager.toolManager = this.toolManager; } // 如果已创建事件管理器,更新它的工具管理器引用 if (this.eventManager) { this.eventManager.toolManager = this.toolManager; } } setLayerManager(layerManager) { this.layerManager = layerManager || null; // 图层管理器引用 // 初始化导出管理器(需要在图层管理器设置后初始化) if (this.layerManager) { this.exportManager = new ExportManager(this, this.layerManager); } // 更新红绿图模式管理器的图层管理器引用 if (this.redGreenModeManager) { this.redGreenModeManager.layerManager = this.layerManager; } if (this.eraserStateManager) { this.eraserStateManager.setLayerManager(this.layerManager); } } /** * 设置命令管理器 * @param {Object} commandManager 命令管理器实例 */ setCommandManager(commandManager) { this.commandManager = commandManager; // 更新红绿图模式管理器的命令管理器引用 if (this.redGreenModeManager) { this.redGreenModeManager.commandManager = this.commandManager; } } /** * 设置液化管理器 * @param {Object} liquifyManager 液化管理器实例 */ setLiquifyManager(liquifyManager) { this.liquifyManager = liquifyManager; } /** * 设置选区管理器 * @param {Object} selectionManager 选区管理器实例 */ setSelectionManager(selectionManager) { this.selectionManager = selectionManager; // 如果已创建事件管理器,更新它的选区管理器引用 if (this.eventManager) { this.eventManager.selectionManager = this.selectionManager; } } /** * 设置部件选择管理器 * @param {Object} partManager 部件选择管理器实例 */ setPartManager(partManager) { this.partManager = partManager; // 如果已创建事件管理器,更新它的部件选择管理器引用 if (this.eventManager) { this.eventManager.partManager = this.partManager; } } // 设置红绿图模式管理器 setRedGreenModeManager(redGreenModeManager) { this.redGreenModeManager = redGreenModeManager; } setupCanvasEvents(activeElementId, layerManager) { // 创建画布事件管理器 this.eventManager = new CanvasEventManager(this.canvas, { toolManager: this.toolManager, animationManager: this.animationManager, thumbnailManager: this.thumbnailManager, editorMode: this.editorMode, activeElementId: activeElementId, layerManager: layerManager, layers: this.layers, lastSelectLayerId: this.lastSelectLayerId, }); // 设置动画交互效果 this.animationManager.setupInteractionAnimations(); } setupCanvasInitEvent(handleCanvasInit) { this.handleCanvasInit = handleCanvasInit; } setupLongPress(callback) { if (this.eventManager) { this.eventManager.setupLongPress(callback); } } updateSelectedElements(opt, activeElementId) { if (this.eventManager) { this.eventManager.updateSelectedElements(opt); } else { const selected = opt.selected[0]; if (selected) { activeElementId.value = selected.id; } } } clearSelectedElements(activeElementId) { if (this.eventManager) { this.eventManager.clearSelectedElements(); } else if (activeElementId) { activeElementId.value = null; } } // 使用动画管理器的缩放方法 animateZoom(point, targetZoom, options = {}) { this.animationManager.animateZoom(point, targetZoom, options); } // 应用缩放(为兼容性保留) _applyZoom(point, zoom, skipUpdate = false) { this.animationManager._applyZoom(point, zoom, skipUpdate); } // 使用动画管理器的平移方法 animatePan(targetPosition, options = {}) { this.animationManager.animatePan(targetPosition, options); } // 应用平移(为兼容性保留) _applyPan(x, y) { this.animationManager._applyPan(x, y); } // 平移到指定元素 panToElement(elementId) { this.animationManager.panToElement(elementId); } // 重置缩放并居中内容 async resetZoom(animated = true) { // 先重置缩放 await this.animationManager.resetZoom(animated); // // 重置视图变换以确保元素位置正确 // this._resetViewportTransform(); // // 居中所有画布元素,包括背景层和其他元素 // this.centerAllObjects(); // 重新渲染画布使变更生效 this.canvas.renderAll(); } // 设置固定图层可擦除状态 setFixedLayerErasable({ type = "isFixed", flag = false }) { const layer = this.layers.value.find((layer) => layer[type]); if (layer) { // 设置固定图层的可擦除状态 layer.locked = flag; // 更新画布对象的erasable属性 const fabricObject = this.canvas .getObjects() .find((obj) => obj.id === layer.id); if (fabricObject) { fabricObject.erasable = flag; fabricObject.set("erasable", flag); fabricObject.setCoords(); // 更新控制点坐标 this.canvas.renderAll(); // 重新渲染画布 } return; } } async setCanvasSize(width, height) { this.width = width; this.height = height; this.canvas.setWidth(width); this.canvas.setHeight(height); // 重置视图变换以确保元素位置正确 // this._resetViewportTransform(); if (this.canvas.getZoom() !== 1 || this.canvas.viewportTransform[0] !== 1) { this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]); await this.resetZoom(); } // 居中所有画布元素,包括背景层和其他元素 await this.centerAllObjects(); // // 重新渲染画布使变更生效 // this.canvas.renderAll(); } // 重置画布大小参照固定图层 async resetCanvasSizeByFixedLayer(){ // 重置画布大小为固定图层的大小 const fixedLayerObj = this.getFixedLayerObject(); const backgroundObject = this.getBackgroundLayerObject(); if (!fixedLayerObj || !backgroundObject) return const fwidth = fixedLayerObj.width * fixedLayerObj.scaleX const fheight = fixedLayerObj.height * fixedLayerObj.scaleY const bwidth = backgroundObject.width * backgroundObject.scaleX const bheight = backgroundObject.height * backgroundObject.scaleY console.log(fixedLayerObj.width, fixedLayerObj.scaleX, fixedLayerObj.height, fixedLayerObj.scaleY, backgroundObject.width, backgroundObject.scaleX, backgroundObject.height, backgroundObject.scaleY,'CanvasManager resetCanvasSizeByFixedLayer') if(Math.abs(fwidth/bwidth - fheight/bheight) < 0.1) return; this.canvasWidth.value = fwidth this.canvasHeight.value = fheight backgroundObject.set({ width: this.canvasWidth.value, height: this.canvasHeight.value, }) this.canvas?.clipPath?.set?.({ width: this.canvasWidth.value, height: this.canvasHeight.value, }) } /** * 重置视图变换,使元素回到原始位置 * @private */ _resetViewportTransform(zoom) { // 保存当前缩放值 const currentZoom = zoom ?? this.canvas.getZoom(); // 重置视图变换,但保留缩放级别 this.canvas.setViewportTransform([currentZoom, 0, 0, currentZoom, 0, 0]); } /** * 居中所有画布元素 * 以背景层为参照,计算背景层的偏移量并应用到所有对象上 * 这样可以保持对象间的相对位置关系不变 */ async centerAllObjects() { if (!this.canvas) return; // 获取所有可见对象(不是背景元素的对象) const allObjects = this.canvas.getObjects(); if (allObjects.length === 0) return; const visibleObjects = allObjects.filter( (obj) => obj.visible !== false && !obj.excludeFromExport ); // 如果没有可见对象,直接返回 if (visibleObjects.length === 0) return; // 获取背景对象 const backgroundObject = visibleObjects.find((obj) => obj.isBackground); this.canvas?.clipPath?.set?.({ left: this.width / 2, top: this.height / 2, originX: "center", originY: "center", }); this.canvas?.clipPath?.setCoords?.(); // 如果只有背景层或没有背景层,使用原有逻辑 if (!backgroundObject) { console.warn("未找到背景层,使用默认居中逻辑"); // 如果只有一个对象且可能是背景,直接居中 if (visibleObjects.length === 1) { const obj = visibleObjects[0]; obj.set({ left: this.width / 2, top: this.height / 2, originX: "center", originY: "center", }); obj.setCoords(); this.canvas.renderAll(); } return; } // 记录背景层居中前的位置 const backgroundOldLeft = backgroundObject.left; const backgroundOldTop = backgroundObject.top; // 计算画布中心点 const canvasCenterX = this.width / 2; const canvasCenterY = this.height / 2; // 设置背景层居中 backgroundObject.set({ left: canvasCenterX, top: canvasCenterY, originX: "center", originY: "center", }); // 计算背景层的偏移量 const deltaX = backgroundObject.left - backgroundOldLeft; const deltaY = backgroundObject.top - backgroundOldTop; // 将相同的偏移量应用到所有其他对象上 const otherObjects = visibleObjects.filter( (obj) => obj !== backgroundObject ); otherObjects.forEach((obj) => { obj.set({ left: obj.left + deltaX, top: obj.top + deltaY, }); obj.setCoords(); // 更新对象的控制点坐标 }); let isMaskLayer = false; // 更新蒙层位置 this.layers.value.forEach((layer) => { if (layer.clippingMask) { isMaskLayer = true; // 如果图层有遮罩,更新遮罩位置 layer.clippingMask.left += deltaX; layer.clippingMask.top += deltaY; if (layer.selectObject) { // 如果有选区 则选区位置也要更新 layer.selectObject.left = layer.clippingMask.left; layer.selectObject.top = layer.clippingMask.top; const { object } = findObjectById( this.canvas, layer.selectObject?.id ); object?.set({ left: layer.clippingMask.left, top: layer.clippingMask.top, }); object?.setCoords(); } } }); if (isMaskLayer) { setTimeout(() => { this.layerManager?.updateLayersObjectsInteractivity?.(false, { isMoveing: true, }); }); } !this.canvas?.clipPath && this.centerBackgroundLayer(this.canvas.width, this.canvas.height); // 如果有背景层,更新蒙层位置 if (backgroundObject && CanvasConfig.isCropBackground) { this.updateMaskPosition(backgroundObject); } // 更新颜色层信息 // const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR); // if(colorObject){ // await this.setObjecCliptInfo(colorObject); // } const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP); if(groupLayer){ const groupRect = new fabric.Rect({}); await this.setObjecCliptInfo(groupRect); groupLayer.clippingMask = groupRect.toObject(); } // 重新渲染画布 this.canvas.renderAll(); } /** * 计算多个对象的总边界框 * @private * @param {Array} objects 要计算边界的对象数组 * @return {Object} 边界信息,包含left、top、width、height */ _calculateObjectsBounds(objects) { if (!objects || objects.length === 0) return null; let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; objects.forEach((obj) => { const bound = obj.getBoundingRect(); minX = Math.min(minX, bound.left); minY = Math.min(minY, bound.top); maxX = Math.max(maxX, bound.left + bound.width); maxY = Math.max(maxY, bound.top + bound.height); }); return { left: minX, top: minY, width: maxX - minX, height: maxY - minY, }; } setCanvasColor(color) { this.backgroundColor = color; // this.canvas.setBackgroundColor( // color, // this.canvas.renderAll.bind(this.canvas) // ); this.thumbnailManager?.generateLayerThumbnail?.( this.layers?.value.find((layer) => layer.isBackground)?.id ); } /** * 居中背景层 * @param {Object} backgroundLayerObject 背景层对象 * @param {Number} canvasWidth 画布宽度 * @param {Number} canvasHeight 画布高度 */ async centerBackgroundLayer(canvasWidth, canvasHeight) { const backgroundLayerObject = this.getBackgroundLayer(); if (!backgroundLayerObject) return false; // const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX; // const bgHeight = // backgroundLayerObject.height * backgroundLayerObject.scaleY; // 计算居中位置 const left = canvasWidth / 2; const top = canvasHeight / 2; backgroundLayerObject.set({ left: left, top: top, originX: "center", originY: "center", }); !CanvasConfig.isCropBackground && this.canvas.renderAll(); // 如果不需要裁剪背景层以外的内容,则渲染画布 // 如果需要裁剪背景层以外的内容,则更新蒙层位置 // 创建或更新蒙层 CanvasConfig.isCropBackground && !this.enabledRedGreenMode && this.createOrUpdateMask(backgroundLayerObject); return true; } /** * 创建或更新蒙层,用于裁剪不可见区域 * @param {Object} backgroundLayerObject 背景层对象 */ createOrUpdateMask(backgroundLayerObject) { if (!backgroundLayerObject) return; const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX; const bgHeight = backgroundLayerObject.height * backgroundLayerObject.scaleY; const left = backgroundLayerObject.left; const top = backgroundLayerObject.top; // 如果已经存在蒙层,则更新它 if (this.maskLayer) { this.canvas.remove(this.maskLayer); } this.canvas.getObjects().forEach((obj) => { if (obj.id === "canvasMaskLayer") { this.canvas.remove(obj); } }) // 创建蒙层 - 使用透明矩形作为裁剪区域 this.maskLayer = new fabric.Rect({ id: "canvasMaskLayer", width: bgWidth, height: bgHeight, left: left, top: top, fill: "transparent", stroke: "transparent", strokeWidth: 1, strokeDashArray: [5, 5], selectable: false, evented: false, hoverCursor: "default", originX: "center", originY: "center", }); // 将蒙层添加到画布 this.canvas.add(this.maskLayer); // 设置蒙层为最顶层 this.maskLayer.bringToFront(); this.canvas.clipPath = new fabric.Rect({ width: bgWidth, height: bgHeight, left: left, top: top, originX: backgroundLayerObject.originX || "left", originY: backgroundLayerObject.originY || "top", absolutePositioned: true, rx: 15, ry: 15, }); } getBackgroundLayer() { if (!this.canvas) return null; const backgroundLayer = this.canvas.getObjects().find((obj) => { return obj.isBackground; }); if (backgroundLayer) return backgroundLayer; // 如果没有找到背景层,则根据图层ID查找 const backgroundLayerId = this.layers.value.find((layer) => { return layer.isBackground; })?.id; const backgroundLayerByBgLayer = this.canvas.getObjects().find((obj) => { return obj.isBackground || obj.id === backgroundLayerId; }); if (!backgroundLayerByBgLayer) { console.warn( "CanvasManager.js = >getBackgroundLayer 方法没有找到背景层" ); } return backgroundLayerByBgLayer; } getFixedLayerObject() { if (!this.canvas) return null; const fixedLayer = this.canvas.getObjects().find((obj) => { return obj.isFixed; }); if (fixedLayer) return fixedLayer; // 如果没有找到固定层,则根据图层ID查找 const fixedLayerId = this.layers.value.find((layer) => { return layer.isFixed; })?.id; const fixedLayerByFixedLayer = this.canvas.getObjects().find((obj) => { return obj.isFixed || obj.id === fixedLayerId; }); if (!fixedLayerByFixedLayer) { console.warn( "CanvasManager.js = >getFixedLayerObject 方法没有找到固定层" ); } return fixedLayerByFixedLayer; } getBackgroundLayerObject() { if (!this.canvas) return null; const backgroundLayer = this.canvas.getObjects().find((obj) => { return obj.isBackground; }); if (backgroundLayer) return backgroundLayer; // 如果没有找到背景层,则根据图层ID查找 const backgroundLayerId = this.layers.value.find((layer) => { return layer.isBackground; })?.id; const backgroundLayerByBgLayer = this.canvas.getObjects().find((obj) => { return obj.isBackground || obj.id === backgroundLayerId; }); if (!backgroundLayerByBgLayer) { console.warn( "CanvasManager.js = >getBackgroundLayerObject 方法没有找到背景层" ); } return backgroundLayerByBgLayer; } getLayerObjectById(layerId) { if (!this.canvas) return null; const layerObject = this.canvas.getObjects().find((obj) => { return obj.id === layerId; }); if (layerObject) return layerObject; // 如果没有找到图层对象,则根据图层ID查找 const layerObjectByLayerId = this.canvas.getObjects().find((obj) => { return obj.id === layerId; }); if (!layerObjectByLayerId) { console.warn( "CanvasManager.js = >getLayerObjectById 方法没有找到图层对象" ); } return layerObjectByLayerId; } getObjectsByIds(ids){ const objects = this.canvas.getObjects().filter((obj) => { return ids.includes(obj.id); }); return objects; } /** * 更新蒙层位置 * @param {Object} backgroundLayerObject 背景层对象 */ updateMaskPosition(backgroundLayerObject) { if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath) return; const left = backgroundLayerObject.left; const top = backgroundLayerObject.top; this.maskLayer.set({ left: left, top: top, width: backgroundLayerObject.width * backgroundLayerObject.scaleX, height: backgroundLayerObject.height * backgroundLayerObject.scaleY, originX: backgroundLayerObject.originX || "left", originY: backgroundLayerObject.originY || "top", }); this.canvas.clipPath.set({ left: left, top: top, width: backgroundLayerObject.width * backgroundLayerObject.scaleX, height: backgroundLayerObject.height * backgroundLayerObject.scaleY, }); this.canvas.renderAll(); } /** * 更新指定图层的缩略图 * @param {String} layerId 图层ID */ updateLayerThumbnail(layerId) { this.thumbnailManager?.generateLayerThumbnail?.(layerId); } /** * 更新所有图层和元素的缩略图 */ updateAllThumbnails() { // 为所有元素生成缩略图 this.thumbnailManager?.generateAllLayerThumbnails?.(this.layers.value); } /** * * 更改固定图层的图片 * @param {String} imageUrl 新的图片URL * @param {Object} options 选项 * @param {String} options.targetLayerType 目标图层类型(background/fixed) * @param {Boolean} options.undoable 是否可撤销,默认不可撤销 * @return {Object} 执行结果 * */ async changeFixedImage(imageUrl, options = {}) { if (!this.layerManager) { console.error("图层管理器未设置,无法更改固定图层图片"); return; } const command = new ChangeFixedImageCommand({ canvas: this.canvas, layerManager: this.layerManager, imageUrl: imageUrl, targetLayerType: options.targetLayerType || "fixed", // background/fixed canvasWidth: this.canvasWidth, canvasHeight: this.canvasHeight, ...options, }); command.undoable = options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销 const result = (await command?.execute?.()) || { success: false, layerId: null, imageUrl: null, }; const findLayer = this.layers.value.find((fItem) => { if (options.targetLayerType === "fixed") { return fItem.isFixed; } if (options.targetLayerType === "background") { return fItem.isBackground; } return false; }); // 如果找到了图层,则生成缩略图 findLayer && this.thumbnailManager?.generateLayerThumbnail(findLayer.id); this.layerManager?.sortLayers?.(); return result; } /** * 导出图片 * @param {Object} options 导出选项 * @param {Boolean} options.isContainBg 是否包含背景图层 * @param {Boolean} options.isContainFixed 是否包含固定图层 * @param {Boolean} options.isContainFixedOther 是否包含其他固定图层 * @param {Boolean} options.isPrintTrimsNoRepeat 是否包含印花图层的不平铺 * @param {Boolean} options.isPrintTrimsRepeat 是否包含印花图层的平铺 * @param {Boolean} options.isContainNormalLayer 是否包含普通图层 * @param {String} options.layerId 导出具体图层ID * @param {Array} options.layerIdArray 导出多个图层ID数组 * @param {String} options.expPicType 导出图片类型 (png/jpg/svg) * @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 * @param {Boolean} options.isEnhanceImg 是否是增强图片 * @param {Boolean} options.isCropByBg 是否使用背景大小裁剪 * @returns {String} 导出的图片数据URL */ async exportImage(options = {}) { if (!this.exportManager) { console.error("导出管理器未初始化,请确保已设置图层管理器"); throw new Error("导出管理器未初始化"); } try { // 如果当前有选中对象,先清除选中状态 否则导出有问题 // this.canvas.discardActiveObject(); // 清除选中状态 // this.canvas.renderAll(); // 重新渲染画布 // 自动设置红绿图模式相关参数 const enhancedOptions = { isPrintTrimsNoRepeat: true, isPrintTrimsRepeat: true, isContainNormalLayer: true, ...options, // 如果没有明确指定,则根据当前模式自动设置 restoreOpacityInRedGreen: options.restoreOpacityInRedGreen !== undefined ? options.restoreOpacityInRedGreen : false, // 默认在红绿图模式下恢复透明度 // excludedLayers: [SpecialLayerId.SPECIAL_GROUP], // 导出时排除的图层ID数组 }; // 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层 if ( this.enabledRedGreenMode && !options.layerId && (!options.layerIdArray || options.layerIdArray.length === 0) ) { console.log("检测到红绿图模式,自动包含所有普通图层进行导出"); // 获取所有非背景、非固定的普通图层ID const normalLayerIds = this.layers?.value ?.filter( (layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther && layer.visible ) ?.map((layer) => layer.id) || []; if (normalLayerIds.length > 0) { enhancedOptions.layerIdArray = normalLayerIds; console.log("红绿图模式导出图层:", normalLayerIds); } } // 处理特殊图层的显示状态 const ptlids = []; if(!enhancedOptions.isPrintTrimsNoRepeat || !enhancedOptions.isPrintTrimsRepeat){ let layers = this.layers?.value?.find((layer) => layer.isPrintTrimsGroup)?.children || []; for(let layer of layers){ if(!layer.visible) continue; let repeat = layer.fabricObjects?.[0]?.fill?.repeat || "no-repeat"; if(typeof repeat !== "string") repeat = "no-repeat"; if(repeat === "no-repeat"){ if(enhancedOptions.isPrintTrimsNoRepeat) continue; }else{ if(enhancedOptions.isPrintTrimsRepeat) continue; } ptlids.push(layer.id); const command = new ToggleChildLayerVisibilityCommand({ canvas: this.canvas, layers: this.layers, layerId: layer.id, layerManager: this.layerManager, }); await command.execute(false); } await this.changeCanvas(); } const res = await this.exportManager.exportImage(enhancedOptions); // 恢复特殊图层的显示状态 if(ptlids.length > 0){ for(let id of ptlids){ const command = new ToggleChildLayerVisibilityCommand({ canvas: this.canvas, layers: this.layers, layerId: id, layerManager: this.layerManager, }); await command.execute(true); } await this.changeCanvas(); } return res; } catch (error) { console.warn("CanvasManager导出图片失败:", error); throw error; } } /** * 导出印花元素颜色信息 * @returns {Object} */ async exportExtraInfo() { // 导出颜色图层信息 const color = await this.exportColorLayer().catch(() => (null)); // 导出印花和元素图层信息 const printTrimsData = await this.exportPrintTrimsLayers().catch(() => ({prints: null, trims: null})); const obj = { color, ...printTrimsData, }; console.log("==========exportExtraInfo:", obj); return obj; } /** * 导出颜色图层 * @returns {Object} 导出的颜色图层数据URL */ async exportColorLayer() { if (!this.exportManager) { console.warn("导出管理器未初始化,请确保已设置图层管理器"); return Promise.reject("颜色图层不存在"); } const object = this.getLayerObjectById(SpecialLayerId.COLOR); if(!object){ console.warn("颜色图层不存在,请确保已添加颜色图层"); return Promise.reject("颜色图层不存在"); } const css = fillToCssStyle(object.fill) const canvas = new fabric.StaticCanvas(); canvas.setDimensions({ width: object.width, height: object.height, backgroundColor: null, imageSmoothingEnabled: true, }); const cloneObject = await new Promise((resolve, reject) => { object.clone(resolve); }); cloneObject.set({ left: canvas.width / 2, top: canvas.height / 2, scaleX: 1, scaleY: 1, visible: true, clipPath: null, }); canvas.add(cloneObject); canvas.renderAll(); const base64 = canvas.toDataURL({ format: "png", quality: 1, }); canvas.clear(); const color = object.originColor; return {css, base64, color}; } /** * 导出印花和元素图层 */ async exportPrintTrimsLayers() { const glayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP); if(!glayer) return Promise.reject("印花和元素图层组不存在"); const ids = glayer.children.map((v) => v.id); const objects = this.getObjectsByIds(ids); const fixedLayerObj = this.getFixedLayerObject(); if(!fixedLayerObj) return Promise.reject("固定图层不存在"); const flWidth = fixedLayerObj.width const flHeight = fixedLayerObj.height const flTop = fixedLayerObj.top const flLeft = fixedLayerObj.left const flScaleX = fixedLayerObj.scaleX const flScaleY = fixedLayerObj.scaleY const prints = []; const trims = []; objects.forEach((v) => { const sourceData = glayer.children.find((v_) => v_.id === v.id)?.metadata?.sourceData; if(!sourceData) return; const obj = { ifSingle: typeof v.fill === "string", level2Type: sourceData.level2Type, designType: sourceData.designType, path: sourceData.path, minIOPath: sourceData.minIOPath, location: [0, 0], scale: [0, 0], angle: v.angle, name: sourceData.name, priority: sourceData.priority, object:{ top: 0, left: 0, scaleX: 0,//对象的缩放比例 scaleY: 0,//对象的缩放比例 opacity: v.opacity, angle: v.angle, flipX: v.flipX, flipY: v.flipY, blendMode: v.globalCompositeOperation, gapX: 0,// 平铺模式下的间距 gapY: 0,// 平铺模式下的间距 } } let left = (v.left - (flLeft - flWidth * flScaleX / 2)); let top = (v.top - (flTop - flHeight * flScaleY / 2)); let width = (v.width * v.scaleX); let height = (v.height * v.scaleY); if(v.originX === "center" && v.originY === "center") { let {x:cx, y:cy} = calculateTopLeftPoint(width, height, left, top, v.angle); left = cx; top = cy; } let oX = left / flScaleX; let oY = top / flScaleY; let oScaleX = (v.width * v.scaleX) / (flWidth * flScaleX); let oScaleY = (v.height * v.scaleY) / (flHeight * flScaleY); obj.object.top = oY; obj.object.left = oX; obj.object.scaleX = oScaleX; obj.object.scaleY = oScaleY; if(obj.ifSingle){ // 单个的是从中心计算的 let {x:cx, y:cy} = calculateCenterPoint(width, height, left, top, v.angle); let oX = (cx-width/2) / flScaleX; let oY = (cy-height/2) / flScaleY; obj.location = [oX, oY]; obj.scale = [oScaleX, oScaleY]; }else{ let fill = v.fill; let fill_ = v.fill_; if(!fill || !fill_) return console.warn("印花元素不存在fill或fill_属性"); let {scale, angle} = getTransformScaleAngle(fill.patternTransform); let scaleX = scale * 5 * v.fill_.width / flWidth; 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; obj.scale = [scaleXY, scaleXY]; obj.angle = angle; obj.location = [left, top]; obj.object.gapX = fill_.gapX; obj.object.gapY = fill_.gapY; } if(sourceData.type === "print"){ prints.push(obj); }else if(sourceData.type === "trims"){ trims.push(obj); } }) // prints.sort((a, b) => a.ifSingle ? 1 : -1); prints.forEach((v, i) => v.priority = i + 1); trims.forEach((v, i) => v.priority = i + 1); return {prints, trims}; } dispose() { // 释放导出管理器资源 if (this.exportManager) { this.exportManager = null; } // 释放事件管理器资源 if (this.eventManager) { this.eventManager.dispose(); this.eventManager = null; } // 释放动画管理器资源 if (this.animationManager) { this.animationManager.dispose(); this.animationManager = null; } // 释放缩略图管理器资源 if (this.thumbnailManager) { this.thumbnailManager.dispose(); this.thumbnailManager = null; } if (this.canvas) { this.canvas.dispose(); } } getJSON() { try { // 清除画布中选中状态 // this.canvas.discardActiveObject(); this.canvas.renderAll(); // 排除颜色图层和特殊组图层 const excludedLayers = [SpecialLayerId.COLOR, SpecialLayerId.SPECIAL_GROUP]; this.layers.value.forEach((layer) => { if(excludedLayers.includes(layer.id)){ excludedLayers.push(...layer.children?.map((child) => child.id)); } }) const canvas = this.canvas.toJSON([ "id", "type", "layerId", "layerName", "isBackground", "isLocked", "isVisible", "isFixed", "parentId", "eraser", "eraserable", "erasable", "customType", "fill_", "scaleX", "scaleY", "top", "left", "width", "height", ]); canvas.objects = canvas.objects.filter((v) => !excludedLayers.includes(v.layerId)); const simplifyLayersData = simplifyLayers( JSON.parse(JSON.stringify(this.layers.value)), excludedLayers ); const data = { canvas, layers: simplifyLayersData, // 简化图层数据 version: "1.0", // 添加版本信息 timestamp: new Date().toISOString(), // 添加时间戳 canvasWidth: this.canvasWidth.value, canvasHeight: this.canvasHeight.value, canvasColor: this.canvasColor.value, activeLayerId: this.layerManager?.activeLayerId?.value, }; this.FixJsonIdLoss(data); console.log("获取画布JSON数据...", data); return JSON.stringify(data); } catch (error) { console.error("获取画布JSON失败:", error); throw new Error("获取画布JSON失败"); } } loadJSON(json, calllBack) { // 确保传入的json是字符串格式 if (typeof json === "object") { json = JSON.stringify(json); } else if (typeof json !== "string") { throw new Error("loadJSON方法需要传入字符串或对象格式的JSON数据"); } // 解析JSON字符串 try { const parsedJson = window.testCanvasJson || JSON.parse(json); console.log("加载画布JSON数据:", parsedJson); this.FixJsonIdLoss(parsedJson); this.canvasWidth.value = parsedJson.canvasWidth || this.width; this.canvasHeight.value = parsedJson.canvasHeight || this.height; this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor; // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { const tempLayers = parsedJson?.layers || []; const canvasData = parsedJson?.canvas; if (!tempLayers) { reject(new Error("JSON数据中缺少layers字段")); return; } if (!canvasData) { reject(new Error("JSON数据中缺少canvas字段")); return; } this.layers.value = tempLayers; // this.canvasWidth.value = parsedJson.canvasWidth || this.width; // this.canvasHeight.value = parsedJson.canvasHeight || this.height; // this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor; // console.log("是否检测到红绿图模式内容:", this.enabledRedGreenMode); // 重置视图变换以确保元素位置正确 this._resetViewportTransform(1); // let canvasClipPath = null; // // 克隆当前裁剪路径 // if (this.canvas?.clipPath) { // canvasClipPath = this.canvas?.clipPath; // } // 清除当前画布内容 // this.canvas.clear(); // 清除画布内容 可以先去掉 这样加载闪动的情况就比较少 如果有问题 可以再打开 // console.log("清除当前画布内容", canvasData); delete canvasData.clipPath; // 删除当前裁剪路径 // 加载画布数据 this.canvas.loadFromJSON(canvasData, async () => { await optimizeCanvasRendering(this.canvas, async () => { // 清空重做栈 this.commandManager?.clear?.(); this.backgroundColor = parsedJson.backgroundColor || "#ffffff"; // if (canvasClipPath) { // // canvasClipPath.set({ // // absolutePositioned: true, // // }); // this.canvas.clipPath = canvasClipPath; // // await new Promise((resolve) => { // // debugger; // // fabric.util.enlivenObjects([canvasClipPath], (clipPaths) => { // // if (clipPaths && clipPaths.length > 0) { // // resolve(clipPaths[0]); // // } else { // // resolve(null); // // } // // }); // // }); // // debugger; // } try { // 重置画布数据 await this.setCanvasSize(this.canvas.width, this.canvas.height); await this.centerBackgroundLayer(this.canvas.width, this.canvas.height); await this.resetCanvasSizeByFixedLayer(); // 重新构建对象关系 // restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects()); // 验证图层关联关系 - 稳定后可以注释 // const isValidate = validateLayerAssociations( // this.layers.value, // this.canvas.getObjects() // ); // console.log("图层关联验证结果:", isValidate); // 排序 // 使用LayerSort工具重新排列画布对象(如果可用) await this?.layerManager?.layerSort?.rearrangeObjectsAsync(); this.layerManager.activeLayerId.value = this.layers.value[0] .children?.length ? this.layers.value[0].children[0].id : this.layers.value[0]?.id || parsedJson?.activeLayerId || null; // // 如果检测到红绿图模式内容,进行缩放调整 // if (this.enabledRedGreenMode) { // this._rescaleRedGreenModeContent(); // } // 重载代码后支持回调中操作一些内容 // 确保所有对象的交互性正确设置 await this.layerManager?.updateLayersObjectsInteractivity?.(); await calllBack?.(); // 更新所有缩略图 setTimeout(() => { this.updateAllThumbnails(); }, 500); console.log("画布JSON数据加载完成"); // 画布初始化事件 this.handleCanvasInit?.(true); resolve(); } catch (error) { console.error("恢复图层数据失败:", error); reject(new Error("恢复图层数据失败: " + error.message)); } }); }); }); } catch (error) { console.error("解析JSON失败:", error); throw new Error("解析JSON失败,请检查输入格式: " + error.message); } } /** 修复JSON数据中的ID丢失问题 */ FixJsonIdLoss(json){ const layers = json?.layers || []; const objects = json?.canvas?.objects || []; layers.forEach((layer) => { if(!layer.fabricObjects?.[0]?.id && !layer.fabricObject?.id){ const obj = objects?.find((o) => o.layerId === layer.id); if(obj) { layer.fabricObjects = [{ id: obj.id, type: obj.type, }] } } }) // 排序 objects.sort((a, b) => { if (a.isBackground) return -1; if (b.isBackground) return 1; }) } /** * 创建其他图层:印花、颜色、元素... * @param {Object} otherData - 其他图层数据 */ async createOtherLayers(otherData, isUpdate = false) { if (!otherData) return console.warn("otherData 为空不需要添加"); const otherData_ = JSON.parse(JSON.stringify(otherData)); console.log("==========创建其他图层", otherData_); const updateColor = !!otherData_.color; const updateSpecialGroup = !!otherData_.printObject || !!otherData_.trims; // 删除颜色图层和特殊组图层 const ids = []; if(isUpdate){ updateColor && ids.push(SpecialLayerId.COLOR) updateSpecialGroup && ids.push(SpecialLayerId.SPECIAL_GROUP) }else{ ids.push(SpecialLayerId.COLOR) ids.push(SpecialLayerId.SPECIAL_GROUP) } this.layers.value = this.layers.value.filter((layer) => { if(ids.includes(layer.id)){ ids.push(...layer.children?.map((child) => child.id)); return false; } return true; }) this.canvas.getObjects().forEach((v) => ids.includes(v.id) && this.canvas.remove(v)) // 创建颜色图层 otherData_.color && await this.createColorLayer(otherData_.color); const printTrimsLayers = [];// 印花和元素图层 const singleLayers = [];// 平铺图层 otherData_.printObject?.prints?.forEach((print, index) => {// 印花 print.name = t("Canvas.Print") + (index + 1); print.type = "print"; if(print.ifSingle){ printTrimsLayers.unshift({...print}); }else{ singleLayers.unshift({...print}); } }) otherData_.trims?.prints?.forEach((trims, index) => {// 元素 trims.name = t("Canvas.Elements") + (index + 1); trims.type = "trims"; printTrimsLayers.unshift({...trims}); }) if(isUpdate ? updateSpecialGroup : true){ await this.createPrintTrimsLayers(printTrimsLayers, singleLayers); } await this.changeCanvas(); } // 设置画布对象的裁剪信息 async setObjecCliptInfo(tagObject, data){ const fixedLayerObj = this.getFixedLayerObject(); if(!fixedLayerObj) return console.warn("固定图层为空"); tagObject.set({ top: fixedLayerObj.top, left: fixedLayerObj.left, width: fixedLayerObj.width, height: fixedLayerObj.height, originX: fixedLayerObj.originX, originY: fixedLayerObj.originY, scaleX: fixedLayerObj.scaleX, scaleY: fixedLayerObj.scaleY, }); var object = fixedLayerObj; const imageUrl = this.props.clothingImageUrl2; if(imageUrl){ object = await new Promise((resolve, reject) => { fabric.Image.fromURL(imageUrl, (imgObject) => { tagObject.set({ width: imgObject.width, height: imgObject.height, }); resolve(imgObject); }, { crossOrigin: "anonymous" }); }); } const canvas = getObjectAlphaToCanvas(object, data); const transparentMask = new fabric.Image(canvas, { top: 0, left: 0, originX: fixedLayerObj.originX, originY: fixedLayerObj.originY, }); tagObject.set('clipPath', transparentMask); } async createColorLayer(color_){ const color = color_ || {r:0,g:0,b:0,a:0}; // if(findLayer(this.layers.value, SpecialLayerId.COLOR)) { // return console.warn("画布中已存在颜色图层"); // } console.log("==========添加颜色图层", color, this.layers.value.length) // 创建颜色图层对象 const colorRect = new fabric.Rect({ id: SpecialLayerId.COLOR, layerId: SpecialLayerId.COLOR, layerName: t("Canvas.color"), isVisible: true, isLocked: true, selectable: false, hasControls: false, hasBorders: false, globalCompositeOperation: BlendMode.MULTIPLY, originColor: color, }); // await this.setObjecCliptInfo(colorRect); const gradientObj = palletToFill(color); const gradient = new fabric.Gradient({ type: 'linear', gradientUnits: 'percentage', ...gradientObj, }) colorRect.set('fill', gradient); this.canvas.add(colorRect); // 创建颜色图层 const colorLayer = createLayer({ id: colorRect.layerId, name: colorRect.layerName, type: LayerType.SHAPE, visible: colorRect.isVisible, locked: colorRect.isLocked, opacity: 1.0, isFixedOther: true, blendMode: BlendMode.MULTIPLY, fabricObjects: [colorRect.toObject(["id", "layerId", "layerName"])], }) const groupIndex = this.layers.value.findIndex(layer => layer.isFixed || layer.isBackground); this.layers.value.splice(groupIndex, 0, colorLayer); } // 创建印花和元素图层 async createPrintTrimsLayers(printTrimsLayers, singleLayers){ // if(findLayer(this.layers.value, SpecialLayerId.SPECIAL_GROUP)) { // return console.warn("画布中已存在印花和元素组图层"); // } console.log("==========添加印花和元素图层组", printTrimsLayers, singleLayers) const fixedLayerObj = this.getFixedLayerObject(); const flWidth = fixedLayerObj.width const flHeight = fixedLayerObj.height const flTop = fixedLayerObj.top const flLeft = fixedLayerObj.left const flScaleX = fixedLayerObj.scaleX const flScaleY = fixedLayerObj.scaleY const children = []; // 添加印花和元素图层 for(let index = 0; index < printTrimsLayers.length; index++){ let item = printTrimsLayers[index]; let id = generateId("layer_image_"); let name = item.name; let image = await new Promise(resolve => { fabric.Image.fromURL(item.path, (fabricImage)=>{ resolve(fabricImage); }, { crossOrigin: "anonymous" }); }) let left = flLeft - flWidth * flScaleX / 2 + (item.location?.[0] || 0) * flScaleX let top = flTop - flHeight * flScaleY / 2 + (item.location?.[1] || 0) * flScaleY let scaleX = flWidth * (item.scale?.[0] || 1) / image.width * flScaleX let scaleY = flHeight * (item.scale?.[1] || 1) / image.height * flScaleY let {x, y} = calculateRotatedTopLeftDeg( image.width * scaleX, image.height * scaleY, left, top, 0, item.angle || 0 ) let angle = item.angle || 0 let opacity = 1 let flipX = false; let flipY = false; let blendMode = BlendMode.MULTIPLY; if(item.type === "trims") blendMode = BlendMode.NORMAL;// 元素正常 if(item.object){ opacity = item.object.opacity flipX = item.object.flipX flipY = item.object.flipY if(item.object.blendMode) blendMode = item.object.blendMode; } image.set({ left: x, top: y, scaleX: scaleX, scaleY: scaleY, angle: angle, opacity: opacity, flipX: flipX, flipY: flipY, globalCompositeOperation: blendMode, id: id, layerId: id, layerName: name, selectable: true, hasControls: true, hasBorders: true, isPrintTrims: true, }); this.canvas.add(image); let layer = createLayer({ id: id, name: name, type: LayerType.BITMAP, visible: true, locked: false, opacity: opacity, isPrintTrims: true, blendMode: blendMode, fabricObjects: [image.toObject(["id", "layerId", "layerName"])], metadata: {sourceData: item, level2Type: item.level2Type}, }) children.push(layer); }; // 添加平铺图层 for(let index = 0; index < singleLayers.length; index++){ let item = singleLayers[index]; let id = generateId("layer_image_"); let name = item.name; let image = await new Promise(resolve => { fabric.Image.fromURL(item.path, (fabricImage)=>{ const imgElement = fabricImage.getElement(); 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); }, { crossOrigin: "anonymous" }); }) 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 top = flTop - flHeight * flScaleY / 2 let left = flLeft - flWidth * flScaleX / 2 let scaleX = flScaleX let scaleY = flScaleY let opacity = 1 let angle = 0 let gapX = 0 let gapY = 0 let fillSource = image let flipX = false; let flipY = false; let blendMode = BlendMode.MULTIPLY; if(item.object){ top += item.object.top * flScaleY left += item.object.left * flScaleX scaleX *= item.object.scaleX scaleY *= item.object.scaleY opacity = item.object.opacity angle = item.object.angle flipX = item.object.flipX flipY = item.object.flipY blendMode = item.object.blendMode || BlendMode.MULTIPLY; gapX = item.object.gapX gapY = item.object.gapY fillSource = imageAddGapToCanvas(image, gapX, gapY); } let rect = new fabric.Rect({ id: id, layerId: id, layerName: name, width: flWidth, height: flHeight, top: top, left: left, scaleX: scaleX, scaleY: scaleY, opacity: opacity, angle: angle, flipX: flipX, flipY: flipY, globalCompositeOperation: blendMode, fill: new fabric.Pattern({ source: fillSource, repeat: "repeat", patternTransform: createPatternTransform(scale, item.angle || 0), offsetX: offsetX, // 水平偏移 offsetY: offsetY, // 垂直偏移 }), fill_ : { source: item.path, gapX: gapX, gapY: gapY, width: image.width, height: image.height, }, isPrintTrims: true, }); this.canvas.add(rect); let layer = createLayer({ id: id, name: name, type: LayerType.BITMAP, visible: true, locked: true, opacity: opacity, isPrintTrims: true, blendMode: BlendMode.MULTIPLY, fabricObjects: [rect.toObject(["id", "layerId", "layerName"])], metadata: {sourceData: item}, }) children.push(layer); }; // if(children.length === 0){ // let layer = createLayer({ // id: generateId("layer_image_"), // name: t("Canvas.EmptyLayer"), // type: LayerType.BITMAP, // visible: true, // locked: false, // opacity: 1.0, // fabricObjects: [], // }) // children.push(layer); // } if(children.length === 0) return; const groupRect = new fabric.Rect({}); await this.setObjecCliptInfo(groupRect); // 插入组图层 const groupIndex = this.layers.value.findIndex(layer => layer.isFixedOther || layer.isFixed || layer.isBackground); const groupLayer = createLayer({ id: SpecialLayerId.SPECIAL_GROUP, name: t("Canvas.PrintAndElementsGroup"), type: LayerType.GROUP, visible: true, locked: false, opacity: 1.0, fabricObjects: [], children: children, clippingMask: groupRect.toObject(), isPrintTrimsGroup: true, }); this.layers.value.splice(groupIndex, 0, groupLayer); } /** * 画布事件变更后 */ async changeCanvas(){ 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); if(ids.length === 0){ ids.unshift(SpecialLayerId.SPECIAL_GROUP); await this.setObjecCliptInfo(colorObject); this.canvas.renderAll(); return; } const base64 = await this.exportManager.exportImage({layerIdArray2: ids, isEnhanceImg: true}); if(!base64) return console.warn("导出图片失败", base64) const canvas = await base64ToCanvas(base64, fixedLayerObj.scaleX * 2, true); const ctx = canvas.getContext('2d'); const width = fixedLayerObj.width; const height = fixedLayerObj.height; const x = (canvas.width - width) / 2; const y = (canvas.height - height) / 2; const data = ctx.getImageData(x, y, width, height); await this.setObjecCliptInfo(colorObject, data); this.canvas.renderAll(); } } /** * 缩放红绿图模式内容以适应当前画布大小 * 确保衣服底图和红绿图永远在画布内可见 * @private */ _rescaleRedGreenModeContent() { if (!this.canvas) return; console.log("正在重新缩放红绿图内容..."); try { // 获取固定图层和普通图层 const fixedLayerObject = this._getFixedLayerObject(); const normalLayerObjects = this._getNormalLayerObjects(); if (!fixedLayerObject) { console.warn("找不到固定图层对象,无法进行红绿图内容缩放"); return; } // 计算边距(画布两侧各留出一定空间) const margin = 50; const maxWidth = this.canvas.width - margin * 2; const maxHeight = this.canvas.height - margin * 2; // 计算原始尺寸 const originalWidth = fixedLayerObject.width * fixedLayerObject.scaleX; const originalHeight = fixedLayerObject.height * fixedLayerObject.scaleY; // 计算需要的缩放比例,确保图像完全适应画布 const scaleX = maxWidth / originalWidth; const scaleY = maxHeight / originalHeight; const scale = Math.min(scaleX, scaleY); console.log( `计算的缩放比例: ${scale},原始尺寸: ${originalWidth}x${originalHeight},目标尺寸: ${maxWidth}x${maxHeight}` ); // 如果缩放比例接近1,不进行缩放 if (Math.abs(scale - 1) < 0.05) { console.log("缩放比例接近1,不进行缩放,仅居中内容"); this.centerAllObjects(); return; } // 缩放固定图层(衣服底图) this._rescaleObject(fixedLayerObject, scale); // 缩放所有普通图层对象(红绿图和其他内容) normalLayerObjects.forEach((obj) => { // 红绿图对象应与底图保持完全一致的位置和大小 if (this._isLikelyRedGreenImage(obj, fixedLayerObject)) { // 完全匹配底图的位置和大小 obj.set({ scaleX: fixedLayerObject.scaleX, scaleY: fixedLayerObject.scaleY, left: fixedLayerObject.left, top: fixedLayerObject.top, originX: fixedLayerObject.originX || "center", originY: fixedLayerObject.originY || "center", }); } else { // 其他普通对象进行等比例缩放 this._rescaleObject(obj, scale); } }); // 重新居中所有内容 this.centerAllObjects(); // 更新所有对象的坐标系统 this.canvas.getObjects().forEach((obj) => { obj.setCoords(); }); // 渲染画布 this.canvas.renderAll(); console.log("红绿图内容缩放完成"); } catch (error) { console.error("缩放红绿图内容时出错:", error); } } /** * 缩放单个对象 * @param {Object} obj fabric对象 * @param {Number} scale 缩放比例 * @private */ _rescaleObject(obj, scale) { if (!obj) return; // 保存原始中心点 const center = obj.getCenterPoint(); // 应用新的缩放 obj.set({ scaleX: obj.scaleX * scale, scaleY: obj.scaleY * scale, }); // 重新定位到原中心点 obj.setPositionByOrigin(center, "center", "center"); obj.setCoords(); } /** * 获取固定图层对象(衣服底图) * @returns {Object|null} 固定图层对象或null * @private */ _getFixedLayerObject() { if (!this.layers || !this.layers.value) return null; // 查找固定图层 const fixedLayer = this.layers.value.find((layer) => layer.isFixed); if (!fixedLayer) return null; // 返回图层中的fabric对象 return fixedLayer.fabricObject || null; } /** * 获取所有普通图层对象(包括红绿图) * @returns {Array} 普通图层对象数组 * @private */ _getNormalLayerObjects() { if (!this.layers || !this.layers.value) return []; // 查找所有非背景、非固定的普通图层 const normalLayers = this.layers.value.filter( (layer) => !layer.isBackground && !layer.isFixed ); // 收集所有普通图层中的对象 const objects = []; normalLayers.forEach((layer) => { // 如果有单个对象属性 if (layer.fabricObject) { objects.push(layer.fabricObject); } // 如果有对象数组 if (Array.isArray(layer.fabricObjects)) { layer.fabricObjects.forEach((obj) => { if (obj) objects.push(obj); }); } }); return objects; } /** * 判断对象是否可能是红绿图 * 通过比较与衣服底图的大小、位置来判断 * @param {Object} obj 要检查的对象 * @param {Object} fixedLayerObject 固定图层对象(衣服底图) * @returns {Boolean} 是否可能是红绿图 * @private */ _isLikelyRedGreenImage(obj, fixedLayerObject) { if (!obj || !fixedLayerObject) return false; // 检查对象是否为图像 if (obj.type !== "image") return false; // 比较尺寸(允许5%的误差) const sizeMatch = Math.abs( obj.width * obj.scaleX - fixedLayerObject.width * fixedLayerObject.scaleX ) < fixedLayerObject.width * fixedLayerObject.scaleX * 0.05 && Math.abs( obj.height * obj.scaleY - fixedLayerObject.height * fixedLayerObject.scaleY ) < fixedLayerObject.height * fixedLayerObject.scaleY * 0.05; // 比较位置(允许一定的偏差) const positionMatch = Math.abs(obj.left - fixedLayerObject.left) < 50 && Math.abs(obj.top - fixedLayerObject.top) < 50; return sizeMatch && positionMatch; } /** * 键盘移动激活对象 * @param {String} direction 移动方向(up, down, left, right) * @param {} step 移动步长 * @private */ moveActiveObject(direction, step = 1) { const objects = []; const activeObject = this.canvas.getActiveObject(); if(!activeObject) return; const initPos = { id: activeObject.id, left: activeObject.left, top: activeObject.top, }; switch(direction) { case "up": activeObject.top -= step; break; case "down": activeObject.top += step; break; case "left": activeObject.left -= step; break; case "right": activeObject.left += step; break; } if(!activeObject.id) return this.canvas.renderAll(); const cmd = new ObjectMoveCommand({ canvas: this.canvas, initPos, finalPos: { id: activeObject.id, left: activeObject.left, top: activeObject.top, }, }); this.commandManager.executeCommand(cmd); } }