diff --git a/src/assets/images/canvas/xiangao.png b/src/assets/images/canvas/xiangao.png new file mode 100644 index 00000000..89774ded Binary files /dev/null and b/src/assets/images/canvas/xiangao.png differ diff --git a/src/assets/images/canvas/xiangaofenge.png b/src/assets/images/canvas/xiangaofenge.png new file mode 100644 index 00000000..a7c53528 Binary files /dev/null and b/src/assets/images/canvas/xiangaofenge.png differ diff --git a/src/assets/images/canvas/yinhua1.jpg b/src/assets/images/canvas/yinhua1.jpg new file mode 100644 index 00000000..9cb7fb93 Binary files /dev/null and b/src/assets/images/canvas/yinhua1.jpg differ diff --git a/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js b/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js index 617c4ea4..a866c299 100644 --- a/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/FillRepeatCommand.js @@ -52,6 +52,7 @@ export class FillRepeatCommand extends Command { console.warn("当前对象不能平铺", object.type); return false; } + console.log("===========", object.toObject(["id", "layerId", "layerName"])) this.oldObjects = object; const img = await new Promise((resolve, reject) => { if (object.type === "rect") { @@ -79,6 +80,8 @@ export class FillRepeatCommand extends Command { source: FillSourceToBase64(img), gapX: 0, gapY: 0, + width: img.width, + height: img.height, }; const bgObject = this.canvasManager.getBackgroundLayerObject(); const pattern = new fabric.Pattern({ @@ -274,6 +277,8 @@ export class FillRepeatGapChangeCommand extends Command { const image = new Image(); image.src = object.fill_.source; await image.decode(); + object.fill_.width = image.width; + object.fill_.height = image.height; // 创建透明 Canvas const tcanvas = document.createElement('canvas'); tcanvas.width = image.width + object.fill_.gapX; diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js index 524b2e37..0aa04417 100644 --- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js @@ -524,6 +524,7 @@ export class RemoveLayerCommand extends Command { this.layerId = options.layerId; this.activeLayerId = options.activeLayerId; this.layerManager = options.layerManager || null; + this.IsOnlyLayer = this.layers.value.filter((v => !v.isFixed && !v.isFixedOther && !v.isBackground)).length <= 1 // 查找要删除的图层 this.layerIndex = this.layers.value.findIndex( @@ -600,7 +601,9 @@ export class RemoveLayerCommand extends Command { ); // 从图层列表中删除 this.layers.value.splice(this.layerIndex, 1); - + if(this.IsOnlyLayer){ + this.addCmd = await this.layerManager?.createLayer?.(null, LayerType.EMPTY, {}, false); + } // 如果删除的是当前活动图层,需要更新活动图层 if (this.isActiveLayer) { // 查找最近的非背景层作为新的活动图层 @@ -633,6 +636,9 @@ export class RemoveLayerCommand extends Command { async undo() { // 恢复图层到原位置 if (this.layerIndex !== -1 && this.removedLayer) { + if(this.IsOnlyLayer && this.addCmd){ + this.addCmd?.undo?.(); + } this.layers.value.splice(this.layerIndex, 0, this.removedLayer); // 使用优化渲染批处理恢复真实对象到画布 @@ -650,7 +656,6 @@ export class RemoveLayerCommand extends Command { } }); }); - await this.layerManager?.updateLayersObjectsInteractivity?.(); this.canvas.renderAll(); @@ -4286,24 +4291,28 @@ export class RemoveChildLayerCommand extends Command { } // 恢复子图层到原位置 this.parentLayer.children.splice(this.childIndex, 0, this.removedChild); - optimizeCanvasRendering(this.canvas, async () => { - this.originalObjects.forEach((obj) => { - // 恢复对象到画布 - this.canvas.add(obj); - // 恢复对象的图层信息 - obj.layerId = this.layerId; - obj.layerName = this.removedChild.name; - obj.setCoords(); // 更新坐标 - }); + await new Promise((resolve) => { + optimizeCanvasRendering(this.canvas, async () => { + this.originalObjects.forEach((obj) => { + // 恢复对象到画布 + this.canvas.add(obj); + // 恢复对象的图层信息 + obj.layerId = this.layerId; + obj.layerName = this.removedChild.name; + obj.setCoords(); // 更新坐标 + }); - // 如果是原活动图层,恢复活动图层 - if (this.isActiveLayer) { - this.activeLayerId.value = this.layerId; - } + // 如果是原活动图层,恢复活动图层 + if (this.isActiveLayer) { + this.activeLayerId.value = this.layerId; + } - // 重新渲染画布 - await this.layerManager?.updateLayersObjectsInteractivity(false); + // 重新渲染画布 + await this.layerManager?.updateLayersObjectsInteractivity(false); + resolve(true); + }); }); + return true; } getInfo() { @@ -4499,6 +4508,9 @@ export class SetColorLayerFillCommand extends Command { this.layer = this.layerManager?.getLayerById(this.object.layerId); this.newFill = options.newFill; this.oldFill = JSON.parse(JSON.stringify(this.object.fill)); + this.layer.blendMode = "multiply"; + this.object.set("globalCompositeOperation", "multiply"); + this.object.set("originColor", options.originColor); } async execute(isUndo = false) { diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayerItem.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayerItem.vue index 79bff827..b9c9cf13 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayerItem.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayerItem.vue @@ -364,14 +364,13 @@ const clickColor = () => { const fill = layerObject.value.fill; if (fill) { const obj = fillToPallet(fill); - console.log("===========:", obj); palletPanel(obj).then((res) => { - console.log("===========:", res); const cmd = new SetColorLayerFillCommand({ canvas: canvasManager.canvas, layerManager: layerManager, object: layerObject.value, newFill: palletToFill(res), + originColor: res, }); layerManager.commandManager.execute(cmd); }); diff --git a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue index b4e5d3a9..5617c4e9 100644 --- a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue +++ b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel/index.vue @@ -264,7 +264,7 @@ * 显示面板 */ function show() { - if (activeObjects.length === 0) return; + if (activeObjects.value.length === 0) return; visible.value = true; closePanel.value = true; } @@ -276,14 +276,14 @@ visible.value = false; } // 获取当前选中的对象 - const activeObjects = reactive([]); + const activeObjects = ref([]); const getActiveObject = (e) => { console.log("==========切换激活对象", e, activeObjects); - activeObjects.splice(0, activeObjects.length, ...e.selected); - activeObjects.forEach((v) => { + activeObjects.value = [...e.selected]; + activeObjects.value.forEach((v) => { v.layer = props.layerManager.getLayerById(v.layerId); }); - if (activeObjects.length === 0) { + if (activeObjects.value.length === 0) { close(); } else { show(); @@ -291,7 +291,7 @@ }; //取消当前选中 const cancelSelect = () => { - activeObjects.splice(0, activeObjects.length); + activeObjects.value = []; close(); }; const lastSelectLayerId = inject("lastSelectLayerId"); @@ -537,7 +537,7 @@ // 更新选中对象属性 const updateActiveObjects = (arrs, keys, isNumber = true) => { arrs.forEach((v) => { - activeObjects.forEach((item) => { + activeObjects.value.forEach((item) => { if (item.id === v.id) { keys.forEach( (key) => (item[key] = isNumber ? Number(v[key]) : v[key]) @@ -545,6 +545,7 @@ } }); }); + activeObjects.value = [...activeObjects.value]; }; // 旋转对象时更新角度 diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 7e1bd2f3..b6088c56 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -58,6 +58,7 @@ const emit = defineEmits([ "changeCanvas", // 画布变更事件 "canvasInit", // 画布初始化事件 "trigger-library", // 触发打开Library选择图片事件 + "before-unmount-export-extra-info", // 组件卸载前导出额外信息事件 ]); const props = defineProps({ @@ -544,12 +545,15 @@ watchEffect(() => { } }); -onBeforeUnmount(() => { +onBeforeUnmount(async () => { // if (import.meta.hot) { // // 热更新 ? // console.log("onBeforeUnmount 开发环境热更新不卸载组件..."); // return; // 开发环境下不卸载组件 // } + const extraInfo = await canvasManager.exportExtraInfo(); + emit("before-unmount-export-extra-info", extraInfo); + console.log("onBeforeUnmount 组件卸载,清理资源..."); canvasManager?.dispose?.(); commandManager?.dispose?.(); @@ -884,6 +888,7 @@ const changeCanvas = async (command) => { ...command, // 传递完整的命令数据 }; emit("changeCanvas", commandData); + canvasManager.changeCanvas(commandData); if ((command.canUndo || command.canRedo) && props.enabledRedGreenMode) { setTimeout(async () => { try { @@ -1008,6 +1013,7 @@ defineExpose({ exportImage: ({ isContainBg = false, // 是否包含背景图层 isContainFixed = false, // 是否包含固定图层 + isContainFixedOther = false, // 是否包含其他固定图层 isCropByBg = false, // 是否使用背景大小裁剪 // 如果为true,则导出时裁剪到背景图层大小 layerId = "", // 导出具体图层ID layerIdArray = [], // 导出多个图层ID数组 @@ -1017,6 +1023,7 @@ defineExpose({ return canvasManager.exportImage({ isContainBg, isContainFixed, + isContainFixedOther, isCropByBg, layerId, layerIdArray, @@ -1046,6 +1053,14 @@ defineExpose({ return result; }, + /** + * 导出所有信息 + * @returns {Object} 包含所有图层信息的对象 + */ + exportExtraInfo: () => { + return canvasManager.exportExtraInfo(); + }, + /** * 拖拽排序图层 * @param {number} oldIndex 原索引 diff --git a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js index eef846ec..936fc4a3 100644 --- a/src/component/Canvas/CanvasEditor/managers/CanvasManager.js +++ b/src/component/Canvas/CanvasEditor/managers/CanvasManager.js @@ -13,6 +13,7 @@ import { createLayer, LayerType, SpecialLayerId, + BlendMode, } from "../utils/layerHelper"; import { ObjectMoveCommand } from "../commands/ObjectCommands"; import { AnimationManager } from "./animation/AnimationManager"; @@ -29,7 +30,10 @@ import { palletToFill, fillToCssStyle, calculateRotatedTopLeftDeg, + calculateCenterPoint, createPatternTransform, + getTransformScaleAngle, + base64ToCanvas, } from "../utils/helper"; import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands"; import { isFunction } from "lodash-es"; @@ -40,7 +44,7 @@ import { } from "../utils/layerUtils"; import { imageModeHandler } from "../utils/imageHelper"; import { getObjectAlphaToCanvas } from "../utils/objectHelper"; -import { AddLayerCommand } from "../commands/LayerCommands"; +import { AddLayerCommand, RemoveLayerCommand } from "../commands/LayerCommands"; import { fa, id } from "element-plus/es/locales.mjs"; import i18n from "@/lang/index.ts"; const {t} = i18n.global; @@ -564,15 +568,14 @@ export class CanvasManager { } // 更新颜色层信息 - const fixedLayerObj = this.getFixedLayerObject(); const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR); - if(colorObject && fixedLayerObj){ - await this.setColorObjectInfo(colorObject, fixedLayerObj); + if(colorObject){ + await this.setObjecCliptInfo(colorObject); } const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP); - if(groupLayer && fixedLayerObj){ + if(groupLayer){ const groupRect = new fabric.Rect({}); - await this.setColorObjectInfo(groupRect, fixedLayerObj); + await this.setObjecCliptInfo(groupRect); groupLayer.clippingMask = groupRect.toObject(); } @@ -807,6 +810,13 @@ export class CanvasManager { return layerObjectByLayerId; } + getObjectsByIds(ids){ + const objects = this.canvas.getObjects().filter((obj) => { + return ids.includes(obj.id); + }); + return objects; + } + /** * 更新蒙层位置 * @param {Object} backgroundLayerObject 背景层对象 @@ -908,6 +918,7 @@ export class CanvasManager { * @param {Object} options 导出选项 * @param {Boolean} options.isContainBg 是否包含背景图层 * @param {Boolean} options.isContainFixed 是否包含固定图层 + * @param {Boolean} options.isContainFixedOther 是否包含其他固定图层 * @param {String} options.layerId 导出具体图层ID * @param {Array} options.layerIdArray 导出多个图层ID数组 * @param {String} options.expPicType 导出图片类型 (png/jpg/svg) @@ -934,6 +945,7 @@ export class CanvasManager { options.restoreOpacityInRedGreen !== undefined ? options.restoreOpacityInRedGreen : false, // 默认在红绿图模式下恢复透明度 + excludedLayers: [SpecialLayerId.SPECIAL_GROUP], }; // 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层 @@ -948,7 +960,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) || []; @@ -964,6 +976,22 @@ export class CanvasManager { } } + /** + * 导出印花元素颜色信息 + * @returns {Object} + */ + async exportExtraInfo() { + // 导出颜色图层信息 + const color = await this.exportColorLayer().catch(() => (null)); + // 导出印花和元素图层信息 + const printTrimsData = await this.exportPrintTrimsLayers().catch(() => ({prints: null, trims: null})); + + return { + color, + ...printTrimsData, + }; + } + /** * 导出颜色图层 * @returns {Object} 导出的颜色图层数据URL @@ -978,13 +1006,12 @@ export class CanvasManager { console.warn("颜色图层不存在,请确保已添加颜色图层"); return Promise.reject("颜色图层不存在"); } - const color = fillToCssStyle(object.fill) + const css = fillToCssStyle(object.fill) const canvas = new fabric.StaticCanvas(); canvas.setDimensions({ width: object.width, height: object.height, backgroundColor: null, - // enableRetinaScaling: true, imageSmoothingEnabled: true, }); const cloneObject = await new Promise((resolve, reject) => { @@ -1005,9 +1032,80 @@ export class CanvasManager { quality: 1, }); canvas.clear(); - return {color, base64}; + const color = object.originColor; + return {css, base64, color}; } + /** + * 导出印花和元素图层 + */ + async exportPrintTrimsLayers() { + const object = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP); + if(!object) return Promise.reject("印花和元素图层组不存在"); + const ids = object.children.map((v) => v.id); + const objects = this.getObjectsByIds(ids).filter((v) => !!v.sourceData); + 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 obj = { + ifSingle: v.sourceData.ifSingle, + level2Type: v.sourceData.level2Type, + designType: v.sourceData.designType, + path: v.sourceData.path, + minIOPath: v.sourceData.minIOPath, + location: [0, 0], + scale: [0, 0], + angle: v.angle, + name: v.sourceData.name, + priority: v.sourceData.priority, + } + if(obj.ifSingle){ + 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); + let {x:cx, y:cy} = calculateCenterPoint(width, height, left, top, v.angle); + let x = (cx-width/2) / flScaleX; + let y = (cy-height/2) / flScaleY; + obj.location = [x, y]; + obj.scale = [(v.width * v.scaleX) / (flWidth * flScaleX), (v.height * v.scaleY) / (flHeight * flScaleY)]; + }else{ + let fill = v.fill; + let fill_ = v.fill_; + if(!fill || !fill_) return; + 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]; + } + 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) { @@ -1105,37 +1203,55 @@ export class CanvasManager { // 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 ); - const data = JSON.stringify({ - canvas: this.canvas.toJSON([ - "id", - "type", - "layerId", - "layerName", - "isBackground", - "isLocked", - "isVisible", - "isFixed", - "parentId", - "eraser", - "eraserable", - "erasable", - "customType", - "fill_", - ]), + 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, - }); + }; console.log("获取画布JSON数据...", data); - return data; + return JSON.stringify(data); } catch (error) { console.error("获取画布JSON失败:", error); throw new Error("获取画布JSON失败"); @@ -1282,32 +1398,46 @@ export class CanvasManager { if (!otherData) return console.warn("otherData 为空不需要添加"); 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) => ids.includes(v.id) && this.canvas.remove(v)) + + // 创建颜色图层 await this.createColorLayer(otherData_.color); - if(findLayer(this.layers.value, SpecialLayerId.SPECIAL_GROUP)){ - console.warn("画布中已存在印花和元素组图层"); - }else{ - 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((print, index) => { - print.name = t("Canvas.Elements") + (index + 1); + const printTrimsLayers = [];// 印花和元素图层 + const singleLayers = [];// 平铺图层 + otherData_?.printObject?.prints?.forEach((print, index) => { + print.name = t("Canvas.Print") + (index + 1); + if(print.ifSingle){ printTrimsLayers.unshift({...print}); - }) - await this.createPrintTrimsLayers(printTrimsLayers, singleLayers); - } + }else{ + singleLayers.unshift({...print}); + } + }) + otherData_?.trims?.prints?.forEach((trims, index) => { + trims.name = t("Canvas.Elements") + (index + 1); + printTrimsLayers.unshift({...trims}); + }) + await this.createPrintTrimsLayers(printTrimsLayers, singleLayers); + + await this.changeCanvas(); } - async setColorObjectInfo(colorRect, fixedLayerObj){ - colorRect.set({ + // 设置画布对象的裁剪信息 + async setObjecCliptInfo(tagObject, data){ + const fixedLayerObj = this.getFixedLayerObject(); + if(!fixedLayerObj) return console.warn("固定图层为空"); + tagObject.set({ top: fixedLayerObj.top, left: fixedLayerObj.left, width: fixedLayerObj.width, @@ -1322,7 +1452,7 @@ export class CanvasManager { if(imageUrl){ object = await new Promise((resolve, reject) => { fabric.Image.fromURL(imageUrl, (imgObject) => { - colorRect.set({ + tagObject.set({ width: imgObject.width, height: imgObject.height, }); @@ -1330,20 +1460,21 @@ export class CanvasManager { }, { crossOrigin: "anonymous" }); }); } - const canvas = getObjectAlphaToCanvas(object); + const canvas = getObjectAlphaToCanvas(object, data); const transparentMask = new fabric.Image(canvas, { top: 0, left: 0, originX: fixedLayerObj.originX, originY: fixedLayerObj.originY, }); - colorRect.set('clipPath', transparentMask); + tagObject.set('clipPath', transparentMask); } async createColorLayer(color){ if(!color) return console.warn("颜色为空不需要添加"); - if(findLayer(this.layers.value, SpecialLayerId.COLOR)) return console.warn("画布中已存在颜色图层"); + // if(findLayer(this.layers.value, SpecialLayerId.COLOR)) { + // return console.warn("画布中已存在颜色图层"); + // } console.log("==========添加颜色图层", color, this.layers.value.length) - const fixedLayerObj = this.getFixedLayerObject(); // 创建颜色图层对象 const colorRect = new fabric.Rect({ id: SpecialLayerId.COLOR, @@ -1351,8 +1482,13 @@ export class CanvasManager { layerName: t("Canvas.color"), isVisible: true, isLocked: true, + selectable: false, + hasControls: false, + hasBorders: false, + globalCompositeOperation: BlendMode.MULTIPLY, + originColor: color, }); - await this.setColorObjectInfo(colorRect, fixedLayerObj); + await this.setObjecCliptInfo(colorRect); const gradientObj = palletToFill(color); const gradient = new fabric.Gradient({ type: 'linear', @@ -1370,6 +1506,7 @@ export class CanvasManager { 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); @@ -1378,6 +1515,9 @@ export class CanvasManager { // 创建印花和元素图层 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 @@ -1389,24 +1529,24 @@ export class CanvasManager { const children = []; // 添加印花和元素图层 for(let index = 0; index < printTrimsLayers.length; index++){ - let print = printTrimsLayers[index]; + let item = printTrimsLayers[index]; let id = generateId("layer_image_"); - let name = print.name; + let name = item.name; let image = await new Promise(resolve => { - fabric.Image.fromURL(print.path, (fabricImage)=>{ - const left = flLeft - flWidth * flScaleX / 2 + (print.location?.[0] || 0) * flScaleX - const top = flTop - flHeight * flScaleY / 2 + (print.location?.[1] || 0) * flScaleY - const scaleX = flWidth * (print.scale?.[0] || 1) / fabricImage.width * flScaleX - const scaleY = flHeight * (print.scale?.[1] || 1) / fabricImage.height * flScaleY + fabric.Image.fromURL(item.path, (fabricImage)=>{ + const left = flLeft - flWidth * flScaleX / 2 + (item.location?.[0] || 0) * flScaleX + const top = flTop - flHeight * flScaleY / 2 + (item.location?.[1] || 0) * flScaleY + const scaleX = flWidth * (item.scale?.[0] || 1) / fabricImage.width * flScaleX + const scaleY = flHeight * (item.scale?.[1] || 1) / fabricImage.height * flScaleY const {x, y} = calculateRotatedTopLeftDeg( fabricImage.width * scaleX, fabricImage.height * scaleY, left, top, 0, - print.angle || 0 + item.angle || 0 ) - const angle = print.angle || 0 + const angle = item.angle || 0 fabricImage.set({ left: x, top: y, @@ -1419,6 +1559,7 @@ export class CanvasManager { selectable: true, hasControls: true, hasBorders: true, + sourceData: item, }); resolve(fabricImage); }, { crossOrigin: "anonymous" }); @@ -1437,11 +1578,11 @@ export class CanvasManager { }; // 添加平铺图层 for(let index = 0; index < singleLayers.length; index++){ - let print = singleLayers[index]; + let item = singleLayers[index]; let id = generateId("layer_image_"); - let name = print.name; + let name = item.name; let image = await new Promise(resolve => { - fabric.Image.fromURL(print.path, (fabricImage)=>{ + fabric.Image.fromURL(item.path, (fabricImage)=>{ const imgElement = fabricImage.getElement(); const tcanvas = document.createElement('canvas'); tcanvas.width = imgElement.width; @@ -1452,12 +1593,11 @@ export class CanvasManager { resolve(tcanvas); }, { crossOrigin: "anonymous" }); }) - console.log("==========添加平铺图层", fixedLayerObj.width,image.width) - let scaleX = fixedLayerObj.width / image.width * (print.scale?.[0] || 1) / 5; - let scaleY = fixedLayerObj.height / image.height * (print.scale?.[1] || 1) / 5; + 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 left = (print.location?.[0] || 0) - image.width * scale / 2 - let top = (print.location?.[1] || 0) - image.height * scale / 2 + let left = (item.location?.[0] || 0) - image.width * scale / 2 + let top = (item.location?.[1] || 0) - image.height * scale / 2 let rect = new fabric.Rect({ id: id, layerId: id, @@ -1470,13 +1610,21 @@ export class CanvasManager { scaleY: fixedLayerObj.scaleY, originX: fixedLayerObj.originX, originY: fixedLayerObj.originY, + sourceData: item, fill: new fabric.Pattern({ source: image, repeat: "repeat", - patternTransform: createPatternTransform(scale, print.angle || 0), + patternTransform: createPatternTransform(scale, item.angle || 0), offsetX: left, // 水平偏移 offsetY: top, // 垂直偏移 }), + fill_ : { + source: item.path, + gapX: 0, + gapY: 0, + width: image.width, + height: image.height, + } }); this.canvas.add(rect); let layer = createLayer({ @@ -1503,7 +1651,7 @@ export class CanvasManager { children.push(layer); } const groupRect = new fabric.Rect({}); - await this.setColorObjectInfo(groupRect, fixedLayerObj); + await this.setObjecCliptInfo(groupRect); // 插入组图层 const groupIndex = this.layers.value.findIndex(layer => layer.isFixedOther || layer.isFixed || layer.isBackground); const groupLayer = createLayer({ @@ -1521,6 +1669,35 @@ export class CanvasManager { 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(); + // } + } + /** * 缩放红绿图模式内容以适应当前画布大小 * 确保衣服底图和红绿图永远在画布内可见 diff --git a/src/component/Canvas/CanvasEditor/managers/ExportManager.js b/src/component/Canvas/CanvasEditor/managers/ExportManager.js index 58514887..9c41437c 100644 --- a/src/component/Canvas/CanvasEditor/managers/ExportManager.js +++ b/src/component/Canvas/CanvasEditor/managers/ExportManager.js @@ -1,7 +1,7 @@ import { fabric } from "fabric-with-all"; import { findObjectById } from "../utils/helper"; import { createRasterizedImage } from "../utils/selectionToImage"; - import { OperationType, SpecialLayerId } from "../utils/layerHelper"; +import { OperationType, SpecialLayerId } from "../utils/layerHelper"; /** * 图片导出管理器 @@ -19,32 +19,38 @@ export class ExportManager { * @param {Object} options 导出选项 * @param {Boolean} options.isContainBg 是否包含背景图层 * @param {Boolean} options.isContainFixed 是否包含固定图层 + * @param {Boolean} options.isContainFixedOther 是否包含其他固定图层 * @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 {Boolean} options.isEnhanceImg 是否是增强图片 + * @param {Array} options.excludedLayers 排除的图层ID数组 * @returns {String} 导出的图片数据URL */ async exportImage(options = {}) { const { isContainBg = false, isContainFixed = false, + isContainFixedOther = false, // 是否包含其他固定图层 isCropByBg = false, // 是否使用背景大小裁剪 layerId = "", layerIdArray = [], + layerIdArray2 = null, expPicType = "png", restoreOpacityInRedGreen = true, isEnhanceImg, // 是否是增强图片 + excludedLayers = [], // 排除的图层ID数组 } = options; try { // 查找颜色图层并隐藏 - const colorLayer = this.layerManager.getLayerById(SpecialLayerId.COLOR); - if (colorLayer && colorLayer.visible) { - colorLayer.visible = false; - await this.layerManager?.updateLayersObjectsInteractivity(); - } + // const colorLayer = this.layerManager.getLayerById(SpecialLayerId.COLOR); + // if (colorLayer && colorLayer.visible) { + // colorLayer.visible = false; + // await this.layerManager?.updateLayersObjectsInteractivity(); + // } // 检查是否为红绿图模式 const isRedGreenMode = this.layerManager?.isInRedGreenMode?.() || false; @@ -67,6 +73,7 @@ export class ExportManager { expPicType, isContainBg, isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 isRedGreenMode, restoreOpacityInRedGreen, isCropByBg, @@ -79,10 +86,13 @@ export class ExportManager { expPicType, isContainBg, isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 isRedGreenMode, restoreOpacityInRedGreen, isCropByBg, isEnhanceImg, // 是否是增强图片 + layerIdArray2, + excludedLayers, // 排除的图层ID数组 ); } catch (error) { console.error("导出图片失败:", error); @@ -155,6 +165,7 @@ export class ExportManager { * @param {String} expPicType 导出类型 * @param {Boolean} isContainBg 是否包含背景图层 * @param {Boolean} isContainFixed 是否包含固定图层 + * @param {Boolean} isContainFixedOther 是否包含其他固定图层 * @param {Boolean} isRedGreenMode 是否为红绿图模式 * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 * @param {Boolean} isCropByBg 是否使用背景大小裁剪 @@ -167,6 +178,7 @@ export class ExportManager { expPicType, isContainBg, isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 isRedGreenMode, restoreOpacityInRedGreen, isCropByBg, // 是否使用背景大小裁剪 @@ -180,7 +192,8 @@ export class ExportManager { const objectsToExport = this._collectObjectsByLayerOrder( layerIdArray, isContainBg, - isContainFixed + isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 ); if (objectsToExport.length === 0) { @@ -212,10 +225,12 @@ export class ExportManager { * @param {String} expPicType 导出类型 * @param {Boolean} isContainBg 是否包含背景图层 * @param {Boolean} isContainFixed 是否包含固定图层 + * @param {Boolean} isContainFixedOther 是否包含其他固定图层 * @param {Boolean} isRedGreenMode 是否为红绿图模式 * @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1 * @param {Boolean} isCropByBg 是否使用背景大小裁剪 * @param {Boolean} isEnhanceImg 是否是增强图片 + * @param {Array} layerIdArray 导出多个图层ID数组2 * @returns {String} 图片数据URL * @private */ @@ -223,16 +238,21 @@ export class ExportManager { expPicType, isContainBg, isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 isRedGreenMode, restoreOpacityInRedGreen, isCropByBg, // 是否使用背景大小裁剪 - isEnhanceImg, // 是否是增强图片 + isEnhanceImg, // 是否是增强图片 + layerIdArray, // 导出所有图层 + excludedLayers, // 排除的图层ID数组 ) { // 按图层顺序收集对象(从底到顶) const objectsToExport = this._collectObjectsByLayerOrder( - null, // 导出所有图层 + layerIdArray, // 导出所有图层 isContainBg, isContainFixed, + isContainFixedOther, // 是否包含其他固定图层 + excludedLayers, ); if (objectsToExport.length === 0) { @@ -288,10 +308,11 @@ export class ExportManager { /** * 从图层收集对象(优化版本 - 通过ID查找画布中的真实对象) * @param {Object} layer 图层对象 + * @param {Boolean} isChildren 是否递归收集子图层的对象 * @returns {Array} 画布中的真实对象数组 * @private */ - _collectObjectsFromLayer(layer) { + _collectObjectsFromLayer(layer, isChildren = true) { if (!layer) { return []; } @@ -320,10 +341,10 @@ export class ExportManager { } // 递归收集子图层的对象 - if (layer.children && layer.children.length > 0) { + 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); + const childObjects = this._collectObjectsFromLayer(childLayer, isChildren); realObjects.push(...childObjects); } } @@ -389,12 +410,14 @@ export class ExportManager { * @param {Array|null} layerIdArray 图层ID数组,null表示所有图层 * @param {Boolean} isContainBg 是否包含背景图层 * @param {Boolean} isContainFixed 是否包含固定图层 + * @param {Boolean} isContainFixedOther 是否包含其他固定图层 + * @param {Array} excludedLayers 排除的图层ID数组 * @returns {Array} 按正确顺序排列的真实对象数组 * @private */ - _collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed) { + _collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed, isContainFixedOther, excludedLayers) { const objectsToExport = []; - const allLayers = this._getAllLayersFlattened(); // 获取扁平化的图层列表 + const allLayers = this._getAllLayersFlattened(excludedLayers); // 获取扁平化的图层列表 // 图层数组是从顶到底的顺序,需要反向遍历以获得从底到顶的渲染顺序 for (let i = allLayers.length - 1; i >= 0; i--) { @@ -404,11 +427,11 @@ export class ExportManager { if (layerIdArray && !layerIdArray.includes(layer.id)) continue; // 检查图层类型过滤条件 - if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed)) + if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther)) continue; if (layer.visible) { - const layerObjects = this._collectObjectsFromLayer(layer); + const layerObjects = this._collectObjectsFromLayer(layer, false); objectsToExport.push(...layerObjects); } } @@ -417,15 +440,19 @@ export class ExportManager { } /** - * 获取扁平化的图层列表(包含子图层) + * 获取扁平化的图层列表(包含子图层),排除指定的图层 + * @param {Array} excludedLayers 排除的图层ID数组 * @returns {Array} 扁平化的图层数组 * @private */ - _getAllLayersFlattened() { + _getAllLayersFlattened(excludedLayers) { const flattenedLayers = []; const rootLayers = this._getAllLayers(); const flattenLayer = (layer) => { + // 检查是否在排除列表中 + if (excludedLayers && excludedLayers.includes(layer.id)) return; + flattenedLayers.push(layer); // 递归处理子图层 @@ -440,7 +467,6 @@ export class ExportManager { for (const layer of rootLayers) { flattenLayer(layer); } - return flattenedLayers; } @@ -1019,10 +1045,11 @@ export class ExportManager { * @param {Object} layer 图层对象 * @param {Boolean} isContainBg 是否包含背景图层 * @param {Boolean} isContainFixed 是否包含固定图层 + * @param {Boolean} isContainFixedOther 是否包含其他固定图层 * @returns {Boolean} 是否应该包含 * @private */ - _shouldIncludeLayer(layer, isContainBg, isContainFixed) { + _shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther) { if (!layer) return false; // 检查背景图层 @@ -1035,6 +1062,11 @@ export class ExportManager { return isContainFixed; } + // 检查其他固定图层 + if (layer.isFixedOther) { + return isContainFixedOther; + } + // 普通图层总是包含 return true; } diff --git a/src/component/Canvas/CanvasEditor/managers/LayerManager.js b/src/component/Canvas/CanvasEditor/managers/LayerManager.js index 272c22fc..2e105cb8 100644 --- a/src/component/Canvas/CanvasEditor/managers/LayerManager.js +++ b/src/component/Canvas/CanvasEditor/managers/LayerManager.js @@ -524,15 +524,16 @@ export class LayerManager { * @param {string} name 图层名称 * @param {string} type 图层类型 * @param {Object} options 额外选项 + * @param {boolean} isCmd 是否创建命令 * @returns {string} 新创建的图层ID */ - async createLayer(name = null, type = LayerType.EMPTY, options = {}) { + async createLayer(name = null, type = LayerType.EMPTY, options = {}, isCmd = true) { // 生成唯一ID const layerId = options.id || options.layerId || generateId("layer_"); // 计算普通图层数量(非背景、非固定) const normalLayersCount = this.layers.value.filter( - (layer) => !layer.isBackground && !layer.isFixed + (layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther ).length; // 计算插入位置,如果没有指定insertIndex,则根据当前选中图层决定插入位置 // 添加到图层列表 @@ -544,7 +545,7 @@ export class LayerManager { // 创建新图层 const newLayer = createLayer({ id: layerId, - name: name || `图层 ${normalLayersCount + 1}`, + name: name || this.t("Canvas.EmptyLayer"), type: type, visible: true, locked: false, @@ -573,13 +574,13 @@ export class LayerManager { } // 执行命令 - if (this.commandManager) { + if (isCmd && this.commandManager) { await this.commandManager.execute(command); - } else { + } else{ await command.execute(); } - return layerId; + return isCmd ? layerId : command; } /** @@ -973,7 +974,7 @@ export class LayerManager { }) // const normalLayers = this.layers.value.filter((l) => !l.isBackground && !l.isFixed && !l.isFixedOther); console.log("普通图层:", normalLayers) - if (isChild ? parentLength <= 1 : normalLayers.length <= 1) { + if (isChild ? parentLength <= 1 : false) {//normalLayers.length <= 1 console.warn("不能删除唯一的普通图层"); message.warning(this.t("Canvas.cannotDeleteOnlyLayer")); return false; @@ -3436,4 +3437,22 @@ export class LayerManager { console.log("🎨 已设置组遮罩移动同步 - 使用 object:modified 事件"); } + + /** + * 获取印花和颜色图层设置了blendMode的图层ID + * @returns {string[]} - 包含blendMode的图层ID数组 + */ + getBlendModeLayerIds() { + const blendModeLayerIds = []; + this.layers.value.forEach(layer => { + if(layer.id === SpecialLayerId.SPECIAL_GROUP){ + layer.children.forEach(child => { + if(child.visible && child.blendMode && child.blendMode !== BlendMode.NORMAL){ + blendModeLayerIds.push(child.id); + } + }); + } + }); + return blendModeLayerIds; + } } diff --git a/src/component/Canvas/CanvasEditor/utils/helper.js b/src/component/Canvas/CanvasEditor/utils/helper.js index 729298ad..5a7fd912 100644 --- a/src/component/Canvas/CanvasEditor/utils/helper.js +++ b/src/component/Canvas/CanvasEditor/utils/helper.js @@ -919,6 +919,26 @@ export function calculateRotatedTopLeftDeg( return { x: newX, y: newY }; } +/** + * 根据左上角坐标计算中心点坐标 + * @param {number} W - 宽度 + * @param {number} H - 高度 + * @param {number} currentX - 当前左上角x坐标 + * @param {number} currentY - 当前左上角y坐标 + * @param {number} currentAngleDeg - 当前角度(度) + * @returns {Object} 中心点坐标 {x, y} + */ +export function calculateCenterPoint(W, H, currentX, currentY, currentAngleDeg) { + const currentAngle = (currentAngleDeg * Math.PI) / 180; + const cosCurrent = Math.cos(currentAngle); + const sinCurrent = Math.sin(currentAngle); + const Cx = currentX + (W / 2) * cosCurrent - (H / 2) * sinCurrent; + const Cy = currentY + (W / 2) * sinCurrent + (H / 2) * cosCurrent; + return { x: Cx, y: Cy }; +} + + + /** * 创建缩放+旋转的变换矩阵 * @param {number} scale - 缩放比例 @@ -959,3 +979,32 @@ export function getTransformScaleAngle(Transform) { const angle = Math.round(Math.atan2(b, a) * 180 / Math.PI); return { scale, angle }; } + +/** + * 图片转换为canvas + * @param {String} base64 - 图片base64编码 + * @param {Number} scale - 缩放比例 + * @param {Boolean} sr - 缩放反转,默认false + * @returns {Promise} canvas元素 +*/ +export async function base64ToCanvas(base64, scale = 1, sr = false) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.src = base64; + image.crossOrigin = 'anonymous'; + image.onload = () => { + image.width = image.width; + image.height = image.height; + const canvas = document.createElement('canvas'); + const width = (sr ? image.width / scale : image.width * scale); + const height = sr ? image.height / scale : image.height * scale; + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, width, height); + ctx.drawImage(image, 0, 0, width, height); + resolve(canvas); + }; + image.onerror = reject; + }); +} diff --git a/src/component/Canvas/CanvasEditor/utils/layerUtils.js b/src/component/Canvas/CanvasEditor/utils/layerUtils.js index 1643eb8d..5effb1db 100644 --- a/src/component/Canvas/CanvasEditor/utils/layerUtils.js +++ b/src/component/Canvas/CanvasEditor/utils/layerUtils.js @@ -155,15 +155,19 @@ export function validateLayerAssociations(layers, canvasObjects) { /** * 简化layers对象属性,只保留必要的属性 * @param {Array} layers 图层数组 + * @param {Array} excludedLayers 排除的图层ID数组 * @returns {Array} 简化后的图层数组 */ -export function simplifyLayers(layers) { +export function simplifyLayers(layers, excludedLayers = []) { if (!layers || !isArray(layers)) { console.warn("simplifyLayers 请传入有效的图层数组:", layers); return []; } return layers.map((layer) => { + // 检查是否在排除列表中 + if (excludedLayers && excludedLayers.includes(layer.id)) return null; + const simplifiedLayer = { id: layer.id, name: layer.name, @@ -211,10 +215,11 @@ export function simplifyLayers(layers) { fill: layer?.fill || null, fillColor: layer.fillColor, selectObject: layer.selectObject, + blendMode: layer.blendMode || null, }; return simplifiedLayer; - }); + }).filter((layer) => !!layer); } /** diff --git a/src/component/Canvas/CanvasEditor/utils/objectHelper.js b/src/component/Canvas/CanvasEditor/utils/objectHelper.js index 96fd3986..8c8fb42e 100644 --- a/src/component/Canvas/CanvasEditor/utils/objectHelper.js +++ b/src/component/Canvas/CanvasEditor/utils/objectHelper.js @@ -61,11 +61,14 @@ export async function restoreFabricObject(serializedObject, canvas) { /** * 获取对象黑白通道画布 + * @param {fabric.Object} object - 要处理的 fabric 对象 + * @param {ImageData} revData - 相反的ImageData,白通道的相同位置是否为透明,revData为白色为透明,黑色为不透明 + * @returns {HTMLCanvasElement|null} 包含黑白通道的画布,或 null 如果失败 */ -export function getObjectAlphaToCanvas(object) { +export function getObjectAlphaToCanvas(object, revData) { const image = object.getElement(); const { width, height } = image; - if(!width || !height){ + if (!width || !height) { console.warn("对象没有元素"); return null; } @@ -80,12 +83,23 @@ export function getObjectAlphaToCanvas(object) { const g = data.data[i + 1]; const b = data.data[i + 2]; const a = data.data[i + 3]; + const revR = revData?.data[i + 0] || 0; + const revG = revData?.data[i + 1] || 0; + const revB = revData?.data[i + 2] || 0; + const revA = revData?.data[i + 3] || 0; if (r || g || b || a) { - data.data[i + 0] = 255; - data.data[i + 1] = 255; - data.data[i + 2] = 255; - data.data[i + 3] = 255; - }else{ + if (revR || revG || revB || revA) { + data.data[i + 0] = 0; + data.data[i + 1] = 0; + data.data[i + 2] = 0; + data.data[i + 3] = 0; + } else { + data.data[i + 0] = 255; + data.data[i + 1] = 255; + data.data[i + 2] = 255; + data.data[i + 3] = 255; + } + } else { data.data[i + 0] = 0; data.data[i + 1] = 0; data.data[i + 2] = 0; diff --git a/src/component/Canvas/CanvasEditor/utils/selectionToImage.js b/src/component/Canvas/CanvasEditor/utils/selectionToImage.js index 34827db7..75e39218 100644 --- a/src/component/Canvas/CanvasEditor/utils/selectionToImage.js +++ b/src/component/Canvas/CanvasEditor/utils/selectionToImage.js @@ -1,6 +1,6 @@ // 栅格化帮助 import { fabric } from "fabric-with-all"; - +import { SpecialLayerId } from "./layerHelper"; /** * 创建栅格化图像 - 重构版本 * 采用复制原对象+裁剪路径的方式,保持原始质量和准确位置 @@ -184,10 +184,16 @@ const createClippedDataURLByCanvas = async ({ console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL"); // 使用优化后的边界计算,确保包含描边区域 - const optimizedBounds = calculateOptimizedBounds( - clippingObject, - fabricObjects - ); + // const optimizedBounds = calculateOptimizedBounds( + // clippingObject, + // fabricObjects + // ); + const optimizedBounds = { + left: clippingObject.left - clippingObject.width / 2, + top: clippingObject.top - clippingObject.height / 2, + width: clippingObject.width, + height: clippingObject.height, + } // 使用高分辨率以保证质量 const pixelRatio = window.devicePixelRatio || 1; @@ -685,6 +691,16 @@ const cloneObjectAsync = (obj) => { return new Promise((resolve, reject) => { obj.clone((cloned) => { if (cloned) { + cloned.set({ + scaleX: obj.scaleX, + scaleY: obj.scaleY, + top: obj.top, + left: obj.left, + width: obj.width, + height: obj.height, + zoomX: obj.zoomX, + zoomY: obj.zoomY, + }) resolve(cloned); } else { reject(new Error("对象克隆失败")); @@ -839,9 +855,8 @@ const renderContentToImage = async ({ }); // 克隆并添加所有需要渲染的对象 - for (const obj of fabricObjects) { - const clonedObj = await cloneObjectAsync(obj); - + for (let obj of fabricObjects) { + let clonedObj = await cloneObjectAsync(obj); // 调整对象位置:将选区左上角作为新的原点(0,0) clonedObj.set({ left: (clonedObj.left - selectionBounds.left) * qualityMultiplier, @@ -853,19 +868,19 @@ const renderContentToImage = async ({ }); // 如果有裁剪路径,也需要调整裁剪路径 - if (clonedObj.clipPath) { + if (clonedObj.clipPath && obj.id !== SpecialLayerId.COLOR) { clonedObj.clipPath.set({ - left: - (clonedObj.clipPath.left - selectionBounds.left) * - qualityMultiplier, - top: - (clonedObj.clipPath.top - selectionBounds.top) * qualityMultiplier, + left: (clonedObj.clipPath.left - selectionBounds.left) * qualityMultiplier, + top: (clonedObj.clipPath.top - selectionBounds.top) * qualityMultiplier, scaleX: (clonedObj.clipPath.scaleX || 1) * qualityMultiplier, scaleY: (clonedObj.clipPath.scaleY || 1) * qualityMultiplier, }); clonedObj.clipPath.setCoords(); // 更新裁剪路径坐标 } - + // if(obj.globalCompositeOperation === "multiply"){ + // clonedObj.clipPath = null; + // } + console.log("==========", obj.id, obj.layerName); contentCanvas.add(clonedObj); } diff --git a/src/component/Canvas/canvasExample.vue b/src/component/Canvas/canvasExample.vue index aebfa8ea..772e1ce0 100644 --- a/src/component/Canvas/canvasExample.vue +++ b/src/component/Canvas/canvasExample.vue @@ -9,8 +9,8 @@ import ToolButton from "@/component/Canvas/ExistsImageList/ToolButton.vue"; const canvasEditor = ref(); const currentView = ref("canvasEditor"); // 默认显示红绿图示例 canvasEditor redGreenExample -const clothingImageUrl = "https://www.minio-api.aida.com.hk/aida-collection-element/24299/Printboard/4eba03bd-4367-4c69-b1a3-3f3177a1be1f.jpg?response-content-type=image%2Fjpeg&response-content-disposition=inline&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20251217%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251217T081126Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=b8223ba37625370c9716024536a08ce1ee5c5a7aaefc47d9baf8bd1e0e4d2d91"; -const clothingImageUrlInit = "/src/assets/work/5.PNG"; +const clothingImageUrl = "/src/assets/images/canvas/xiangao.png"; +const clothingImageUrlInit = "/src/assets/images/canvas/xiangaofenge.png"; const imageData = [ { @@ -71,8 +71,10 @@ const editorConfig = { const exportImage = async () => { if (canvasEditor.value) { const base64 = await canvasEditor.value.exportImage({ - isContainFixed: true, // 是否导出底图 + isContainFixed: false, // 是否导出底图 + isContainFixedOther: false, // 是否导出其他固定图层 isContainBg: false, // 是否导出背景 + isEnhanceImg: false, // 是否导出增强图片 }); // 模拟下载图片 @@ -99,6 +101,16 @@ const exportColorLayer = async () => { } }; +// 导出所有信息 +const exportExtraInfo = async () => { + if (canvasEditor.value) { + const extraInfo = await canvasEditor.value.exportExtraInfo(); + console.log("==========导出信息:", extraInfo); + } +}; + + + const changeCanvas = (command) => { console.log(command); }; @@ -194,12 +206,19 @@ const frontBackChange =(value)=>{ // 自定义工具配置相关 const customToolsList = ref([ { - id: "exportPNG", + id: "exportColorLayer", title: "导出颜色图层", action: exportColorLayer, label: "导颜", class: "export-btn", }, + { + id: "exportExtraInfo", + title: "导出印花颜色等信息", + action: exportExtraInfo, + label: "导E", + class: "export-btn", + }, { id: "exportPNG", title: "导出PNG", //导出画布图片 @@ -272,6 +291,40 @@ const customToolsList = ref([ class: "export-btn", }, ]); +const otherData = { + color: {rgba: {r:255,g:0,b:0,a:1}}, + printObject: { + prints: [ + { + ifSingle: false, + level2Type: "Pattern", + designType: "Library", + path: "/src/assets/images/canvas/yinhua1.jpg", + location: [250, 780], + scale: [0.5 * 0.7, 0.272541 * 0.7], + angle: 0, + }, + { + ifSingle: true, + level2Type: "Pattern", + designType: "Library", + path: "/src/assets/images/canvas/yinhua1.jpg", + location: [250, 780], + scale: [0.5 * 0.7, 0.272541 * 0.7], + angle: 0, + }, + { + ifSingle: true, + level2Type: "Pattern", + designType: "Library", + path: "/src/assets/images/canvas/yinhua1.jpg", + location: [300, 500], + scale: [0.5 * 0.4, 0.272541 * 0.4], + angle: 0, + } + ] + }, +}