From c6b1bdbdf1aedff1cadd743553e1e8182a1fb5da 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:20:26 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E7=BA=A2=E7=BB=BF=E5=9B=BE=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CanvasEditor/components/ToolsSidebar.vue | 1 + src/component/Canvas/CanvasEditor/index.vue | 5 +- .../CanvasEditor/managers/ExportManager.js | 2121 +++++++++-------- .../managers/command/CommandManager.js | 14 +- 4 files changed, 1074 insertions(+), 1067 deletions(-) diff --git a/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue b/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue index 69cb3214..1df8bfb3 100644 --- a/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue +++ b/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue @@ -55,6 +55,7 @@ commandManager.setChangeCallback((info) => { emit("undo-redo-status-changed", { canUndo: canUndo.value, canRedo: canRedo.value, + type: info.type, commandManager, }); }); diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 1d00d0e5..b14987ef 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -907,7 +907,8 @@ } emit("changeCanvas", commandData) canvasManager.changeCanvas() - if ((command.canUndo || command.canRedo) && props.enabledRedGreenMode) { + const type = command.type + if (props.enabledRedGreenMode && (type === "undo" || type === "redo")) { setTimeout(async () => { try { const imageData = await canvasManager.exportImage({ @@ -1057,7 +1058,7 @@ } = {}) => { loading.value = true canvasManager?.canvas?.discardActiveObject() - if(isFrontBackUpdata)await canvasManager?.changeCanvas() + if (isFrontBackUpdata) await canvasManager?.changeCanvas() var base64 = await canvasManager.exportImage({ isContainBg, isContainFixed, diff --git a/src/component/Canvas/CanvasEditor/managers/ExportManager.js b/src/component/Canvas/CanvasEditor/managers/ExportManager.js index 98dc6532..eef7d462 100644 --- a/src/component/Canvas/CanvasEditor/managers/ExportManager.js +++ b/src/component/Canvas/CanvasEditor/managers/ExportManager.js @@ -8,1077 +8,1080 @@ import { OperationType, SpecialLayerId } from "../utils/layerHelper"; * 负责处理画布的图片导出功能,支持多种导出选项和图层过滤 */ export class ExportManager { - constructor(canvasManager, layerManager) { - this.canvasManager = canvasManager; - this.layerManager = layerManager; - this.canvas = canvasManager.canvas; - } + constructor(canvasManager, layerManager) { + this.canvasManager = canvasManager; + this.layerManager = layerManager; + this.canvas = canvasManager.canvas; + } - /** - * 导出图片 - * @param {Object} options 导出选项 - * @param {Boolean} options.isContainBg 是否包含背景图层 - * @param {Boolean} options.isContainFixed 是否包含固定图层 - * @param {Boolean} options.isContainFixedOther 是否包含其他固定图层 - * @param {Boolean} options.isContainNormalLayer 是否包含普通图层 - * @param {Boolean} options.isCropByBg 是否使用背景大小裁剪 - * @param {String} options.layerId 导出具体图层ID - * @param {Array} options.layerIdArray 导出多个图层ID数组 - * @param {Array} options.layerIdArray2 导出多个图层ID数组2 - * @param {String} options.expPicType 导出图片类型 (png/jpg/svg) - * @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 - * @param {Boolean} options.isEnhanceImg 是否是增强图片 - * @param {Array} options.excludedLayers 排除的图层ID数组 - * @returns {String} 导出的图片数据URL - */ - async exportImage(options = {}) { - const { - isContainBg = false, - isContainFixed = false, - isContainFixedOther = false, // 是否包含其他固定图层 - isContainNormalLayer = true, // 是否包含普通图层 - isCropByBg = false, // 是否使用背景大小裁剪 - layerId = "", - layerIdArray = [], - layerIdArray2 = null, - expPicType = "png", - restoreOpacityInRedGreen = true, - isEnhanceImg, // 是否是增强图片 - excludedLayers = [], // 排除的图层ID数组 - } = options; - try { + /** + * 导出图片 + * @param {Object} options 导出选项 + * @param {Boolean} options.isContainBg 是否包含背景图层 + * @param {Boolean} options.isContainFixed 是否包含固定图层 + * @param {Boolean} options.isContainFixedOther 是否包含其他固定图层 + * @param {Boolean} options.isContainNormalLayer 是否包含普通图层 + * @param {Boolean} options.isCropByBg 是否使用背景大小裁剪 + * @param {String} options.layerId 导出具体图层ID + * @param {Array} options.layerIdArray 导出多个图层ID数组 + * @param {Array} options.layerIdArray2 导出多个图层ID数组2 + * @param {String} options.expPicType 导出图片类型 (png/jpg/svg) + * @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 + * @param {Boolean} options.isEnhanceImg 是否是增强图片 + * @param {Array} options.excludedLayers 排除的图层ID数组 + * @returns {String} 导出的图片数据URL + */ + async exportImage(options = {}) { + const { + isContainBg = false, + isContainFixed = false, + isContainFixedOther = false, // 是否包含其他固定图层 + isContainNormalLayer = true, // 是否包含普通图层 + isCropByBg = false, // 是否使用背景大小裁剪 + layerId = "", + layerIdArray = [], + layerIdArray2 = null, + expPicType = "png", + restoreOpacityInRedGreen = true, + isEnhanceImg, // 是否是增强图片 + excludedLayers = [], // 排除的图层ID数组 + } = options; + try { - // 检查是否为红绿图模式 - const isRedGreenMode = this.layerManager?.isInRedGreenMode?.() || false; - // 如果指定了具体图层ID,导出指定图层 - if (layerId) { - return this._exportSpecificLayer( - layerId, - expPicType, - isRedGreenMode, - restoreOpacityInRedGreen, - isCropByBg, - isEnhanceImg, // 是否是增强图片 - ); - } + // 检查是否为红绿图模式 + const isRedGreenMode = this.layerManager?.isInRedGreenMode?.() || false; + // 如果指定了具体图层ID,导出指定图层 + if (layerId) { + return this._exportSpecificLayer( + layerId, + expPicType, + isRedGreenMode, + restoreOpacityInRedGreen, + isCropByBg, + isEnhanceImg, // 是否是增强图片 + ); + } - // 如果指定了多个图层ID,导出多个图层 - if (layerIdArray && layerIdArray.length > 0) { - return this._exportMultipleLayers( - layerIdArray, - expPicType, - isContainBg, - isContainFixed, - isContainFixedOther, // 是否包含其他固定图层 - isContainNormalLayer, // 是否包含普通图层 - isRedGreenMode, - restoreOpacityInRedGreen, - isCropByBg, - isEnhanceImg, // 是否是增强图片 - ); - } + // 如果指定了多个图层ID,导出多个图层 + if (layerIdArray && layerIdArray.length > 0) { + return this._exportMultipleLayers( + layerIdArray, + expPicType, + isContainBg, + isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 + isContainNormalLayer, // 是否包含普通图层 + isRedGreenMode, + restoreOpacityInRedGreen, + isCropByBg, + isEnhanceImg, // 是否是增强图片 + ); + } - // 默认导出所有可见图层 - return this._exportAllLayers( - expPicType, - isContainBg, - isContainFixed, + // 默认导出所有可见图层 + return this._exportAllLayers( + expPicType, + isContainBg, + isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 + isContainNormalLayer, // 是否包含普通图层 + isRedGreenMode, + restoreOpacityInRedGreen, + isCropByBg, + isEnhanceImg, // 是否是增强图片 + layerIdArray2, + excludedLayers, // 排除的图层ID数组 + ); + } catch (error) { + console.error("导出图片失败:", error); + throw new Error(`图片导出失败: ${error.message}`); + } + } + + /** + * 导出指定单个图层 + * @param {String} layerId 图层ID + * @param {String} expPicType 导出类型 + * @param {Boolean} isRedGreenMode 是否为红绿图模式 + * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 + * @param {Boolean} isCropByBg 是否使用背景大小裁剪 + * @param {Boolean} isEnhanceImg 是否是增强图片 + * @returns {String} 图片数据URL + * @private + */ + async _exportSpecificLayer( + layerId, + expPicType, + isRedGreenMode, + restoreOpacityInRedGreen, + isCropByBg, // 是否使用背景大小裁剪 + isEnhanceImg, // 是否是增强图片 + ) { + if (!this.layerManager) { + throw new Error("图层管理器未初始化"); + } + + const layer = this._getLayerById(layerId); + if (!layer) { + throw new Error(`未找到ID为 ${layerId} 的图层`); + } + + if (!layer.visible) { + console.warn(`图层 ${layer.name} 不可见,将导出空白图片`); + } + + // 收集所有需要导出的对象 + const objectsToExport = this._collectObjectsFromLayer(layer); + + if (objectsToExport.length === 0) { + console.warn(`图层 ${layer.name} 没有可导出的对象`); + return this._generateEmptyImage(expPicType); + } + + // 红绿图模式下使用固定尺寸和裁剪 + if (isRedGreenMode) { + return this._exportWithRedGreenMode( + objectsToExport, + expPicType, + restoreOpacityInRedGreen, + ); + } + + // 普通模式使用画布尺寸 + return await this._exportWithCanvasSize( + objectsToExport, + expPicType, + restoreOpacityInRedGreen, + isCropByBg, // 是否使用背景大小裁剪 + isEnhanceImg, // 是否是增强图片 + ); + } + + /** + * 导出多个指定图层 + * @param {Array} layerIdArray 图层ID数组 + * @param {String} expPicType 导出类型 + * @param {Boolean} isContainBg 是否包含背景图层 + * @param {Boolean} isContainFixed 是否包含固定图层 + * @param {Boolean} isContainFixedOther 是否包含其他固定图层 + * @param {Boolean} isContainNormalLayer 是否包含普通图层 + * @param {Boolean} isRedGreenMode 是否为红绿图模式 + * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 + * @param {Boolean} isCropByBg 是否使用背景大小裁剪 + * @param {Boolean} isEnhanceImg 是否是增强图片 + * @returns {String} 图片数据URL + * @private + */ + async _exportMultipleLayers( + layerIdArray, + expPicType, + isContainBg, + isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 + isContainNormalLayer = true, // 是否包含普通图层 + isRedGreenMode, + restoreOpacityInRedGreen, + isCropByBg, // 是否使用背景大小裁剪 + isEnhanceImg, // 是否是增强图片 + ) { + if (!this.layerManager) { + throw new Error("图层管理器未初始化"); + } + + // 按图层顺序收集对象(从底到顶) + const objectsToExport = this._collectObjectsByLayerOrder( + layerIdArray, + isContainBg, + isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 + isContainNormalLayer, // 是否包含普通图层 + ); + + if (objectsToExport.length === 0) { + console.warn("没有可导出的对象"); + return this._generateEmptyImage(expPicType); + } + + // 红绿图模式下使用固定尺寸和裁剪 + if (isRedGreenMode) { + return this._exportWithRedGreenMode( + objectsToExport, + expPicType, + restoreOpacityInRedGreen + ); + } + + // 普通模式使用画布尺寸 + return await this._exportWithCanvasSize( + objectsToExport, + expPicType, + restoreOpacityInRedGreen, + isCropByBg, // 是否使用背景大小裁剪 + isEnhanceImg, // 是否是增强图片 + ); + } + + /** + * 导出所有图层 + * @param {String} expPicType 导出类型 + * @param {Boolean} isContainBg 是否包含背景图层 + * @param {Boolean} isContainFixed 是否包含固定图层 + * @param {Boolean} isContainFixedOther 是否包含其他固定图层 + * @param {Boolean} isContainNormalLayer 是否包含普通图层 + * @param {Boolean} isRedGreenMode 是否为红绿图模式 + * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 + * @param {Boolean} isCropByBg 是否使用背景大小裁剪 + * @param {Boolean} isEnhanceImg 是否是增强图片 + * @param {Array} layerIdArray 导出多个图层ID数组2 + * @returns {String} 图片数据URL + * @private + */ + async _exportAllLayers( + expPicType, + isContainBg, + isContainFixed, isContainFixedOther, // 是否包含其他固定图层 isContainNormalLayer, // 是否包含普通图层 - isRedGreenMode, - restoreOpacityInRedGreen, - isCropByBg, + isRedGreenMode, + restoreOpacityInRedGreen, + isCropByBg, // 是否使用背景大小裁剪 isEnhanceImg, // 是否是增强图片 - layerIdArray2, + layerIdArray, // 导出所有图层 excludedLayers, // 排除的图层ID数组 - ); - } catch (error) { - console.error("导出图片失败:", error); - throw new Error(`图片导出失败: ${error.message}`); - } - } + ) { + // 按图层顺序收集对象(从底到顶) + const objectsToExport = this._collectObjectsByLayerOrder( + layerIdArray, // 导出所有图层 + isContainBg, + isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 + isContainNormalLayer, // 是否包含普通图层 + excludedLayers, + ); - /** - * 导出指定单个图层 - * @param {String} layerId 图层ID - * @param {String} expPicType 导出类型 - * @param {Boolean} isRedGreenMode 是否为红绿图模式 - * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 - * @param {Boolean} isCropByBg 是否使用背景大小裁剪 - * @param {Boolean} isEnhanceImg 是否是增强图片 - * @returns {String} 图片数据URL - * @private - */ - async _exportSpecificLayer( - layerId, - expPicType, - isRedGreenMode, - restoreOpacityInRedGreen, + if (objectsToExport.length === 0) { + console.warn("没有可导出的对象"); + return this._generateEmptyImage(expPicType); + } + + // 红绿图模式下使用固定尺寸和裁剪 + if (isRedGreenMode) { + return this._exportWithRedGreenMode( + objectsToExport, + expPicType, + restoreOpacityInRedGreen + ); + } + let canvasClipPath = this.canvas.clipPath; + if (isCropByBg) { + const cropWidth = + this.canvasManager?.canvasWidth?.value || + this.canvas?.canvasWidth || + this.canvas.width; + const cropHeight = + this.canvasManager?.canvasHeight?.value || + this.canvas?.canvasHeight || + this.canvas.height; + canvasClipPath = new fabric.Rect({ + left: this.canvas.width / 2, + top: this.canvas.height / 2, + width: cropWidth, + height: cropHeight, + originX: "center", + originY: "center", + fill: "#fff", + stroke: "transparent", + strokeWidth: 0, + }); + canvasClipPath.set({ + absolutePositioned: true, + }); + canvasClipPath.setCoords(); + } + // 普通模式使用画布尺寸 + return await this._exportWithCanvasSize( + objectsToExport, + expPicType, + restoreOpacityInRedGreen, + canvasClipPath, + isCropByBg, // 是否使用背景大小裁剪 + isEnhanceImg, // 是否是增强图片 + ); + } + + /** + * 从图层收集对象(优化版本 - 通过ID查找画布中的真实对象) + * @param {Object} layer 图层对象 + * @param {Boolean} isChildren 是否递归收集子图层的对象 + * @returns {Array} 画布中的真实对象数组 + * @private + */ + _collectObjectsFromLayer(layer, isChildren = true) { + if (!layer) { + return []; + } + + const realObjects = []; + + // 收集当前图层的对象 + if (layer.fabricObjects && layer.fabricObjects.length > 0) { + for (const layerObj of layer.fabricObjects) { + if (!layerObj || !layerObj.id) continue; + + // 通过ID在画布中查找真实对象 + const realObj = this._findRealObjectById(layerObj.id); + if (realObj && realObj.visible !== false) { + realObjects.push(realObj); + } + } + } + + if (layer.fabricObject) { + // 通过ID在画布中查找真实对象 + const realObj = this._findRealObjectById(layer.fabricObject.id); + if (realObj && realObj.visible !== false) { + realObjects.push(realObj); + } + } + + // 递归收集子图层的对象 + if (isChildren && layer.children && layer.children.length > 0) { + for (let i = layer.children.length - 1; i >= 0; i--) { + const childLayer = layer.children[i]; + const childObjects = this._collectObjectsFromLayer(childLayer, isChildren); + realObjects.push(...childObjects); + } + } + + return realObjects; + } + + /** + * 通过ID在画布中查找真实对象 + * @param {String} objectId 对象ID + * @returns {Object|null} 画布中的真实对象 + * @private + */ + _findRealObjectById(objectId) { + if (!objectId || !this.canvas) { + return null; + } + + try { + // 使用helper工具查找对象 + const result = findObjectById(this.canvas, objectId); + return result?.object || null; + } catch (error) { + console.warn(`查找对象 ${objectId} 失败:`, error); + return null; + } + } + + /** + * 导出对象 + * @param {Object} obj fabric对象 + * @param {String} expPicType 导出类型 + * @param {Boolean} isRedGreenMode 是否为红绿图模式 + * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 + * @returns {String} 图片数据URL + * @private + */ + async _exportObject( + obj, + expPicType, + isRedGreenMode, + restoreOpacityInRedGreen + ) { + // 红绿图模式下使用固定尺寸和裁剪 + if (isRedGreenMode) { + return this._exportWithRedGreenMode( + [obj], + expPicType, + restoreOpacityInRedGreen + ); + } + + // 普通模式使用画布尺寸 + return await this._exportWithCanvasSize( + [obj], + expPicType, + restoreOpacityInRedGreen + ); + } + + /** + * 按图层顺序收集对象(优化版本 - 从底到顶) + * @param {Array|null} layerIdArray 图层ID数组,null表示所有图层 + * @param {Boolean} isContainBg 是否包含背景图层 + * @param {Boolean} isContainFixed 是否包含固定图层 + * @param {Boolean} isContainFixedOther 是否包含其他固定图层 + * @param {Boolean} isContainNormalLayer 是否包含普通图层 + * @param {Array} excludedLayers 排除的图层ID数组 + * @returns {Array} 按正确顺序排列的真实对象数组 + * @private + */ + _collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer, excludedLayers) { + const objectsToExport = []; + const allLayers = this._getAllLayersFlattened(excludedLayers); // 获取扁平化的图层列表 + + // 图层数组是从顶到底的顺序,需要反向遍历以获得从底到顶的渲染顺序 + for (let i = allLayers.length - 1; i >= 0; i--) { + const layer = allLayers[i]; + + // 如果指定了图层ID数组,只处理指定的图层 + if (layerIdArray && !layerIdArray.includes(layer.id)) continue; + + // 检查图层类型过滤条件 + if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer)) + continue; + + if (layer.visible) { + const layerObjects = this._collectObjectsFromLayer(layer, false); + objectsToExport.push(...layerObjects); + } + } + + return objectsToExport; + } + + /** + * 获取扁平化的图层列表(包含子图层),排除指定的图层 + * @param {Array} excludedLayers 排除的图层ID数组 + * @returns {Array} 扁平化的图层数组 + * @private + */ + _getAllLayersFlattened(excludedLayers) { + const flattenedLayers = []; + const rootLayers = this._getAllLayers(); + + const flattenLayer = (layer) => { + // 检查是否在排除列表中 + if (excludedLayers && excludedLayers.includes(layer.id)) return; + + flattenedLayers.push(layer); + + // 递归处理子图层 + if (layer.children && layer.children.length > 0) { + for (const childLayer of layer.children) { + flattenLayer(childLayer); + } + } + }; + + // 处理所有根图层 + for (const layer of rootLayers) { + flattenLayer(layer); + } + return flattenedLayers; + } + + /** + * 计算对象组的边界 + * @param {Array} objects 对象数组 + * @returns {Object} 边界信息 {left, top, width, height} + * @private + */ + _calculateGroupBounds(objects) { + if (!objects || objects.length === 0) { + return { left: 0, top: 0, width: 1, height: 1 }; + } + + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + + objects.forEach((obj) => { + if (!obj || typeof obj.getBoundingRect !== "function") { + return; + } + + const bounds = obj.getBoundingRect(); + minX = Math.min(minX, bounds.left); + minY = Math.min(minY, bounds.top); + maxX = Math.max(maxX, bounds.left + bounds.width); + maxY = Math.max(maxY, bounds.top + bounds.height); + }); + + if (minX === Infinity || minY === Infinity) { + return { left: 0, top: 0, width: 1, height: 1 }; + } + + // 添加小量边距避免边缘裁切 + const padding = 2; + return { + left: minX - padding, + top: minY - padding, + width: maxX - minX + padding * 2, + height: maxY - minY + padding * 2, + }; + } + + /** + * 克隆对象并添加到临时画布,调整位置偏移 + * @param {fabric.Canvas} tempCanvas 临时画布 + * @param {Object} obj 要克隆的对象 + * @param {Object} bounds 边界信息 + * @param {Boolean} isRedGreenMode 是否为红绿图模式 + * @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度 + * @returns {Promise} 克隆的对象 + * @private + */ + async _cloneAndAddObjectWithOffset( + tempCanvas, + obj, + bounds, + isRedGreenMode, + restoreOpacityInRedGreen + ) { + try { + const cloned = await this._cloneObjectForExport( + obj, + isRedGreenMode && restoreOpacityInRedGreen + ); + + if (cloned) { + // 获取对象当前边界 + const objBounds = obj.getBoundingRect(); + + // 计算相对于组边界的偏移 + const offsetX = objBounds.left - bounds.left; + const offsetY = objBounds.top - bounds.top; + + // 设置新位置(相对于临时画布的原点) + cloned.set({ + left: offsetX + objBounds.width / 2, + top: offsetY + objBounds.height / 2, + originX: "center", + originY: "center", + }); + + cloned.setCoords(); + tempCanvas.add(cloned); + return cloned; + } + } catch (error) { + console.warn(`克隆对象失败: ${obj?.id || "未知"}`, error); + } + + return null; + } + + /** + * 红绿图模式导出(使用固定图层底图作为画布尺寸和裁剪区域) + * @param {Array} objectsToExport 要导出的对象数组 + * @param {String} expPicType 导出类型 + * @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度为1 + * @returns {String} 图片数据URL + * @private + */ + async _exportWithRedGreenMode( + objectsToExport, + expPicType, + restoreOpacityInRedGreen + ) { + // 获取固定图层对象(衣服底图)作为参考 + const fixedLayerObject = + this._getFixedLayerObject() ?? this.canvas.clipPath; + if (!fixedLayerObject) { + console.warn("红绿图模式下未找到固定图层对象,使用画布尺寸"); + return await this._exportWithCanvasSize( + objectsToExport, + expPicType, + restoreOpacityInRedGreen, + ); + } + + // 使用固定图层的实际显示尺寸作为导出画布尺寸 + const canvasWidth = (fixedLayerObject.width); + const canvasHeight = (fixedLayerObject.height); + + console.log(`红绿图模式导出,画布尺寸: ${canvasWidth}x${canvasHeight}`); + const tempFabricCanvas = new fabric.StaticCanvas() + tempFabricCanvas.setDimensions({ + width: canvasWidth, + height: canvasHeight, + backgroundColor: null, + // enableRetinaScaling: true, + imageSmoothingEnabled: true, + }); + // 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) + try { + // 克隆并添加所有对象到临时画布,需要调整位置相对于固定图层 + for (let i = 0; i < objectsToExport.length; i++) { + const obj = objectsToExport[i]; + const cloned = await this._cloneObjectForExport( + obj, + restoreOpacityInRedGreen && true + ); + if (cloned) { + let scaleX = cloned.scaleX / fixedLayerObject.scaleX + let scaleY = cloned.scaleY / fixedLayerObject.scaleY + let top = (cloned.top - oy) * scaleY + let left = (cloned.left - ox) * scaleX + cloned.set({ + left: left, + top: top, + scaleX: scaleX, + scaleY: scaleY, + }); + // 更新对象坐标 + cloned.setCoords(); + tempFabricCanvas.add(cloned); + } + } + + // 渲染画布 + tempFabricCanvas.renderAll(); + + // 生成图片 + return this._generateHighQualityDataURL(tempFabricCanvas, expPicType); + } finally { + this._cleanupTempCanvas(tempFabricCanvas); + } + } + + /** + * 普通模式导出(使用画布尺寸) + * @param {Array} objectsToExport 要导出的对象数组 + * @param {String} expPicType 导出类型 + * @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度为1 + * @param {Object} maskObject 裁剪对象 + * @param {Boolean} isCropByBg 是否使用背景大小裁剪 + * @param {Boolean} isEnhanceImg 是否是增强图片 + * @returns {String} 图片数据URL + * @private + */ + async _exportWithCanvasSize( + objectsToExport, + expPicType, + restoreOpacityInRedGreen, + maskObject, // 裁剪对象 isCropByBg, // 是否使用背景大小裁剪 isEnhanceImg, // 是否是增强图片 - ) { - if (!this.layerManager) { - throw new Error("图层管理器未初始化"); - } + ) { + // 使用当前画布尺寸 + // const canvasWidth = + // this.canvasManager?.canvasWidth?.value || this.canvas.width; + // const canvasHeight = + // this.canvasManager?.canvasHeight?.value || this.canvas.height; - const layer = this._getLayerById(layerId); - if (!layer) { - throw new Error(`未找到ID为 ${layerId} 的图层`); - } - - if (!layer.visible) { - console.warn(`图层 ${layer.name} 不可见,将导出空白图片`); - } - - // 收集所有需要导出的对象 - const objectsToExport = this._collectObjectsFromLayer(layer); - - if (objectsToExport.length === 0) { - console.warn(`图层 ${layer.name} 没有可导出的对象`); - return this._generateEmptyImage(expPicType); - } - - // 红绿图模式下使用固定尺寸和裁剪 - if (isRedGreenMode) { - return this._exportWithRedGreenMode( - objectsToExport, - expPicType, - restoreOpacityInRedGreen, - ); - } - - // 普通模式使用画布尺寸 - return await this._exportWithCanvasSize( - objectsToExport, - expPicType, - restoreOpacityInRedGreen, + // console.log(`普通模式导出,画布尺寸: ${canvasWidth}x${canvasHeight}`); + // 使用图层栅格化的方法导出图片 + const dataURL = await createRasterizedImage({ + canvas: this.canvas, + fabricObjects: objectsToExport, + format: expPicType, // 导出格式 + isReturenDataURL: true, // 返回数据URL + maskObject: maskObject ?? null, // 使用裁剪对象 + trimWhitespace: true, // 裁剪空白 + trimPadding: 0, // 裁剪边距 + restoreOpacityInRedGreen, isCropByBg, // 是否使用背景大小裁剪 isEnhanceImg, // 是否是增强图片 - ); - } - - /** - * 导出多个指定图层 - * @param {Array} layerIdArray 图层ID数组 - * @param {String} expPicType 导出类型 - * @param {Boolean} isContainBg 是否包含背景图层 - * @param {Boolean} isContainFixed 是否包含固定图层 - * @param {Boolean} isContainFixedOther 是否包含其他固定图层 - * @param {Boolean} isContainNormalLayer 是否包含普通图层 - * @param {Boolean} isRedGreenMode 是否为红绿图模式 - * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 - * @param {Boolean} isCropByBg 是否使用背景大小裁剪 - * @param {Boolean} isEnhanceImg 是否是增强图片 - * @returns {String} 图片数据URL - * @private - */ - async _exportMultipleLayers( - layerIdArray, - expPicType, - isContainBg, - isContainFixed, - isContainFixedOther, // 是否包含其他固定图层 - isContainNormalLayer = true, // 是否包含普通图层 - isRedGreenMode, - restoreOpacityInRedGreen, - isCropByBg, // 是否使用背景大小裁剪 - isEnhanceImg, // 是否是增强图片 - ) { - if (!this.layerManager) { - throw new Error("图层管理器未初始化"); - } - - // 按图层顺序收集对象(从底到顶) - const objectsToExport = this._collectObjectsByLayerOrder( - layerIdArray, - isContainBg, - isContainFixed, - isContainFixedOther, // 是否包含其他固定图层 - isContainNormalLayer, // 是否包含普通图层 - ); - - if (objectsToExport.length === 0) { - console.warn("没有可导出的对象"); - return this._generateEmptyImage(expPicType); - } - - // 红绿图模式下使用固定尺寸和裁剪 - if (isRedGreenMode) { - return this._exportWithRedGreenMode( - objectsToExport, - expPicType, - restoreOpacityInRedGreen - ); - } - - // 普通模式使用画布尺寸 - return await this._exportWithCanvasSize( - objectsToExport, - expPicType, - restoreOpacityInRedGreen, - isCropByBg, // 是否使用背景大小裁剪 - isEnhanceImg, // 是否是增强图片 - ); - } - - /** - * 导出所有图层 - * @param {String} expPicType 导出类型 - * @param {Boolean} isContainBg 是否包含背景图层 - * @param {Boolean} isContainFixed 是否包含固定图层 - * @param {Boolean} isContainFixedOther 是否包含其他固定图层 - * @param {Boolean} isContainNormalLayer 是否包含普通图层 - * @param {Boolean} isRedGreenMode 是否为红绿图模式 - * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 - * @param {Boolean} isCropByBg 是否使用背景大小裁剪 - * @param {Boolean} isEnhanceImg 是否是增强图片 - * @param {Array} layerIdArray 导出多个图层ID数组2 - * @returns {String} 图片数据URL - * @private - */ - async _exportAllLayers( - expPicType, - isContainBg, - isContainFixed, - isContainFixedOther, // 是否包含其他固定图层 - isContainNormalLayer, // 是否包含普通图层 - isRedGreenMode, - restoreOpacityInRedGreen, - isCropByBg, // 是否使用背景大小裁剪 - isEnhanceImg, // 是否是增强图片 - layerIdArray, // 导出所有图层 - excludedLayers, // 排除的图层ID数组 - ) { - // 按图层顺序收集对象(从底到顶) - const objectsToExport = this._collectObjectsByLayerOrder( - layerIdArray, // 导出所有图层 - isContainBg, - isContainFixed, - isContainFixedOther, // 是否包含其他固定图层 - isContainNormalLayer, // 是否包含普通图层 - excludedLayers, - ); - - if (objectsToExport.length === 0) { - console.warn("没有可导出的对象"); - return this._generateEmptyImage(expPicType); - } - - // 红绿图模式下使用固定尺寸和裁剪 - if (isRedGreenMode) { - return this._exportWithRedGreenMode( - objectsToExport, - expPicType, - restoreOpacityInRedGreen - ); - } - let canvasClipPath = this.canvas.clipPath; - if (isCropByBg) { - const cropWidth = - this.canvasManager?.canvasWidth?.value || - this.canvas?.canvasWidth || - this.canvas.width; - const cropHeight = - this.canvasManager?.canvasHeight?.value || - this.canvas?.canvasHeight || - this.canvas.height; - canvasClipPath = new fabric.Rect({ - left: this.canvas.width / 2, - top: this.canvas.height / 2, - width: cropWidth, - height: cropHeight, - originX: "center", - originY: "center", - fill: "#fff", - stroke: "transparent", - strokeWidth: 0, - }); - canvasClipPath.set({ - absolutePositioned: true, - }); - canvasClipPath.setCoords(); - } - // 普通模式使用画布尺寸 - return await this._exportWithCanvasSize( - objectsToExport, - expPicType, - restoreOpacityInRedGreen, - canvasClipPath, - isCropByBg, // 是否使用背景大小裁剪 - isEnhanceImg, // 是否是增强图片 - ); - } - - /** - * 从图层收集对象(优化版本 - 通过ID查找画布中的真实对象) - * @param {Object} layer 图层对象 - * @param {Boolean} isChildren 是否递归收集子图层的对象 - * @returns {Array} 画布中的真实对象数组 - * @private - */ - _collectObjectsFromLayer(layer, isChildren = true) { - if (!layer) { - return []; - } - - const realObjects = []; - - // 收集当前图层的对象 - if (layer.fabricObjects && layer.fabricObjects.length > 0) { - for (const layerObj of layer.fabricObjects) { - if (!layerObj || !layerObj.id) continue; - - // 通过ID在画布中查找真实对象 - const realObj = this._findRealObjectById(layerObj.id); - if (realObj && realObj.visible !== false) { - realObjects.push(realObj); - } - } - } - - if (layer.fabricObject) { - // 通过ID在画布中查找真实对象 - const realObj = this._findRealObjectById(layer.fabricObject.id); - if (realObj && realObj.visible !== false) { - realObjects.push(realObj); - } - } - - // 递归收集子图层的对象 - if (isChildren && layer.children && layer.children.length > 0) { - for (let i = layer.children.length - 1; i >= 0; i--) { - const childLayer = layer.children[i]; - const childObjects = this._collectObjectsFromLayer(childLayer, isChildren); - realObjects.push(...childObjects); - } - } - - return realObjects; - } - - /** - * 通过ID在画布中查找真实对象 - * @param {String} objectId 对象ID - * @returns {Object|null} 画布中的真实对象 - * @private - */ - _findRealObjectById(objectId) { - if (!objectId || !this.canvas) { - return null; - } - - try { - // 使用helper工具查找对象 - const result = findObjectById(this.canvas, objectId); - return result?.object || null; - } catch (error) { - console.warn(`查找对象 ${objectId} 失败:`, error); - return null; - } - } - - /** - * 导出对象 - * @param {Object} obj fabric对象 - * @param {String} expPicType 导出类型 - * @param {Boolean} isRedGreenMode 是否为红绿图模式 - * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 - * @returns {String} 图片数据URL - * @private - */ - async _exportObject( - obj, - expPicType, - isRedGreenMode, - restoreOpacityInRedGreen - ) { - // 红绿图模式下使用固定尺寸和裁剪 - if (isRedGreenMode) { - return this._exportWithRedGreenMode( - [obj], - expPicType, - restoreOpacityInRedGreen - ); - } - - // 普通模式使用画布尺寸 - return await this._exportWithCanvasSize( - [obj], - expPicType, - restoreOpacityInRedGreen - ); - } - - /** - * 按图层顺序收集对象(优化版本 - 从底到顶) - * @param {Array|null} layerIdArray 图层ID数组,null表示所有图层 - * @param {Boolean} isContainBg 是否包含背景图层 - * @param {Boolean} isContainFixed 是否包含固定图层 - * @param {Boolean} isContainFixedOther 是否包含其他固定图层 - * @param {Boolean} isContainNormalLayer 是否包含普通图层 - * @param {Array} excludedLayers 排除的图层ID数组 - * @returns {Array} 按正确顺序排列的真实对象数组 - * @private - */ - _collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer, excludedLayers) { - const objectsToExport = []; - const allLayers = this._getAllLayersFlattened(excludedLayers); // 获取扁平化的图层列表 - - // 图层数组是从顶到底的顺序,需要反向遍历以获得从底到顶的渲染顺序 - for (let i = allLayers.length - 1; i >= 0; i--) { - const layer = allLayers[i]; - - // 如果指定了图层ID数组,只处理指定的图层 - if (layerIdArray && !layerIdArray.includes(layer.id)) continue; - - // 检查图层类型过滤条件 - if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer)) - continue; - - if (layer.visible) { - const layerObjects = this._collectObjectsFromLayer(layer, false); - objectsToExport.push(...layerObjects); - } - } - - return objectsToExport; - } - - /** - * 获取扁平化的图层列表(包含子图层),排除指定的图层 - * @param {Array} excludedLayers 排除的图层ID数组 - * @returns {Array} 扁平化的图层数组 - * @private - */ - _getAllLayersFlattened(excludedLayers) { - const flattenedLayers = []; - const rootLayers = this._getAllLayers(); - - const flattenLayer = (layer) => { - // 检查是否在排除列表中 - if (excludedLayers && excludedLayers.includes(layer.id)) return; - - flattenedLayers.push(layer); - - // 递归处理子图层 - if (layer.children && layer.children.length > 0) { - for (const childLayer of layer.children) { - flattenLayer(childLayer); - } - } - }; - - // 处理所有根图层 - for (const layer of rootLayers) { - flattenLayer(layer); - } - return flattenedLayers; - } - - /** - * 计算对象组的边界 - * @param {Array} objects 对象数组 - * @returns {Object} 边界信息 {left, top, width, height} - * @private - */ - _calculateGroupBounds(objects) { - if (!objects || objects.length === 0) { - return { left: 0, top: 0, width: 1, height: 1 }; - } - - let minX = Infinity; - let minY = Infinity; - let maxX = -Infinity; - let maxY = -Infinity; - - objects.forEach((obj) => { - if (!obj || typeof obj.getBoundingRect !== "function") { - return; - } - - const bounds = obj.getBoundingRect(); - minX = Math.min(minX, bounds.left); - minY = Math.min(minY, bounds.top); - maxX = Math.max(maxX, bounds.left + bounds.width); - maxY = Math.max(maxY, bounds.top + bounds.height); - }); - - if (minX === Infinity || minY === Infinity) { - return { left: 0, top: 0, width: 1, height: 1 }; - } - - // 添加小量边距避免边缘裁切 - const padding = 2; - return { - left: minX - padding, - top: minY - padding, - width: maxX - minX + padding * 2, - height: maxY - minY + padding * 2, - }; - } - - /** - * 克隆对象并添加到临时画布,调整位置偏移 - * @param {fabric.Canvas} tempCanvas 临时画布 - * @param {Object} obj 要克隆的对象 - * @param {Object} bounds 边界信息 - * @param {Boolean} isRedGreenMode 是否为红绿图模式 - * @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度 - * @returns {Promise} 克隆的对象 - * @private - */ - async _cloneAndAddObjectWithOffset( - tempCanvas, - obj, - bounds, - isRedGreenMode, - restoreOpacityInRedGreen - ) { - try { - const cloned = await this._cloneObjectForExport( - obj, - isRedGreenMode && restoreOpacityInRedGreen - ); - - if (cloned) { - // 获取对象当前边界 - const objBounds = obj.getBoundingRect(); - - // 计算相对于组边界的偏移 - const offsetX = objBounds.left - bounds.left; - const offsetY = objBounds.top - bounds.top; - - // 设置新位置(相对于临时画布的原点) - cloned.set({ - left: offsetX + objBounds.width / 2, - top: offsetY + objBounds.height / 2, - originX: "center", - originY: "center", - }); - - cloned.setCoords(); - tempCanvas.add(cloned); - return cloned; - } - } catch (error) { - console.warn(`克隆对象失败: ${obj?.id || "未知"}`, error); - } - - return null; - } - - /** - * 红绿图模式导出(使用固定图层底图作为画布尺寸和裁剪区域) - * @param {Array} objectsToExport 要导出的对象数组 - * @param {String} expPicType 导出类型 - * @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度为1 - * @returns {String} 图片数据URL - * @private - */ - async _exportWithRedGreenMode( - objectsToExport, - expPicType, - restoreOpacityInRedGreen - ) { - // 获取固定图层对象(衣服底图)作为参考 - const fixedLayerObject = - this._getFixedLayerObject() ?? this.canvas.clipPath; - if (!fixedLayerObject) { - console.warn("红绿图模式下未找到固定图层对象,使用画布尺寸"); - return await this._exportWithCanvasSize( - objectsToExport, - expPicType, - restoreOpacityInRedGreen, - ); - } - - // 使用固定图层的实际显示尺寸作为导出画布尺寸 - const canvasWidth = (fixedLayerObject.width); - const canvasHeight = (fixedLayerObject.height); - - console.log(`红绿图模式导出,画布尺寸: ${canvasWidth}x${canvasHeight}`); - const tempFabricCanvas = new fabric.StaticCanvas() - tempFabricCanvas.setDimensions({ - width: canvasWidth, - height: canvasHeight, - backgroundColor: null, - // enableRetinaScaling: true, - imageSmoothingEnabled: true, - }); - // tempFabricCanvas.setZoom(1); - console.log("==========", fixedLayerObject) - try { - // 克隆并添加所有对象到临时画布,需要调整位置相对于固定图层 - for (let i = 0; i < objectsToExport.length; i++) { - const obj = objectsToExport[i]; - const cloned = await this._cloneObjectForExport( - obj, - restoreOpacityInRedGreen && true - ); - if (cloned) { - cloned.set({ - left: canvasWidth / 2, - top: canvasHeight / 2, - scaleX: cloned.scaleX / fixedLayerObject.scaleX, - scaleY: cloned.scaleY / fixedLayerObject.scaleY, - originX: "center", - originY: "center", - }); - console.log("==========", {...cloned}) - // 更新对象坐标 - cloned.setCoords(); - tempFabricCanvas.add(cloned); - } - } - - // 渲染画布 - tempFabricCanvas.renderAll(); - - // 生成图片 - return this._generateHighQualityDataURL(tempFabricCanvas, expPicType); - } finally { - this._cleanupTempCanvas(tempFabricCanvas); - } - } - - /** - * 普通模式导出(使用画布尺寸) - * @param {Array} objectsToExport 要导出的对象数组 - * @param {String} expPicType 导出类型 - * @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度为1 - * @param {Object} maskObject 裁剪对象 - * @param {Boolean} isCropByBg 是否使用背景大小裁剪 - * @param {Boolean} isEnhanceImg 是否是增强图片 - * @returns {String} 图片数据URL - * @private - */ - async _exportWithCanvasSize( - objectsToExport, - expPicType, - restoreOpacityInRedGreen, - maskObject, // 裁剪对象 - isCropByBg, // 是否使用背景大小裁剪 - isEnhanceImg, // 是否是增强图片 - ) { - // 使用当前画布尺寸 - // const canvasWidth = - // this.canvasManager?.canvasWidth?.value || this.canvas.width; - // const canvasHeight = - // this.canvasManager?.canvasHeight?.value || this.canvas.height; - - // console.log(`普通模式导出,画布尺寸: ${canvasWidth}x${canvasHeight}`); - // 使用图层栅格化的方法导出图片 - const dataURL = await createRasterizedImage({ - canvas: this.canvas, - fabricObjects: objectsToExport, - format: expPicType, // 导出格式 - isReturenDataURL: true, // 返回数据URL - maskObject: maskObject ?? null, // 使用裁剪对象 - trimWhitespace: true, // 裁剪空白 - trimPadding: 0, // 裁剪边距 - restoreOpacityInRedGreen, - isCropByBg, // 是否使用背景大小裁剪 - isEnhanceImg, // 是否是增强图片 - }); - - // console.log("导出图片数据URL:", dataURL); - return dataURL; - - // // 创建与画布相同尺寸的临时画布 - // const scaleFactor = 2; // 高清导出 - // const tempCanvas = document.createElement("canvas"); - // tempCanvas.width = canvasWidth * scaleFactor; - // tempCanvas.height = canvasHeight * scaleFactor; - // tempCanvas.style.width = canvasWidth + "px"; - // tempCanvas.style.height = canvasHeight + "px"; - - // const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, { - // width: canvasWidth, - // height: canvasHeight, - // backgroundColor: null, - // }); - - // tempFabricCanvas.enableRetinaScaling = true; - // tempFabricCanvas.imageSmoothingEnabled = true; - // tempFabricCanvas.setZoom(1); - - // try { - // // 克隆并添加所有对象到临时画布 - // for (const obj of objectsToExport) { - // const cloned = await this._cloneObjectForExport( - // obj, - // restoreOpacityInRedGreen && false, // 普通模式不强制恢复透明度 - // ); - // if (cloned) { - // tempFabricCanvas.add(cloned); - // } - // } - - // // 渲染画布 - // tempFabricCanvas.renderAll(); - - // // 生成图片 - // return this._generateHighQualityDataURL(tempCanvas, expPicType); - // } finally { - // this._cleanupTempCanvas(tempFabricCanvas); - // } - } - - /** - * 获取固定图层对象 - * @returns {Object|null} 固定图层对象 - * @private - */ - _getFixedLayerObject() { - const allLayers = this._getAllLayers(); - const fixedLayer = allLayers.find((layer) => layer.isFixed); - - if (!fixedLayer || !fixedLayer.fabricObject) { - return null; - } - - // 如果有ID,通过ID查找画布中的实际对象 - if (fixedLayer.fabricObject.id) { - const result = findObjectById(this.canvas, fixedLayer.fabricObject.id); - return result.object || fixedLayer.fabricObject; - } - - return fixedLayer.fabricObject; - } - - /** - * 异步克隆fabric对象(参照createRasterizedImage的方法) - * @param {fabric.Object} obj 要克隆的对象 - * @param {Array} propertiesToInclude 要包含的属性 - * @returns {Promise} 克隆的对象 - * @private - */ - _cloneObjectAsync( - obj, - propertiesToInclude = ["id", "layerId", "layerName", "name", "scaleX", "scaleY"] - ) { - return new Promise((resolve, reject) => { - if (!obj) { - resolve(null); - return; - } - - try { - obj.clone((cloned) => { - if (cloned) { - resolve(cloned); - } else { - reject(new Error("对象克隆失败")); - } - }, propertiesToInclude); - } catch (error) { - console.warn("克隆对象失败:", error); - resolve(null); - } - }); - } - - /** - * 克隆对象用于导出(优化版本) - * @param {Object} obj fabric对象 - * @param {Boolean} forceRestoreOpacity 是否强制恢复透明度为1 - * @param {Boolean} removeClipPath 是否移除裁剪路径 - * @returns {Promise} 克隆的对象 - * @private - */ - async _cloneObjectForExport( - obj, - forceRestoreOpacity = false, - removeClipPath = true - ) { - if (!obj) return null; - - try { - // 使用异步克隆方法 - const cloned = await this._cloneObjectAsync(obj); - - if (cloned) { - // 保持原始位置和属性 - cloned.set({ - selectable: false, - evented: false, - visible: true, - }); - - // 如果需要恢复透明度 - if (forceRestoreOpacity) { - cloned.set({ opacity: 1 }); - } - - // 移除裁剪路径以避免绝对路径问题 - if (removeClipPath && cloned.clipPath) { - console.log(`移除对象 ${cloned.id || "未知"} 的裁剪路径`); - cloned.clipPath = null; - } - - return cloned; - } - } catch (error) { - console.warn("克隆对象失败:", error); - } - - return null; - } - - /** - * 导出对象组 - * @param {Array} objectsToExport 要导出的对象数组 - * @param {String} expPicType 导出类型 - * @param {Boolean} isRedGreenMode 是否为红绿图模式 - * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 - * @returns {Promise} 图片数据URL - * @private - */ - async _exportObjectsAsGroup( - objectsToExport, - expPicType, - isRedGreenMode = false, - restoreOpacityInRedGreen = true - ) { - if (!objectsToExport || objectsToExport.length === 0) { - throw new Error("没有可导出的对象"); - } - - // 计算所有对象的边界 - const bounds = this._calculateGroupBounds(objectsToExport); - console.log("导出边界:", bounds); - - // 创建高质量临时画布 - const scaleFactor = 2; // 高清导出 - const tempCanvas = document.createElement("canvas"); - tempCanvas.width = bounds.width * scaleFactor; - tempCanvas.height = bounds.height * scaleFactor; - tempCanvas.style.width = bounds.width + "px"; - tempCanvas.style.height = bounds.height + "px"; - - const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, { - width: bounds.width, - height: bounds.height, - backgroundColor: null, // 透明背景 - }); - - // 启用高清缩放和图像平滑 - tempFabricCanvas.enableRetinaScaling = true; - tempFabricCanvas.imageSmoothingEnabled = true; - tempFabricCanvas.setZoom(scaleFactor); - - try { - // 克隆所有对象并添加到临时画布 - const clonedObjects = []; - for (const obj of objectsToExport) { - const cloned = await this._cloneAndAddObjectWithOffset( - tempFabricCanvas, - obj, - bounds, - isRedGreenMode, - restoreOpacityInRedGreen - ); - if (cloned) { - clonedObjects.push(cloned); - } - } - - console.log(`成功克隆 ${clonedObjects.length} 个对象进行导出`); - - // 渲染画布 - tempFabricCanvas.renderAll(); - - // 生成高质量数据URL - return this._generateHighQualityDataURL(tempCanvas, expPicType); - } finally { - this._cleanupTempCanvas(tempFabricCanvas); - } - } - - /** - * 获取裁剪路径对象(优化版本) - * @param {Object} fixedBounds 固定图层边界 - * @returns {Promise} 裁剪路径对象 - * @private - */ - async _getClipPathObject(fixedBounds) { - try { - // const allLayers = this._getAllLayers(); - - // // 查找第一个有裁剪遮罩的图层 - // let clipObject = null; - - // for (const layer of allLayers) { - // if (layer.clippingMask?.id) { - // const result = findObjectById(this.canvas, layer.clippingMask.id); - // if (result?.object) { - // clipObject = result.object; - // break; - // } - // } - // } - const clipObject = this.canvas?.clipPath; - if (!clipObject) { - console.warn("未找到可用的裁剪对象"); - return null; - } - - // 克隆对象作为裁剪路径 - const clonedClipPath = await this._cloneObjectForExport( - clipObject, - false, - false - ); - - if (!clonedClipPath) { - console.warn("无法克隆裁剪对象"); - return null; - } - - // 调整裁剪路径的位置相对于固定图层 - clonedClipPath.set({ - left: clonedClipPath.left - fixedBounds.left, - top: clonedClipPath.top - fixedBounds.top, - absolutePositioned: true, // 使用绝对定位 - }); - - // 更新坐标 - clonedClipPath.setCoords(); - - console.log("成功创建裁剪路径:", { - objectType: clonedClipPath.type, - position: { left: clonedClipPath.left, top: clonedClipPath.top }, - size: { width: clonedClipPath.width, height: clonedClipPath.height }, - }); - - return clonedClipPath; - } catch (error) { - console.error("获取裁剪路径失败:", error); - return null; - } - } - - /** - * 生成高质量数据URL - * @param {HTMLCanvasElement} canvas 画布元素 - * @param {String} expPicType 导出类型 - * @returns {String} 数据URL - * @private - */ - _generateHighQualityDataURL(canvas, expPicType) { - const format = expPicType.toLowerCase(); - - switch (format) { - case "jpg": - case "jpeg": - // 对于JPEG,使用较高质量,但JPEG不支持透明背景 - return canvas.toDataURL("image/jpeg", 0.95); - case "svg": - // SVG导出需要特殊处理,这里先返回高质量PNG - console.warn("SVG导出暂未实现,返回高质量PNG格式"); - return canvas.toDataURL("image/png", 1.0); - case "png": - default: - // PNG使用最高质量,支持透明背景 - return canvas.toDataURL("image/png", 1.0); - } - } - - /** - * 生成空白图片 - * @param {String} expPicType 导出类型 - * @returns {String} 空白图片数据URL - * @private - */ - _generateEmptyImage(expPicType) { - const emptyCanvas = document.createElement("canvas"); - emptyCanvas.width = 1; - emptyCanvas.height = 1; - - // 确保透明背景 - const ctx = emptyCanvas.getContext("2d"); - ctx.clearRect(0, 0, 1, 1); - - return this._generateHighQualityDataURL(emptyCanvas, expPicType); - } - - /** - * 清理临时画布资源 - * @param {fabric.StaticCanvas} tempFabricCanvas 临时Fabric画布 - * @private - */ - _cleanupTempCanvas(tempFabricCanvas) { - if (tempFabricCanvas) { - try { - tempFabricCanvas.dispose(); - } catch (error) { - console.warn("清理临时画布失败:", error); - } - } - } - - /** - * 获取所有图层 - * @returns {Array} 图层数组 - * @private - */ - _getAllLayers() { - if (this.layerManager && this.layerManager.layers) { - return this.layerManager.layers.value || []; - } - return []; - } - - /** - * 根据ID获取图层 - * @param {String} layerId 图层ID - * @returns {Object|null} 图层对象 - * @private - */ - _getLayerById(layerId) { - if (this.layerManager && this.layerManager.getLayerById) { - return this.layerManager.getLayerById(layerId); - } - - // 备用方法:直接从图层数组中查找 - const allLayers = this._getAllLayers(); - return allLayers.find((layer) => layer.id === layerId) || null; - } - - /** - * 检查图层是否应该包含在导出中 - * @param {Object} layer 图层对象 - * @param {Boolean} isContainBg 是否包含背景图层 - * @param {Boolean} isContainFixed 是否包含固定图层 - * @param {Boolean} isContainFixedOther 是否包含其他固定图层 - * @param {Boolean} isContainNormalLayer 是否包含普通图层 - * @returns {Boolean} 是否应该包含 - * @private - */ - _shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer) { - if (!layer) return false; - - // 检查背景图层 - if (layer.isBackground) { - return isContainBg; - } - - // 检查固定图层 - if (layer.isFixed) { - return isContainFixed; - } - - // 检查其他固定图层 - if (layer.isFixedOther) { - return isContainFixedOther; - } - - // 印花图层始终导出 - if (layer.isPrintTrims || layer.isPrintTrimsGroup) { - return true; - } - - // 普通图层 - return isContainNormalLayer; - } + }); + + // console.log("导出图片数据URL:", dataURL); + return dataURL; + + // // 创建与画布相同尺寸的临时画布 + // const scaleFactor = 2; // 高清导出 + // const tempCanvas = document.createElement("canvas"); + // tempCanvas.width = canvasWidth * scaleFactor; + // tempCanvas.height = canvasHeight * scaleFactor; + // tempCanvas.style.width = canvasWidth + "px"; + // tempCanvas.style.height = canvasHeight + "px"; + + // const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, { + // width: canvasWidth, + // height: canvasHeight, + // backgroundColor: null, + // }); + + // tempFabricCanvas.enableRetinaScaling = true; + // tempFabricCanvas.imageSmoothingEnabled = true; + // tempFabricCanvas.setZoom(1); + + // try { + // // 克隆并添加所有对象到临时画布 + // for (const obj of objectsToExport) { + // const cloned = await this._cloneObjectForExport( + // obj, + // restoreOpacityInRedGreen && false, // 普通模式不强制恢复透明度 + // ); + // if (cloned) { + // tempFabricCanvas.add(cloned); + // } + // } + + // // 渲染画布 + // tempFabricCanvas.renderAll(); + + // // 生成图片 + // return this._generateHighQualityDataURL(tempCanvas, expPicType); + // } finally { + // this._cleanupTempCanvas(tempFabricCanvas); + // } + } + + /** + * 获取固定图层对象 + * @returns {Object|null} 固定图层对象 + * @private + */ + _getFixedLayerObject() { + const allLayers = this._getAllLayers(); + const fixedLayer = allLayers.find((layer) => layer.isFixed); + + if (!fixedLayer || !fixedLayer.fabricObject) { + return null; + } + + // 如果有ID,通过ID查找画布中的实际对象 + if (fixedLayer.fabricObject.id) { + const result = findObjectById(this.canvas, fixedLayer.fabricObject.id); + return result.object || fixedLayer.fabricObject; + } + + return fixedLayer.fabricObject; + } + + /** + * 异步克隆fabric对象(参照createRasterizedImage的方法) + * @param {fabric.Object} obj 要克隆的对象 + * @param {Array} propertiesToInclude 要包含的属性 + * @returns {Promise} 克隆的对象 + * @private + */ + _cloneObjectAsync( + obj, + propertiesToInclude = ["id", "layerId", "layerName", "name", "scaleX", "scaleY"] + ) { + return new Promise((resolve, reject) => { + if (!obj) { + resolve(null); + return; + } + + try { + obj.clone((cloned) => { + if (cloned) { + resolve(cloned); + } else { + reject(new Error("对象克隆失败")); + } + }, propertiesToInclude); + } catch (error) { + console.warn("克隆对象失败:", error); + resolve(null); + } + }); + } + + /** + * 克隆对象用于导出(优化版本) + * @param {Object} obj fabric对象 + * @param {Boolean} forceRestoreOpacity 是否强制恢复透明度为1 + * @param {Boolean} removeClipPath 是否移除裁剪路径 + * @returns {Promise} 克隆的对象 + * @private + */ + async _cloneObjectForExport( + obj, + forceRestoreOpacity = false, + removeClipPath = true + ) { + if (!obj) return null; + + try { + // 使用异步克隆方法 + const cloned = await this._cloneObjectAsync(obj); + + if (cloned) { + // 保持原始位置和属性 + cloned.set({ + selectable: false, + evented: false, + visible: true, + }); + + // 如果需要恢复透明度 + if (forceRestoreOpacity) { + cloned.set({ opacity: 1 }); + } + + // 移除裁剪路径以避免绝对路径问题 + if (removeClipPath && cloned.clipPath) { + console.log(`移除对象 ${cloned.id || "未知"} 的裁剪路径`); + cloned.clipPath = null; + } + + return cloned; + } + } catch (error) { + console.warn("克隆对象失败:", error); + } + + return null; + } + + /** + * 导出对象组 + * @param {Array} objectsToExport 要导出的对象数组 + * @param {String} expPicType 导出类型 + * @param {Boolean} isRedGreenMode 是否为红绿图模式 + * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 + * @returns {Promise} 图片数据URL + * @private + */ + async _exportObjectsAsGroup( + objectsToExport, + expPicType, + isRedGreenMode = false, + restoreOpacityInRedGreen = true + ) { + if (!objectsToExport || objectsToExport.length === 0) { + throw new Error("没有可导出的对象"); + } + + // 计算所有对象的边界 + const bounds = this._calculateGroupBounds(objectsToExport); + console.log("导出边界:", bounds); + + // 创建高质量临时画布 + const scaleFactor = 2; // 高清导出 + const tempCanvas = document.createElement("canvas"); + tempCanvas.width = bounds.width * scaleFactor; + tempCanvas.height = bounds.height * scaleFactor; + tempCanvas.style.width = bounds.width + "px"; + tempCanvas.style.height = bounds.height + "px"; + + const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, { + width: bounds.width, + height: bounds.height, + backgroundColor: null, // 透明背景 + }); + + // 启用高清缩放和图像平滑 + tempFabricCanvas.enableRetinaScaling = true; + tempFabricCanvas.imageSmoothingEnabled = true; + tempFabricCanvas.setZoom(scaleFactor); + + try { + // 克隆所有对象并添加到临时画布 + const clonedObjects = []; + for (const obj of objectsToExport) { + const cloned = await this._cloneAndAddObjectWithOffset( + tempFabricCanvas, + obj, + bounds, + isRedGreenMode, + restoreOpacityInRedGreen + ); + if (cloned) { + clonedObjects.push(cloned); + } + } + + console.log(`成功克隆 ${clonedObjects.length} 个对象进行导出`); + + // 渲染画布 + tempFabricCanvas.renderAll(); + + // 生成高质量数据URL + return this._generateHighQualityDataURL(tempCanvas, expPicType); + } finally { + this._cleanupTempCanvas(tempFabricCanvas); + } + } + + /** + * 获取裁剪路径对象(优化版本) + * @param {Object} fixedBounds 固定图层边界 + * @returns {Promise} 裁剪路径对象 + * @private + */ + async _getClipPathObject(fixedBounds) { + try { + // const allLayers = this._getAllLayers(); + + // // 查找第一个有裁剪遮罩的图层 + // let clipObject = null; + + // for (const layer of allLayers) { + // if (layer.clippingMask?.id) { + // const result = findObjectById(this.canvas, layer.clippingMask.id); + // if (result?.object) { + // clipObject = result.object; + // break; + // } + // } + // } + const clipObject = this.canvas?.clipPath; + if (!clipObject) { + console.warn("未找到可用的裁剪对象"); + return null; + } + + // 克隆对象作为裁剪路径 + const clonedClipPath = await this._cloneObjectForExport( + clipObject, + false, + false + ); + + if (!clonedClipPath) { + console.warn("无法克隆裁剪对象"); + return null; + } + + // 调整裁剪路径的位置相对于固定图层 + clonedClipPath.set({ + left: clonedClipPath.left - fixedBounds.left, + top: clonedClipPath.top - fixedBounds.top, + absolutePositioned: true, // 使用绝对定位 + }); + + // 更新坐标 + clonedClipPath.setCoords(); + + console.log("成功创建裁剪路径:", { + objectType: clonedClipPath.type, + position: { left: clonedClipPath.left, top: clonedClipPath.top }, + size: { width: clonedClipPath.width, height: clonedClipPath.height }, + }); + + return clonedClipPath; + } catch (error) { + console.error("获取裁剪路径失败:", error); + return null; + } + } + + /** + * 生成高质量数据URL + * @param {HTMLCanvasElement} canvas 画布元素 + * @param {String} expPicType 导出类型 + * @returns {String} 数据URL + * @private + */ + _generateHighQualityDataURL(canvas, expPicType) { + const format = expPicType.toLowerCase(); + + switch (format) { + case "jpg": + case "jpeg": + // 对于JPEG,使用较高质量,但JPEG不支持透明背景 + return canvas.toDataURL("image/jpeg", 0.95); + case "svg": + // SVG导出需要特殊处理,这里先返回高质量PNG + console.warn("SVG导出暂未实现,返回高质量PNG格式"); + return canvas.toDataURL("image/png", 1.0); + case "png": + default: + // PNG使用最高质量,支持透明背景 + return canvas.toDataURL("image/png", 1.0); + } + } + + /** + * 生成空白图片 + * @param {String} expPicType 导出类型 + * @returns {String} 空白图片数据URL + * @private + */ + _generateEmptyImage(expPicType) { + const emptyCanvas = document.createElement("canvas"); + emptyCanvas.width = 1; + emptyCanvas.height = 1; + + // 确保透明背景 + const ctx = emptyCanvas.getContext("2d"); + ctx.clearRect(0, 0, 1, 1); + + return this._generateHighQualityDataURL(emptyCanvas, expPicType); + } + + /** + * 清理临时画布资源 + * @param {fabric.StaticCanvas} tempFabricCanvas 临时Fabric画布 + * @private + */ + _cleanupTempCanvas(tempFabricCanvas) { + if (tempFabricCanvas) { + try { + tempFabricCanvas.dispose(); + } catch (error) { + console.warn("清理临时画布失败:", error); + } + } + } + + /** + * 获取所有图层 + * @returns {Array} 图层数组 + * @private + */ + _getAllLayers() { + if (this.layerManager && this.layerManager.layers) { + return this.layerManager.layers.value || []; + } + return []; + } + + /** + * 根据ID获取图层 + * @param {String} layerId 图层ID + * @returns {Object|null} 图层对象 + * @private + */ + _getLayerById(layerId) { + if (this.layerManager && this.layerManager.getLayerById) { + return this.layerManager.getLayerById(layerId); + } + + // 备用方法:直接从图层数组中查找 + const allLayers = this._getAllLayers(); + return allLayers.find((layer) => layer.id === layerId) || null; + } + + /** + * 检查图层是否应该包含在导出中 + * @param {Object} layer 图层对象 + * @param {Boolean} isContainBg 是否包含背景图层 + * @param {Boolean} isContainFixed 是否包含固定图层 + * @param {Boolean} isContainFixedOther 是否包含其他固定图层 + * @param {Boolean} isContainNormalLayer 是否包含普通图层 + * @returns {Boolean} 是否应该包含 + * @private + */ + _shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer) { + if (!layer) return false; + + // 检查背景图层 + if (layer.isBackground) { + return isContainBg; + } + + // 检查固定图层 + if (layer.isFixed) { + return isContainFixed; + } + + // 检查其他固定图层 + if (layer.isFixedOther) { + return isContainFixedOther; + } + + // 印花图层始终导出 + if (layer.isPrintTrims || layer.isPrintTrimsGroup) { + return true; + } + + // 普通图层 + return isContainNormalLayer; + } } diff --git a/src/component/Canvas/CanvasEditor/managers/command/CommandManager.js b/src/component/Canvas/CanvasEditor/managers/command/CommandManager.js index 65fd54b4..f88e3d36 100644 --- a/src/component/Canvas/CanvasEditor/managers/command/CommandManager.js +++ b/src/component/Canvas/CanvasEditor/managers/command/CommandManager.js @@ -180,7 +180,7 @@ export class CommandManager { this._recordPerformance("execute", command.constructor.name, duration); // 通知状态变化 - this._notifyStateChange(); + this._notifyStateChange("execute"); console.log(`✅ 命令执行成功: ${command.constructor.name}`); return result; @@ -219,7 +219,7 @@ export class CommandManager { this._recordPerformance("undo", command.constructor.name, duration); // 通知状态变化 - this._notifyStateChange(); + this._notifyStateChange("undo"); console.log(`✅ 命令撤销成功: ${command.constructor.name}`); return result; @@ -258,7 +258,7 @@ export class CommandManager { this._recordPerformance("redo", command.constructor.name, duration); // 通知状态变化 - this._notifyStateChange(); + this._notifyStateChange("redo"); console.log(`✅ 命令重做成功: ${command.constructor.name}`); return result; @@ -298,7 +298,7 @@ export class CommandManager { this.undoStack = []; this.redoStack = []; - this._notifyStateChange(); + this._notifyStateChange("clear"); // console.log("📝 命令历史已清空"); } @@ -417,10 +417,12 @@ export class CommandManager { * 通知状态变化 * @private */ - _notifyStateChange() { + _notifyStateChange(type) { if (this.onStateChange) { try { - this.onStateChange(this.getState()); + const obj = this.getState(); + obj.type = type; + this.onStateChange(obj); } catch (error) { console.error("状态变化回调执行失败:", error); } 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 2/5] 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{ From f43c56236b7412351ed20a1c956e69fef52a59bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E9=B9=8F?= <2916022834@qq.com> Date: Tue, 14 Apr 2026 10:02:06 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E6=B6=B2=E5=8C=96=E8=8B=B9=E6=9E=9C?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E9=97=AA=E5=B1=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../managers/liquify/LiquifyRealTimeUpdater.js | 12 +++++++----- src/component/Canvas/canvasExample.vue | 8 ++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyRealTimeUpdater.js b/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyRealTimeUpdater.js index a3c85684..15e2f797 100644 --- a/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyRealTimeUpdater.js +++ b/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyRealTimeUpdater.js @@ -85,7 +85,7 @@ export class LiquifyRealTimeUpdater { if (isDrawing && this.config.useDirectUpdate) { // 拖拽过程中使用快速更新(降低质量以提高性能) - this._fastUpdate(imageData); + await this._fastUpdate(imageData); } else { // 拖拽结束后使用完整更新(最高质量) await this._fullUpdate(imageData); @@ -124,7 +124,7 @@ export class LiquifyRealTimeUpdater { * @param {ImageData} imageData 图像数据 * @private */ - _fastUpdate(imageData) { + async _fastUpdate(imageData) { if (!this.targetObject || !this.targetObject._element) { return; } @@ -138,12 +138,14 @@ export class LiquifyRealTimeUpdater { // 直接更新fabric对象的图像源(使用PNG格式保持质量) const targetElement = this.targetObject._element; - // 方案1: 直接设置src属性(最高性能) const dataURL = this.tempCanvas.toDataURL("image/png", quality); - if (targetElement.src !== dataURL) { - targetElement.src = dataURL; + // targetElement.src = dataURL; + const image = new Image(); + image.src = dataURL; + await image.decode(); + this.targetObject.setElement(image); // 关键优化:直接设置fabric对象为脏状态,但不立即渲染 // this.targetObject.dirty = false; // 标记为不需要立即渲染 diff --git a/src/component/Canvas/canvasExample.vue b/src/component/Canvas/canvasExample.vue index 513e2778..49aadbb3 100644 --- a/src/component/Canvas/canvasExample.vue +++ b/src/component/Canvas/canvasExample.vue @@ -447,6 +447,7 @@ +