diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index 5ba9c81d..30be134d 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -9,7 +9,13 @@ import { isGroupLayer, OperationType, OperationTypes, + findLayer, + createLayer, + LayerType, + SpecialLayerId, + BlendMode, } from "../utils/layerHelper"; +import { ObjectMoveCommand } from "../commands/ObjectCommands"; import { AnimationManager } from "./animation/AnimationManager"; import { createCanvas } from "../utils/canvasFactory"; import { CanvasEventManager } from "./events/CanvasEventManager"; @@ -21,6 +27,15 @@ import { 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"; @@ -30,6 +45,11 @@ import { validateLayerAssociations, } from "../utils/layerUtils"; import { imageModeHandler } from "../utils/imageHelper"; +import { getObjectAlphaToCanvas } from "../utils/objectHelper"; +import { AddLayerCommand, RemoveLayerCommand, ToggleChildLayerVisibilityCommand } from "../commands/LayerCommands"; +import { fa, id } from "element-plus/es/locales.mjs"; +import i18n from "@/lang/index.ts"; +const {t} = i18n.global; export class CanvasManager { constructor(canvasElement, options) { @@ -50,6 +70,8 @@ export class CanvasManager { this.isFixedErasable = options.isFixedErasable || false; // 是否允许擦除固定图层 this.eraserStateManager = null; // 橡皮擦状态管理器引用 this.handleCanvasInit = null; // 画布初始化回调函数 + this.props = options.props || {}; + this.emit = options.emit || (() => {}); // 初始化画布 this.initializeCanvas(); } @@ -83,10 +105,10 @@ export class CanvasManager { this.canvas.thumbnailManager = this.thumbnailManager; // 将缩略图管理器绑定到画布 - // // 设置画布辅助线 - // initAligningGuidelines(this.canvas); + // 设置画布辅助线 + initAligningGuidelines(this.canvas); - // // 设置画布中心线 + // 设置画布中心线 // initCenteringGuidelines(this.canvas); // 初始化画布事件监听器 @@ -156,6 +178,7 @@ export class CanvasManager { // 返回false表示使用默认行为(直接添加到画布) return false; }; + this.eraserStateManager = new EraserStateManager( this.canvas, @@ -283,6 +306,19 @@ export class CanvasManager { } } + /** + * 设置部件选择管理器 + * @param {Object} partManager 部件选择管理器实例 + */ + setPartManager(partManager) { + this.partManager = partManager; + + // 如果已创建事件管理器,更新它的部件选择管理器引用 + if (this.eventManager) { + this.eventManager.partManager = this.partManager; + } + } + // 设置红绿图模式管理器 setRedGreenModeManager(redGreenModeManager) { this.redGreenModeManager = redGreenModeManager; @@ -408,12 +444,41 @@ export class CanvasManager { } // 居中所有画布元素,包括背景层和其他元素 - this.centerAllObjects(); + 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 @@ -431,9 +496,8 @@ export class CanvasManager { * 以背景层为参照,计算背景层的偏移量并应用到所有对象上 * 这样可以保持对象间的相对位置关系不变 */ - centerAllObjects() { + async centerAllObjects() { if (!this.canvas) return; - // 获取所有可见对象(不是背景元素的对象) const allObjects = this.canvas.getObjects(); if (allObjects.length === 0) return; @@ -448,9 +512,6 @@ export class CanvasManager { // 获取背景对象 const backgroundObject = visibleObjects.find((obj) => obj.isBackground); - !this.canvas?.clipPath && - this.centerBackgroundLayer(this.canvas.width, this.canvas.height); - this.canvas?.clipPath?.set?.({ left: this.width / 2, top: this.height / 2, @@ -496,7 +557,6 @@ export class CanvasManager { // 计算背景层的偏移量 const deltaX = backgroundObject.left - backgroundOldLeft; const deltaY = backgroundObject.top - backgroundOldTop; - // 将相同的偏移量应用到所有其他对象上 const otherObjects = visibleObjects.filter( (obj) => obj !== backgroundObject @@ -543,14 +603,29 @@ export class CanvasManager { }); }); } + + !this.canvas?.clipPath && + this.centerBackgroundLayer(this.canvas.width, this.canvas.height); // 如果有背景层,更新蒙层位置 if (backgroundObject && CanvasConfig.isCropBackground) { this.updateMaskPosition(backgroundObject); } + // 更新颜色层信息 + // const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR); + // if(colorObject){ + // await this.setObjecCliptInfo(colorObject); + // } + const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP); + if(groupLayer){ + const groupRect = new fabric.Rect({}); + await this.setObjecCliptInfo(groupRect); + groupLayer.clippingMask = groupRect.toObject(); + } + // 重新渲染画布 - // this.canvas.renderAll(); + this.canvas.renderAll(); } /** @@ -600,7 +675,7 @@ export class CanvasManager { * @param {Number} canvasWidth 画布宽度 * @param {Number} canvasHeight 画布高度 */ - centerBackgroundLayer(canvasWidth, canvasHeight) { + async centerBackgroundLayer(canvasWidth, canvasHeight) { const backgroundLayerObject = this.getBackgroundLayer(); if (!backgroundLayerObject) return false; @@ -646,6 +721,11 @@ export class CanvasManager { 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({ @@ -679,6 +759,8 @@ export class CanvasManager { originX: backgroundLayerObject.originX || "left", originY: backgroundLayerObject.originY || "top", absolutePositioned: true, + rx: 15, + ry: 15, }); } getBackgroundLayer() { @@ -706,6 +788,82 @@ export class CanvasManager { return backgroundLayerByBgLayer; } + getFixedLayerObject() { + if (!this.canvas) return null; + const fixedLayer = this.canvas.getObjects().find((obj) => { + return obj.isFixed; + }); + + if (fixedLayer) return fixedLayer; + + // 如果没有找到固定层,则根据图层ID查找 + const fixedLayerId = this.layers.value.find((layer) => { + return layer.isFixed; + })?.id; + + const fixedLayerByFixedLayer = this.canvas.getObjects().find((obj) => { + return obj.isFixed || obj.id === fixedLayerId; + }); + if (!fixedLayerByFixedLayer) { + console.warn( + "CanvasManager.js = >getFixedLayerObject 方法没有找到固定层" + ); + } + + return fixedLayerByFixedLayer; + } + getBackgroundLayerObject() { + if (!this.canvas) return null; + const backgroundLayer = this.canvas.getObjects().find((obj) => { + return obj.isBackground; + }); + + if (backgroundLayer) return backgroundLayer; + + // 如果没有找到背景层,则根据图层ID查找 + const backgroundLayerId = this.layers.value.find((layer) => { + return layer.isBackground; + })?.id; + + const backgroundLayerByBgLayer = this.canvas.getObjects().find((obj) => { + return obj.isBackground || obj.id === backgroundLayerId; + }); + if (!backgroundLayerByBgLayer) { + console.warn( + "CanvasManager.js = >getBackgroundLayerObject 方法没有找到背景层" + ); + } + + return backgroundLayerByBgLayer; + } + getLayerObjectById(layerId) { + if (!this.canvas) return null; + + const layerObject = this.canvas.getObjects().find((obj) => { + return obj.id === layerId; + }); + + if (layerObject) return layerObject; + + // 如果没有找到图层对象,则根据图层ID查找 + const layerObjectByLayerId = this.canvas.getObjects().find((obj) => { + return obj.id === layerId; + }); + if (!layerObjectByLayerId) { + console.warn( + "CanvasManager.js = >getLayerObjectById 方法没有找到图层对象" + ); + } + + return layerObjectByLayerId; + } + getObjectsByIds(ids){ + const objects = this.canvas.getObjects().filter((obj) => { + return ids.includes(obj.id); + }); + return objects; + } + /** * 更新蒙层位置 * @param {Object} backgroundLayerObject 背景层对象 @@ -713,7 +871,6 @@ export class CanvasManager { updateMaskPosition(backgroundLayerObject) { if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath) return; - const left = backgroundLayerObject.left; const top = backgroundLayerObject.top; @@ -798,7 +955,7 @@ export class CanvasManager { // 如果找到了图层,则生成缩略图 findLayer && this.thumbnailManager?.generateLayerThumbnail(findLayer.id); - + this.layerManager?.sortLayers?.(); return result; } @@ -807,11 +964,16 @@ export class CanvasManager { * @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 = {}) { @@ -826,12 +988,16 @@ export class CanvasManager { // this.canvas.renderAll(); // 重新渲染画布 // 自动设置红绿图模式相关参数 const enhancedOptions = { + isPrintTrimsNoRepeat: true, + isPrintTrimsRepeat: true, + isContainNormalLayer: true, ...options, // 如果没有明确指定,则根据当前模式自动设置 restoreOpacityInRedGreen: options.restoreOpacityInRedGreen !== undefined ? options.restoreOpacityInRedGreen : false, // 默认在红绿图模式下恢复透明度 + // excludedLayers: [SpecialLayerId.SPECIAL_GROUP], // 导出时排除的图层ID数组 }; // 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层 @@ -846,7 +1012,7 @@ export class CanvasManager { const normalLayerIds = this.layers?.value ?.filter( - (layer) => !layer.isBackground && !layer.isFixed && layer.visible + (layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther && layer.visible ) ?.map((layer) => layer.id) || []; @@ -855,13 +1021,215 @@ export class CanvasManager { console.log("红绿图模式导出图层:", normalLayerIds); } } - return await this.exportManager.exportImage(enhancedOptions); + + // 处理特殊图层的显示状态 + 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.error("CanvasManager导出图片失败:", error); + console.warn("CanvasManager导出图片失败:", error); throw error; } } + /** + * 导出印花元素颜色信息 + * @returns {Object} + */ + async exportExtraInfo() { + // 导出颜色图层信息 + const color = await this.exportColorLayer().catch(() => (null)); + // 导出印花和元素图层信息 + const printTrimsData = await this.exportPrintTrimsLayers().catch(() => ({prints: null, trims: null})); + + const obj = { + color, + ...printTrimsData, + }; + console.log("==========exportExtraInfo:", obj); + return obj; + } + + /** + * 导出颜色图层 + * @returns {Object} 导出的颜色图层数据URL + */ + async exportColorLayer() { + if (!this.exportManager) { + console.warn("导出管理器未初始化,请确保已设置图层管理器"); + return Promise.reject("颜色图层不存在"); + } + const object = this.getLayerObjectById(SpecialLayerId.COLOR); + if(!object){ + console.warn("颜色图层不存在,请确保已添加颜色图层"); + return Promise.reject("颜色图层不存在"); + } + const css = fillToCssStyle(object.fill) + const canvas = new fabric.StaticCanvas(); + canvas.setDimensions({ + width: object.width, + height: object.height, + backgroundColor: null, + imageSmoothingEnabled: true, + }); + const cloneObject = await new Promise((resolve, reject) => { + object.clone(resolve); + }); + cloneObject.set({ + left: canvas.width / 2, + top: canvas.height / 2, + scaleX: 1, + scaleY: 1, + visible: true, + clipPath: null, + }); + canvas.add(cloneObject); + canvas.renderAll(); + const base64 = canvas.toDataURL({ + format: "png", + quality: 1, + }); + canvas.clear(); + const color = object.originColor; + return {css, base64, color}; + } + + /** + * 导出印花和元素图层 + */ + async exportPrintTrimsLayers() { + const glayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP); + if(!glayer) return Promise.reject("印花和元素图层组不存在"); + const ids = glayer.children.map((v) => v.id); + const objects = this.getObjectsByIds(ids); + const fixedLayerObj = this.getFixedLayerObject(); + if(!fixedLayerObj) return Promise.reject("固定图层不存在"); + const flWidth = fixedLayerObj.width + const flHeight = fixedLayerObj.height + const flTop = fixedLayerObj.top + const flLeft = fixedLayerObj.left + const flScaleX = fixedLayerObj.scaleX + const flScaleY = fixedLayerObj.scaleY + const prints = []; + const trims = []; + objects.forEach((v) => { + const sourceData = glayer.children.find((v_) => v_.id === v.id)?.metadata?.sourceData; + if(!sourceData) return; + const obj = { + ifSingle: typeof v.fill === "string", + level2Type: sourceData.level2Type, + designType: sourceData.designType, + path: sourceData.path, + minIOPath: sourceData.minIOPath, + location: [0, 0], + scale: [0, 0], + angle: v.angle, + name: sourceData.name, + priority: sourceData.priority, + object:{ + top: 0, + left: 0, + scaleX: 0,//对象的缩放比例 + scaleY: 0,//对象的缩放比例 + opacity: v.opacity, + angle: v.angle, + flipX: v.flipX, + flipY: v.flipY, + blendMode: v.globalCompositeOperation, + gapX: 0,// 平铺模式下的间距 + gapY: 0,// 平铺模式下的间距 + } + } + let left = (v.left - (flLeft - flWidth * flScaleX / 2)); + let top = (v.top - (flTop - flHeight * flScaleY / 2)); + let width = (v.width * v.scaleX); + let height = (v.height * v.scaleY); + if(v.originX === "center" && v.originY === "center") { + let {x:cx, y:cy} = calculateTopLeftPoint(width, height, left, top, v.angle); + left = cx; + top = cy; + } + let oX = left / flScaleX; + let oY = top / flScaleY; + let oScaleX = (v.width * v.scaleX) / (flWidth * flScaleX); + let oScaleY = (v.height * v.scaleY) / (flHeight * flScaleY); + obj.object.top = oY; + obj.object.left = oX; + obj.object.scaleX = oScaleX; + obj.object.scaleY = oScaleY; + if(obj.ifSingle){ + // 单个的是从中心计算的 + let {x:cx, y:cy} = calculateCenterPoint(width, height, left, top, v.angle); + let oX = (cx-width/2) / flScaleX; + let oY = (cy-height/2) / flScaleY; + obj.location = [oX, oY]; + obj.scale = [oScaleX, oScaleY]; + }else{ + let fill = v.fill; + let fill_ = v.fill_; + if(!fill || !fill_) return console.warn("印花元素不存在fill或fill_属性"); + let {scale, angle} = getTransformScaleAngle(fill.patternTransform); + let scaleX = scale * 5 * v.fill_.width / flWidth; + let scaleY = scale * 5 * v.fill_.height / flHeight; + let scaleXY = flWidth > flHeight ? scaleX : scaleY; + + let left = fill.offsetX + v.fill_.width * scale / 2; + let top = fill.offsetY + v.fill_.height * scale / 2; + + obj.scale = [scaleXY, scaleXY]; + obj.angle = angle; + obj.location = [left, top]; + obj.object.gapX = fill_.gapX; + obj.object.gapY = fill_.gapY; + } + if(obj.level2Type === "Pattern"){ + prints.push(obj); + }else if(obj.level2Type === "Embroidery"){ + 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) { @@ -892,109 +1260,67 @@ export class CanvasManager { } getJSON() { - // // 简化图层数据,在loadJSON时要根据id恢复引用 - // const simplifyLayers = (layers) => { - // return layers.map((layer) => { - // if (layer?.children?.length) { - // layer.children = layer.children.map((child) => { - // return { - // id: child.id, - // type: child.type, - // layerId: child.layerId, - // layerName: child.layerName, - // isBackground: child.isBackground, - // isLocked: child.isLocked, - // isVisible: child.isVisible, - // isFixed: child.isFixed, - // parentId: child.parentId, - // fabricObject: child.fabricObject - // ? { - // id: child.fabricObject.id, - // type: child.fabricObject.type, - // layerId: child.fabricObject.layerId, - // layerName: child.fabricObject.layerName, - // } - // : {}, - // fabricObjects: - // child.fabricObjects?.map((obj) => ({ - // id: obj.id, - // type: obj.type, - // layerId: obj.layerId, - // layerName: obj.layerName, - // })) || [], - // }; - // }); - // } - // return { - // id: layer.id, - // type: layer.type, - // layerId: layer.layerId, - // layerName: layer.layerName, - // isBackground: layer.isBackground, - // isLocked: layer.isLocked, - // isVisible: layer.isVisible, - // isFixed: layer.isFixed, - // parentId: layer.parentId, - // fabricObject: child.fabricObject - // ? { - // id: child.fabricObject.id, - // type: child.fabricObject.type, - // layerId: child.fabricObject.layerId, - // layerName: child.fabricObject.layerName, - // } - // : {}, - // fabricObjects: - // child.fabricObjects?.map((obj) => ({ - // id: obj.id, - // type: obj.type, - // layerId: obj.layerId, - // layerName: obj.layerName, - // })) || [], - // children: layer.children, - // }; - // }); - // }; try { // 清除画布中选中状态 - this.canvas.discardActiveObject(); + // 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)) + JSON.parse(JSON.stringify(this.layers.value)), + excludedLayers ); - console.log("获取画布JSON数据...", simplifyLayersData); - return JSON.stringify({ - canvas: this.canvas.toJSON([ - "id", - "type", - "layerId", - "layerName", - "isBackground", - "isLocked", - "isVisible", - "isFixed", - "parentId", - "eraser", - "eraserable", - "erasable", - "customType", - ]), + const data = { + canvas, layers: simplifyLayersData, // 简化图层数据 - // layers: JSON.stringify(JSON.parse(JSON.stringify(this.layers.value))), // 全数据 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) { - console.log("加载画布JSON数据:", json); // 确保传入的json是字符串格式 if (typeof json === "object") { @@ -1005,11 +1331,12 @@ export class CanvasManager { // 解析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 || []; @@ -1031,7 +1358,7 @@ export class CanvasManager { // this.canvasHeight.value = parsedJson.canvasHeight || this.height; // this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor; - console.log("是否检测到红绿图模式内容:", this.enabledRedGreenMode); + // console.log("是否检测到红绿图模式内容:", this.enabledRedGreenMode); // 重置视图变换以确保元素位置正确 this._resetViewportTransform(1); @@ -1043,7 +1370,7 @@ export class CanvasManager { // 清除当前画布内容 // this.canvas.clear(); // 清除画布内容 可以先去掉 这样加载闪动的情况就比较少 如果有问题 可以再打开 - console.log("清除当前画布内容", canvasData); + // console.log("清除当前画布内容", canvasData); delete canvasData.clipPath; // 删除当前裁剪路径 // 加载画布数据 this.canvas.loadFromJSON(canvasData, async () => { @@ -1070,8 +1397,9 @@ export class CanvasManager { // } try { // 重置画布数据 - this.setCanvasSize(this.canvas.width, this.canvas.height); - this.centerBackgroundLayer(this.canvas.width, this.canvas.height); + 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()); // 验证图层关联关系 - 稳定后可以注释 @@ -1096,14 +1424,11 @@ export class CanvasManager { // } // 重载代码后支持回调中操作一些内容 - await calllBack?.(); // 确保所有对象的交互性正确设置 - await this.layerManager?.updateLayersObjectsInteractivity?.( - false - ); - console.log(this.layerManager.layers.value); + await this.layerManager?.updateLayersObjectsInteractivity?.(); + await calllBack?.(); // 更新所有缩略图 setTimeout(() => { this.updateAllThumbnails(); @@ -1147,6 +1472,377 @@ export class CanvasManager { }) } + + /** + * 创建其他图层:印花、颜色、元素... + * @param {Object} otherData - 其他图层数据 + */ + async createOtherLayers(otherData, isUpdate = false) { + if (!otherData) return console.warn("otherData 为空不需要添加"); + const otherData_ = JSON.parse(JSON.stringify(otherData)); + console.log("==========创建其他图层", otherData_); + + const updateColor = !!otherData_.color; + const updateSpecialGroup = !!otherData_.printObject || !!otherData_.trims; + // 删除颜色图层和特殊组图层 + const ids = []; + if(isUpdate){ + updateColor && ids.push(SpecialLayerId.COLOR) + updateSpecialGroup && ids.push(SpecialLayerId.SPECIAL_GROUP) + }else{ + ids.push(SpecialLayerId.COLOR) + ids.push(SpecialLayerId.SPECIAL_GROUP) + } + this.layers.value = this.layers.value.filter((layer) => { + if(ids.includes(layer.id)){ + ids.push(...layer.children?.map((child) => child.id)); + return false; + } + return true; + }) + this.canvas.getObjects().forEach((v) => ids.includes(v.id) && this.canvas.remove(v)) + + + // 创建颜色图层 + otherData_.color && await this.createColorLayer(otherData_.color); + + const printTrimsLayers = [];// 印花和元素图层 + const singleLayers = [];// 平铺图层 + otherData_.printObject?.prints?.forEach((print, index) => {// 印花 + print.name = t("Canvas.Print") + (index + 1); + if(print.ifSingle){ + printTrimsLayers.unshift({...print}); + }else{ + singleLayers.unshift({...print}); + } + }) + otherData_.trims?.prints?.forEach((trims, index) => {// 元素 + trims.name = t("Canvas.Elements") + (index + 1); + printTrimsLayers.unshift({...trims}); + }) + if(isUpdate ? updateSpecialGroup : true){ + await this.createPrintTrimsLayers(printTrimsLayers, singleLayers); + } + await this.changeCanvas(); + } + + // 设置画布对象的裁剪信息 + async setObjecCliptInfo(tagObject, data){ + const fixedLayerObj = this.getFixedLayerObject(); + if(!fixedLayerObj) return console.warn("固定图层为空"); + tagObject.set({ + top: fixedLayerObj.top, + left: fixedLayerObj.left, + width: fixedLayerObj.width, + height: fixedLayerObj.height, + originX: fixedLayerObj.originX, + originY: fixedLayerObj.originY, + scaleX: fixedLayerObj.scaleX, + scaleY: fixedLayerObj.scaleY, + }); + var object = fixedLayerObj; + const imageUrl = this.props.clothingImageUrl2; + if(imageUrl){ + object = await new Promise((resolve, reject) => { + fabric.Image.fromURL(imageUrl, (imgObject) => { + tagObject.set({ + width: imgObject.width, + height: imgObject.height, + }); + resolve(imgObject); + }, { crossOrigin: "anonymous" }); + }); + } + const canvas = getObjectAlphaToCanvas(object, data); + const transparentMask = new fabric.Image(canvas, { + top: 0, + left: 0, + originX: fixedLayerObj.originX, + originY: fixedLayerObj.originY, + }); + tagObject.set('clipPath', transparentMask); + } + async createColorLayer(color_){ + const color = color_ || {r:0,g:0,b:0,a:0}; + // if(findLayer(this.layers.value, SpecialLayerId.COLOR)) { + // return console.warn("画布中已存在颜色图层"); + // } + console.log("==========添加颜色图层", color, this.layers.value.length) + // 创建颜色图层对象 + const colorRect = new fabric.Rect({ + id: SpecialLayerId.COLOR, + layerId: SpecialLayerId.COLOR, + layerName: t("Canvas.color"), + isVisible: true, + isLocked: true, + selectable: false, + hasControls: false, + hasBorders: false, + globalCompositeOperation: BlendMode.MULTIPLY, + originColor: color, + }); + // await this.setObjecCliptInfo(colorRect); + const gradientObj = palletToFill(color); + const gradient = new fabric.Gradient({ + type: 'linear', + gradientUnits: 'percentage', + ...gradientObj, + }) + colorRect.set('fill', gradient); + this.canvas.add(colorRect); + // 创建颜色图层 + const colorLayer = createLayer({ + id: colorRect.layerId, + name: colorRect.layerName, + type: LayerType.SHAPE, + visible: colorRect.isVisible, + locked: colorRect.isLocked, + opacity: 1.0, + isFixedOther: true, + blendMode: BlendMode.MULTIPLY, + fabricObjects: [colorRect.toObject(["id", "layerId", "layerName"])], + }) + const groupIndex = this.layers.value.findIndex(layer => layer.isFixed || layer.isBackground); + this.layers.value.splice(groupIndex, 0, colorLayer); + } + + // 创建印花和元素图层 + async createPrintTrimsLayers(printTrimsLayers, singleLayers){ + // if(findLayer(this.layers.value, SpecialLayerId.SPECIAL_GROUP)) { + // return console.warn("画布中已存在印花和元素组图层"); + // } + console.log("==========添加印花和元素图层组", printTrimsLayers, singleLayers) + const fixedLayerObj = this.getFixedLayerObject(); + const flWidth = fixedLayerObj.width + const flHeight = fixedLayerObj.height + const flTop = fixedLayerObj.top + const flLeft = fixedLayerObj.left + const flScaleX = fixedLayerObj.scaleX + const flScaleY = fixedLayerObj.scaleY + const children = []; + // 添加印花和元素图层 + for(let index = 0; index < printTrimsLayers.length; index++){ + let item = printTrimsLayers[index]; + let id = generateId("layer_image_"); + let name = item.name; + let image = await new Promise(resolve => { + fabric.Image.fromURL(item.path, (fabricImage)=>{ + resolve(fabricImage); + }, { crossOrigin: "anonymous" }); + }) + let left = flLeft - flWidth * flScaleX / 2 + (item.location?.[0] || 0) * flScaleX + let top = flTop - flHeight * flScaleY / 2 + (item.location?.[1] || 0) * flScaleY + let scaleX = flWidth * (item.scale?.[0] || 1) / image.width * flScaleX + let scaleY = flHeight * (item.scale?.[1] || 1) / image.height * flScaleY + let {x, y} = calculateRotatedTopLeftDeg( + image.width * scaleX, + image.height * scaleY, + left, + top, + 0, + item.angle || 0 + ) + let angle = item.angle || 0 + + let opacity = 1 + let flipX = false; + let flipY = false; + let blendMode = BlendMode.MULTIPLY; + if(item.level2Type === "Embroidery") blendMode = BlendMode.NORMAL;// 元素正常 + if(item.object){ + opacity = item.object.opacity + flipX = item.object.flipX + flipY = item.object.flipY + if(item.object.blendMode) blendMode = item.object.blendMode; + } + image.set({ + left: x, + top: y, + scaleX: scaleX, + scaleY: scaleY, + angle: angle, + opacity: opacity, + flipX: flipX, + flipY: flipY, + globalCompositeOperation: blendMode, + id: id, + layerId: id, + layerName: name, + selectable: true, + hasControls: true, + hasBorders: true, + isPrintTrims: true, + }); + this.canvas.add(image); + let layer = createLayer({ + id: id, + name: name, + type: LayerType.BITMAP, + visible: true, + locked: false, + opacity: opacity, + isPrintTrims: true, + blendMode: blendMode, + fabricObjects: [image.toObject(["id", "layerId", "layerName"])], + metadata: {sourceData: item, level2Type: item.level2Type}, + }) + children.push(layer); + }; + // 添加平铺图层 + for(let index = 0; index < singleLayers.length; index++){ + let item = singleLayers[index]; + let id = generateId("layer_image_"); + let name = item.name; + let image = await new Promise(resolve => { + fabric.Image.fromURL(item.path, (fabricImage)=>{ + const imgElement = fabricImage.getElement(); + const tcanvas = document.createElement('canvas'); + tcanvas.width = imgElement.width; + tcanvas.height = imgElement.height; + const ctx = tcanvas.getContext('2d'); + ctx.clearRect(0, 0, tcanvas.width, tcanvas.height); + ctx.drawImage(imgElement, 0, 0); + resolve(tcanvas); + }, { crossOrigin: "anonymous" }); + }) + let scaleX_ = fixedLayerObj.width / image.width * (item.scale?.[0] || 1) / 5; + let scaleY_ = fixedLayerObj.height / image.height * (item.scale?.[1] || 1) / 5; + let scale = fixedLayerObj.width > fixedLayerObj.height ? scaleX_ : scaleY_; + let offsetX = (item.location?.[0] || 0) - image.width * scale / 2 + let offsetY = (item.location?.[1] || 0) - image.height * scale / 2 + let top = fixedLayerObj.top - fixedLayerObj.height * fixedLayerObj.scaleY / 2 + let left = fixedLayerObj.left - fixedLayerObj.width * fixedLayerObj.scaleX / 2 + let scaleX = fixedLayerObj.scaleX + let scaleY = fixedLayerObj.scaleY + let opacity = 1 + let angle = 0 + let gapX = 0 + let gapY = 0 + let fillSource = image + let flipX = false; + let flipY = false; + let blendMode = BlendMode.MULTIPLY; + if(item.object){ + top += item.object.top * fixedLayerObj.scaleY + left += item.object.left * fixedLayerObj.scaleX + scaleX *= item.object.scaleX + scaleY *= item.object.scaleY + opacity = item.object.opacity + angle = item.object.angle + flipX = item.object.flipX + flipY = item.object.flipY + blendMode = item.object.blendMode || BlendMode.MULTIPLY; + gapX = item.object.gapX + gapY = item.object.gapY + fillSource = imageAddGapToCanvas(image, gapX, gapY); + } + let rect = new fabric.Rect({ + id: id, + layerId: id, + layerName: name, + width: fixedLayerObj.width, + height: fixedLayerObj.height, + top: top, + left: left, + scaleX: scaleX, + scaleY: scaleY, + opacity: opacity, + angle: angle, + flipX: flipX, + flipY: flipY, + globalCompositeOperation: blendMode, + fill: new fabric.Pattern({ + source: fillSource, + repeat: "repeat", + patternTransform: createPatternTransform(scale, item.angle || 0), + offsetX: offsetX, // 水平偏移 + offsetY: offsetY, // 垂直偏移 + }), + fill_ : { + source: item.path, + gapX: gapX, + gapY: gapY, + width: image.width, + height: image.height, + }, + isPrintTrims: true, + }); + this.canvas.add(rect); + let layer = createLayer({ + id: id, + name: name, + type: LayerType.BITMAP, + visible: true, + locked: true, + opacity: opacity, + isPrintTrims: true, + blendMode: BlendMode.MULTIPLY, + fabricObjects: [rect.toObject(["id", "layerId", "layerName"])], + metadata: {sourceData: item}, + }) + children.push(layer); + }; + // if(children.length === 0){ + // let layer = createLayer({ + // id: generateId("layer_image_"), + // name: t("Canvas.EmptyLayer"), + // type: LayerType.BITMAP, + // visible: true, + // locked: false, + // opacity: 1.0, + // fabricObjects: [], + // }) + // children.push(layer); + // } + if(children.length === 0) return; + const groupRect = new fabric.Rect({}); + await this.setObjecCliptInfo(groupRect); + // 插入组图层 + const groupIndex = this.layers.value.findIndex(layer => layer.isFixedOther || layer.isFixed || layer.isBackground); + const groupLayer = createLayer({ + id: SpecialLayerId.SPECIAL_GROUP, + name: t("Canvas.PrintAndElementsGroup"), + type: LayerType.GROUP, + visible: true, + locked: false, + opacity: 1.0, + fabricObjects: [], + children: children, + clippingMask: groupRect.toObject(), + isPrintTrimsGroup: true, + }); + this.layers.value.splice(groupIndex, 0, groupLayer); + } + + /** + * 画布事件变更后 + */ + async changeCanvas(){ + const fixedLayerObj = this.getFixedLayerObject(); + if(!fixedLayerObj) return console.warn("固定图层对象不存在", fixedLayerObj) + const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR); + if(colorObject){ + const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP); + if(ids.length === 0){ + ids.unshift(SpecialLayerId.SPECIAL_GROUP); + await this.setObjecCliptInfo(colorObject); + this.canvas.renderAll(); + return; + } + const base64 = await this.exportManager.exportImage({layerIdArray2: ids, isEnhanceImg: true}); + if(!base64) return console.warn("导出图片失败", base64) + const canvas = await base64ToCanvas(base64, fixedLayerObj.scaleX * 2, true); + const ctx = canvas.getContext('2d'); + const width = fixedLayerObj.width; + const height = fixedLayerObj.height; + const x = (canvas.width - width) / 2; + const y = (canvas.height - height) / 2; + const data = ctx.getImageData(x, y, width, height); + await this.setObjecCliptInfo(colorObject, data); + this.canvas.renderAll(); + } + } + /** * 缩放红绿图模式内容以适应当前画布大小 * 确保衣服底图和红绿图永远在画布内可见 @@ -1270,6 +1966,7 @@ export class CanvasManager { return fixedLayer.fabricObject || null; } + /** * 获取所有普通图层对象(包括红绿图) * @returns {Array} 普通图层对象数组 @@ -1336,4 +2033,46 @@ export class CanvasManager { return sizeMatch && positionMatch; } + + /** + * 键盘移动激活对象 + * @param {String} direction 移动方向(up, down, left, right) + * @param {} step 移动步长 + * @private + */ + moveActiveObject(direction, step = 1) { + const objects = []; + const activeObject = this.canvas.getActiveObject(); + if(!activeObject) return; + const initPos = { + id: activeObject.id, + left: activeObject.left, + top: activeObject.top, + }; + switch(direction) { + case "up": + activeObject.top -= step; + break; + case "down": + activeObject.top += step; + break; + case "left": + activeObject.left -= step; + break; + case "right": + activeObject.left += step; + break; + } + if(!activeObject.id) return this.canvas.renderAll(); + const cmd = new ObjectMoveCommand({ + canvas: this.canvas, + initPos, + finalPos: { + id: activeObject.id, + left: activeObject.left, + top: activeObject.top, + }, + }); + this.commandManager.executeCommand(cmd); + } }