feat: add background fill functionality for layers

- Implemented FillLayerBackgroundCommand to fill the background of layers with a specified color.
- Introduced BackgroundFillManager to manage background fill operations.
- Updated LayerManager to include fillLayerBackground method.
- Enhanced LayersPanel with a color picker for filling layer backgrounds.
- Modified RasterizeLayerCommand to reflect changes in terminology and functionality.
- Adjusted LayerSort to ensure filled layers are rendered at the correct z-index.
- Updated relevant utility functions and components to support new fill feature.
This commit is contained in:
bighuixiang
2025-07-14 23:42:28 +08:00
parent 24e9ba8ae5
commit fc9a3eddc2
12 changed files with 524 additions and 57 deletions

View File

@@ -0,0 +1,134 @@
import { Command } from "./Command";
import { findLayerRecursively } from "../utils/layerHelper";
import { fabric } from "fabric-with-all";
import {
findObjectById,
generateId,
insertObjectAtZIndex,
removeCanvasObjectByObject,
} from "../utils/helper";
/**
* 填充组图层背景命令
*/
export class FillLayerBackgroundCommand extends Command {
constructor(options) {
super({ name: "填充组图层背景", saveState: true });
this.canvas = options.canvas;
this.layers = options.layers;
this.canvasManager = options.canvasManager;
this.layerId = options.layerId;
this.fillColor = options.fillColor;
this.oldFill = null;
this.oldFillColor = null;
this.newFill = null;
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
this.layer = layer;
this.group = null;
this.originalfabricObjects = this._collectOriginalObjects(); // 记录所有的原始对象
}
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];
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;
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.group?.toObject?.(["id", "layerId"]) || this.group];
removeCanvasObjectByObject(this.canvas, layer.fabricObjects[0]);
insertObjectAtZIndex(this.canvas, this.group, insertIndex, false); // 插入到画布的指定位置
// 将新填充对象添加到画布
}
this.canvas.renderAll();
layer.fill = 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();
this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id);
return true;
}
_collectOriginalObjects() {
if (this.layer.children && this.layer.children.length > 0) {
// 如果是组图层收集所有子图层的fabric对象
return this.layer.children
.flatMap((child) => child.fabricObjects || [])
.map((obj) => {
return findObjectById(this.canvas.value, obj.id)?.object || obj;
});
} else if (this.layer.fabricObjects && this.layer.fabricObjects.length > 0) {
// 如果是普通图层直接返回其fabric对象
return this.layer.fabricObjects.map((obj) => {
return findObjectById(this.canvas.value, obj.id)?.object || obj;
});
} else {
// 如果没有fabric对象返回空数组
return [];
}
}
}

View File

@@ -0,0 +1,181 @@
import { Command } from "./Command";
import { findLayerRecursively } from "../utils/layerHelper";
import { fabric } from "fabric-with-all";
import {
findObjectById,
generateId,
insertObjectAtZIndex,
removeCanvasObjectByObject,
} from "../utils/helper";
import { restoreFabricObject } from "../utils/objectHelper";
/**
* 填充图层背景命令
*/
export class FillLayerBackgroundCommand extends Command {
constructor(options) {
super({ name: "填充图层背景", saveState: true });
this.canvas = options.canvas;
this.layers = options.layers;
this.canvasManager = options.canvasManager;
this.layerId = options.layerId;
this.fillColor = options.fillColor;
this.oldFill = null;
this.oldFillColor = null;
this.newFill = null;
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
this.layer = layer;
this.group = null;
this.isGroupLayer = layer.isGroup || !!layer.children?.length || false; // 是否为组图层
this.originalfabricObjects = this._collectOriginalObjects(); // 记录所有的原始对象
}
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 { 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,
});
}
if (layer.clippingMask) {
// 反序列化 clippingMask
const clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas);
clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({
// 设置绝对定位
// ...getOriginObjectInfo(layer.clippingMask), // 恢复原定位
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: generateId("fill-"),
selectable: false,
evented: false,
originX: clippingMaskFabricObject.originX || "center",
originY: clippingMaskFabricObject.originY || "center",
scaleX: clippingMaskFabricObject.scaleX || 1,
scaleY: clippingMaskFabricObject.scaleY || 1,
});
this.newFill.clipPath = clippingMaskFabricObject; // 设置填充的遮罩
this.newFill.dirty = true; // 标记为脏,以便重新渲染
this.newFill.setCoords();
}
// 判断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;
// 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]);
insertObjectAtZIndex(this.canvas, this.newFill, insertIndex, false); // 插入到画布的指定位置
// 将新填充对象添加到画布
} else if (layer.children && layer.children.length > 0) {
// 如果是组图层,直接添加到组中
let insertIndex =
this.canvas
.getObjects()
.findIndex(
(obj) =>
obj.id === this.originalfabricObjects[this.originalfabricObjects.length - 1]?.id
) || 0;
insertIndex = insertIndex == -1 ? 0 : insertIndex;
insertObjectAtZIndex(this.canvas, this.newFill, insertIndex, false); // 插入到画布的指定位置
}
this.canvas.renderAll();
layer.fill = this.newFill.toObject(["id", "layerId"]);
layer.fillColor = this.fillColor;
// 重新排序
// 使用LayerSort工具重新排列画布对象如果可用
await this?.canvasManager?.layerManager?.layerSort?.rearrangeObjects?.();
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();
this.canvasManager.thumbnailManager?.generateLayerThumbnail(this.layer.id);
return true;
}
_collectOriginalObjects() {
if (this.layer.children && this.layer.children.length > 0) {
// 如果是组图层收集所有子图层的fabric对象
return this.layer.children
.flatMap((child) => child.fabricObjects || [])
.map((obj) => {
return findObjectById(this.canvas.value, obj.id)?.object || obj;
});
} else if (this.layer.fabricObjects && this.layer.fabricObjects.length > 0) {
// 如果是普通图层直接返回其fabric对象
return this.layer.fabricObjects.map((obj) => {
return findObjectById(this.canvas.value, obj.id)?.object || obj;
});
} else {
// 如果没有fabric对象返回空数组
return [];
}
}
}

