From 5f29e488ed2a24935bff74e4ea7c783ebf2f3379 Mon Sep 17 00:00:00 2001 From: bighuixiang <472705331@qq.com> Date: Wed, 16 Jul 2025 11:35:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=A1=AB=E5=85=85?= =?UTF-8?q?=E7=BB=84=E5=9B=BE=E5=B1=82=E8=83=8C=E6=99=AF=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=AE=9E=E6=97=B6=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=92=8C=E6=92=A4=E9=94=80=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=B9?= =?UTF-8?q?=E8=BF=9B=E5=A1=AB=E5=85=85=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FillGroupLayerBackgroundCommand.js | 178 +++++++++++++----- .../commands/FillLayerBackgroundCommand.js | 147 ++++++++++----- .../commands/RasterizeLayerCommand.js | 48 ++--- .../components/LayersPanel/LayersPanel.vue | 72 +++++-- .../managers/BackgroundFillManager.js | 11 +- .../CanvasEditor/managers/LayerManager.js | 67 +++---- .../CanvasEditor/managers/ThumbnailManager.js | 24 +-- .../Canvas/CanvasEditor/utils/LayerSort.js | 8 +- .../CanvasEditor/utils/rasterizedImage.js | 18 +- 9 files changed, 372 insertions(+), 201 deletions(-) diff --git a/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js b/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js index 4913e7e5..4b3eef5c 100644 --- a/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/FillGroupLayerBackgroundCommand.js @@ -7,11 +7,12 @@ import { insertObjectAtZIndex, removeCanvasObjectByObject, } from "../utils/helper"; +import { restoreFabricObject } from "../utils/objectHelper"; /** * 填充组图层背景命令 */ -export class FillLayerBackgroundCommand extends Command { +export class FillGroupLayerBackgroundCommand extends Command { constructor(options) { super({ name: "填充组图层背景", saveState: true }); this.canvas = options.canvas; @@ -30,54 +31,85 @@ export class FillLayerBackgroundCommand extends Command { this.group = null; this.originalfabricObjects = this._collectOriginalObjects(); // 记录所有的原始对象 + + // 计算所有对象的边界 + this.originalInfo = this._getCurrentObjectsBoundingRect(); } async execute() { const layer = this.layer; - // 只允许填充背景层或普通图层 if (!layer.isBackground && !layer.isFixed && !layer.fabricObjects?.length) return false; - // 记录原填充色 this.oldFill = layer.fill ?? null; this.oldFillColor = layer.oldFillColor ?? null; - // 更新fabric对象填充 - // 判断是否有遮罩层 有遮罩使用遮罩层大小 否则使用第一个大小 - const originalInfo = - findObjectById(this.canvas.value, layer.fabricObjects?.[0]?.id)?.object?.getBoundingRect?.( - true, - true - ) || - layer?.clippingMask || - layer.fabricObjects?.[0]; + // 构建填充对象 + let clippingMaskFabricObject = null; + if (layer.clippingMask) { + clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas); + clippingMaskFabricObject.clipPath = null; + clippingMaskFabricObject.set({ absolutePositioned: true }); + this.newFill = new fabric.Rect({ + width: clippingMaskFabricObject.width, + height: clippingMaskFabricObject.height, + left: clippingMaskFabricObject.left || 0, + top: clippingMaskFabricObject.top || 0, + fill: this.fillColor, + layerId: this.layerId, + id: this.oldFill?.id || generateId("fill-"), + selectable: false, + evented: false, + originX: clippingMaskFabricObject.originX || "center", + originY: clippingMaskFabricObject.originY || "center", + scaleX: clippingMaskFabricObject.scaleX || 1, + scaleY: clippingMaskFabricObject.scaleY || 1, + // type: "fill", + }); + this.newFill.clipPath = clippingMaskFabricObject; + // this.newFill.dirty = true; + // this.newFill.setCoords(); + } else { + const originalInfo = this.originalInfo; + if (originalInfo) { + this.newFill = new fabric.Rect({ + width: originalInfo.width, + height: originalInfo.height, + left: originalInfo.left + originalInfo.width / 2 || 0, + top: originalInfo.top + originalInfo.height / 2 || 0, + fill: this.fillColor, + layerId: this.layerId, + id: this.oldFill?.id || generateId("fill-"), + selectable: false, + evented: false, + originX: "center", + originY: "center", + // type: "fill", + }); + } + } - this.newFill = new fabric.Rect({ - width: originalInfo.width, - height: originalInfo.height, - left: originalInfo.left || 0, - top: originalInfo.top || 0, - fill: this.fillColor, - layerId: this.layerId, - id: generateId("fill-"), - selectable: false, - evented: false, - originX: originalInfo.originX || "center", - originY: originalInfo.originY || "center", - scaleX: originalInfo.scaleX || 1, - scaleY: originalInfo.scaleY || 1, - }); - - // 判断fabricObjects是否是组,是组则添加填充到最前面,否则创建组 - if (layer.fabricObjects && layer.fabricObjects.length > 0) { - // 如果是组,直接添加到组中 - // 否则创建一个新的组 插入到原本的图层对象前面 - const layerObjects = this.canvas - .getObjects() - .reverse() - .filter((obj) => obj.layerId === this.layerId); - let insertIndex = - this.canvas.getObjects()?.findIndex((obj) => obj.id === layer.fabricObjects?.[0]?.id) || 0; - insertIndex = insertIndex == -1 ? 0 : insertIndex; + // 判断fabricObjects是否是组对象 + const firstObj = layer.fabricObjects?.[0]; + const canvasObj = findObjectById(this.canvas, firstObj?.id)?.object; + if ((canvasObj && canvasObj.type === "group") || canvasObj._objects?.length > 0) { + this.newFill.set({ + left: 0, + top: 0, + }); + // 将新填充对象添加到组的最前面 + // canvasObj._objects.unshift(this.newFill); + canvasObj.insertAt(this.newFill, 0, false); + // canvasObj.addWithUpdate(); + canvasObj.setCoords(); + canvasObj.setObjectsCoords(); + // canvasObj.dirty = true; // 标记为脏对象 + // this.canvas.renderAll(); + // this.group = canvasObj; + } else if (layer.fabricObjects && layer.fabricObjects.length > 0) { + // 普通对象,组成新组 + const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId); + let insertIndex = this.canvas.getObjects()?.findIndex((obj) => obj.id === firstObj?.id) ?? 0; + insertIndex = insertIndex === -1 ? 0 : insertIndex; this.group = new fabric.Group([this.newFill, ...layerObjects]); this.group.set({ @@ -85,29 +117,47 @@ export class FillLayerBackgroundCommand extends Command { layerId: layerObjects[0]?.layerId, }); this.group.setCoords(); - layer.fabricObjects = [this.group?.toObject?.(["id", "layerId"]) || this.group]; - removeCanvasObjectByObject(this.canvas, layer.fabricObjects[0]); - - insertObjectAtZIndex(this.canvas, this.group, insertIndex, false); // 插入到画布的指定位置 - // 将新填充对象添加到画布 + removeCanvasObjectByObject(this.canvas, layerObjects[0]); + insertObjectAtZIndex(this.canvas, this.group, insertIndex, false); } + // layer.fabricObjects = [this.group?.toObject?.(["id", "layerId"]) || this.group]; this.canvas.renderAll(); - - layer.fill = this.newFill.toObject(["id", "layerId"]); + layer.fill = null; // this.newFill.toObject(["id", "layerId"]); layer.fillColor = this.fillColor; - this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); return true; } - async undo() { this.layer.fillColor = this.oldFillColor; this.layer.fill = this.oldFill; - this.group.removeWithUpdate(this.newFill); - this.canvas.remove(this.newFill); - this.canvas.renderAll(); + // 判断fabricObjects是否是组对象 + const firstObj = this.layer.fabricObjects?.[0]; + const canvasObj = this.group || findObjectById(this.canvas, firstObj?.id)?.object; + + if ((canvasObj && canvasObj.type === "group") || canvasObj?._objects?.length > 0) { + // 移除新添加的填充对象 + if (canvasObj._objects?.[0] === this.newFill) { + canvasObj._objects.shift(); + canvasObj.addWithUpdate(); + canvasObj.setCoords(); + canvasObj.dirty = true; + } + this.canvas.renderAll(); + } else if (this.group) { + // 如果是新建的group,需要拆分还原 + // 先移除新建的group + this.canvas.remove(this.group); + // 重新添加原始对象 + this.originalfabricObjects.forEach((obj) => { + if (!this.canvas.getObjects().includes(obj)) { + this.canvas.add(obj); + } + }); + this.canvas.renderAll(); + this.group = null; + } this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); return true; @@ -131,4 +181,30 @@ export class FillLayerBackgroundCommand extends Command { return []; } } + // 获取当前所有对象的边界信息 + _getCurrentObjectsBoundingRect() { + // this.originalfabricObjects = this._collectOriginalObjects(); + let minLeft = Infinity, + minTop = Infinity, + maxRight = -Infinity, + maxBottom = -Infinity; + console.log("计算当前所有对象的边界信息:===>", this.originalfabricObjects.length); + this.originalfabricObjects?.forEach((obj) => { + const { object } = findObjectById(this.canvas, obj.id) || {}; + if (object) { + const rect = object.getBoundingRect({ absolute: true, includeStroke: false }); + minLeft = Math.min(minLeft, rect.left); + minTop = Math.min(minTop, rect.top); + maxRight = Math.max(maxRight, rect.left + rect.width); + maxBottom = Math.max(maxBottom, rect.top + rect.height); + } + }); + if (minLeft === Infinity || minTop === Infinity) return null; + return { + left: minLeft, + top: minTop, + width: maxRight - minLeft, + height: maxBottom - minTop, + }; + } } diff --git a/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js b/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js index 55e10a4b..47cb3ee0 100644 --- a/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/FillLayerBackgroundCommand.js @@ -20,6 +20,7 @@ export class FillLayerBackgroundCommand extends Command { this.canvasManager = options.canvasManager; this.layerId = options.layerId; this.fillColor = options.fillColor; + this.isRetimeUpdate = options.isRetimeUpdate; // 是否实时更新 this.oldFill = null; this.oldFillColor = null; this.newFill = null; @@ -33,6 +34,8 @@ export class FillLayerBackgroundCommand extends Command { this.isGroupLayer = layer.isGroup || !!layer.children?.length || false; // 是否为组图层 this.originalfabricObjects = this._collectOriginalObjects(); // 记录所有的原始对象 + // 计算所有对象的边界 + this.originalInfo = this._getCurrentObjectsBoundingRect(); } async execute() { @@ -45,31 +48,11 @@ export class FillLayerBackgroundCommand extends Command { this.oldFillColor = layer.oldFillColor ?? null; // 更新fabric对象填充 - // 判断是否有遮罩层 有遮罩使用遮罩层大小 否则使用第一个大小 - const { object } = findObjectById(this.canvas, layer.fabricObjects?.[0]?.id); - - if (object) { - const originalInfo = object?.getBoundingRect?.(true, true); - this.newFill = new fabric.Rect({ - width: originalInfo.width, - height: originalInfo.height, - left: originalInfo.left || 0, - top: originalInfo.top || 0, - fill: this.fillColor, - layerId: this.layerId, - id: generateId("fill-"), - selectable: false, - evented: false, - originX: object.originX || "center", - originY: object.originY || "center", - scaleX: object.scaleX || 1, - scaleY: object.scaleY || 1, - }); - } - + // 1.如果图层有遮罩则用遮罩 2.如果没有遮罩则汇总当前fabricObjects下的所有对象的边界 + let clippingMaskFabricObject = null; if (layer.clippingMask) { // 反序列化 clippingMask - const clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas); + clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas); clippingMaskFabricObject.clipPath = null; clippingMaskFabricObject.set({ @@ -85,40 +68,51 @@ export class FillLayerBackgroundCommand extends Command { top: clippingMaskFabricObject.top || 0, fill: this.fillColor, layerId: this.layerId, - id: generateId("fill-"), + id: this.oldFill?.id || generateId("fill-"), selectable: false, evented: false, originX: clippingMaskFabricObject.originX || "center", originY: clippingMaskFabricObject.originY || "center", scaleX: clippingMaskFabricObject.scaleX || 1, scaleY: clippingMaskFabricObject.scaleY || 1, + type: "fill", }); this.newFill.clipPath = clippingMaskFabricObject; // 设置填充的遮罩 this.newFill.dirty = true; // 标记为脏,以便重新渲染 this.newFill.setCoords(); + } else { + const originalInfo = this.originalInfo; + if (originalInfo) { + this.newFill = new fabric.Rect({ + width: originalInfo.width, + height: originalInfo.height, + left: originalInfo.left + originalInfo.width / 2 || 0, + top: originalInfo.top + originalInfo.height / 2 || 0, + fill: this.fillColor, + layerId: this.layerId, + id: this.oldFill?.id || generateId("fill-"), + selectable: false, + evented: false, + originX: "center", + originY: "center", + type: "fill", + }); + } } // 判断fabricObjects是否是组,是组则添加填充到最前面,否则创建组 if (layer.fabricObjects && layer.fabricObjects.length > 0) { - // 如果是组,直接添加到组中 - // 否则创建一个新的组 插入到原本的图层对象前面 - // const layerObjects = this.canvas - // .getObjects() - // .reverse() - // .filter((obj) => obj.layerId === this.layerId); let insertIndex = this.canvas.getObjects()?.findIndex((obj) => obj.id === layer.fabricObjects?.[0]?.id) || 0; + if (this.oldFill) { + // 如果有旧填充,先获取旧的索引再移除旧填充 + insertIndex = + this.canvas.getObjects()?.findIndex((obj) => obj.id === this.oldFill?.id) || 0; + removeCanvasObjectByObject(this.canvas, this.oldFill); + } insertIndex = insertIndex == -1 ? 0 : insertIndex; - - // this.group = new fabric.Group([this.newFill, ...layerObjects]); - // this.group.set({ - // id: layerObjects[0]?.id || generateId("group-"), - // layerId: layerObjects[0]?.layerId, - // }); - // this.group.setCoords(); - layer.fabricObjects = [this.newFill?.toObject?.(["id", "layerId"]) || this.newFill]; - // removeCanvasObjectByObject(this.canvas, layer.fabricObjects[0]); + // layer.fabricObjects = [this.newFill?.toObject?.(["id", "layerId"]) || this.newFill]; insertObjectAtZIndex(this.canvas, this.newFill, insertIndex, false); // 插入到画布的指定位置 // 将新填充对象添加到画布 } else if (layer.children && layer.children.length > 0) { @@ -130,6 +124,13 @@ export class FillLayerBackgroundCommand extends Command { (obj) => obj.id === this.originalfabricObjects[this.originalfabricObjects.length - 1]?.id ) || 0; + + if (this.oldFill) { + // 如果有旧填充,先获取旧的索引再移除旧填充 + insertIndex = + this.canvas.getObjects()?.findIndex((obj) => obj.id === this.oldFill?.id) || 0; + removeCanvasObjectByObject(this.canvas, this.oldFill); + } insertIndex = insertIndex == -1 ? 0 : insertIndex; insertObjectAtZIndex(this.canvas, this.newFill, insertIndex, false); // 插入到画布的指定位置 @@ -137,25 +138,43 @@ export class FillLayerBackgroundCommand extends Command { this.canvas.renderAll(); - layer.fill = this.newFill.toObject(["id", "layerId"]); - layer.fillColor = this.fillColor; + // 实时更新的时候不生成缩略图 + if (!this.isRetimeUpdate) { + layer.fill = this.newFill.toObject(["id", "layerId"]); + layer.fillColor = this.fillColor; + this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); - // 重新排序 - // 使用LayerSort工具重新排列画布对象(如果可用) - await this?.canvasManager?.layerManager?.layerSort?.rearrangeObjects?.(); - - this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); + // 重新排序 + // 使用LayerSort工具重新排列画布对象(如果可用) + // await this?.canvasManager?.layerManager?.layerSort?.rearrangeObjects?.(); + } return true; } async undo() { - this.layer.fillColor = this.oldFillColor; - this.layer.fill = this.oldFill; - - this.group.removeWithUpdate(this.newFill); - this.canvas.remove(this.newFill); + // this.group.removeWithUpdate(this.newFill); + // this.canvas.remove(this.newFill); + if (this.oldFill) { + // 如果有旧填充,恢复旧填充 + const { object } = findObjectById(this.canvas, this.oldFill.id); + if (object) { + this.newFill.set({ + fill: this.oldFill.fill, + left: this.oldFill.left, + top: this.oldFill.top, + width: this.oldFill.width, + height: this.oldFill.height, + }); + this.newFill.setCoords(); + } + } else { + // 如果没有旧填充,移除新填充 + removeCanvasObjectByObject(this.canvas, this.layer.fill); + } this.canvas.renderAll(); + this.layer.fillColor = this.oldFillColor; + this.layer.fill = this.oldFill; this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id); return true; } @@ -178,4 +197,30 @@ export class FillLayerBackgroundCommand extends Command { return []; } } + // 获取当前所有对象的边界信息 + _getCurrentObjectsBoundingRect() { + // this.originalfabricObjects = this._collectOriginalObjects(); + let minLeft = Infinity, + minTop = Infinity, + maxRight = -Infinity, + maxBottom = -Infinity; + console.log("计算当前所有对象的边界信息:===>", this.originalfabricObjects.length); + this.originalfabricObjects?.forEach((obj) => { + const { object } = findObjectById(this.canvas, obj.id) || {}; + if (object) { + const rect = object.getBoundingRect({ absolute: true, includeStroke: false }); + minLeft = Math.min(minLeft, rect.left); + minTop = Math.min(minTop, rect.top); + maxRight = Math.max(maxRight, rect.left + rect.width); + maxBottom = Math.max(maxBottom, rect.top + rect.height); + } + }); + if (minLeft === Infinity || minTop === Infinity) return null; + return { + left: minLeft, + top: minTop, + width: maxRight - minLeft, + height: maxBottom - minTop, + }; + } } diff --git a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js index 925b7540..aaf72373 100644 --- a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js +++ b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js @@ -228,18 +228,18 @@ export class RasterizeLayerCommand extends Command { const objectsWithZIndex = []; this.layersToRasterize.forEach((layer) => { - if (layer.fill) { - const { object } = findObjectById(this.canvas, layer.fill.id); - if (object) { - // 获取对象在画布中的z-index(数组索引) - const zIndex = allCanvasObjects.indexOf(object); - objectsWithZIndex.push({ - object: object, - zIndex: zIndex, - layerObj: layer.fill, - }); - } - } + // if (layer.fill) { + // const { object } = findObjectById(this.canvas, layer.fill.id); + // if (object) { + // // 获取对象在画布中的z-index(数组索引) + // const zIndex = allCanvasObjects.indexOf(object); + // objectsWithZIndex.push({ + // object: object, + // zIndex: zIndex, + // layerObj: layer.fill, + // }); + // } + // } if (layer.fabricObjects && layer.fabricObjects.length > 0) { layer.fabricObjects.forEach((layerObj) => { if (layerObj && layerObj.id) { @@ -532,18 +532,18 @@ export class ExportLayerToImageCommand extends Command { const objectsWithZIndex = []; this.layersToRasterize.forEach((layer) => { - if (layer.fill) { - const { object } = findObjectById(this.canvas, layer.fill.id); - if (object) { - // 获取对象在画布中的z-index(数组索引) - const zIndex = allCanvasObjects.indexOf(object); - objectsWithZIndex.push({ - object: object, - zIndex: zIndex, - layerObj: layer.fill, - }); - } - } + // if (layer.fill) { + // const { object } = findObjectById(this.canvas, layer.fill.id); + // if (object) { + // // 获取对象在画布中的z-index(数组索引) + // const zIndex = allCanvasObjects.indexOf(object); + // objectsWithZIndex.push({ + // object: object, + // zIndex: zIndex, + // layerObj: layer.fill, + // }); + // } + // } if (layer.fabricObjects && layer.fabricObjects.length > 0) { layer.fabricObjects.forEach((layerObj) => { if (layerObj && layerObj.id) { diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue index 23d558e1..d01efea6 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue @@ -1,5 +1,5 @@