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:
1
src/assets/icons/CThemeColor.svg
Normal file
1
src/assets/icons/CThemeColor.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1752460567249" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3540" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M876.8 176h-294.4l-67.2 134.4H137.6v128H16l121.6 409.6h806.4v-672z m-230.4 67.2h227.2v67.2h-259.2z m176 195.2H211.2v-60.8h665.6v243.2z" fill="#D8987C" p-id="3541"></path><path d="M873.6 540.192a250.464 250.464 0 0 1 50.4 45.408 149.312 149.312 0 0 1 5.44 194.784 165.856 165.856 0 0 1-101.184 56.48 298.464 298.464 0 0 1-108.192-2.336 352.416 352.416 0 0 0-51.488-5.632 28.8 28.8 0 0 0-33.568 23.648 30.24 30.24 0 0 0 5.76 23.744 339.616 339.616 0 0 0 27.136 35.456 69.728 69.728 0 0 1 16.928 31.68 25.184 25.184 0 0 1-15.712 30.272 203.776 203.776 0 0 1-100.448 17.664 479.36 479.36 0 0 1-247.552-81.76 218.816 218.816 0 0 1-93.696-141.312 187.904 187.904 0 0 1 32-145.408 325.76 325.76 0 0 1 138.176-115.2 481.728 481.728 0 0 1 373.44-19.2c1.344 0.64 100.256 53.664 102.56 51.712z" fill="#A86749" p-id="3542"></path><path d="M359.872 725.632a79.136 79.136 0 0 1 51.04 16.448 36.032 36.032 0 0 1 10.24 49.056 35.2 35.2 0 0 1-10.24 10.496 88.608 88.608 0 0 1-102.4 0 36.096 36.096 0 0 1-10.528-49.12 34.752 34.752 0 0 1 10.752-10.944 78.784 78.784 0 0 1 51.136-15.936z" fill="#3783FF" p-id="3543"></path><path d="M350.88 685.792a61.568 61.568 0 0 1-37.504-11.872 28.8 28.8 0 0 1-9.6-38.848 27.872 27.872 0 0 1 9.28-9.6 67.616 67.616 0 0 1 78.656 0.8 28.352 28.352 0 0 1 7.52 38.784 27.648 27.648 0 0 1-7.2 7.488 62.496 62.496 0 0 1-41.152 13.248z" fill="#4DE94C" p-id="3544"></path><path d="M451.488 591.68a46.88 46.88 0 0 1-34.304-12.32 23.072 23.072 0 0 1-2.784-32 26.048 26.048 0 0 1 2.784-2.848 52.992 52.992 0 0 1 67.968 0 23.072 23.072 0 0 1 2.784 32 21.696 21.696 0 0 1-4.096 3.936 47.552 47.552 0 0 1-32.352 11.232z" fill="#FFEE00" p-id="3545"></path><path d="M507.456 813.184a89.6 89.6 0 0 1 57.376 18.176 39.488 39.488 0 0 1 11.936 53.664 38.4 38.4 0 0 1-11.936 12.224 101.024 101.024 0 0 1-115.2 0 39.584 39.584 0 0 1-12.48-53.696 38.56 38.56 0 0 1 12.8-12.8 89.6 89.6 0 0 1 57.504-17.568z" fill="#4C1AC6" p-id="3546"></path><path d="M992 430.912a185.152 185.152 0 0 1-36.832 61.088 1493.056 1493.056 0 0 1-119.968 130.688 16.416 16.416 0 0 1-17.184 6.048 21.92 21.92 0 0 1-8.032-4.896q-13.728-13.088-27.008-26.656a15.2 15.2 0 0 1-2.432-20.896 17.824 17.824 0 0 1 1.664-1.824 21.248 21.248 0 0 1 1.632-1.76 1551.104 1551.104 0 0 1 131.456-118.4 272 272 0 0 1 45.472-30.272 81.824 81.824 0 0 1 16.224-6.048 11.296 11.296 0 0 1 14.912 6.208v0.224z" fill="#2A3E4F" p-id="3547"></path><path d="M640 734.816a2.56 2.56 0 0 1 0.96 0.192A72.288 72.288 0 0 0 736 720a49.248 49.248 0 0 0 13.184-38.72 37.536 37.536 0 0 0-32-32.192 32.8 32.8 0 0 0-29.536 13.056 49.504 49.504 0 0 0-9.088 21.024A82.368 82.368 0 0 1 640 734.016z" fill="#7E8C8D" p-id="3548"></path><path d="M805.12 633.344c-10.432-10.464-20.992-20.832-31.52-31.232a7.552 7.552 0 0 0-10.752-1.536 7.648 7.648 0 0 0-1.984 2.304q-13.408 17.408-26.592 34.944a7.008 7.008 0 0 0 0.256 9.6l0.352 0.32q11.808 11.712 23.648 23.296a7.104 7.104 0 0 0 10.144 0.8c12-8.864 24.032-17.728 35.84-26.848a25.6 25.6 0 0 0 4.352-6.4 37.28 37.28 0 0 0-3.744-5.248z" fill="#F89B36" p-id="3549"></path><path d="M571.648 547.872a36.64 36.64 0 0 1-27.488-10.272 19.744 19.744 0 0 1-1.536-27.392l1.536-1.6a41.184 41.184 0 0 1 54.4 0 19.744 19.744 0 0 1 1.536 27.424 18.656 18.656 0 0 1-2.56 2.464 37.28 37.28 0 0 1-25.888 9.376z" fill="#FF8C00" p-id="3550"></path><path d="M640 734.816s125.28-36.352 85.088-83.648c0 0-27.424-12.576-46.528 32.224A125.664 125.664 0 0 1 640 734.816z" fill="#8F9FA0" p-id="3551"></path><path d="M745.76 658.72l43.968-40.704-16-16a7.648 7.648 0 0 0-10.976-1.248 7.072 7.072 0 0 0-1.76 2.016c-6.4 8.288-36.896 34.24-26.016 45.024z" fill="#E9BB42" p-id="3552"></path><path d="M796.064 610.336L992 424.128s1.312-12.576-14.976-6.4-25.6-5.664-194.976 156.64c0 0-12.48 9.6 0.768 22.72z" fill="#32495D" p-id="3553"></path><path d="M695.072 557.12a36.832 36.832 0 0 1-27.488-10.272 19.744 19.744 0 0 1-1.536-27.424 17.76 17.76 0 0 1 1.536-1.568 41.184 41.184 0 0 1 54.4 0 19.744 19.744 0 0 1 1.536 27.36 17.568 17.568 0 0 1-2.624 2.496 36.992 36.992 0 0 1-25.824 9.408z" fill="#F60000" p-id="3554"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -69,6 +69,10 @@ const contextMenuPosition = ref({ x: 0, y: 0 });
|
||||
const contextMenuLayer = ref(null);
|
||||
const contextMenuItems = ref([]);
|
||||
|
||||
// 颜色填充相关
|
||||
const fillColor = ref("#ffffff"); // 默认填充颜色
|
||||
const fillColorRef = ref(null);
|
||||
|
||||
// 计算属性:可排序的根级图层(排除背景层和固定层)
|
||||
const sortableRootLayers = computed(() => {
|
||||
if (!layers) return [];
|
||||
@@ -644,9 +648,33 @@ function buildContextMenuItems(layer) {
|
||||
danger: true,
|
||||
action: () => deleteSelectedLayers(),
|
||||
},
|
||||
// 栅格化图层
|
||||
// 组合图层
|
||||
{
|
||||
label: "栅格化图层",
|
||||
label: "填充图层",
|
||||
icon: "CThemeColor",
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
|
||||
action: () => {
|
||||
// 调用浏览器原生颜色选择器
|
||||
fillColorRef.value.click();
|
||||
// 监听颜色选择器的变化
|
||||
fillColorRef.value.addEventListener("change", () => {
|
||||
const selectedColor = fillColor.value;
|
||||
layerManager
|
||||
.fillLayerBackground(layer.id, selectedColor)
|
||||
.then(() => {
|
||||
console.log(`✅ 已填充图层 ${layer.name} 背景颜色: ${selectedColor}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`❌ 填充图层 ${layer.name} 背景颜色失败:`, error);
|
||||
});
|
||||
});
|
||||
// 隐藏右键菜单
|
||||
hideContextMenu();
|
||||
},
|
||||
},
|
||||
// 组合图层
|
||||
{
|
||||
label: "组合图层",
|
||||
icon: "CPicture",
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
|
||||
action: () => {
|
||||
@@ -654,7 +682,7 @@ function buildContextMenuItems(layer) {
|
||||
hideContextMenu();
|
||||
},
|
||||
},
|
||||
// 栅格化图层
|
||||
// 导出图层
|
||||
{
|
||||
label: "导出图层",
|
||||
icon: "CExport",
|
||||
@@ -689,7 +717,7 @@ function buildContextMenuItems(layer) {
|
||||
},
|
||||
},
|
||||
// {
|
||||
// label: "合并组", // 不需要了 同栅格化功能一样了
|
||||
// label: "合并组", // 不需要了 同组合功能一样了
|
||||
// icon: "CMergeGroup",
|
||||
// disabled: !isGroupLayer || isMultiple,
|
||||
// action: () => {
|
||||
@@ -1058,22 +1086,22 @@ async function handleChildLayersSort(event, childrenLayers, parentId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 栅格化图层
|
||||
// 组合图层
|
||||
async function rasterizeLayer(layerId) {
|
||||
if (!layerManager?.rasterizeLayer) {
|
||||
console.warn("栅格化功能不可用");
|
||||
console.warn("组合功能不可用");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const success = await layerManager.rasterizeLayer(layerId);
|
||||
if (success) {
|
||||
console.log(`✅ 成功栅格化图层: ${layerId}`);
|
||||
console.log(`✅ 成功组合图层: ${layerId}`);
|
||||
} else {
|
||||
console.warn("栅格化图层失败");
|
||||
console.warn("组合图层失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("栅格化图层时发生错误:", error);
|
||||
console.error("组合图层时发生错误:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1591,6 +1619,9 @@ async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex
|
||||
:items="contextMenuItems"
|
||||
@close="hideContextMenu"
|
||||
/>
|
||||
|
||||
<!-- 颜色填充选择组件 -->
|
||||
<input v-model="fillColor" ref="fillColorRef" type="color" style="display: none" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { FillLayerBackgroundCommand } from "../commands/FillLayerBackgroundCommand";
|
||||
|
||||
export class BackgroundFillManager {
|
||||
constructor({ canvas, layers, commandManager, canvasManager }) {
|
||||
this.canvas = canvas;
|
||||
this.layers = layers;
|
||||
this.commandManager = commandManager;
|
||||
this.canvasManager = canvasManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充指定图层背景
|
||||
* @param {string} layerId 图层ID
|
||||
* @param {string} fillColor 填充颜色
|
||||
*/
|
||||
async fillLayerBackground(layerId, fillColor) {
|
||||
const command = new FillLayerBackgroundCommand({
|
||||
canvas: this.canvas,
|
||||
layers: this.layers,
|
||||
layerId,
|
||||
fillColor,
|
||||
canvasManager: this.canvasManager,
|
||||
});
|
||||
if (this.commandManager) {
|
||||
await this.commandManager.execute(command);
|
||||
} else {
|
||||
await command.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,6 +486,23 @@ export class CanvasManager {
|
||||
obj.setCoords(); // 更新对象的控制点坐标
|
||||
});
|
||||
|
||||
let isMaskLayer = false;
|
||||
// 更新蒙层位置
|
||||
this.layers.value.forEach((layer) => {
|
||||
if (layer.clippingMask) {
|
||||
isMaskLayer = true;
|
||||
// 如果图层有遮罩,更新遮罩位置
|
||||
layer.clippingMask.left += deltaX;
|
||||
layer.clippingMask.top += deltaY;
|
||||
}
|
||||
});
|
||||
|
||||
if (isMaskLayer) {
|
||||
requestAnimationFrame(() => {
|
||||
this.layerManager?.updateLayersObjectsInteractivity?.();
|
||||
});
|
||||
}
|
||||
|
||||
// 如果有背景层,更新蒙层位置
|
||||
if (backgroundObject && CanvasConfig.isCropBackground) {
|
||||
this.updateMaskPosition(backgroundObject);
|
||||
@@ -906,7 +923,7 @@ export class CanvasManager {
|
||||
"eraserable",
|
||||
"erasable",
|
||||
]),
|
||||
layers: JSON.stringify(simplifyLayersData), // 简化图层数据
|
||||
layers: simplifyLayersData, // 简化图层数据
|
||||
// layers: JSON.stringify(JSON.parse(JSON.stringify(this.layers.value))), // 全数据
|
||||
version: "1.0", // 添加版本信息
|
||||
timestamp: new Date().toISOString(), // 添加时间戳
|
||||
@@ -939,7 +956,7 @@ export class CanvasManager {
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const tempLayers = JSON.parse(parsedJson?.layers) || [];
|
||||
const tempLayers = parsedJson?.layers || [];
|
||||
const canvasData = parsedJson?.canvas;
|
||||
|
||||
if (!tempLayers) {
|
||||
@@ -999,14 +1016,14 @@ export class CanvasManager {
|
||||
// 重置画布数据
|
||||
this.setCanvasSize(this.canvas.width, this.canvas.height);
|
||||
// 重新构建对象关系
|
||||
restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
|
||||
// restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
|
||||
// 验证图层关联关系 - 稳定后可以注释
|
||||
const isValidate = validateLayerAssociations(
|
||||
this.layers.value,
|
||||
this.canvas.getObjects()
|
||||
);
|
||||
// const isValidate = validateLayerAssociations(
|
||||
// this.layers.value,
|
||||
// this.canvas.getObjects()
|
||||
// );
|
||||
|
||||
console.log("图层关联验证结果:", isValidate);
|
||||
// console.log("图层关联验证结果:", isValidate);
|
||||
// 排序
|
||||
// 使用LayerSort工具重新排列画布对象(如果可用)
|
||||
await this?.layerManager?.layerSort?.rearrangeObjects();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BackgroundFillManager } from "./BackgroundFillManager";
|
||||
import {
|
||||
AddLayerCommand,
|
||||
PasteLayerCommand,
|
||||
@@ -89,6 +90,13 @@ export class LayerManager {
|
||||
this.commandManager = options.commandManager;
|
||||
this.canvasManager = options.canvasManager || null;
|
||||
|
||||
this.backgroundFillManager = new BackgroundFillManager({
|
||||
canvas: this.canvas,
|
||||
layers: this.layers,
|
||||
commandManager: this.commandManager,
|
||||
canvasManager: this.canvasManager,
|
||||
});
|
||||
|
||||
// 编辑器模式:draw(绘画)、select(选择)、pan(拖拽)
|
||||
this.editorMode = options.editorMode || CanvasConfig.defaultTool;
|
||||
|
||||
@@ -332,6 +340,14 @@ export class LayerManager {
|
||||
}
|
||||
// 如果是组图层 则给所有子对象设置裁剪对象
|
||||
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
layer.children.forEach((childLayer) => {
|
||||
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
|
||||
if (childObj) {
|
||||
@@ -339,6 +355,15 @@ export class LayerManager {
|
||||
childObj.dirty = true; // 标记为脏对象
|
||||
childObj.setCoords();
|
||||
}
|
||||
|
||||
if (childLayer.fill) {
|
||||
const fabricObject = this.canvas.getObjects().find((o) => o.id === childLayer.fill.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.clipPath = clippingMaskFabricObject;
|
||||
fabricObject.dirty = true; // 标记为脏对象
|
||||
fabricObject.setCoords();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
layer.fabricObjects?.forEach((obj) => {
|
||||
@@ -349,9 +374,28 @@ export class LayerManager {
|
||||
fabricObject.setCoords();
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充图层背景
|
||||
* @param {string} layerId 图层ID
|
||||
* @param {string} fillColor 填充颜色
|
||||
*/
|
||||
async fillLayerBackground(layerId, fillColor) {
|
||||
await this.backgroundFillManager.fillLayerBackground(layerId, fillColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新图层
|
||||
* @param {string} name 图层名称
|
||||
@@ -908,9 +952,25 @@ export class LayerManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (child.fill) {
|
||||
// 如果图层有填充颜色,设置所有对象的填充颜色
|
||||
const { object } = findObjectById(this.canvas, child.fill.id);
|
||||
if (object) {
|
||||
acc.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (layer.fill) {
|
||||
// 如果图层有填充颜色,设置所有对象的填充颜色
|
||||
const { object } = findObjectById(this.canvas, layer.fill.id);
|
||||
if (object) {
|
||||
allObjects.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
if (allObjects.length) {
|
||||
// 切换到选择模式
|
||||
this?.toolManager?.setTool(OperationType.SELECT);
|
||||
@@ -2742,7 +2802,7 @@ export class LayerManager {
|
||||
* @returns {boolean} 是否可以栅格化
|
||||
*/
|
||||
canRasterizeLayer(layerId) {
|
||||
const layer = this.getLayerById(layerId);
|
||||
const layer = findLayerRecursively(this.layers.value, layerId)?.layer;
|
||||
if (!layer) return false;
|
||||
|
||||
// 不允许栅格化背景图层和固定图层
|
||||
@@ -2751,13 +2811,9 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
// 检查图层是否有内容可以栅格化
|
||||
if (layer.type === "group") {
|
||||
if (layer.type === "group" || layer.children.length > 0) {
|
||||
// 组图层:检查是否有子图层且子图层有内容
|
||||
return (
|
||||
layer.children &&
|
||||
layer.children.length > 0 &&
|
||||
layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0)
|
||||
);
|
||||
return layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0);
|
||||
} else {
|
||||
// 普通图层:检查是否有对象
|
||||
return layer.fabricObjects && layer.fabricObjects.length > 0;
|
||||
@@ -2835,7 +2891,7 @@ export class LayerManager {
|
||||
let isUpdating = false;
|
||||
let lastUpdateTime = 0;
|
||||
let hasMoved = false; // 追踪是否实际发生了移动
|
||||
const UPDATE_THRESHOLD = 16; // 约60fps
|
||||
const UPDATE_THRESHOLD = 32; // 约60fps
|
||||
|
||||
// 移动开始事件处理
|
||||
const handleMovingStart = (e) => {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* - https://mrdoob.com/projects/harmony/
|
||||
* - http://perfectionkills.com/exploring-canvas-drawing-techniques/
|
||||
*/
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
(function (fabric) {
|
||||
|
||||
@@ -96,6 +96,10 @@ export class LayerSort {
|
||||
* @returns {number} 更新后的z-index
|
||||
*/
|
||||
processLayerObjects(layer, currentZIndex, zIndexMap) {
|
||||
if (layer.fill) {
|
||||
// 如果图层有填充,则填充在最底层
|
||||
zIndexMap.set(layer.fill.id, currentZIndex++);
|
||||
}
|
||||
// 检查是否有子图层(组图层)
|
||||
if (layer.children?.length > 0) {
|
||||
// 处理每个子图层
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isArray } from "lodash-es";
|
||||
import { fill, isArray } from "lodash-es";
|
||||
|
||||
/**
|
||||
* 图层关联工具类
|
||||
@@ -193,6 +193,8 @@ export function simplifyLayers(layers) {
|
||||
.filter((obj) => obj !== null)
|
||||
: [],
|
||||
children: layer.children && isArray(layer.children) ? simplifyLayers(layer.children) : [],
|
||||
fill: layer?.fill || null,
|
||||
fillColor: layer.fillColor,
|
||||
};
|
||||
|
||||
return simplifiedLayer;
|
||||
|
||||
Reference in New Issue
Block a user