From 4352f7c2f46125ed16a2446202361aa257dfdd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E9=B9=8F?= <2916022834@qq.com> Date: Mon, 13 Apr 2026 11:52:24 +0800 Subject: [PATCH] 111 --- src/component/Canvas/CanvasEditor/index.vue | 5 +- .../CanvasEditor/managers/CanvasManager.js | 4176 +++++++++-------- .../CanvasEditor/managers/ExportManager.js | 2 +- src/component/Detail/canvas/index.vue | 1 + 4 files changed, 2099 insertions(+), 2085 deletions(-) diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index b14987ef..e94e6ff8 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -1058,7 +1058,10 @@ } = {}) => { loading.value = true canvasManager?.canvas?.discardActiveObject() - if (isFrontBackUpdata) await canvasManager?.changeCanvas() + if (isFrontBackUpdata) { + await canvasManager?.setSpecialCliptInfo(true, true) + canvasManager.canvas.renderAll() + } var base64 = await canvasManager.exportImage({ isContainBg, isContainFixed, diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index 84fc497b..08a3c9bb 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -1,18 +1,18 @@ import { fabric } from "fabric-with-all"; import initAligningGuidelines, { - initCenteringGuidelines, + initCenteringGuidelines, } from "../utils/helperLine"; import { ThumbnailManager } from "./ThumbnailManager"; import { ExportManager } from "./ExportManager"; import { - findLayerRecursively, - isGroupLayer, - OperationType, - OperationTypes, - createLayer, - LayerType, - SpecialLayerId, - BlendMode, + findLayerRecursively, + isGroupLayer, + OperationType, + OperationTypes, + createLayer, + LayerType, + SpecialLayerId, + BlendMode, } from "../utils/layerHelper"; import { ObjectMoveCommand } from "../commands/ObjectCommands"; import { AnimationManager } from "./animation/AnimationManager"; @@ -21,2118 +21,2128 @@ import { CanvasEventManager } from "./events/CanvasEventManager"; import CanvasConfig from "../config/canvasConfig"; import { EraserStateManager } from "./EraserStateManager"; import { - deepClone, - findObjectById, - generateId, - optimizeCanvasRendering, - palletToFill, - fillToCssStyle, - calculateRotatedTopLeftDeg, - calculateCenterPoint, - calculateTopLeftPoint, - createPatternTransform, - getTransformScaleAngle, - base64ToCanvas, - imageAddGapToCanvas, + 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, + 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; +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.awaitCanvasRun = null; - this.canvasChangeing = false; - // 初始化画布 - 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?.addDrawPartImage(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 activeTool = this.toolManager?.activeTool?.value; - if(activeTool === OperationType.PART_ERASER){ - return this.partManager?.onErasingEnd(affectedObjects); - } - 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, { - canvasManager: this, - 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(); + 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.awaitCanvasRun = null; + this.canvasChangeing = false; + // 初始化画布 + this.initializeCanvas(); } - // 重新渲染画布 - this.canvas.renderAll(); - } + 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, + }); - /** - * 计算多个对象的总边界框 - * @private - * @param {Array} objects 要计算边界的对象数组 - * @return {Object} 边界信息,包含left、top、width、height - */ - _calculateObjectsBounds(objects) { - if (!objects || objects.length === 0) return null; + // 初始化动画管理器 + this.animationManager = new AnimationManager(this.canvas, { + currentZoom: this.currentZoom, + wheelThrottleTime: 15, // 降低滚轮事件节流时间,提高响应性 + defaultEase: "power2.lin", + defaultDuration: 0.3, // 缩短默认动画时间 + }); - let minX = Infinity; - let minY = Infinity; - let maxX = -Infinity; - let maxY = -Infinity; + // 初始化缩略图管理器 + this.thumbnailManager = new ThumbnailManager(this.canvas, { + // 可以根据需求自定义选项 + // layerThumbSize: { width: 32, height: 32 }, + // elementThumbSize: { width: 32, height: 24 }, + layers: this.layers, + }); - 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); - }); + this.canvas.thumbnailManager = this.thumbnailManager; // 将缩略图管理器绑定到画布 - return { - left: minX, - top: minY, - width: maxX - minX, - height: maxY - minY, - }; - } + // 设置画布辅助线 + initAligningGuidelines(this.canvas); - 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 - ); - } + // 设置画布中心线 + // initCenteringGuidelines(this.canvas); - /** - * 居中背景层 - * @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; - } - getObjectsByIdOrLayerId(ids){ - const objects = this.canvas.getObjects().filter((obj) => { - return ids.includes(obj.id) || ids.includes(obj.layerId); - }); - 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("颜色图层不存在"); + // 初始化画布事件监听器 + this._initCanvasEvents(); } - 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.getObjectsByIdOrLayerId(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, i) => { - const label = glayer.children.find((v_) => (v_.id === v.layerId || v_.id === v.id)); - const sourceData = label?.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: i + 1, - 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,// 平铺模式下的间距 - fill_repeat: "", - } - } - 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; + // 添加图像到指定图层 + async addImageToLayer({ targetLayerId, fabricImage, ...options }) { + // 如果图层管理器存在,将图像合并到当前活动图层 + if (this.layerManager) { + // 获取当前活动图层 + let activeLayer = targetLayerId + ? findLayerRecursively(this.layers.value, targetLayerId)?.layer + : this.layerManager.getActiveLayer(); - 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; - obj.object.fill_repeat = fill.repeat; - } - 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) { - this.canvas.loading.value = true; - // 确保传入的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 layerIds = []; - const layers = json?.layers || []; - const objects = json?.canvas?.objects || []; - layers.forEach((layer) => { - layerIds.push(layer.id); - layer.children?.forEach((child) => layerIds.push(child.id)); - 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; - }) - // 排除的对象id - const excludedObjects = [SpecialLayerId.PART_SELECTOR]; - json.canvas.objects = objects.filter((v) => { - // 指定ID排除 - if(excludedObjects.includes(v.id)) return false; - - if(v.isBackground || v.isFixed) return true; - // 当前图层不存在当前对象 - if(!layerIds.includes(v.layerId)) return false; - return true - }); - } - - - /** - * 创建其他图层:印花、颜色、元素... - * @param {Object} otherData - 其他图层数据 - */ - async createOtherLayers(otherData) { - if (!otherData) return console.warn("otherData 为空不需要添加"); - let resolve = ()=>{}; - this.awaitCanvasRun = ()=>(new Promise((v) => resolve = v)) - const otherData_ = JSON.parse(JSON.stringify(otherData)); - console.log("==========创建其他图层", otherData_); - - // 删除颜色图层和特殊组图层 - const ids = [SpecialLayerId.COLOR, 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) => { - if(ids.includes(v.id) || ids.includes(v.layerId)){ - this.canvas.remove(v) - } - }) - - - // 创建颜色图层 - 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(printTrimsLayers.length || singleLayers.length){ - await this.createPrintTrimsLayers(printTrimsLayers, singleLayers); - } - await this.changeCanvas(); - console.log("==========创建其他图层成功"); - resolve(); - this.awaitCanvasRun = null; - } - - // 设置画布对象的裁剪信息 - 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, + if (activeLayer) { + // 确保新图像具有正确的图层信息 + fabricImage.set({ + layerId: activeLayer.id, + layerName: activeLayer.name, + id: fabricImage.id || generateId("brush_img_"), }); - resolve(imgObject); - }, { crossOrigin: "anonymous" }); + + 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?.addDrawPartImage(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 activeTool = this.toolManager?.activeTool?.value; + if (activeTool === OperationType.PART_ERASER) { + return this.partManager?.onErasingEnd(affectedObjects); + } + 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); }); } - 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.NORMAL; - // 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}, - object: image, - }) - 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.NORMAL; - let fill_repeat = "repeat" - 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 - if(item.object.blendMode) blendMode = item.object.blendMode; - gapX = item.object.gapX - gapY = item.object.gapY - fillSource = imageAddGapToCanvas(image, gapX, gapY); - if(item.object.fill_repeat) fill_repeat = item.object.fill_repeat; - } - 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: fill_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: false, - opacity: opacity, - isPrintTrims: true, - blendMode: blendMode, - fabricObjects: [rect.toObject(["id", "layerId", "layerName"])], - metadata: {sourceData: item}, - object: rect, - }) - 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; - // 印花元素排序 - if(new Set(children.map(v => v.metadata.sourceData.priority)).size === children.length){ - children.sort((a, b) => b.metadata.sourceData.priority - a.metadata.sourceData.priority); - } - children.forEach(layer => { - this.canvas.add(layer.object); - }); - 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(fids = [], isBeforeChange = false){ - if(!isBeforeChange) this.canvasChangeing = false; - 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).filter(id => !fids.includes(id)); - if(ids.length === 0){ - ids.unshift(SpecialLayerId.SPECIAL_GROUP); - await this.setObjecCliptInfo(colorObject); - this.canvas.renderAll(); + /** + * 设置编辑器模式 + * @param {string} mode 'draw'、'select'或'pan' + */ + toolChanged(mode) { + if (!OperationTypes.includes(mode)) { + console.warn(`不支持的编辑器模式: ${mode}`); 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.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, { + canvasManager: this, + 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(); } - } - /** 画布变更之前 */ - async beforeChangeCanvas(objects){ - if(this.canvasChangeing) return; - const ids = objects.filter(v => { - return v.isPrintTrims && v.globalCompositeOperation && v.globalCompositeOperation !== BlendMode.NORMAL - }).map(v => v.layerId); - if(ids.length > 0){ - this.canvasChangeing = true; - this.canvas.getObjects().forEach(v => { - if(ids.includes(v.layerId)){ - v.globalCompositeOperation_ = v.globalCompositeOperation; - v.globalCompositeOperation = BlendMode.NORMAL; + + // 设置固定图层可擦除状态 + 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); + } + + this.setSpecialCliptInfo(false, true) + + // 重新渲染画布 + 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; + } + getObjectsByIdOrLayerId(ids) { + const objects = this.canvas.getObjects().filter((obj) => { + return ids.includes(obj.id) || ids.includes(obj.layerId); + }); + 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(); - await this.changeCanvas(ids, true); } - } - /** - * 缩放红绿图模式内容以适应当前画布大小 - * 确保衣服底图和红绿图永远在画布内可见 - * @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; + /** + * 更新指定图层的缩略图 + * @param {String} layerId 图层ID + */ + updateLayerThumbnail(layerId) { + this.thumbnailManager?.generateLayerThumbnail?.(layerId); } - if(!activeObject.id) return this.canvas.renderAll(); - const cmd = new ObjectMoveCommand({ - canvas: this.canvas, - initPos, - finalPos: { + + /** + * 更新所有图层和元素的缩略图 + */ + 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.getObjectsByIdOrLayerId(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, i) => { + const label = glayer.children.find((v_) => (v_.id === v.layerId || v_.id === v.id)); + const sourceData = label?.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: i + 1, + 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,// 平铺模式下的间距 + fill_repeat: "", + } + } + 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; + obj.object.fill_repeat = fill.repeat; + } + 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) { + this.canvas.loading.value = true; + // 确保传入的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 layerIds = []; + const layers = json?.layers || []; + const objects = json?.canvas?.objects || []; + layers.forEach((layer) => { + layerIds.push(layer.id); + layer.children?.forEach((child) => layerIds.push(child.id)); + 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; + }) + // 排除的对象id + const excludedObjects = [SpecialLayerId.PART_SELECTOR]; + json.canvas.objects = objects.filter((v) => { + // 指定ID排除 + if (excludedObjects.includes(v.id)) return false; + + if (v.isBackground || v.isFixed) return true; + // 当前图层不存在当前对象 + if (!layerIds.includes(v.layerId)) return false; + return true + }); + } + + + /** + * 创建其他图层:印花、颜色、元素... + * @param {Object} otherData - 其他图层数据 + */ + async createOtherLayers(otherData) { + if (!otherData) return console.warn("otherData 为空不需要添加"); + let resolve = () => { }; + this.awaitCanvasRun = () => (new Promise((v) => resolve = v)) + const otherData_ = JSON.parse(JSON.stringify(otherData)); + console.log("==========创建其他图层", otherData_); + + // 删除颜色图层和特殊组图层 + const ids = [SpecialLayerId.COLOR, 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) => { + if (ids.includes(v.id) || ids.includes(v.layerId)) { + this.canvas.remove(v) + } + }) + + + // 创建颜色图层 + 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 (printTrimsLayers.length || singleLayers.length) { + await this.createPrintTrimsLayers(printTrimsLayers, singleLayers); + } + await this.changeCanvas(); + console.log("==========创建其他图层成功"); + resolve(); + this.awaitCanvasRun = null; + } + + //设置印花元素颜色的裁剪信息 + async setSpecialCliptInfo(isColor = true, isGroup = true) { + // 更新颜色层信息 + if (isColor) { + const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR); + if (colorObject) { + await this.setObjecCliptInfo(colorObject); + } + + } + // 更新特殊组图层信息 + if (isGroup) { + const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP); + if (groupLayer) { + const groupRect = new fabric.Rect({}); + await this.setObjecCliptInfo(groupRect); + groupLayer.clippingMask = groupRect.toObject(); + } + } + } + // 设置画布对象的裁剪信息 + 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.NORMAL; + // 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 }, + object: image, + }) + 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.NORMAL; + let fill_repeat = "repeat" + 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 + if (item.object.blendMode) blendMode = item.object.blendMode; + gapX = item.object.gapX + gapY = item.object.gapY + fillSource = imageAddGapToCanvas(image, gapX, gapY); + if (item.object.fill_repeat) fill_repeat = item.object.fill_repeat; + } + 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: fill_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: false, + opacity: opacity, + isPrintTrims: true, + blendMode: blendMode, + fabricObjects: [rect.toObject(["id", "layerId", "layerName"])], + metadata: { sourceData: item }, + object: rect, + }) + 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; + // 印花元素排序 + if (new Set(children.map(v => v.metadata.sourceData.priority)).size === children.length) { + children.sort((a, b) => b.metadata.sourceData.priority - a.metadata.sourceData.priority); + } + children.forEach(layer => { + this.canvas.add(layer.object); + }); + 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(fids = [], isBeforeChange = false) { + if (!isBeforeChange) this.canvasChangeing = false; + 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).filter(id => !fids.includes(id)); + 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(); + } + } + /** 画布变更之前 */ + async beforeChangeCanvas(objects) { + if (this.canvasChangeing) return; + const ids = objects.filter(v => { + return v.isPrintTrims && v.globalCompositeOperation && v.globalCompositeOperation !== BlendMode.NORMAL + }).map(v => v.layerId); + if (ids.length > 0) { + this.canvasChangeing = true; + this.canvas.getObjects().forEach(v => { + if (ids.includes(v.layerId)) { + v.globalCompositeOperation_ = v.globalCompositeOperation; + v.globalCompositeOperation = BlendMode.NORMAL; + } + }) + this.canvas.renderAll(); + await this.changeCanvas(ids, true); + } + } + + /** + * 缩放红绿图模式内容以适应当前画布大小 + * 确保衣服底图和红绿图永远在画布内可见 + * @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, - }, - }); - this.commandManager.executeCommand(cmd); - } + }; + 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); + } } diff --git a/src/component/Canvas/CanvasEditor/managers/ExportManager.js b/src/component/Canvas/CanvasEditor/managers/ExportManager.js index eef7d462..9bc621fd 100644 --- a/src/component/Canvas/CanvasEditor/managers/ExportManager.js +++ b/src/component/Canvas/CanvasEditor/managers/ExportManager.js @@ -608,7 +608,7 @@ export class ExportManager { // tempFabricCanvas.setZoom(1); const ox = fixedLayerObject.left - fixedLayerObject.width * fixedLayerObject.scaleX / 2 const oy = fixedLayerObject.top - fixedLayerObject.height * fixedLayerObject.scaleY / 2 - console.log("==========", fixedLayerObject, ox, oy) + // console.log("==========", fixedLayerObject, ox, oy) try { // 克隆并添加所有对象到临时画布,需要调整位置相对于固定图层 for (let i = 0; i < objectsToExport.length; i++) { diff --git a/src/component/Detail/canvas/index.vue b/src/component/Detail/canvas/index.vue index 3411955f..bd9a0832 100644 --- a/src/component/Detail/canvas/index.vue +++ b/src/component/Detail/canvas/index.vue @@ -247,6 +247,7 @@ export default defineComponent({ let front = detailData.frontBack.front[detailData.imgDomIndex] let back = detailData.frontBack.back[detailData.imgDomIndex] store.commit('DesignDetail/updataDetailItem',{maskUrl:value}) + await nextTick() if(!detailData.selectDetail.partialDesign.partialDesignPath && !detailData.selectDetail.partialDesign.partialDesignBase64){ await privewDetail() }else{