合并画布
This commit is contained in:
@@ -212,6 +212,10 @@ export class BackgroundSizeCommand extends Command {
|
||||
this.canvas.setWidth(this.newWidth);
|
||||
this.canvas.setHeight(this.newHeight);
|
||||
|
||||
console.log(
|
||||
`调整画布大小:${this.oldWidth}x${this.oldHeight} -> ${this.newWidth}x${this.newHeight}`
|
||||
);
|
||||
|
||||
// 如果使用 CanvasManager,通知它画布大小变化
|
||||
if (
|
||||
this.canvasManager &&
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createLayer,
|
||||
findLayerRecursively,
|
||||
LayerType,
|
||||
OperationType,
|
||||
} from "../utils/layerHelper";
|
||||
import { Command } from "./Command";
|
||||
import {
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
optimizeCanvasRendering,
|
||||
} from "../utils/helper";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { rasterizeCanvasObjects } from "../utils/imageHelper";
|
||||
|
||||
/**
|
||||
* 合并组图层命令 - 将图层及其子图层合并为单个图层
|
||||
@@ -30,7 +32,9 @@ export class MergeGroupLayerCommand extends Command {
|
||||
this.originalLayers = [...this.layers.value];
|
||||
this.originalObjects = [...this.canvas.getObjects()];
|
||||
this.flattenedLayer = null;
|
||||
this.flattenedLayerId = null;
|
||||
this.flattenedLayerId =
|
||||
generateId("flattened_") ||
|
||||
`flattened_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
this.existingGroupId = null; // 用于查找现有组对象
|
||||
// 组对象相关
|
||||
@@ -150,11 +154,6 @@ export class MergeGroupLayerCommand extends Command {
|
||||
console.log("单个对象,已更新关联关系");
|
||||
}
|
||||
|
||||
// 生成新图层ID
|
||||
this.flattenedLayerId = `flattened_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
|
||||
// 创建展平后的图层
|
||||
this.flattenedLayer = createLayer({
|
||||
id: this.flattenedLayerId,
|
||||
@@ -190,12 +189,14 @@ export class MergeGroupLayerCommand extends Command {
|
||||
this.activeLayerId.value = this.flattenedLayerId;
|
||||
|
||||
// 重新渲染画布
|
||||
await optimizeCanvasRendering(this.canvas, () => {
|
||||
this.canvas.renderAll();
|
||||
});
|
||||
// await optimizeCanvasRendering(this.canvas, () => {
|
||||
// this.canvas.renderAll();
|
||||
// });
|
||||
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
|
||||
this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.newGroupId);
|
||||
|
||||
console.log(`已合并图层:${this.flattenedLayer.name}`);
|
||||
return this.flattenedLayerId;
|
||||
}
|
||||
@@ -530,558 +531,3 @@ export class MergeGroupLayerCommand extends Command {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 栅格化图层命令
|
||||
* 将图层中的所有矢量对象转换为位图图像
|
||||
* 支持普通图层和组图层的栅格化
|
||||
*/
|
||||
export class RasterizeLayerCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "栅格化图层",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.layerId = options.layerId;
|
||||
this.activeLayerId = options.activeLayerId;
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找目标图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
this.layer = layer;
|
||||
this.parentLayer = parent;
|
||||
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
||||
|
||||
// 保存原始状态用于撤销
|
||||
this.originalLayers = [...this.layers.value];
|
||||
this.originalCanvasObjects = [...this.canvas.getObjects()];
|
||||
this.originalObjectStates = new Map();
|
||||
|
||||
// 栅格化结果
|
||||
this.rasterizedImage = null;
|
||||
this.rasterizedImageId = null;
|
||||
this.rasterizedLayerId = null;
|
||||
this.rasterizedLayer = null;
|
||||
|
||||
// 要栅格化的图层和对象
|
||||
this.layersToRasterize = [];
|
||||
this.objectsToRasterize = [];
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.layer) {
|
||||
throw new Error(`图层 ${this.layerId} 不存在`);
|
||||
}
|
||||
|
||||
// 检查是否可以栅格化
|
||||
if (this.layer.isBackground || this.layer.isFixed) {
|
||||
throw new Error("背景图层和固定图层不能栅格化");
|
||||
}
|
||||
|
||||
try {
|
||||
// 收集要栅格化的图层和对象
|
||||
this._collectLayersAndObjects();
|
||||
|
||||
if (this.objectsToRasterize.length === 0) {
|
||||
throw new Error("图层没有内容可栅格化");
|
||||
}
|
||||
|
||||
// 保存原始对象状态
|
||||
this._saveOriginalObjectStates();
|
||||
|
||||
// 创建栅格化图像
|
||||
const rasterizedImage = await this._createRasterizedImage();
|
||||
|
||||
// 创建新的栅格化图层并替换原图层
|
||||
await this._createRasterizedLayer(rasterizedImage);
|
||||
|
||||
console.log(`✅ 图层 ${this.layer.name} 栅格化完成`);
|
||||
return this.rasterizedLayerId;
|
||||
} catch (error) {
|
||||
console.error("栅格化图层失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.originalLayers || !this.originalCanvasObjects) {
|
||||
throw new Error("没有可恢复的原始数据");
|
||||
}
|
||||
|
||||
try {
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 清空画布
|
||||
this.canvas.discardActiveObject();
|
||||
this.canvas.clear();
|
||||
|
||||
// 恢复原始对象及其状态
|
||||
this.originalCanvasObjects.forEach((obj) => {
|
||||
// 如果保存了该对象的原始状态,则恢复状态
|
||||
if (this.originalObjectStates.has(obj.id)) {
|
||||
const originalState = this.originalObjectStates.get(obj.id);
|
||||
obj.set(originalState);
|
||||
}
|
||||
|
||||
this.canvas.add(obj);
|
||||
obj.setCoords();
|
||||
});
|
||||
|
||||
// 恢复原始图层结构
|
||||
this.layers.value = [...this.originalLayers];
|
||||
|
||||
// 恢复原活动图层
|
||||
this.activeLayerId.value = this.layerId;
|
||||
|
||||
// 更新画布交互性
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
});
|
||||
|
||||
console.log(`↩️ 图层 ${this.layer.name} 栅格化已撤销`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("撤销栅格化失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集要栅格化的图层和对象
|
||||
* @private
|
||||
*/
|
||||
_collectLayersAndObjects() {
|
||||
if (this.isGroupLayer) {
|
||||
// 组图层:收集自身和所有子图层
|
||||
this.layersToRasterize = this._collectLayersToRasterize(this.layer);
|
||||
} else {
|
||||
// 普通图层:只收集自身
|
||||
this.layersToRasterize = [this.layer];
|
||||
}
|
||||
|
||||
// 收集所有图层的fabricObjects并按画布z-index顺序排序
|
||||
const allCanvasObjects = this.canvas.getObjects();
|
||||
const objectsWithZIndex = [];
|
||||
|
||||
this.layersToRasterize.forEach((layer) => {
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
layer.fabricObjects.forEach((layerObj) => {
|
||||
if (layerObj && layerObj.id) {
|
||||
const { object } = findObjectById(this.canvas, layerObj.id);
|
||||
if (object) {
|
||||
// 获取对象在画布中的z-index(数组索引)
|
||||
const zIndex = allCanvasObjects.indexOf(object);
|
||||
objectsWithZIndex.push({
|
||||
object: object,
|
||||
zIndex: zIndex,
|
||||
layerObj: layerObj,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 按z-index排序,确保保持原有的渲染顺序
|
||||
objectsWithZIndex.sort((a, b) => a.zIndex - b.zIndex);
|
||||
|
||||
// 提取排序后的对象
|
||||
this.objectsToRasterize = objectsWithZIndex.map((item) => item.object);
|
||||
|
||||
console.log(
|
||||
`📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行栅格化`
|
||||
);
|
||||
console.log(
|
||||
"🔢 对象z-index顺序:",
|
||||
objectsWithZIndex.map((item) => ({
|
||||
id: item.object.id,
|
||||
type: item.object.type,
|
||||
zIndex: item.zIndex,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集要栅格化的图层(递归收集子图层)
|
||||
* @param {Object} sourceLayer 源图层
|
||||
* @returns {Array} 图层数组
|
||||
* @private
|
||||
*/
|
||||
_collectLayersToRasterize(sourceLayer) {
|
||||
const result = [sourceLayer];
|
||||
|
||||
// 如果是组图层,收集所有子图层
|
||||
if (sourceLayer.children && sourceLayer.children.length > 0) {
|
||||
sourceLayer.children.forEach((childLayer) => {
|
||||
if (childLayer) {
|
||||
result.push(...this._collectLayersToRasterize(childLayer));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存原始对象状态
|
||||
* @private
|
||||
*/
|
||||
_saveOriginalObjectStates() {
|
||||
this.objectsToRasterize.forEach((object) => {
|
||||
if (object && object.id) {
|
||||
const originalState = {
|
||||
left: object.left,
|
||||
top: object.top,
|
||||
scaleX: object.scaleX,
|
||||
scaleY: object.scaleY,
|
||||
angle: object.angle,
|
||||
flipX: object.flipX,
|
||||
flipY: object.flipY,
|
||||
opacity: object.opacity,
|
||||
originX: object.originX,
|
||||
originY: object.originY,
|
||||
layerId: object.layerId,
|
||||
layerName: object.layerName,
|
||||
width: object.width,
|
||||
height: object.height,
|
||||
strokeWidth: object.strokeWidth,
|
||||
visible: object.visible,
|
||||
};
|
||||
this.originalObjectStates.set(object.id, originalState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建栅格化图像
|
||||
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
|
||||
* @private
|
||||
*/
|
||||
async _createRasterizedImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// 计算所有对象的总边界
|
||||
const bounds = this._calculateObjectsBounds(this.objectsToRasterize);
|
||||
if (!bounds) {
|
||||
reject(new Error("无法计算对象边界"));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🎯 栅格化边界信息:", bounds);
|
||||
|
||||
// 创建临时画布进行栅格化
|
||||
const tempCanvas = this._createTempCanvas(bounds);
|
||||
const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
backgroundColor: "transparent",
|
||||
renderOnAddRemove: false,
|
||||
});
|
||||
|
||||
// 直接复制对象到临时画布,不使用临时组
|
||||
this._addObjectsToTempCanvas(
|
||||
tempFabricCanvas,
|
||||
this.objectsToRasterize,
|
||||
bounds
|
||||
);
|
||||
|
||||
// 强制渲染临时画布
|
||||
tempFabricCanvas.renderOnAddRemove = true;
|
||||
tempFabricCanvas.renderAll();
|
||||
|
||||
// 等待渲染完成后生成图像
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 生成高质量图像
|
||||
const dataUrl = tempFabricCanvas.toDataURL({
|
||||
format: "png",
|
||||
quality: 1.0,
|
||||
multiplier: 1, // 降低multiplier避免性能问题
|
||||
});
|
||||
|
||||
console.log("📷 栅格化图像生成完成,DataURL长度:", dataUrl.length);
|
||||
|
||||
// 创建fabric图像对象
|
||||
fabric.Image.fromURL(
|
||||
dataUrl,
|
||||
(img) => {
|
||||
// 设置图像属性,使用原始边界的中心点
|
||||
this.rasterizedImageId = generateId("rasterized_");
|
||||
img.set({
|
||||
id: this.rasterizedImageId,
|
||||
left: bounds.centerX,
|
||||
top: bounds.centerY,
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
selectable: true,
|
||||
evented: true,
|
||||
});
|
||||
|
||||
this.rasterizedImage = img;
|
||||
|
||||
// 清理临时画布
|
||||
this._cleanupTempCanvas(tempFabricCanvas);
|
||||
|
||||
console.log("✅ 栅格化图像对象创建完成");
|
||||
resolve(img);
|
||||
},
|
||||
{ crossOrigin: "anonymous" }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("生成图像时发生错误:", error);
|
||||
this._cleanupTempCanvas(tempFabricCanvas);
|
||||
reject(error);
|
||||
}
|
||||
}, 100); // 给一点时间确保渲染完成
|
||||
} catch (error) {
|
||||
console.error("创建栅格化图像时发生错误:", error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算对象边界
|
||||
* @param {Array} objects 对象数组
|
||||
* @returns {Object|null} 边界信息
|
||||
* @private
|
||||
*/
|
||||
_calculateObjectsBounds(objects) {
|
||||
if (!objects || objects.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let minLeft = Infinity;
|
||||
let minTop = Infinity;
|
||||
let maxRight = -Infinity;
|
||||
let maxBottom = -Infinity;
|
||||
|
||||
objects.forEach((obj) => {
|
||||
try {
|
||||
// 使用更准确的边界计算,不包含绝对变换
|
||||
const bounds = obj.getBoundingRect(false, false);
|
||||
|
||||
console.log(`📐 对象 ${obj.id || obj.type} 边界:`, bounds);
|
||||
|
||||
minLeft = Math.min(minLeft, bounds.left);
|
||||
minTop = Math.min(minTop, bounds.top);
|
||||
maxRight = Math.max(maxRight, bounds.left + bounds.width);
|
||||
maxBottom = Math.max(maxBottom, bounds.top + bounds.height);
|
||||
} catch (error) {
|
||||
console.warn(`计算对象 ${obj.id || obj.type} 边界时发生错误:`, error);
|
||||
// 备选方案:使用对象的基础位置信息
|
||||
const left = obj.left || 0;
|
||||
const top = obj.top || 0;
|
||||
const width = (obj.width || 100) * (obj.scaleX || 1);
|
||||
const height = (obj.height || 100) * (obj.scaleY || 1);
|
||||
|
||||
minLeft = Math.min(minLeft, left - width / 2);
|
||||
minTop = Math.min(minTop, top - height / 2);
|
||||
maxRight = Math.max(maxRight, left + width / 2);
|
||||
maxBottom = Math.max(maxBottom, top + height / 2);
|
||||
}
|
||||
});
|
||||
|
||||
const padding = 10; // 增加边距确保不被裁剪
|
||||
|
||||
const bounds = {
|
||||
left: minLeft - padding,
|
||||
top: minTop - padding,
|
||||
width: maxRight - minLeft + padding * 2,
|
||||
height: maxBottom - minTop + padding * 2,
|
||||
centerX: (minLeft + maxRight) / 2,
|
||||
centerY: (minTop + maxBottom) / 2,
|
||||
};
|
||||
|
||||
// 确保最小尺寸
|
||||
bounds.width = Math.max(bounds.width, 50);
|
||||
bounds.height = Math.max(bounds.height, 50);
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时画布
|
||||
* @param {Object} bounds 边界信息
|
||||
* @returns {HTMLCanvasElement} 临时画布
|
||||
* @private
|
||||
*/
|
||||
_createTempCanvas(bounds) {
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
tempCanvas.width = Math.ceil(bounds.width);
|
||||
tempCanvas.height = Math.ceil(bounds.height);
|
||||
|
||||
const ctx = tempCanvas.getContext("2d");
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = "high";
|
||||
|
||||
// 设置透明背景
|
||||
ctx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log(
|
||||
`🖼️ 创建临时画布尺寸: ${tempCanvas.width} x ${tempCanvas.height}`
|
||||
);
|
||||
|
||||
return tempCanvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加对象到临时画布
|
||||
* @param {fabric.StaticCanvas} tempCanvas 临时画布
|
||||
* @param {Array} objects 对象数组
|
||||
* @param {Object} bounds 边界信息
|
||||
* @private
|
||||
*/
|
||||
_addObjectsToTempCanvas(tempCanvas, objects, bounds) {
|
||||
console.log(`📥 开始添加 ${objects.length} 个对象到临时画布`);
|
||||
|
||||
objects.forEach((obj, index) => {
|
||||
try {
|
||||
// 深度克隆对象,避免影响原对象
|
||||
const clonedObj = fabric.util.object.clone(obj);
|
||||
|
||||
// 计算对象在临时画布中的新位置
|
||||
// 将对象从原始画布坐标系转换到临时画布坐标系
|
||||
const newLeft = obj.left - bounds.left;
|
||||
const newTop = obj.top - bounds.top;
|
||||
|
||||
// 设置对象在临时画布中的位置,保持原有的originX和originY
|
||||
clonedObj.set({
|
||||
left: newLeft,
|
||||
top: newTop,
|
||||
// 保持原有的变换属性
|
||||
scaleX: obj.scaleX,
|
||||
scaleY: obj.scaleY,
|
||||
angle: obj.angle,
|
||||
flipX: obj.flipX,
|
||||
flipY: obj.flipY,
|
||||
opacity: obj.opacity,
|
||||
originX: obj.originX,
|
||||
originY: obj.originY,
|
||||
// 确保可见
|
||||
visible: true,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`📍 对象 ${index + 1} 位置转换: (${obj.left}, ${
|
||||
obj.top
|
||||
}) -> (${newLeft}, ${newTop})`
|
||||
);
|
||||
|
||||
tempCanvas.add(clonedObj);
|
||||
} catch (error) {
|
||||
console.error(`添加对象 ${obj.id || obj.type} 到临时画布失败:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(
|
||||
`✅ 临时画布对象添加完成,共 ${tempCanvas.getObjects().length} 个对象`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理临时画布
|
||||
* @param {fabric.StaticCanvas} tempCanvas 临时画布
|
||||
* @private
|
||||
*/
|
||||
_cleanupTempCanvas(tempCanvas) {
|
||||
try {
|
||||
if (tempCanvas) {
|
||||
tempCanvas.clear();
|
||||
tempCanvas.dispose();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("清理临时画布时发生错误:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建栅格化图层并替换原图层
|
||||
* @param {fabric.Image} rasterizedImage 栅格化后的图像
|
||||
* @private
|
||||
*/
|
||||
async _createRasterizedLayer(rasterizedImage) {
|
||||
// 从画布中移除原有对象
|
||||
this.objectsToRasterize.forEach((obj) => {
|
||||
removeCanvasObjectByObject(this.canvas, obj);
|
||||
});
|
||||
|
||||
// 添加栅格化图像到画布
|
||||
this.canvas.add(rasterizedImage);
|
||||
this.canvas.setActiveObject(rasterizedImage);
|
||||
|
||||
// 生成新图层ID
|
||||
this.rasterizedLayerId = generateId("rasterized_layer_");
|
||||
|
||||
// 创建新的栅格化图层
|
||||
this.rasterizedLayer = createLayer({
|
||||
id: this.rasterizedLayerId,
|
||||
name: `${this.layer.name} (栅格化)`,
|
||||
type: LayerType.BITMAP,
|
||||
visible: this.layer.visible,
|
||||
locked: this.layer.locked,
|
||||
opacity: this.layer.opacity,
|
||||
fabricObjects: [rasterizedImage],
|
||||
});
|
||||
|
||||
// 更新图像对象的图层关联
|
||||
rasterizedImage.set({
|
||||
layerId: this.rasterizedLayerId,
|
||||
layerName: this.rasterizedLayer.name,
|
||||
});
|
||||
|
||||
// 替换图层结构
|
||||
if (this.isGroupLayer) {
|
||||
// 组图层:移除所有相关图层
|
||||
const layerIdsToRemove = this.layersToRasterize.map((layer) => layer.id);
|
||||
this.layers.value = this.layers.value.filter(
|
||||
(layer) => !layerIdsToRemove.includes(layer.id)
|
||||
);
|
||||
} else {
|
||||
// 普通图层:移除原图层
|
||||
const layerIndex = this.layers.value.findIndex(
|
||||
(l) => l.id === this.layerId
|
||||
);
|
||||
if (layerIndex !== -1) {
|
||||
this.layers.value.splice(layerIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 在适当位置添加新的栅格化图层
|
||||
const insertIndex = this.layers.value.findIndex(
|
||||
(l) => l.id === (this.parentLayer?.id || this.layerId)
|
||||
);
|
||||
if (insertIndex !== -1) {
|
||||
this.layers.value.splice(insertIndex, 0, this.rasterizedLayer);
|
||||
} else {
|
||||
this.layers.value.push(this.rasterizedLayer);
|
||||
}
|
||||
|
||||
// 设置为活动图层
|
||||
this.activeLayerId.value = this.rasterizedLayerId;
|
||||
|
||||
// 重新渲染画布
|
||||
await optimizeCanvasRendering(this.canvas, () => {
|
||||
this.canvas.renderAll();
|
||||
});
|
||||
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
|
||||
console.log(`🎨 栅格化图层 ${this.rasterizedLayer.name} 创建完成`);
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
originalLayerId: this.layerId,
|
||||
originalLayerName: this.layer?.name,
|
||||
rasterizedLayerId: this.rasterizedLayerId,
|
||||
rasterizedLayerName: this.rasterizedLayer?.name,
|
||||
isGroupLayer: this.isGroupLayer,
|
||||
objectCount: this.objectsToRasterize?.length || 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +302,18 @@ export class RemoveLayerCommand extends Command {
|
||||
);
|
||||
this.removedLayer = this.layers.value[this.layerIndex];
|
||||
this.isActiveLayer = this.layerId === this.activeLayerId.value;
|
||||
// this.beforeLayers = [...this.layers.value]; // 备份原图层列表
|
||||
|
||||
// 从Canvas中找到真实对象并备份,确保撤销和重做时对象的一致性
|
||||
this.originalObjects = [];
|
||||
if (this.removedLayer) {
|
||||
// 从画布中获取真实的对象引用
|
||||
this.originalObjects = this.canvas.getObjects().filter((obj) => {
|
||||
return obj.layerId === this.layerId;
|
||||
});
|
||||
}
|
||||
|
||||
// 备份原活动图层ID
|
||||
this.originalActiveLayerId = this.activeLayerId.value;
|
||||
}
|
||||
|
||||
execute() {
|
||||
@@ -311,19 +322,22 @@ export class RemoveLayerCommand extends Command {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从画布中移除图层中的所有对象
|
||||
if (
|
||||
this.removedLayer.fabricObjects &&
|
||||
this.removedLayer.fabricObjects.length > 0
|
||||
) {
|
||||
this.removedLayer.fabricObjects.forEach((obj) => {
|
||||
// 从画布中移除图层中的所有真实对象
|
||||
this.originalObjects.forEach((obj) => {
|
||||
if (this.canvas.getObjects().includes(obj)) {
|
||||
this.canvas.remove(obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 如果是背景图层,移除特殊对象
|
||||
if (this.removedLayer.isBackground && this.removedLayer.fabricObject) {
|
||||
this.canvas.remove(this.removedLayer.fabricObject);
|
||||
const { object } = findObjectById(
|
||||
this.canvas,
|
||||
this.removedLayer.fabricObject.id
|
||||
);
|
||||
if (object) {
|
||||
this.canvas.remove(object);
|
||||
}
|
||||
}
|
||||
|
||||
// 从图层列表中删除
|
||||
@@ -347,38 +361,49 @@ export class RemoveLayerCommand extends Command {
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
console.log(
|
||||
`✅ 已移除图层: ${this.removedLayer.name} (ID: ${this.layerId})`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
undo() {
|
||||
// 恢复图层
|
||||
// 恢复图层到原位置
|
||||
if (this.layerIndex !== -1 && this.removedLayer) {
|
||||
this.layers.value.splice(this.layerIndex, 0, this.removedLayer);
|
||||
|
||||
// 恢复图层中的所有对象到画布
|
||||
if (
|
||||
this.removedLayer.fabricObjects &&
|
||||
this.removedLayer.fabricObjects.length > 0
|
||||
) {
|
||||
this.removedLayer.fabricObjects.forEach((obj) => {
|
||||
// 使用优化渲染批处理恢复真实对象到画布
|
||||
optimizeCanvasRendering(this.canvas, () => {
|
||||
this.originalObjects.forEach((obj) => {
|
||||
// 恢复对象到画布
|
||||
this.canvas.add(obj);
|
||||
// 确保对象的图层信息正确
|
||||
obj.layerId = this.layerId;
|
||||
obj.layerName = this.removedLayer.name;
|
||||
obj.setCoords(); // 更新坐标
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是背景图层,恢复特殊对象
|
||||
if (this.removedLayer.isBackground && this.removedLayer.fabricObject) {
|
||||
this.canvas.add(this.removedLayer.fabricObject);
|
||||
}
|
||||
// 如果是背景图层,恢复特殊对象
|
||||
if (this.removedLayer.isBackground && this.removedLayer.fabricObject) {
|
||||
// 检查对象是否已在画布中
|
||||
const { object } = findObjectById(
|
||||
this.canvas,
|
||||
this.removedLayer.fabricObject.id
|
||||
);
|
||||
if (!object) {
|
||||
this.canvas.add(this.removedLayer.fabricObject);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 如果删除的是当前活动图层,恢复活动图层
|
||||
if (this.isActiveLayer) {
|
||||
this.activeLayerId.value = this.layerId;
|
||||
}
|
||||
|
||||
// 重新渲染画布
|
||||
if (this.canvas) {
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
console.log(
|
||||
`↩️ 已恢复图层: ${this.removedLayer.name} (ID: ${this.layerId})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +412,7 @@ export class RemoveLayerCommand extends Command {
|
||||
name: this.name,
|
||||
layerName: this.removedLayer?.name || "未知图层",
|
||||
layerId: this.layerId,
|
||||
objectCount: this.originalObjects.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -503,56 +529,40 @@ export class ToggleLayerVisibilityCommand extends Command {
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.layerId = options.layerId;
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找图层
|
||||
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
this.layer = layer;
|
||||
this.oldVisibility = this.layer ? this.layer.visible : null;
|
||||
// this.oldVisibility = this.layer ? this.layer.visible : null;
|
||||
}
|
||||
|
||||
execute() {
|
||||
async execute() {
|
||||
if (!this.layer) {
|
||||
console.error(`图层 ${this.layerId} 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 切换可见性
|
||||
this.layer.visible = !this.oldVisibility;
|
||||
this.layer.visible = !this.layer.visible;
|
||||
|
||||
// 更新画布上图层对象的可见性
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.visible = this.layer.visible;
|
||||
});
|
||||
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
// 更新画布上对象的可选择状态
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
|
||||
return true;
|
||||
return this.layer.visible;
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this.layer) {
|
||||
// 恢复可见性
|
||||
this.layer.visible = this.oldVisibility;
|
||||
|
||||
// 更新画布上图层对象的可见性
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.visible = this.oldVisibility;
|
||||
});
|
||||
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
}
|
||||
async undo() {
|
||||
return await this.execute(); // 直接调用execute方法来恢复可见性
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
@@ -565,6 +575,71 @@ export class ToggleLayerVisibilityCommand extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换子图层可见性命令
|
||||
*/
|
||||
export class ToggleChildLayerVisibilityCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "切换子图层可见性",
|
||||
saveState: false,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.layerId = options.layerId;
|
||||
this.parentId = options.parentId;
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找父图层和子图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
this.parentLayer = parent;
|
||||
this.childLayer = layer;
|
||||
|
||||
// this.oldVisibility = this.childLayer ? this.childLayer.visible : null;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.childLayer) {
|
||||
throw new Error("找不到要切换可见性的子图层");
|
||||
}
|
||||
|
||||
// 切换可见性
|
||||
this.childLayer.visible = !this.childLayer.visible;
|
||||
|
||||
// 更新画布上图层对象的可见性
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.visible = this.childLayer.visible;
|
||||
});
|
||||
}
|
||||
|
||||
// 更新画布上对象的可选择状态
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
return this.childLayer.visible;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
return await this.execute(); // 直接调用execute方法来恢复可见性
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
layerName: this.childLayer?.name || "未知子图层",
|
||||
layerId: this.layerId,
|
||||
parentId: this.parentId,
|
||||
newVisibility: this.childLayer?.visible,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名图层命令
|
||||
*/
|
||||
@@ -1031,9 +1106,9 @@ export class GroupLayersCommand extends Command {
|
||||
// 备份原图层
|
||||
this.originalLayers = [...this.layers.value];
|
||||
// 新组ID
|
||||
this.groupId = `group_layer_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
this.groupId =
|
||||
generateId("group_layer_") ||
|
||||
`group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
this.originalActiveLayerId = this.activeLayerId.value; // 备份原活动图层ID
|
||||
}
|
||||
@@ -1097,6 +1172,8 @@ export class GroupLayersCommand extends Command {
|
||||
// 更新当前活动图层
|
||||
this.activeLayerId.value = this.layerIds[0];
|
||||
|
||||
this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.groupId);
|
||||
|
||||
return this.groupId;
|
||||
}
|
||||
|
||||
@@ -2199,9 +2276,9 @@ export class LayerObjectsToGroupCommand extends Command {
|
||||
* @private
|
||||
*/
|
||||
_updateThumbnail() {
|
||||
if (this.canvas.thumbnailManager) {
|
||||
this.canvas.thumbnailManager.generateLayerThumbnail(this.activeLayer.id);
|
||||
}
|
||||
// this.canvas?.thumbnailManager?.generateLayerThumbnail?.(
|
||||
// this.activeLayer.id
|
||||
// );
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
@@ -2232,8 +2309,12 @@ export class CreateImageLayerCommand extends Command {
|
||||
this.toolManager = options.toolManager;
|
||||
this.layerName = options.layerName || null;
|
||||
|
||||
this.imageId = generateId("image_");
|
||||
|
||||
// 存储执行过程中的结果
|
||||
this.newLayerId = null;
|
||||
this.newLayerId =
|
||||
generateId("layer_image_") ||
|
||||
`layer_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
this.commands = [];
|
||||
this.executedCommands = [];
|
||||
}
|
||||
@@ -2251,12 +2332,15 @@ export class CreateImageLayerCommand extends Command {
|
||||
const fileName =
|
||||
this.layerName || `图片 ${new Date().toLocaleTimeString()}`;
|
||||
|
||||
this.fabricImage.set({
|
||||
id: this.imageId,
|
||||
});
|
||||
// 1. 创建新图层命令
|
||||
const createLayerCmd = new AddLayerCommand({
|
||||
canvas: this.layerManager.canvas,
|
||||
layers: this.layerManager.layers,
|
||||
newLayer: createLayer({
|
||||
id: this.newLayerId || generateId("layer"),
|
||||
id: this.newLayerId,
|
||||
name: fileName,
|
||||
type: LayerType.BITMAP,
|
||||
visible: true,
|
||||
@@ -2658,6 +2742,11 @@ export class CutLayerCommand extends Command {
|
||||
this.cutLayer = null;
|
||||
this.clipboardData = null;
|
||||
this.wasActiveLayer = false;
|
||||
|
||||
// 生成新图层ID和名称
|
||||
this.newLayerId =
|
||||
generateId("layer_") ||
|
||||
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
@@ -2678,9 +2767,6 @@ export class CutLayerCommand extends Command {
|
||||
console.warn("不能复制背景图层");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成新图层ID和名称
|
||||
this.newLayerId = `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const newName = `${sourceLayer.name} copy`;
|
||||
|
||||
// 创建新图层
|
||||
@@ -2781,100 +2867,6 @@ export class CutLayerCommand extends Command {
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
sourceLayerId: this.layerId,
|
||||
newLayerId: this.newLayerId,
|
||||
newLayerName: this.newLayer?.name,
|
||||
objectCount: this.createdObjects.length,
|
||||
isChildLayer: this.isChildLayer,
|
||||
parentLayerId: this.parentLayer?.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制图层到指定位置命令
|
||||
*/
|
||||
export class DuplicateLayerCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "复制图层",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.layerId = options.layerId;
|
||||
this.activeLayerId = options.activeLayerId;
|
||||
this.insertIndex = options.insertIndex || null;
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
this.newLayer = null;
|
||||
this.newLayerId = null;
|
||||
this.createdObjects = [];
|
||||
}
|
||||
|
||||
async execute() {
|
||||
const { layer } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
const sourceLayer = layer;
|
||||
if (!sourceLayer) {
|
||||
console.error(`源图层 ${this.layerId} 不存在`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 不允许复制背景图层
|
||||
if (sourceLayer.isBackground) {
|
||||
console.warn("不能复制背景图层");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成新图层ID和名称
|
||||
this.newLayerId = `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const newName = `${sourceLayer.name} copy`;
|
||||
|
||||
// 创建新图层
|
||||
this.newLayer = createLayer({
|
||||
id: this.newLayerId,
|
||||
name: newName,
|
||||
type: sourceLayer.type,
|
||||
visible: sourceLayer.visible,
|
||||
locked: sourceLayer.locked,
|
||||
opacity: sourceLayer.opacity,
|
||||
blendMode: sourceLayer.blendMode,
|
||||
fabricObjects: [],
|
||||
children: sourceLayer.children ? [...sourceLayer.children] : [],
|
||||
layerProperties: sourceLayer.layerProperties
|
||||
? { ...sourceLayer.layerProperties }
|
||||
: {},
|
||||
metadata: sourceLayer.metadata ? { ...sourceLayer.metadata } : {},
|
||||
});
|
||||
|
||||
// 计算插入位置
|
||||
const sourceIndex = this.layers.value.findIndex(
|
||||
(l) => l.id === this.layerId
|
||||
);
|
||||
const insertIndex =
|
||||
this.insertIndex !== null ? this.insertIndex : sourceIndex + 1;
|
||||
|
||||
// 插入新图层
|
||||
this.layers.value.splice(insertIndex, 0, this.newLayer);
|
||||
|
||||
// 复制源图层中的对象
|
||||
if (sourceLayer.fabricObjects && sourceLayer.fabricObjects.length > 0) {
|
||||
await this._duplicateObjects(sourceLayer.fabricObjects);
|
||||
}
|
||||
|
||||
// 设置为活动图层
|
||||
this.activeLayerId.value = this.newLayerId;
|
||||
|
||||
// 重新渲染画布
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
|
||||
console.log(`已复制图层:${newName}`);
|
||||
return this.newLayerId;
|
||||
}
|
||||
|
||||
async _duplicateObjects(sourceObjects) {
|
||||
const serializedObjects = sourceObjects.map((obj) => {
|
||||
// 序列化对象时保留必要的属性
|
||||
@@ -2914,27 +2906,6 @@ export class DuplicateLayerCommand extends Command {
|
||||
});
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.newLayer) return;
|
||||
|
||||
// 从图层列表中删除新图层
|
||||
const index = this.layers.value.findIndex((l) => l.id === this.newLayerId);
|
||||
if (index !== -1) {
|
||||
this.layers.value.splice(index, 1);
|
||||
}
|
||||
|
||||
// 从画布中移除所有创建的对象
|
||||
this.createdObjects.forEach((obj) => {
|
||||
this.canvas.remove(obj);
|
||||
});
|
||||
|
||||
// 恢复原活动图层
|
||||
this.activeLayerId.value = this.layerId;
|
||||
|
||||
// 重新渲染画布
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
@@ -2942,6 +2913,8 @@ export class DuplicateLayerCommand extends Command {
|
||||
newLayerId: this.newLayerId,
|
||||
newLayerName: this.newLayer?.name,
|
||||
objectCount: this.createdObjects.length,
|
||||
isChildLayer: this.isChildLayer,
|
||||
parentLayerId: this.parentLayer?.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2963,15 +2936,13 @@ export class CreateAdjustmentLayerCommand extends Command {
|
||||
this.insertIndex = options.insertIndex || null;
|
||||
|
||||
this.newLayer = null;
|
||||
this.newLayerId = null;
|
||||
// 生成新图层ID
|
||||
this.newLayerId =
|
||||
generateId("adj_layer_") ||
|
||||
`adj_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
execute() {
|
||||
// 生成新图层ID
|
||||
this.newLayerId = `adj_layer_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
|
||||
// 创建调整图层
|
||||
this.newLayer = createLayer({
|
||||
id: this.newLayerId,
|
||||
@@ -3959,6 +3930,10 @@ export class RemoveChildLayerCommand extends Command {
|
||||
) ?? -1;
|
||||
this.removedChild = this.parentLayer?.children?.[this.childIndex];
|
||||
this.isActiveLayer = this.layerId === this.activeLayerId.value;
|
||||
|
||||
this.originalObjects = this.canvas.getObjects().filter((obj) => {
|
||||
return obj.layerId === this.layerId;
|
||||
});
|
||||
}
|
||||
|
||||
async execute() {
|
||||
@@ -3972,8 +3947,9 @@ export class RemoveChildLayerCommand extends Command {
|
||||
this.removedChild.fabricObjects.length > 0
|
||||
) {
|
||||
this.removedChild.fabricObjects.forEach((obj) => {
|
||||
if (this.canvas.getObjects().includes(obj)) {
|
||||
this.canvas.remove(obj);
|
||||
const { object } = findObjectById(this.canvas, obj.id);
|
||||
if (object) {
|
||||
this.canvas.remove(...this.originalObjects);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4001,29 +3977,34 @@ export class RemoveChildLayerCommand extends Command {
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.parentLayer || this.childIndex === -1 || !this.removedChild)
|
||||
if (
|
||||
!this.parentLayer ||
|
||||
this.childIndex === -1 ||
|
||||
!this.removedChild ||
|
||||
this.originalObjects.length === 0
|
||||
) {
|
||||
return;
|
||||
|
||||
}
|
||||
// 恢复子图层到原位置
|
||||
this.parentLayer.children.splice(this.childIndex, 0, this.removedChild);
|
||||
|
||||
// 恢复子图层中的所有对象到画布
|
||||
if (
|
||||
this.removedChild.fabricObjects &&
|
||||
this.removedChild.fabricObjects.length > 0
|
||||
) {
|
||||
this.removedChild.fabricObjects.forEach((obj) => {
|
||||
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();
|
||||
// 重新渲染画布
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
});
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
@@ -4117,6 +4098,7 @@ export class ChildLayerLockCommand extends Command {
|
||||
this.layers = options.layers;
|
||||
this.layerId = options.layerId;
|
||||
this.parentId = options.parentId;
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找父图层和子图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
@@ -4163,61 +4145,3 @@ export class ChildLayerLockCommand extends Command {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换子图层可见性命令
|
||||
*/
|
||||
export class ToggleChildLayerVisibilityCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "切换子图层可见性",
|
||||
saveState: false,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.layerId = options.layerId;
|
||||
this.parentId = options.parentId;
|
||||
|
||||
// 查找父图层和子图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
this.parentLayer = parent;
|
||||
this.childLayer = layer;
|
||||
|
||||
this.oldVisibility = this.childLayer ? this.childLayer.visible : null;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.childLayer) {
|
||||
throw new Error("找不到要切换可见性的子图层");
|
||||
}
|
||||
|
||||
// 切换可见性
|
||||
this.childLayer.visible = !this.oldVisibility;
|
||||
|
||||
// 更新画布上对象的可选择状态
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
return true;
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (this.childLayer) {
|
||||
this.childLayer.visible = this.oldVisibility;
|
||||
|
||||
// 更新画布上对象的可选择状态
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
}
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
layerName: this.childLayer?.name || "未知子图层",
|
||||
layerId: this.layerId,
|
||||
parentId: this.parentId,
|
||||
newVisibility: this.childLayer?.visible,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ export class AddObjectToLayerCommand extends Command {
|
||||
this.canvas.remove(object);
|
||||
}
|
||||
|
||||
this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
||||
// 重置为首次执行状态,以便重做时能正确恢复位置
|
||||
this.isFirstExecution = false; // 保持为false,因为已经执行过一次了
|
||||
});
|
||||
@@ -473,18 +473,28 @@ export class ChangeFixedImageCommand extends Command {
|
||||
this.scale = options.scale || { x: 1, y: 1 };
|
||||
this.preserveTransform = options.preserveTransform ?? false; // 默认不保留变换
|
||||
|
||||
this.options = options || {};
|
||||
|
||||
this.scale.x = options.scaleX || 1;
|
||||
this.scale.y = options.scaleY || 1;
|
||||
this.position.x = options.left || this.canvas.width / 2;
|
||||
this.position.y = options.top || this.canvas.height / 2;
|
||||
|
||||
// 用于回滚的状态
|
||||
this.previousImage = null;
|
||||
this.previousTransform = null;
|
||||
this.previousObjectId = null; // 保存之前对象的ID
|
||||
this.targetLayer = null;
|
||||
this.newObjectId = null; // 保存新对象的ID
|
||||
this.newObjectId = generateId("fixed_"); // 保存新对象的ID
|
||||
this.isExecuted = false;
|
||||
|
||||
// 错误处理
|
||||
this.maxRetries = options.maxRetries || 3;
|
||||
this.retryCount = 0;
|
||||
this.timeoutMs = options.timeoutMs || 10000;
|
||||
|
||||
// 查找目标图层
|
||||
this.targetLayer = this.findTargetLayer();
|
||||
}
|
||||
|
||||
async execute() {
|
||||
@@ -668,12 +678,9 @@ export class ChangeFixedImageCommand extends Command {
|
||||
|
||||
async applyImageToLayer(newImage) {
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 生成新对象ID
|
||||
this.newObjectId = generateId();
|
||||
|
||||
// 设置基本属性
|
||||
newImage.set({
|
||||
id: this.newObjectId,
|
||||
id: this.targetLayer?.fabricObject?.id || this.newObjectId,
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
isBackground: this.targetLayer.isBackground,
|
||||
@@ -884,6 +891,8 @@ export class AddImageToLayerCommand extends Command {
|
||||
this.targetLayer = null;
|
||||
this.isExecuted = false;
|
||||
|
||||
this.imageId = generateId("image_");
|
||||
|
||||
// 错误处理
|
||||
this.maxRetries = options.maxRetries || 3;
|
||||
this.retryCount = 0;
|
||||
@@ -907,7 +916,7 @@ export class AddImageToLayerCommand extends Command {
|
||||
const newImage = await this.loadImageWithRetry();
|
||||
|
||||
// 添加图像到图层
|
||||
await this.addImageToLayer(newImage);
|
||||
await this.addImageToLayer(newImage, this.imageId);
|
||||
|
||||
this.isExecuted = true;
|
||||
|
||||
@@ -1061,10 +1070,7 @@ export class AddImageToLayerCommand extends Command {
|
||||
});
|
||||
}
|
||||
|
||||
async addImageToLayer(newImage) {
|
||||
// 生成唯一ID
|
||||
const objectId = generateId();
|
||||
|
||||
async addImageToLayer(newImage, objectId) {
|
||||
// 设置图像属性
|
||||
newImage.set({
|
||||
id: objectId,
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
import { Command } from "./Command";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import {
|
||||
LayerType,
|
||||
OperationType,
|
||||
createLayer,
|
||||
findLayerRecursively,
|
||||
} from "../utils/layerHelper";
|
||||
import {
|
||||
generateId,
|
||||
optimizeCanvasRendering,
|
||||
findObjectById,
|
||||
removeCanvasObjectByObject,
|
||||
} from "../utils/helper";
|
||||
import { createRasterizedImage } from "../utils/rasterizedImage";
|
||||
|
||||
/**
|
||||
* 栅格化图层命令
|
||||
* 将图层中的所有矢量对象转换为位图图像
|
||||
* 支持普通图层和组图层的栅格化
|
||||
*/
|
||||
export class RasterizeLayerCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "栅格化图层",
|
||||
saveState: true,
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layers = options.layers;
|
||||
this.layerId = options.layerId; // 指定要栅格化的图层ID
|
||||
// 是否包含锁定对象
|
||||
this.hasLocked = options.hasLocked || true;
|
||||
// 是否包含隐藏对象
|
||||
this.hasHidden = options.hasHidden || false;
|
||||
|
||||
this.activeLayerId = options.activeLayerId;
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找目标图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
this.layer = layer;
|
||||
this.parentLayer = parent;
|
||||
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
||||
|
||||
// 保存原始状态用于撤销
|
||||
this.originalLayers = [...this.layers.value];
|
||||
this.originalCanvasObjects = [...this.canvas.getObjects()];
|
||||
this.originalObjectStates = new Map();
|
||||
|
||||
// 栅格化结果
|
||||
this.rasterizedImage = null;
|
||||
this.rasterizedImageId = null;
|
||||
// 生成新图层ID
|
||||
this.rasterizedLayerId = generateId("rasterized_layer_");
|
||||
this.resterizedId = generateId("rasterized_");
|
||||
|
||||
this.rasterizedLayer = null;
|
||||
|
||||
// 要栅格化的图层和对象
|
||||
this.layersToRasterize = [];
|
||||
this.objectsToRasterize = [];
|
||||
}
|
||||
|
||||
async execute() {
|
||||
// 查找目标图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
this.layer = layer;
|
||||
this.parentLayer = parent;
|
||||
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
||||
|
||||
if (!this.layer) {
|
||||
throw new Error(`图层 ${this.layerId} 不存在`);
|
||||
}
|
||||
|
||||
// 检查是否可以栅格化
|
||||
if (this.layer.isBackground || this.layer.isFixed) {
|
||||
throw new Error("背景图层和固定图层不能栅格化");
|
||||
}
|
||||
|
||||
try {
|
||||
// 收集要栅格化的图层和对象
|
||||
this._collectLayersAndObjects();
|
||||
|
||||
if (this.objectsToRasterize.length === 0) {
|
||||
throw new Error("图层没有内容可栅格化");
|
||||
}
|
||||
|
||||
// 保存原始对象状态
|
||||
this._saveOriginalObjectStates();
|
||||
|
||||
this.canvas.discardActiveObject();
|
||||
this.canvas.renderAll();
|
||||
|
||||
// 创建栅格化图像
|
||||
const rasterizedImage = await createRasterizedImage({
|
||||
canvas: this.canvas,
|
||||
fabricObjects: this.objectsToRasterize,
|
||||
});
|
||||
|
||||
// 创建新的栅格化图层并替换原图层
|
||||
await this._createRasterizedLayer(rasterizedImage);
|
||||
|
||||
// 切换到选择工具
|
||||
this.layerManager?.toolManager?.setTool?.(OperationType.SELECT);
|
||||
|
||||
console.log(`✅ 图层 ${this.layer.name} 栅格化完成`);
|
||||
|
||||
this.canvas?.thumbnailManager?.generateLayerThumbnail?.(
|
||||
this.rasterizedLayerId
|
||||
);
|
||||
return this.rasterizedLayerId;
|
||||
} catch (error) {
|
||||
console.error("栅格化图层失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (!this.originalLayers || !this.originalCanvasObjects) {
|
||||
throw new Error("没有可恢复的原始数据");
|
||||
}
|
||||
|
||||
try {
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 清空画布
|
||||
this.canvas.discardActiveObject();
|
||||
this.canvas.clear();
|
||||
|
||||
// 恢复原始对象及其状态
|
||||
this.originalCanvasObjects.forEach((obj) => {
|
||||
// 如果保存了该对象的原始状态,则恢复状态
|
||||
if (this.originalObjectStates.has(obj.id)) {
|
||||
const originalState = this.originalObjectStates.get(obj.id);
|
||||
obj.set(originalState);
|
||||
}
|
||||
|
||||
this.canvas.add(obj);
|
||||
obj.setCoords();
|
||||
});
|
||||
|
||||
// 恢复原始图层结构
|
||||
this.layers.value = [...this.originalLayers];
|
||||
|
||||
// 恢复原活动图层
|
||||
this.activeLayerId.value = this.layerId;
|
||||
|
||||
// 更新画布交互性
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
});
|
||||
|
||||
console.log(`↩️ 图层 ${this.layer.name} 栅格化已撤销`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("撤销栅格化失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集要栅格化的图层和对象
|
||||
* @private
|
||||
*/
|
||||
_collectLayersAndObjects() {
|
||||
if (this.isGroupLayer) {
|
||||
// 组图层:收集自身和所有子图层
|
||||
this.layersToRasterize = this._collectLayersToRasterize(this.layer);
|
||||
} else {
|
||||
// 普通图层:只收集自身
|
||||
this.layersToRasterize = [this.layer];
|
||||
}
|
||||
|
||||
// 收集所有图层的fabricObjects并按画布z-index顺序排序
|
||||
const allCanvasObjects = this.canvas.getObjects();
|
||||
const objectsWithZIndex = [];
|
||||
|
||||
this.layersToRasterize.forEach((layer) => {
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
layer.fabricObjects.forEach((layerObj) => {
|
||||
if (layerObj && layerObj.id) {
|
||||
const { object } = findObjectById(this.canvas, layerObj.id);
|
||||
if (object) {
|
||||
// 获取对象在画布中的z-index(数组索引)
|
||||
const zIndex = allCanvasObjects.indexOf(object);
|
||||
objectsWithZIndex.push({
|
||||
object: object,
|
||||
zIndex: zIndex,
|
||||
layerObj: layerObj,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 按z-index排序,确保保持原有的渲染顺序
|
||||
objectsWithZIndex.sort((a, b) => a.zIndex - b.zIndex);
|
||||
|
||||
// 提取排序后的对象
|
||||
this.objectsToRasterize = objectsWithZIndex.map((item) => item.object);
|
||||
|
||||
console.log(
|
||||
`📊 收集到 ${this.layersToRasterize.length} 个图层,${this.objectsToRasterize.length} 个对象进行栅格化`
|
||||
);
|
||||
console.log(
|
||||
"🔢 对象z-index顺序:",
|
||||
objectsWithZIndex.map((item) => ({
|
||||
id: item.object.id,
|
||||
type: item.object.type,
|
||||
zIndex: item.zIndex,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集要栅格化的图层(递归收集子图层)
|
||||
* @param {Object} sourceLayer 源图层
|
||||
* @returns {Array} 图层数组
|
||||
* @private
|
||||
*/
|
||||
_collectLayersToRasterize(sourceLayer) {
|
||||
const result = [sourceLayer];
|
||||
|
||||
// 如果是组图层,收集所有子图层
|
||||
if (sourceLayer.children && sourceLayer.children.length > 0) {
|
||||
sourceLayer.children.forEach((childLayer) => {
|
||||
if (childLayer) {
|
||||
result.push(...this._collectLayersToRasterize(childLayer));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存原始对象状态
|
||||
* @private
|
||||
*/
|
||||
_saveOriginalObjectStates() {
|
||||
this.objectsToRasterize.forEach((object) => {
|
||||
if (object && object.id) {
|
||||
const originalState = {
|
||||
left: object.left,
|
||||
top: object.top,
|
||||
scaleX: object.scaleX,
|
||||
scaleY: object.scaleY,
|
||||
angle: object.angle,
|
||||
flipX: object.flipX,
|
||||
flipY: object.flipY,
|
||||
opacity: object.opacity,
|
||||
originX: object.originX,
|
||||
originY: object.originY,
|
||||
layerId: object.layerId,
|
||||
layerName: object.layerName,
|
||||
width: object.width,
|
||||
height: object.height,
|
||||
strokeWidth: object.strokeWidth,
|
||||
visible: object.visible,
|
||||
};
|
||||
this.originalObjectStates.set(object.id, originalState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建栅格化图层并替换原图层
|
||||
* @param {fabric.Image} rasterizedImage 栅格化后的图像
|
||||
* @private
|
||||
*/
|
||||
async _createRasterizedLayer(rasterizedImage) {
|
||||
// 从画布中移除原有对象
|
||||
this.objectsToRasterize.forEach((obj) => {
|
||||
removeCanvasObjectByObject(this.canvas, obj);
|
||||
});
|
||||
|
||||
// 添加栅格化图像到画布
|
||||
this.canvas.add(rasterizedImage);
|
||||
this.canvas.setActiveObject(rasterizedImage);
|
||||
|
||||
// 创建新的栅格化图层
|
||||
this.rasterizedLayer = createLayer({
|
||||
id: this.rasterizedLayerId,
|
||||
name: `${this.layer.name} (栅格化)`,
|
||||
type: LayerType.BITMAP,
|
||||
visible: this.layer.visible,
|
||||
locked: this.layer.locked,
|
||||
opacity: this.layer.opacity,
|
||||
fabricObjects: [rasterizedImage],
|
||||
});
|
||||
|
||||
// 更新图像对象的图层关联
|
||||
rasterizedImage.set({
|
||||
id: this.resterizedId,
|
||||
type: "image",
|
||||
layerId: this.rasterizedLayerId,
|
||||
layerName: this.rasterizedLayer.name,
|
||||
});
|
||||
|
||||
// 在适当位置添加新的栅格化图层
|
||||
// 1.当前如果是子图层,则插入到子图层的位置
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
|
||||
let insertIndex = 0;
|
||||
// 说明是子图层
|
||||
if (parent) {
|
||||
this.layers.value.some((l, index) => {
|
||||
if (l.id === parent.id) {
|
||||
insertIndex = this.layers.value?.[index].children?.findIndex(
|
||||
(fItem) => fItem.id === this.layerId
|
||||
);
|
||||
return true; // 找到父图层,停止循环
|
||||
}
|
||||
return false; // 继续查找
|
||||
});
|
||||
} else {
|
||||
insertIndex = this.layers.value.findIndex((l) => l.id === this.layerId);
|
||||
}
|
||||
|
||||
if (insertIndex !== -1) {
|
||||
if (parent) {
|
||||
const pIndex = this.layers.value.findIndex((l) => l.id === parent.id);
|
||||
this.layers.value[pIndex].children?.splice?.(
|
||||
insertIndex,
|
||||
1,
|
||||
this.rasterizedLayer
|
||||
);
|
||||
} else this.layers.value.splice(insertIndex, 1, this.rasterizedLayer);
|
||||
} else {
|
||||
// 2.如果没有找到父图层,则添加到顶层
|
||||
this.layers.value.unshift(this.rasterizedLayer);
|
||||
}
|
||||
|
||||
// // 替换图层结构
|
||||
// if (this.isGroupLayer) {
|
||||
// // 组图层:移除所有相关图层
|
||||
// const layerIdsToRemove = this.layersToRasterize.map((layer) => layer.id);
|
||||
// this.layers.value = this.layers.value.filter(
|
||||
// (layer) => !layerIdsToRemove.includes(layer.id)
|
||||
// );
|
||||
// } else {
|
||||
// // 普通图层:移除原图层
|
||||
// const layerIndex = this.layers.value.findIndex(
|
||||
// (l) => l.id === this.layerId
|
||||
// );
|
||||
// if (layerIndex !== -1) {
|
||||
// this.layers.value.splice(layerIndex, 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 设置为活动图层
|
||||
this.activeLayerId.value = this.rasterizedLayerId;
|
||||
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
|
||||
console.log(`🎨 栅格化图层 ${this.rasterizedLayer.name} 创建完成`);
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
originalLayerId: this.layerId,
|
||||
originalLayerName: this.layer?.name,
|
||||
rasterizedLayerId: this.rasterizedLayerId,
|
||||
rasterizedLayerName: this.rasterizedLayer?.name,
|
||||
isGroupLayer: this.isGroupLayer,
|
||||
objectCount: this.objectsToRasterize?.length || 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
// 存储加载的图片对象
|
||||
this.clothingImage = null;
|
||||
this.redGreenImage = null;
|
||||
this.redGreenImageMask = null;
|
||||
|
||||
// 存储新创建的图层ID
|
||||
this.newEmptyLayerId = null;
|
||||
@@ -68,7 +69,11 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
// 3. 保存原始状态
|
||||
this.originalBackgroundObject = backgroundLayer.fabricObject
|
||||
? {
|
||||
...backgroundLayer.fabricObject.toObject(),
|
||||
...backgroundLayer.fabricObject.toObject([
|
||||
"id",
|
||||
"type",
|
||||
"layerId",
|
||||
]),
|
||||
ref: backgroundLayer.fabricObject,
|
||||
}
|
||||
: null;
|
||||
@@ -136,12 +141,31 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
// clipPathImg.set({
|
||||
// absolutePositioned: true,
|
||||
// });
|
||||
this.redGreenImage.set({
|
||||
absolutePositioned: true,
|
||||
|
||||
// 克隆衣服底图作为裁剪对象
|
||||
this.redGreenImageMask = await new Promise((resolve, reject) => {
|
||||
this.redGreenImage.clone((clonedImg) => {
|
||||
if (!clonedImg) {
|
||||
reject(new Error("无法克隆红绿图"));
|
||||
return;
|
||||
}
|
||||
resolve(clonedImg);
|
||||
});
|
||||
});
|
||||
|
||||
this.redGreenImageMask.set({
|
||||
absolutePositioned: true,
|
||||
opacity: 0.01, // 设置为几乎透明
|
||||
type: "redGreenImageMask",
|
||||
id: generateId("redGreenImageMask_"),
|
||||
});
|
||||
// this.canvas.add(this.redGreenImageMask);
|
||||
this.canvas.clipPath = this.redGreenImageMask;
|
||||
this.redGreenImageMask.sendToBack();
|
||||
this.redGreenImageMask.setCoords();
|
||||
|
||||
const activeLayer = this.layerManager.getActiveLayer();
|
||||
activeLayer.clippingMask = this.redGreenImage.toObject(["id"]);
|
||||
// activeLayer.clippingMask = this.redGreenImageMask.toObject(["id"]);
|
||||
activeLayer.opacity = this.normalLayerOpacity;
|
||||
// activeLayer?.fabricObjects.forEach((obj) => {
|
||||
// obj.set({
|
||||
@@ -181,9 +205,9 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
async _createAndActivateEmptyLayer() {
|
||||
// 创建新的空白图层
|
||||
const newLayerName = "绘制图层";
|
||||
const newLayerId = this.layerManager.createLayer(
|
||||
const newLayerId = await this.layerManager.createLayer(
|
||||
newLayerName,
|
||||
LayerType.GROUP,
|
||||
LayerType.BITMAP,
|
||||
{
|
||||
undoable: false,
|
||||
}
|
||||
@@ -306,7 +330,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
*/
|
||||
async _setupBackgroundLayer(backgroundLayer, clothingImage) {
|
||||
let backgroundObject = backgroundLayer.fabricObject;
|
||||
let { object } = findObjectById(this.canvas, backgroundObject.id);
|
||||
const { object } = findObjectById(this.canvas, backgroundObject.id);
|
||||
|
||||
if (!object) {
|
||||
// 创建白色背景矩形
|
||||
@@ -389,12 +413,16 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
|
||||
// 清除固定图层原有内容
|
||||
if (fixedLayer.fabricObject) {
|
||||
this.canvas.remove(fixedLayer.fabricObject);
|
||||
const { object } = findObjectById(
|
||||
this.canvas,
|
||||
fixedLayer.fabricObject.id
|
||||
);
|
||||
if (object) this.canvas.remove(object);
|
||||
}
|
||||
|
||||
// 添加到画布和固定图层
|
||||
this.canvas.add(img);
|
||||
fixedLayer.fabricObject = img;
|
||||
fixedLayer.fabricObject = img.toObject(["id", "type", "layerId"]);
|
||||
this.clothingImage = img;
|
||||
}
|
||||
|
||||
@@ -424,15 +452,15 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
// 清除普通图层原有内容
|
||||
if (normalLayer.fabricObjects) {
|
||||
normalLayer.fabricObjects.forEach((obj) => {
|
||||
this.canvas.remove(obj);
|
||||
const { object } = findObjectById(this.canvas, obj.id);
|
||||
if (object) {
|
||||
this.canvas.remove(object);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 给img设置裁剪,裁剪图为衣服底图
|
||||
|
||||
// 添加到画布和普通图层
|
||||
this.canvas.add(img);
|
||||
normalLayer.fabricObjects = [img];
|
||||
normalLayer.fabricObjects = [img.toObject(["id", "type", "layerId"])];
|
||||
this.redGreenImage = img;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { generateId, optimizeCanvasRendering } from "../utils/helper";
|
||||
import { createLayer, LayerType, OperationType } from "../utils/layerHelper";
|
||||
import { Command } from "./Command";
|
||||
|
||||
/**
|
||||
@@ -302,3 +304,323 @@ export class CompositeTextCommand extends Command {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文本命令
|
||||
* 用于创建文本对象和图层的组合操作
|
||||
*/
|
||||
export class CreateTextCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: "创建文本",
|
||||
});
|
||||
this.canvas = options.canvas;
|
||||
this.layerManager = options.layerManager;
|
||||
this.x = options.x;
|
||||
this.y = options.y;
|
||||
this.textOptions = options.textOptions || {};
|
||||
// 生成唯一ID
|
||||
this.textId = options?.textId || this.generateId("text_");
|
||||
this.layerId = options?.layerId || generateId("text_layer_");
|
||||
|
||||
// 生成的对象和图层信息
|
||||
this.textObject = null;
|
||||
this.oldActiveLayerId = null;
|
||||
|
||||
// 默认文本属性
|
||||
this.defaultOptions = {
|
||||
text: "双击编辑文本",
|
||||
fontFamily: "Arial",
|
||||
fontSize: 24,
|
||||
fontWeight: "normal",
|
||||
fontStyle: "normal",
|
||||
textAlign: "left",
|
||||
fill: "#000000",
|
||||
opacity: 1,
|
||||
underline: false,
|
||||
overline: false,
|
||||
linethrough: false,
|
||||
textBackgroundColor: "transparent",
|
||||
lineHeight: 1.16,
|
||||
charSpacing: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.canvas || !this.layerManager) {
|
||||
console.error("Canvas或LayerManager不存在");
|
||||
return null;
|
||||
}
|
||||
// 保存当前活动图层
|
||||
this.oldActiveLayerId = this.layerManager.activeLayerId?.value;
|
||||
|
||||
// 合并默认选项和用户选项
|
||||
const finalOptions = {
|
||||
...this.defaultOptions,
|
||||
...this.textOptions,
|
||||
left: this.x,
|
||||
top: this.y,
|
||||
};
|
||||
|
||||
try {
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 创建文本对象
|
||||
this.textObject = new fabric.IText(finalOptions.text, {
|
||||
...finalOptions,
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
});
|
||||
|
||||
// 创建文本图层
|
||||
const layerName = this.textOptions.layerName || "文本图层";
|
||||
const layer = createLayer({
|
||||
id: this.layerId,
|
||||
name: layerName,
|
||||
type: LayerType.TEXT,
|
||||
});
|
||||
|
||||
// 设置对象的图层关联
|
||||
this.textObject.set({
|
||||
id: this.textId,
|
||||
layerId: this.layerId,
|
||||
layerName: layerName,
|
||||
});
|
||||
|
||||
// 智能插入图层到合适位置
|
||||
this._insertLayerAtCorrectPosition(layer);
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(this.textObject);
|
||||
|
||||
// 取消其他对象的选中状态
|
||||
this.canvas.discardActiveObject();
|
||||
|
||||
// 设置新创建的文本对象为活动对象
|
||||
this.canvas.setActiveObject(this.textObject);
|
||||
|
||||
// 更新图层的对象列表
|
||||
if (layer) {
|
||||
layer.fabricObjects = layer.fabricObjects || [];
|
||||
layer.fabricObjects.push(
|
||||
this.textObject.toObject(["id", "layerId", "layerName"])
|
||||
);
|
||||
}
|
||||
|
||||
// 现在可以安全地设置为活动图层
|
||||
this.layerManager.setActiveLayer(this.layerId);
|
||||
|
||||
// 更新对象交互性
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
||||
|
||||
// 切换到选择工具
|
||||
this.layerManager?.toolManager?.setTool?.(OperationType.SELECT);
|
||||
});
|
||||
|
||||
console.log(
|
||||
`✅ 文本对象已创建: "${finalOptions.text}",位置: (${this.x}, ${this.y})`
|
||||
);
|
||||
return this.textObject;
|
||||
} catch (error) {
|
||||
console.error("创建文本对象失败:", error);
|
||||
// 如果创建失败,需要清理已创建的资源
|
||||
await this.undo();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能插入图层到正确位置
|
||||
* 根据当前激活图层位置确定新图层插入位置
|
||||
* @param {Object} newLayer 要插入的新图层
|
||||
* @private
|
||||
*/
|
||||
_insertLayerAtCorrectPosition(newLayer) {
|
||||
const layers = this.layerManager.layers.value;
|
||||
const currentActiveLayerId = this.layerManager.activeLayerId?.value;
|
||||
|
||||
// 如果没有当前激活图层,插入到顶部(索引0)
|
||||
if (!currentActiveLayerId) {
|
||||
layers.splice(0, 0, newLayer);
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找当前激活图层的位置
|
||||
const {
|
||||
layer: activeLayer,
|
||||
parent: parentLayer,
|
||||
index: activeIndex,
|
||||
} = this._findLayerPosition(currentActiveLayerId);
|
||||
|
||||
if (!activeLayer) {
|
||||
// 没找到激活图层,插入到顶部
|
||||
layers.splice(0, 0, newLayer);
|
||||
return;
|
||||
}
|
||||
|
||||
// 确定插入位置
|
||||
let insertIndex = 0;
|
||||
|
||||
if (parentLayer) {
|
||||
// 当前激活图层是子图层
|
||||
// 在同一父图层内,插入到激活子图层之上
|
||||
insertIndex = Math.max(0, activeIndex);
|
||||
parentLayer.children = parentLayer.children || [];
|
||||
parentLayer.children.splice(insertIndex, 0, newLayer);
|
||||
|
||||
console.log(
|
||||
`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`
|
||||
);
|
||||
} else {
|
||||
// 当前激活图层是一级图层
|
||||
// 在一级图层中,插入到激活图层之上
|
||||
const activeLayerIndex = layers.findIndex(
|
||||
(layer) => layer.id === currentActiveLayerId
|
||||
);
|
||||
insertIndex = Math.max(0, activeLayerIndex);
|
||||
layers.splice(insertIndex, 0, newLayer);
|
||||
|
||||
console.log(`新图层已插入到一级图层位置: ${insertIndex}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找图层位置信息
|
||||
* @param {String} layerId 图层ID
|
||||
* @returns {Object} 包含图层、父图层和索引的对象
|
||||
* @private
|
||||
*/
|
||||
_findLayerPosition(layerId) {
|
||||
const layers = this.layerManager.layers.value;
|
||||
|
||||
// 先在一级图层中查找
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
const layer = layers[i];
|
||||
|
||||
if (layer.id === layerId) {
|
||||
return {
|
||||
layer: layer,
|
||||
parent: null,
|
||||
index: i,
|
||||
};
|
||||
}
|
||||
|
||||
// 在子图层中查找
|
||||
if (layer.children && Array.isArray(layer.children)) {
|
||||
for (let j = 0; j < layer.children.length; j++) {
|
||||
const childLayer = layer.children[j];
|
||||
if (childLayer.id === layerId) {
|
||||
return {
|
||||
layer: childLayer,
|
||||
parent: layer,
|
||||
index: j,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
layer: null,
|
||||
parent: null,
|
||||
index: -1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一ID
|
||||
* @returns {String} 唯一ID
|
||||
*/
|
||||
generateId() {
|
||||
return `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
name: this.name,
|
||||
textId: this.textObject?.id,
|
||||
layerId: this.layerId,
|
||||
text: this.textOptions.text || this.defaultOptions.text,
|
||||
position: { x: this.x, y: this.y },
|
||||
};
|
||||
}
|
||||
|
||||
async undo() {
|
||||
try {
|
||||
// 从画布移除文本对象
|
||||
if (this.textObject && this.canvas) {
|
||||
this.canvas.remove(this.textObject);
|
||||
}
|
||||
|
||||
// 智能移除创建的图层
|
||||
if (this.layerId && this.layerManager) {
|
||||
this._removeLayerFromCorrectPosition();
|
||||
}
|
||||
|
||||
// 恢复原活动图层
|
||||
if (this.oldActiveLayerId && this.layerManager) {
|
||||
// 检查原活动图层是否还存在
|
||||
const originalLayer = this.layerManager.getLayerById(
|
||||
this.oldActiveLayerId
|
||||
);
|
||||
if (originalLayer) {
|
||||
this.layerManager.setActiveLayer(this.oldActiveLayerId);
|
||||
} else {
|
||||
// 如果原图层不存在,设置为第一个可用的普通图层
|
||||
const availableLayers = this.layerManager.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed && !layer.locked
|
||||
);
|
||||
if (availableLayers.length > 0) {
|
||||
this.layerManager.setActiveLayer(availableLayers[0].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新对象交互性
|
||||
await this.layerManager.updateLayersObjectsInteractivity();
|
||||
|
||||
// 重新渲染画布
|
||||
if (this.canvas) {
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
console.log(`↩️ 文本创建操作已撤销`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("撤销文本创建操作失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能移除图层
|
||||
* 根据图层位置(一级图层或子图层)进行相应的移除操作
|
||||
* @private
|
||||
*/
|
||||
_removeLayerFromCorrectPosition() {
|
||||
const layers = this.layerManager.layers.value;
|
||||
|
||||
// 查找图层位置信息
|
||||
const positionInfo = this._findLayerPosition(this.layerId);
|
||||
|
||||
if (!positionInfo.layer) {
|
||||
console.warn(`要移除的图层不存在: ${this.layerId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (positionInfo.parent) {
|
||||
// 从子图层中移除
|
||||
if (positionInfo.parent.children && positionInfo.index >= 0) {
|
||||
positionInfo.parent.children.splice(positionInfo.index, 1);
|
||||
console.log(
|
||||
`已从子图层移除: ${this.layerId} (父图层: ${positionInfo.parent.name})`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 从一级图层中移除
|
||||
if (positionInfo.index >= 0) {
|
||||
layers.splice(positionInfo.index, 1);
|
||||
console.log(`已从一级图层移除: ${this.layerId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user