View File

@@ -12,19 +12,19 @@ import { message } from "ant-design-vue";
import { restoreFabricObject } from "../utils/objectHelper";
/**
* 栅格化图层命令
* 组合图层命令
* 将图层中的所有矢量对象转换为位图图像
* 支持普通图层和组图层的栅格化
* 支持普通图层和组图层的组合
*/
export class RasterizeLayerCommand extends Command {
constructor(options) {
super({
name: "栅格化图层",
name: "组合图层",
saveState: true,
});
this.canvas = options.canvas;
this.layers = options.layers;
this.layerId = options.layerId; // 指定要栅格化的图层ID
this.layerId = options.layerId; // 指定要组合的图层ID
// 是否包含锁定对象
this.hasLocked = options.hasLocked || true;
// 是否包含隐藏对象
@@ -43,9 +43,9 @@ export class RasterizeLayerCommand extends Command {
throw new Error(`图层 ${this.layerId} 不存在`);
}
// 检查是否可以栅格化
// 检查是否可以组合
if (this.layer.isBackground || this.layer.isFixed) {
throw new Error("背景图层和固定图层不能栅格化");
throw new Error("背景图层和固定图层不能组合");
}
// 生成新图层ID
@@ -53,7 +53,7 @@ export class RasterizeLayerCommand extends Command {
this.resterizedId = generateId("rasterized_");
this.rasterizedLayer = null;
// 要栅格化的图层和对象
// 要组合的图层和对象
this.layersToRasterize = [];
this.objectsToRasterize = [];
@@ -67,7 +67,7 @@ export class RasterizeLayerCommand extends Command {
}
if (this.objectsToRasterize.length === 0) {
throw new Error("图层没有内容可栅格化");
throw new Error("图层没有内容可组合");
}
try {
@@ -77,7 +77,7 @@ export class RasterizeLayerCommand extends Command {
// 检查是否有遮罩对象
const maskObject = await this._getMaskObject();
// 创建栅格化图像
// 创建组合图像
const rasterizedImage = await createRasterizedImage({
canvas: this.canvas,
fabricObjects: this.objectsToRasterize,
@@ -86,18 +86,18 @@ export class RasterizeLayerCommand extends Command {
isGroupWithMask: this.isGroupLayer && !!maskObject, // 标记是否为带遮罩的组
});
// 创建新的栅格化图层并替换原图层
// 创建新的组合图层并替换原图层
await this._createRasterizedLayer(rasterizedImage);
// 切换到选择工具
this.layerManager?.toolManager?.setTool?.(OperationType.SELECT);
console.log(`✅ 图层 ${this.layer.name} 栅格化完成`);
console.log(`✅ 图层 ${this.layer.name} 组合完成`);
this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.rasterizedLayerId);
return this.rasterizedLayerId;
} catch (error) {
console.error("栅格化图层失败:", error);
console.error("组合图层失败:", error);
throw error;
}
}
@@ -109,7 +109,7 @@ export class RasterizeLayerCommand extends Command {
try {
await optimizeCanvasRendering(this.canvas, async () => {
// 移除栅格化后的图像对象
// 移除组合后的图像对象
if (this.rasterizedImage) {
removeCanvasObjectByObject(this.canvas, this.rasterizedImage);
}
@@ -141,10 +141,10 @@ export class RasterizeLayerCommand extends Command {
await this.layerManager?.updateLayersObjectsInteractivity(false);
});
console.log(`↩️ 图层 ${this.layer.name} 栅格化已撤销`);
console.log(`↩️ 图层 ${this.layer.name} 组合已撤销`);
return true;
} catch (error) {
console.error("撤销栅格化失败:", error);
console.error("撤销组合失败:", error);
throw error;
}
}
@@ -154,7 +154,7 @@ export class RasterizeLayerCommand extends Command {
* @private
*/
_initializeCommand() {
// 收集要栅格化的图层和对象
// 收集要组合的图层和对象
this._collectLayersAndObjects();
// 保存原始图层结构(深拷贝相关部分)
@@ -211,7 +211,7 @@ export class RasterizeLayerCommand extends Command {
}
/**
* 收集要栅格化的图层和对象
* 收集要组合的图层和对象
* @private
*/
_collectLayersAndObjects() {
@@ -253,7 +253,7 @@ export class RasterizeLayerCommand extends Command {
this.objectsToRasterize = objectsWithZIndex.map((item) => item.object);
console.log(
`📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行栅格化`
`📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行组合`
);
console.log(
"🔢 对象z-index顺序:",
@@ -266,7 +266,7 @@ export class RasterizeLayerCommand extends Command {
}
/**
* 收集要栅格化的图层(递归收集子图层)
* 收集要组合的图层(递归收集子图层)
* @param {Object} sourceLayer 源图层
* @returns {Array} 图层数组
* @private
@@ -287,12 +287,12 @@ export class RasterizeLayerCommand extends Command {
}
/**
* 创建栅格化图层并替换原图层
* @param {fabric.Image} rasterizedImage 栅格化后的图像
* 创建组合图层并替换原图层
* @param {fabric.Image} rasterizedImage 组合后的图像
* @private
*/
async _createRasterizedLayer(rasterizedImage) {
// 保存栅格化图像的引用用于撤销
// 保存组合图像的引用用于撤销
this.rasterizedImage = rasterizedImage;
// 从画布中移除原有对象
@@ -300,14 +300,14 @@ export class RasterizeLayerCommand extends Command {
removeCanvasObjectByObject(this.canvas, obj);
});
// 添加栅格化图像到画布
// 添加组合图像到画布
this.canvas.add(rasterizedImage);
this.canvas.setActiveObject(rasterizedImage);
// 创建新的栅格化图层
// 创建新的组合图层
this.rasterizedLayer = createLayer({
id: this.rasterizedLayerId,
name: `${this.layer.name} (栅格化)`,
name: `${this.layer.name} (组合)`,
type: LayerType.BITMAP,
visible: this.layer.visible,
locked: this.layer.locked,
@@ -323,7 +323,7 @@ export class RasterizeLayerCommand extends Command {
layerName: this.rasterizedLayer.name,
});
// 在适当位置添加新的栅格化图层
// 在适当位置添加新的组合图层
this._replaceLayerInStructure();
// 设置为活动图层
@@ -331,7 +331,7 @@ export class RasterizeLayerCommand extends Command {
await this.layerManager?.updateLayersObjectsInteractivity(false);
console.log(`🎨 栅格化图层 ${this.rasterizedLayer.name} 创建完成`);
console.log(`🎨 组合图层 ${this.rasterizedLayer.name} 创建完成`);
}
/**
@@ -407,7 +407,7 @@ export class RasterizeLayerCommand extends Command {
/**
* 导出图层命令
* 将图层中的所有矢量对象转换为位图图像
* 支持普通图层和组图层的栅格化
* 支持普通图层和组图层的组合
*/
export class ExportLayerToImageCommand extends Command {
constructor(options) {
@@ -487,7 +487,7 @@ export class ExportLayerToImageCommand extends Command {
}
/**
* 收集要栅格化的图层和对象
* 收集要组合的图层和对象
* @private
*/
_collectLayersAndObjects() {
@@ -529,7 +529,7 @@ export class ExportLayerToImageCommand extends Command {
this.objectsToRasterize = objectsWithZIndex.map((item) => item.object);
console.log(
`📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行栅格化`
`📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行组合`
);
console.log(
"🔢 对象z-index顺序:",
@@ -542,7 +542,7 @@ export class ExportLayerToImageCommand extends Command {
}
/**
* 收集要栅格化的图层(递归收集子图层)
* 收集要组合的图层(递归收集子图层)
* @param {Object} sourceLayer 源图层
* @returns {Array} 图层数组
* @private

View File

@@ -206,6 +206,16 @@ export class UpdateGroupMaskPositionCommand extends Command {
}
});
if (layer?.fill) {
// 更新组图层的填充颜色
const fabricObject = this.canvas.getObjects().find((o) => o.id === layer?.fill.id);
if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true;
fabricObject.setCoords();
}
}
if (isUndo) {
this.activeSelection.set({
left: this.originalSelectionPosition.left - this.deltaX,