feat: 裁剪组裁剪跟随选择组移动

This commit is contained in:
bighuixiang
2025-07-14 01:00:23 +08:00
parent 96e13cb22a
commit 24e9ba8ae5
80 changed files with 2052 additions and 4292 deletions

View File

@@ -22,9 +22,7 @@ export class CreateBackgroundLayerCommand extends Command {
execute() { execute() {
// 检查是否已经存在背景图层 // 检查是否已经存在背景图层
const existingBgLayer = this.layers.value.find( const existingBgLayer = this.layers.value.find((layer) => layer.isBackground);
(layer) => layer.isBackground
);
if (existingBgLayer) { if (existingBgLayer) {
console.warn("已存在背景层,不重复创建"); console.warn("已存在背景层,不重复创建");
return existingBgLayer.id; return existingBgLayer.id;
@@ -34,11 +32,7 @@ export class CreateBackgroundLayerCommand extends Command {
const bgObject = this._createBackgroundObject(); const bgObject = this._createBackgroundObject();
// 将背景对象添加到图层中 // 将背景对象添加到图层中
this.backgroundLayer.fabricObject = bgObject.toObject([ this.backgroundLayer.fabricObject = bgObject.toObject(["id", "layerId", "type"]);
"id",
"layerId",
"type",
]);
// 添加图层到最底部 // 添加图层到最底部
this.layers.value.push(this.backgroundLayer); this.layers.value.push(this.backgroundLayer);
@@ -54,9 +48,7 @@ export class CreateBackgroundLayerCommand extends Command {
undo() { undo() {
// 从图层列表中删除背景图层 // 从图层列表中删除背景图层
const bgLayerIndex = this.layers.value.findIndex( const bgLayerIndex = this.layers.value.findIndex((layer) => layer.isBackground);
(layer) => layer.isBackground
);
if (bgLayerIndex !== -1) { if (bgLayerIndex !== -1) {
this.layers.value.splice(bgLayerIndex, 1); this.layers.value.splice(bgLayerIndex, 1);
} }
@@ -82,8 +74,7 @@ export class CreateBackgroundLayerCommand extends Command {
// 确保背景色为白色,如果没有设置或者是透明的话 // 确保背景色为白色,如果没有设置或者是透明的话
const backgroundColor = const backgroundColor =
this.backgroundLayer.backgroundColor && this.backgroundLayer.backgroundColor && this.backgroundLayer.backgroundColor !== "transparent"
this.backgroundLayer.backgroundColor !== "transparent"
? this.backgroundLayer.backgroundColor ? this.backgroundLayer.backgroundColor
: "#ffffff"; : "#ffffff";
@@ -139,10 +130,7 @@ export class UpdateBackgroundCommand extends Command {
// 查找背景图层 // 查找背景图层
this.bgLayer = this.layers.value.find((layer) => layer.isBackground); this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
this.oldBackgroundColor = this.oldColor; this.oldBackgroundColor = this.oldColor;
this.backgroundObject = findObjectById( this.backgroundObject = findObjectById(this.canvas, this.bgLayer.fabricObject.id).object;
this.canvas,
this.bgLayer.fabricObject.id
).object;
} }
execute() { execute() {
@@ -163,9 +151,7 @@ export class UpdateBackgroundCommand extends Command {
this.backgroundColorValue.value = this.backgroundColor; // 设置背景颜色 this.backgroundColorValue.value = this.backgroundColor; // 设置背景颜色
// 生成缩略图 // 生成缩略图
this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.( this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(this.bgLayer.id);
this.bgLayer.id
);
return true; return true;
} }
@@ -186,9 +172,7 @@ export class UpdateBackgroundCommand extends Command {
this.backgroundColorValue.value = this.oldBackgroundColor; // 恢复背景颜色 this.backgroundColorValue.value = this.oldBackgroundColor; // 恢复背景颜色
// 生成缩略图 // 生成缩略图
this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.( this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(this.bgLayer.id);
this.bgLayer.id
);
// 如果有旧颜色,恢复到旧颜色 // 如果有旧颜色,恢复到旧颜色
return true; return true;
} }
@@ -223,10 +207,7 @@ export class BackgroundSizeCommand extends Command {
this.bgLayer = this.layers.value.find((layer) => layer.isBackground); this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
// 记录原尺寸 // 记录原尺寸
this.backgroundObject = findObjectById( this.backgroundObject = findObjectById(this.canvas, this.bgLayer.fabricObject.id).object;
this.canvas,
this.bgLayer.fabricObject.id
).object;
this.oldWidth = this.backgroundObject.width; this.oldWidth = this.backgroundObject.width;
this.oldHeight = this.backgroundObject.height; this.oldHeight = this.backgroundObject.height;
@@ -255,8 +236,7 @@ export class BackgroundSizeCommand extends Command {
// 调整背景对象大小 // 调整背景对象大小
if (this.bgLayer && this.backgroundObject) { if (this.bgLayer && this.backgroundObject) {
// 保持原有的背景颜色,如果没有设置则使用白色 // 保持原有的背景颜色,如果没有设置则使用白色
const currentFill = const currentFill = this.backgroundObject.fill || this.bgLayer.backgroundColor || "#ffffff";
this.backgroundObject.fill || this.bgLayer.backgroundColor || "#ffffff";
this.backgroundObject.set({ this.backgroundObject.set({
width: this.newWidth, width: this.newWidth,
@@ -343,10 +323,7 @@ export class BackgroundSizeWithScaleCommand extends Command {
// 查找背景图层 // 查找背景图层
this.bgLayer = this.layers.value.find((layer) => layer.isBackground); this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
this.backgroundObject = findObjectById( this.backgroundObject = findObjectById(this.canvas, this.bgLayer.fabricObject.id).object;
this.canvas,
this.bgLayer.fabricObject.id
).object;
// 计算缩放比例 // 计算缩放比例
const scaleXRatio = this.newWidth / this.oldWidth; const scaleXRatio = this.newWidth / this.oldWidth;
@@ -409,24 +386,21 @@ export class BackgroundSizeWithScaleCommand extends Command {
// 统一缩放:使用平均值,保持相对比例的同时允许适度的形变 // 统一缩放:使用平均值,保持相对比例的同时允许适度的形变
this.uniformScale = Math.sqrt(scaleXRatio * scaleYRatio); this.uniformScale = Math.sqrt(scaleXRatio * scaleYRatio);
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2; this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
this.offsetY = this.offsetY = (this.newHeight - this.oldHeight * this.uniformScale) / 2;
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
break; break;
case "fit": case "fit":
// 适应模式:使用较小值,确保所有内容都在画布内,可能有留白 // 适应模式:使用较小值,确保所有内容都在画布内,可能有留白
this.uniformScale = Math.min(scaleXRatio, scaleYRatio); this.uniformScale = Math.min(scaleXRatio, scaleYRatio);
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2; this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
this.offsetY = this.offsetY = (this.newHeight - this.oldHeight * this.uniformScale) / 2;
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
break; break;
case "fill": case "fill":
// 填充模式:使用较大值,填满画布,可能有部分内容被裁切 // 填充模式:使用较大值,填满画布,可能有部分内容被裁切
this.uniformScale = Math.max(scaleXRatio, scaleYRatio); this.uniformScale = Math.max(scaleXRatio, scaleYRatio);
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2; this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
this.offsetY = this.offsetY = (this.newHeight - this.oldHeight * this.uniformScale) / 2;
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
break; break;
case "stretch": case "stretch":
@@ -441,8 +415,7 @@ export class BackgroundSizeWithScaleCommand extends Command {
// 默认使用uniform模式 // 默认使用uniform模式
this.uniformScale = Math.sqrt(scaleXRatio * scaleYRatio); this.uniformScale = Math.sqrt(scaleXRatio * scaleYRatio);
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2; this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
this.offsetY = this.offsetY = (this.newHeight - this.oldHeight * this.uniformScale) / 2;
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
} }
} }
@@ -462,8 +435,7 @@ export class BackgroundSizeWithScaleCommand extends Command {
// 调整背景对象大小和位置 // 调整背景对象大小和位置
if (this.bgLayer && this.backgroundObject) { if (this.bgLayer && this.backgroundObject) {
// 保持原有的背景颜色,如果没有设置则使用白色 // 保持原有的背景颜色,如果没有设置则使用白色
const currentFill = const currentFill = this.backgroundObject.fill || this.bgLayer.backgroundColor || "#ffffff";
this.backgroundObject.fill || this.bgLayer.backgroundColor || "#ffffff";
this.backgroundObject.set({ this.backgroundObject.set({
width: this.newWidth, width: this.newWidth,
@@ -480,12 +452,10 @@ export class BackgroundSizeWithScaleCommand extends Command {
// 计算基于原始画布的缩放比例 // 计算基于原始画布的缩放比例
const baseScaleX = const baseScaleX =
this.newWidth / this.newWidth / this.objectStates[0]?.obj._originalState?.baseCanvasWidth ||
this.objectStates[0]?.obj._originalState?.baseCanvasWidth ||
this.newWidth / this.oldWidth; this.newWidth / this.oldWidth;
const baseScaleY = const baseScaleY =
this.newHeight / this.newHeight / this.objectStates[0]?.obj._originalState?.baseCanvasHeight ||
this.objectStates[0]?.obj._originalState?.baseCanvasHeight ||
this.newHeight / this.oldHeight; this.newHeight / this.oldHeight;
// 根据策略缩放所有非背景对象 // 根据策略缩放所有非背景对象
@@ -505,13 +475,11 @@ export class BackgroundSizeWithScaleCommand extends Command {
const baseUniformScale = Math.sqrt(baseScaleX * baseScaleY); const baseUniformScale = Math.sqrt(baseScaleX * baseScaleY);
const baseOffsetX = const baseOffsetX =
(this.newWidth - (this.newWidth -
(obj._originalState?.baseCanvasWidth || this.oldWidth) * (obj._originalState?.baseCanvasWidth || this.oldWidth) * baseUniformScale) /
baseUniformScale) /
2; 2;
const baseOffsetY = const baseOffsetY =
(this.newHeight - (this.newHeight -
(obj._originalState?.baseCanvasHeight || this.oldHeight) * (obj._originalState?.baseCanvasHeight || this.oldHeight) * baseUniformScale) /
baseUniformScale) /
2; 2;
obj.set({ obj.set({
@@ -537,10 +505,7 @@ export class BackgroundSizeWithScaleCommand extends Command {
this.canvas.setHeight(this.oldHeight); this.canvas.setHeight(this.oldHeight);
// 如果使用 CanvasManager通知它画布大小恢复 // 如果使用 CanvasManager通知它画布大小恢复
if ( if (this.canvasManager && typeof this.canvasManager.updateCanvasSize === "function") {
this.canvasManager &&
typeof this.canvasManager.updateCanvasSize === "function"
) {
this.canvasManager.updateCanvasSize(this.oldWidth, this.oldHeight); this.canvasManager.updateCanvasSize(this.oldWidth, this.oldHeight);
} }
@@ -568,12 +533,9 @@ export class BackgroundSizeWithScaleCommand extends Command {
const baseScaleX = this.oldWidth / originalState.baseCanvasWidth; const baseScaleX = this.oldWidth / originalState.baseCanvasWidth;
const baseScaleY = this.oldHeight / originalState.baseCanvasHeight; const baseScaleY = this.oldHeight / originalState.baseCanvasHeight;
const baseUniformScale = Math.sqrt(baseScaleX * baseScaleY); const baseUniformScale = Math.sqrt(baseScaleX * baseScaleY);
const baseOffsetX = const baseOffsetX = (this.oldWidth - originalState.baseCanvasWidth * baseUniformScale) / 2;
(this.oldWidth - originalState.baseCanvasWidth * baseUniformScale) /
2;
const baseOffsetY = const baseOffsetY =
(this.oldHeight - originalState.baseCanvasHeight * baseUniformScale) / (this.oldHeight - originalState.baseCanvasHeight * baseUniformScale) / 2;
2;
obj.set({ obj.set({
left: originalState.left * baseUniformScale + baseOffsetX, left: originalState.left * baseUniformScale + baseOffsetX,

View File

@@ -26,9 +26,7 @@ export class BrushSizeCommand extends BaseBrushCommand {
super({ super({
...options, ...options,
name: `设置笔刷大小: ${options.size}`, name: `设置笔刷大小: ${options.size}`,
description: `将笔刷大小从 ${options.previousSize || "?"} 设为 ${ description: `将笔刷大小从 ${options.previousSize || "?"} 设为 ${options.size}`,
options.size
}`,
}); });
this.size = options.size; this.size = options.size;
@@ -66,9 +64,7 @@ export class BrushColorCommand extends BaseBrushCommand {
super({ super({
...options, ...options,
name: `设置笔刷颜色: ${options.color}`, name: `设置笔刷颜色: ${options.color}`,
description: `将笔刷颜色从 ${options.previousColor || "?"} 设为 ${ description: `将笔刷颜色从 ${options.previousColor || "?"} 设为 ${options.color}`,
options.color
}`,
}); });
this.color = options.color; this.color = options.color;
@@ -106,14 +102,11 @@ export class BrushOpacityCommand extends BaseBrushCommand {
super({ super({
...options, ...options,
name: `设置笔刷透明度: ${options.opacity}`, name: `设置笔刷透明度: ${options.opacity}`,
description: `将笔刷透明度从 ${options.previousOpacity || "?"} 设为 ${ description: `将笔刷透明度从 ${options.previousOpacity || "?"} 设为 ${options.opacity}`,
options.opacity
}`,
}); });
this.opacity = options.opacity; this.opacity = options.opacity;
this.previousOpacity = this.previousOpacity = options.previousOpacity || this.brushStore.state.opacity;
options.previousOpacity || this.brushStore.state.opacity;
} }
execute() { execute() {
@@ -147,9 +140,7 @@ export class BrushTypeCommand extends BaseBrushCommand {
super({ super({
...options, ...options,
name: `设置笔刷类型: ${options.brushType}`, name: `设置笔刷类型: ${options.brushType}`,
description: `将笔刷类型从 ${options.previousType || "?"} 设为 ${ description: `将笔刷类型从 ${options.previousType || "?"} 设为 ${options.brushType}`,
options.brushType
}`,
}); });
this.brushType = options.brushType; this.brushType = options.brushType;
@@ -192,9 +183,7 @@ export class TextureCommand extends BaseBrushCommand {
super({ super({
...options, ...options,
name: options.enabled ? "启用笔刷材质" : "禁用笔刷材质", name: options.enabled ? "启用笔刷材质" : "禁用笔刷材质",
description: options.enabled description: options.enabled ? `启用材质: ${options.path || "[默认]"}` : "禁用笔刷材质",
? `启用材质: ${options.path || "[默认]"}`
: "禁用笔刷材质",
}); });
this.enabled = options.enabled; this.enabled = options.enabled;
@@ -260,9 +249,7 @@ export class BrushPresetCommand extends BaseBrushCommand {
*/ */
constructor(options = {}) { constructor(options = {}) {
const presetName = const presetName =
typeof options.preset === "object" typeof options.preset === "object" ? options.preset.name : `预设 ${options.preset}`;
? options.preset.name
: `预设 ${options.preset}`;
super({ super({
...options, ...options,
@@ -303,14 +290,10 @@ export class BrushPresetCommand extends BaseBrushCommand {
this.brushStore.applyPreset(this.preset); this.brushStore.applyPreset(this.preset);
} else if (typeof this.preset === "object") { } else if (typeof this.preset === "object") {
// 应用自定义预设对象 // 应用自定义预设对象
if (this.preset.size !== undefined) if (this.preset.size !== undefined) this.brushStore.setBrushSize(this.preset.size);
this.brushStore.setBrushSize(this.preset.size); if (this.preset.color !== undefined) this.brushStore.setBrushColor(this.preset.color);
if (this.preset.color !== undefined) if (this.preset.opacity !== undefined) this.brushStore.setBrushOpacity(this.preset.opacity);
this.brushStore.setBrushColor(this.preset.color); if (this.preset.type !== undefined) this.brushStore.setBrushType(this.preset.type);
if (this.preset.opacity !== undefined)
this.brushStore.setBrushOpacity(this.preset.opacity);
if (this.preset.type !== undefined)
this.brushStore.setBrushType(this.preset.type);
if (this.preset.textureEnabled !== undefined) { if (this.preset.textureEnabled !== undefined) {
this.brushStore.setTextureEnabled(this.preset.textureEnabled); this.brushStore.setTextureEnabled(this.preset.textureEnabled);
@@ -542,8 +525,7 @@ export class TexturePresetCommand extends BaseBrushCommand {
* @param {Object} options.brushManager 笔刷管理器实例 * @param {Object} options.brushManager 笔刷管理器实例
*/ */
constructor(options = {}) { constructor(options = {}) {
const presetName = const presetName = typeof options.preset === "object" ? options.preset.name : options.preset;
typeof options.preset === "object" ? options.preset.name : options.preset;
super({ super({
...options, ...options,
@@ -614,10 +596,7 @@ export class TextureUploadCommand extends BaseBrushCommand {
}); });
this.file = options.file; this.file = options.file;
this.name = this.name = options.name || options.file?.name?.replace(/\.[^/.]+$/, "") || "自定义纹理";
options.name ||
options.file?.name?.replace(/\.[^/.]+$/, "") ||
"自定义纹理";
this.category = options.category || "自定义材质"; this.category = options.category || "自定义材质";
this.texturePresetManager = options.texturePresetManager; this.texturePresetManager = options.texturePresetManager;
this.brushManager = options.brushManager; this.brushManager = options.brushManager;

View File

@@ -24,8 +24,7 @@ export class ClearSelectionContentCommand extends Command {
this.oldLayer = [...this.layers.value]; // 获取原图层对象 this.oldLayer = [...this.layers.value]; // 获取原图层对象
const { layer } = const { layer } = findLayerRecursively(this.layers.value, this.targetLayerId) || {};
findLayerRecursively(this.layers.value, this.targetLayerId) || {};
// 栅格化相关属性 // 栅格化相关属性
this.originalLayerBackup = null; this.originalLayerBackup = null;
@@ -37,17 +36,10 @@ export class ClearSelectionContentCommand extends Command {
// this.originalLayerObject = fabric.util.object.clone(originalLayerObject); // 获取原图层对象 // this.originalLayerObject = fabric.util.object.clone(originalLayerObject); // 获取原图层对象
this.oldLayerObjects = originalLayerObject.toObject([ this.oldLayerObjects = originalLayerObject.toObject(["id", "layerId", "layerName"]); // 获取原图层的所有对象
"id",
"layerId",
"layerName",
]); // 获取原图层的所有对象
this.rasterizedImage = null; this.rasterizedImage = null;
this.targetLayer = findLayerRecursively( this.targetLayer = findLayerRecursively(this.canvas, this.targetLayerId)?.layer;
this.canvas,
this.targetLayerId
)?.layer;
this.layerRasterized = false; this.layerRasterized = false;
@@ -88,8 +80,7 @@ export class ClearSelectionContentCommand extends Command {
} }
// 确定目标图层 // 确定目标图层
const layerId = const layerId = this.targetLayerId || this.layerManager.getActiveLayerId();
this.targetLayerId || this.layerManager.getActiveLayerId();
this.targetLayer = this.layerManager.getLayerById(layerId); this.targetLayer = this.layerManager.getLayerById(layerId);
if (!this.targetLayer) { if (!this.targetLayer) {
console.error("无法清除选区内容:目标图层无效"); console.error("无法清除选区内容:目标图层无效");
@@ -107,9 +98,7 @@ export class ClearSelectionContentCommand extends Command {
} }
// 创建反转选区(保留选区外的内容) // 创建反转选区(保留选区外的内容)
const invertedSelection = await this._createInvertedSelection( const invertedSelection = await this._createInvertedSelection(this.selectionObject);
this.selectionObject
);
if (!invertedSelection) { if (!invertedSelection) {
console.error("创建反转选区失败"); console.error("创建反转选区失败");
return false; return false;
@@ -137,10 +126,7 @@ export class ClearSelectionContentCommand extends Command {
// 恢复原图层状态 // 恢复原图层状态
// 移除栅格化后的图像 // 移除栅格化后的图像
if ( if (this.rasterizedImage && this.canvas.getObjects().includes(this.rasterizedImage)) {
this.rasterizedImage &&
this.canvas.getObjects().includes(this.rasterizedImage)
) {
this.canvas.remove(this.rasterizedImage); this.canvas.remove(this.rasterizedImage);
} }
@@ -199,10 +185,7 @@ export class ClearSelectionContentCommand extends Command {
let scaleFactor = this.baseResolutionScale; let scaleFactor = this.baseResolutionScale;
if (this.highResolutionEnabled) { if (this.highResolutionEnabled) {
const currentZoom = this.canvas.getZoom?.() || 1; const currentZoom = this.canvas.getZoom?.() || 1;
scaleFactor = Math.max( scaleFactor = Math.max(scaleFactor || this.canvas?.getRetinaScaling?.(), currentZoom);
scaleFactor || this.canvas?.getRetinaScaling?.(),
currentZoom
);
scaleFactor = Math.min(scaleFactor, 3); scaleFactor = Math.min(scaleFactor, 3);
} }
@@ -286,9 +269,7 @@ export class ClearSelectionContentCommand extends Command {
layerBounds.left + layerBounds.width layerBounds.left + layerBounds.width
} ${layerBounds.top} L ${layerBounds.left + layerBounds.width} ${ } ${layerBounds.top} L ${layerBounds.left + layerBounds.width} ${
layerBounds.top + layerBounds.height layerBounds.top + layerBounds.height
} L ${layerBounds.left} ${layerBounds.top + layerBounds.height} Z ${ } L ${layerBounds.left} ${layerBounds.top + layerBounds.height} Z ${selectionObject.path}`;
selectionObject.path
}`;
const invertedPath = new fabric.Path(pathString, { const invertedPath = new fabric.Path(pathString, {
fillRule: "evenodd", fillRule: "evenodd",
@@ -329,14 +310,8 @@ export class ClearSelectionContentCommand extends Command {
if (!bounds) { if (!bounds) {
bounds = { ...objBounds }; bounds = { ...objBounds };
} else { } else {
const right = Math.max( const right = Math.max(bounds.left + bounds.width, objBounds.left + objBounds.width);
bounds.left + bounds.width, const bottom = Math.max(bounds.top + bounds.height, objBounds.top + objBounds.height);
objBounds.left + objBounds.width
);
const bottom = Math.max(
bounds.top + bounds.height,
objBounds.top + objBounds.height
);
bounds.left = Math.min(bounds.left, objBounds.left); bounds.left = Math.min(bounds.left, objBounds.left);
bounds.top = Math.min(bounds.top, objBounds.top); bounds.top = Math.min(bounds.top, objBounds.top);
@@ -370,15 +345,10 @@ export class ClearSelectionContentCommand extends Command {
// 递归获取图层及其子图层的所有对象 // 递归获取图层及其子图层的所有对象
const collectLayerObjects = (currentLayer) => { const collectLayerObjects = (currentLayer) => {
// 处理图层的fabricObjects // 处理图层的fabricObjects
if ( if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
currentLayer.fabricObjects &&
Array.isArray(currentLayer.fabricObjects)
) {
currentLayer.fabricObjects.forEach((fabricObj) => { currentLayer.fabricObjects.forEach((fabricObj) => {
if (fabricObj && fabricObj.id) { if (fabricObj && fabricObj.id) {
const realObject = canvasObjects.find( const realObject = canvasObjects.find((canvasObj) => canvasObj.id === fabricObj.id);
(canvasObj) => canvasObj.id === fabricObj.id
);
if (realObject && realObject.visible !== false) { if (realObject && realObject.visible !== false) {
objects.push(realObject); objects.push(realObject);
} }
@@ -420,16 +390,12 @@ export class ClearSelectionContentCommand extends Command {
} }
// 移除栅格化后的图像 // 移除栅格化后的图像
if ( if (this.rasterizedImage && this.canvas.getObjects().includes(this.rasterizedImage)) {
this.rasterizedImage &&
this.canvas.getObjects().includes(this.rasterizedImage)
) {
this.canvas.remove(this.rasterizedImage); this.canvas.remove(this.rasterizedImage);
} }
// 恢复图层的fabricObjects // 恢复图层的fabricObjects
this.targetLayer.fabricObjects = this.targetLayer.fabricObjects = this.originalLayerBackup.fabricObjects || [];
this.originalLayerBackup.fabricObjects || [];
// 重新创建并添加对象到画布 // 重新创建并添加对象到画布
if (this.targetLayer.fabricObjects.length > 0) { if (this.targetLayer.fabricObjects.length > 0) {

View File

@@ -109,10 +109,7 @@ export class CompositeCommand extends Command {
console.log(`✅ 子命令执行成功: ${command.constructor.name}`); console.log(`✅ 子命令执行成功: ${command.constructor.name}`);
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令执行失败: ${command.constructor.name}`, error);
`❌ 子命令执行失败: ${command.constructor.name}`,
error
);
// 执行失败时,撤销已执行的命令 // 执行失败时,撤销已执行的命令
await this._rollbackExecutedCommands(); await this._rollbackExecutedCommands();
@@ -142,9 +139,7 @@ export class CompositeCommand extends Command {
return true; return true;
} }
console.log( console.log(`↩️ 开始撤销复合命令,共 ${this.executedCommands.length} 个子命令`);
`↩️ 开始撤销复合命令,共 ${this.executedCommands.length} 个子命令`
);
try { try {
// 逆序撤销已执行的命令 // 逆序撤销已执行的命令
@@ -164,10 +159,7 @@ export class CompositeCommand extends Command {
results.push(finalResult); results.push(finalResult);
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
`❌ 子命令撤销失败: ${command.constructor.name}`,
error
);
// 撤销失败不中断整个撤销过程,但要记录错误 // 撤销失败不中断整个撤销过程,但要记录错误
} }
} else { } else {
@@ -203,10 +195,7 @@ export class CompositeCommand extends Command {
} }
console.log(`✅ 子命令回滚成功: ${command.constructor.name}`); console.log(`✅ 子命令回滚成功: ${command.constructor.name}`);
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令回滚失败: ${command.constructor.name}`, error);
`❌ 子命令回滚失败: ${command.constructor.name}`,
error
);
// 回滚失败不中断整个回滚过程 // 回滚失败不中断整个回滚过程
} }
} }
@@ -238,9 +227,7 @@ export class CompositeCommand extends Command {
commandCount: this.commands.length, commandCount: this.commands.length,
executedCount: this.executedCommands.length, executedCount: this.executedCommands.length,
isExecuting: this.isExecuting, isExecuting: this.isExecuting,
subCommands: this.commands.map((cmd) => subCommands: this.commands.map((cmd) => (cmd.getInfo ? cmd.getInfo() : cmd.constructor.name)),
cmd.getInfo ? cmd.getInfo() : cmd.constructor.name
),
}; };
} }
} }

View File

@@ -85,9 +85,7 @@ export class CrossLevelMoveCommand extends Command {
this.layers.value.forEach((layer) => { this.layers.value.forEach((layer) => {
if (layer.fabricObject) { if (layer.fabricObject) {
states[layer.id] = { states[layer.id] = {
isInCanvas: this.canvas isInCanvas: this.canvas ? this.canvas.contains(layer.fabricObject) : false,
? this.canvas.contains(layer.fabricObject)
: false,
parentGroupId: null, parentGroupId: null,
visible: layer.fabricObject.visible, visible: layer.fabricObject.visible,
}; };
@@ -127,11 +125,7 @@ export class CrossLevelMoveCommand extends Command {
* 验证移动的有效性 * 验证移动的有效性
*/ */
validateMove() { validateMove() {
const layer = this.findLayer( const layer = this.findLayer(this.layerId, this.fromContainerType, this.fromParentId);
this.layerId,
this.fromContainerType,
this.fromParentId
);
if (!layer) { if (!layer) {
throw new Error(`找不到要移动的图层: ${this.layerId}`); throw new Error(`找不到要移动的图层: ${this.layerId}`);
@@ -153,9 +147,7 @@ export class CrossLevelMoveCommand extends Command {
// 检查目标父图层是否为组 // 检查目标父图层是否为组
if (this.toContainerType === "child") { if (this.toContainerType === "child") {
const targetParent = this.layers.value.find( const targetParent = this.layers.value.find((layer) => layer.id === this.toParentId);
(layer) => layer.id === this.toParentId
);
if (!targetParent || !isGroupLayer(targetParent)) { if (!targetParent || !isGroupLayer(targetParent)) {
throw new Error("目标图层不是组图层"); throw new Error("目标图层不是组图层");
} }
@@ -198,15 +190,9 @@ export class CrossLevelMoveCommand extends Command {
try { try {
// 根据移动类型执行对应操作 // 根据移动类型执行对应操作
if ( if (this.fromContainerType === "root" && this.toContainerType === "child") {
this.fromContainerType === "root" &&
this.toContainerType === "child"
) {
this.moveRootToGroup(draggedLayer); this.moveRootToGroup(draggedLayer);
} else if ( } else if (this.fromContainerType === "child" && this.toContainerType === "root") {
this.fromContainerType === "child" &&
this.toContainerType === "root"
) {
this.moveGroupToRoot(draggedLayer); this.moveGroupToRoot(draggedLayer);
} else if ( } else if (
this.fromContainerType === "child" && this.fromContainerType === "child" &&
@@ -228,6 +214,17 @@ export class CrossLevelMoveCommand extends Command {
await this.layerManager?.updateLayersObjectsInteractivity(); await this.layerManager?.updateLayersObjectsInteractivity();
this.canvas?.renderAll(); this.canvas?.renderAll();
// 生成缩略图
this.fromParentId &&
this.layerManager?.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(
this.fromParentId
);
this.toParentId &&
this.layerManager?.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(
this.toParentId
);
console.log("✅ 跨层级移动命令执行成功"); console.log("✅ 跨层级移动命令执行成功");
return true; return true;
} catch (error) { } catch (error) {
@@ -266,7 +263,6 @@ export class CrossLevelMoveCommand extends Command {
this.restoreFabricObjectStates(this.beforeState.fabricObjects); this.restoreFabricObjectStates(this.beforeState.fabricObjects);
await this.layerManager?.updateLayersObjectsInteractivity(); await this.layerManager?.updateLayersObjectsInteractivity();
this.canvas?.renderAll();
// 刷新画布 // 刷新画布
this.canvas?.renderAll(); this.canvas?.renderAll();
console.log("✅ 跨层级移动命令撤销成功"); console.log("✅ 跨层级移动命令撤销成功");
@@ -299,9 +295,7 @@ export class CrossLevelMoveCommand extends Command {
// 根据图层的parentId重新放置 // 根据图层的parentId重新放置
if (layer.parentId) { if (layer.parentId) {
const parentLayer = this.layers.value.find( const parentLayer = this.layers.value.find((l) => l.id === layer.parentId);
(l) => l.id === layer.parentId
);
if ( if (
parentLayer && parentLayer &&
parentLayer.fabricObject && parentLayer.fabricObject &&
@@ -396,14 +390,9 @@ export class CrossLevelMoveCommand extends Command {
layer.children = layerData.children layer.children = layerData.children
.map((childData) => { .map((childData) => {
// 从原始图层数组中查找子图层 // 从原始图层数组中查找子图层
let childLayer = originalLayers.find( let childLayer = originalLayers.find((l) => l.id === childData.id);
(l) => l.id === childData.id
);
if (!childLayer) { if (!childLayer) {
childLayer = this.findLayerInOriginalStructure( childLayer = this.findLayerInOriginalStructure(childData.id, originalLayers);
childData.id,
originalLayers
);
} }
if (childLayer) { if (childLayer) {
// 更新子图层属性 // 更新子图层属性
@@ -465,9 +454,7 @@ export class CrossLevelMoveCommand extends Command {
// 根据目标状态放置对象 // 根据目标状态放置对象
if (targetState.parentGroupId) { if (targetState.parentGroupId) {
// 应该在特定组中 // 应该在特定组中
const parentLayer = this.layers.value.find( const parentLayer = this.layers.value.find((l) => l.id === targetState.parentGroupId);
(l) => l.id === targetState.parentGroupId
);
if ( if (
parentLayer && parentLayer &&
parentLayer.fabricObject && parentLayer.fabricObject &&
@@ -532,12 +519,7 @@ export class CrossLevelMoveCommand extends Command {
*/ */
safeRemoveFromContainer(fabricObject, container, containerName) { safeRemoveFromContainer(fabricObject, container, containerName) {
return this.safeFabricOperation(() => { return this.safeFabricOperation(() => {
if ( if (container && container.remove && container.contains && container.contains(fabricObject)) {
container &&
container.remove &&
container.contains &&
container.contains(fabricObject)
) {
container.remove(fabricObject); container.remove(fabricObject);
console.log(`✅ 成功从${containerName}移除对象`); console.log(`✅ 成功从${containerName}移除对象`);
return true; return true;
@@ -556,11 +538,7 @@ export class CrossLevelMoveCommand extends Command {
// 从所有组中移除 // 从所有组中移除
this.layers.value.forEach((layer) => { this.layers.value.forEach((layer) => {
if (layer.fabricObject && layer.fabricObject.type === "group") { if (layer.fabricObject && layer.fabricObject.type === "group") {
this.safeRemoveFromContainer( this.safeRemoveFromContainer(fabricObject, layer.fabricObject, `${layer.id}`);
fabricObject,
layer.fabricObject,
`${layer.id}`
);
} }
}); });
} }
@@ -575,9 +553,7 @@ export class CrossLevelMoveCommand extends Command {
newIndex: this.newIndex, newIndex: this.newIndex,
}); });
const targetParent = this.layers.value.find( const targetParent = this.layers.value.find((layer) => layer.id === this.toParentId);
(layer) => layer.id === this.toParentId
);
if (!targetParent) { if (!targetParent) {
throw new Error(`找不到目标父图层: ${this.toParentId}`); throw new Error(`找不到目标父图层: ${this.toParentId}`);
} }
@@ -588,9 +564,7 @@ export class CrossLevelMoveCommand extends Command {
} }
// 从顶级图层数组中移除 // 从顶级图层数组中移除
const rootIndex = this.layers.value.findIndex( const rootIndex = this.layers.value.findIndex((layer) => layer.id === draggedLayer.id);
(layer) => layer.id === draggedLayer.id
);
if (rootIndex !== -1) { if (rootIndex !== -1) {
this.layers.value.splice(rootIndex, 1); this.layers.value.splice(rootIndex, 1);
console.log(`🗑️ 从顶级图层数组中移除图层 ${draggedLayer.id}`); console.log(`🗑️ 从顶级图层数组中移除图层 ${draggedLayer.id}`);
@@ -599,20 +573,14 @@ export class CrossLevelMoveCommand extends Command {
// 更新图层关系 // 更新图层关系
draggedLayer.parentId = this.toParentId; draggedLayer.parentId = this.toParentId;
targetParent.children.splice(this.newIndex, 0, draggedLayer); targetParent.children.splice(this.newIndex, 0, draggedLayer);
console.log( console.log(`📂 将图层 ${draggedLayer.id} 添加到父图层 ${this.toParentId} 的children中`);
`📂 将图层 ${draggedLayer.id} 添加到父图层 ${this.toParentId} 的children中`
);
// 处理fabric对象的层级关系 // 处理fabric对象的层级关系
if (draggedLayer.fabricObject && targetParent.fabricObject) { if (draggedLayer.fabricObject && targetParent.fabricObject) {
console.log(`🎨 处理fabric对象层级关系`); console.log(`🎨 处理fabric对象层级关系`);
// 从画布中移除 // 从画布中移除
this.safeRemoveFromContainer( this.safeRemoveFromContainer(draggedLayer.fabricObject, this.canvas, "画布");
draggedLayer.fabricObject,
this.canvas,
"画布"
);
// 添加到父组 // 添加到父组
this.safeAddToContainer( this.safeAddToContainer(
@@ -635,22 +603,16 @@ export class CrossLevelMoveCommand extends Command {
newIndex: this.newIndex, newIndex: this.newIndex,
}); });
const sourceParent = this.layers.value.find( const sourceParent = this.layers.value.find((layer) => layer.id === this.fromParentId);
(layer) => layer.id === this.fromParentId
);
if (!sourceParent || !sourceParent.children) { if (!sourceParent || !sourceParent.children) {
throw new Error(`找不到源父图层或其children数组: ${this.fromParentId}`); throw new Error(`找不到源父图层或其children数组: ${this.fromParentId}`);
} }
// 从源父图层的children中移除 // 从源父图层的children中移除
const childIndex = sourceParent.children.findIndex( const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
(child) => child.id === draggedLayer.id
);
if (childIndex !== -1) { if (childIndex !== -1) {
sourceParent.children.splice(childIndex, 1); sourceParent.children.splice(childIndex, 1);
console.log( console.log(`🗑️ 从父图层 ${this.fromParentId} 的children中移除图层 ${draggedLayer.id}`);
`🗑️ 从父图层 ${this.fromParentId} 的children中移除图层 ${draggedLayer.id}`
);
} }
// 处理fabric对象的层级关系 // 处理fabric对象的层级关系
@@ -675,9 +637,7 @@ export class CrossLevelMoveCommand extends Command {
// 计算在顶级图层中的插入位置 // 计算在顶级图层中的插入位置
const targetIndex = Math.min(this.newIndex, this.layers.value.length); const targetIndex = Math.min(this.newIndex, this.layers.value.length);
this.layers.value.splice(targetIndex, 0, draggedLayer); this.layers.value.splice(targetIndex, 0, draggedLayer);
console.log( console.log(`📂 将图层 ${draggedLayer.id} 添加到顶级图层数组位置 ${targetIndex}`);
`📂 将图层 ${draggedLayer.id} 添加到顶级图层数组位置 ${targetIndex}`
);
console.log("✅ 组内图层移动到顶级完成"); console.log("✅ 组内图层移动到顶级完成");
} }
@@ -693,26 +653,18 @@ export class CrossLevelMoveCommand extends Command {
newIndex: this.newIndex, newIndex: this.newIndex,
}); });
const sourceParent = this.layers.value.find( const sourceParent = this.layers.value.find((layer) => layer.id === this.fromParentId);
(layer) => layer.id === this.fromParentId const targetParent = this.layers.value.find((layer) => layer.id === this.toParentId);
);
const targetParent = this.layers.value.find(
(layer) => layer.id === this.toParentId
);
if (!sourceParent || !targetParent) { if (!sourceParent || !targetParent) {
throw new Error("找不到源父图层或目标父图层"); throw new Error("找不到源父图层或目标父图层");
} }
// 从源父图层中移除 // 从源父图层中移除
const childIndex = sourceParent.children.findIndex( const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
(child) => child.id === draggedLayer.id
);
if (childIndex !== -1) { if (childIndex !== -1) {
sourceParent.children.splice(childIndex, 1); sourceParent.children.splice(childIndex, 1);
console.log( console.log(`🗑️ 从源父图层 ${this.fromParentId} 中移除图层 ${draggedLayer.id}`);
`🗑️ 从源父图层 ${this.fromParentId} 中移除图层 ${draggedLayer.id}`
);
// 从源父组的fabricObject中移除 // 从源父组的fabricObject中移除
if (draggedLayer.fabricObject && sourceParent.fabricObject) { if (draggedLayer.fabricObject && sourceParent.fabricObject) {
@@ -726,9 +678,7 @@ export class CrossLevelMoveCommand extends Command {
// 更新图层的parentId // 更新图层的parentId
draggedLayer.parentId = this.toParentId; draggedLayer.parentId = this.toParentId;
console.log( console.log(`🔗 更新图层 ${draggedLayer.id} 的parentId为 ${this.toParentId}`);
`🔗 更新图层 ${draggedLayer.id} 的parentId为 ${this.toParentId}`
);
// 确保目标父图层有children数组 // 确保目标父图层有children数组
if (!targetParent.children) { if (!targetParent.children) {

View File

@@ -1,8 +1,4 @@
import { import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
createLayer,
findInChildLayers,
LayerType,
} from "../utils/layerHelper.js";
import { createRasterizedImage } from "../utils/selectionToImage.js"; import { createRasterizedImage } from "../utils/selectionToImage.js";
import { CompositeCommand, Command } from "./Command.js"; import { CompositeCommand, Command } from "./Command.js";
import { CreateImageLayerCommand } from "./LayerCommands.js"; import { CreateImageLayerCommand } from "./LayerCommands.js";
@@ -56,9 +52,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
const selectionObject = this.selectionManager.getSelectionObject(); const selectionObject = this.selectionManager.getSelectionObject();
if (selectionObject) { if (selectionObject) {
try { try {
this._clonedSelectionObject = await this._cloneObject( this._clonedSelectionObject = await this._cloneObject(selectionObject);
selectionObject
);
console.log("套索抠图:选区对象已克隆保存"); console.log("套索抠图:选区对象已克隆保存");
} catch (error) { } catch (error) {
console.error("套索抠图:克隆选区对象失败:", error); console.error("套索抠图:克隆选区对象失败:", error);
@@ -294,10 +288,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
await command.undo(); await command.undo();
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
`❌ 子命令撤销失败: ${command.constructor.name}`,
error
);
// 子命令撤销失败不中断整个撤销过程 // 子命令撤销失败不中断整个撤销过程
} }
} }
@@ -361,16 +352,11 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
// 递归获取图层及其子图层的所有对象 // 递归获取图层及其子图层的所有对象
const collectLayerObjects = (currentLayer) => { const collectLayerObjects = (currentLayer) => {
// 处理图层的fabricObjects // 处理图层的fabricObjects
if ( if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
currentLayer.fabricObjects &&
Array.isArray(currentLayer.fabricObjects)
) {
currentLayer.fabricObjects.forEach((fabricRef) => { currentLayer.fabricObjects.forEach((fabricRef) => {
if (fabricRef && fabricRef.id) { if (fabricRef && fabricRef.id) {
// 从画布中查找真实的对象 // 从画布中查找真实的对象
const realObject = canvasObjects.find( const realObject = canvasObjects.find((obj) => obj.id === fabricRef.id);
(obj) => obj.id === fabricRef.id
);
if (realObject && realObject.visible) { if (realObject && realObject.visible) {
objects.push(realObject); objects.push(realObject);
} }
@@ -380,9 +366,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
// 处理单个fabricObject背景图层等 // 处理单个fabricObject背景图层等
if (currentLayer.fabricObject && currentLayer.fabricObject.id) { if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
const realObject = canvasObjects.find( const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
(obj) => obj.id === currentLayer.fabricObject.id
);
if (realObject && realObject.visible) { if (realObject && realObject.visible) {
objects.push(realObject); objects.push(realObject);
} }
@@ -413,11 +397,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
* @returns {String} 抠图结果的DataURL * @returns {String} 抠图结果的DataURL
* @private * @private
*/ */
async _performCutoutWithRasterized( async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
sourceObjects,
selectionObject,
selectionBounds
) {
try { try {
console.log("=== 开始使用createRasterizedImage执行抠图 ==="); console.log("=== 开始使用createRasterizedImage执行抠图 ===");
console.log(`源对象数量: ${sourceObjects.length}`); console.log(`源对象数量: ${sourceObjects.length}`);
@@ -456,10 +436,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
// 如果createRasterizedImage失败回退到原始方法 // 如果createRasterizedImage失败回退到原始方法
console.log("⚠️ 回退到原始抠图方法..."); console.log("⚠️ 回退到原始抠图方法...");
return await this._performCutout( return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
{ fabricObjects: sourceObjects },
selectionObject
);
} }
} }
@@ -490,9 +467,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
try { try {
// 收集源图层中的可见对象 // 收集源图层中的可见对象
const visibleObjects = sourceLayer.fabricObjects.filter( const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
(obj) => obj.visible
);
if (visibleObjects.length === 0) { if (visibleObjects.length === 0) {
throw new Error("源图层没有可见对象"); throw new Error("源图层没有可见对象");
@@ -545,10 +520,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
let highResolutionScale = 1; let highResolutionScale = 1;
if (this.highResolutionEnabled) { if (this.highResolutionEnabled) {
const devicePixelRatio = window.devicePixelRatio || 1; const devicePixelRatio = window.devicePixelRatio || 1;
highResolutionScale = Math.max( highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
this.baseResolutionScale,
devicePixelRatio * 2
);
} }
// 创建用于导出的高分辨率canvas // 创建用于导出的高分辨率canvas
@@ -559,12 +531,8 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
colorSpace: "srgb", colorSpace: "srgb",
}); });
const actualWidth = Math.round( const actualWidth = Math.round(renderBounds.width * highResolutionScale);
renderBounds.width * highResolutionScale const actualHeight = Math.round(renderBounds.height * highResolutionScale);
);
const actualHeight = Math.round(
renderBounds.height * highResolutionScale
);
exportCanvas.width = actualWidth; exportCanvas.width = actualWidth;
exportCanvas.height = actualHeight; exportCanvas.height = actualHeight;
@@ -773,17 +741,14 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
} }
}); });
} else if (objectType === "polygon") { } else if (objectType === "polygon") {
fabric.Polygon.fromObject( fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
this.serializedSelectionObject, if (polygon) {
(polygon) => { console.log("多边形选区对象反序列化成功");
if (polygon) { resolve(polygon);
console.log("多边形选区对象反序列化成功"); } else {
resolve(polygon); reject(new Error("多边形选区对象反序列化失败"));
} else {
reject(new Error("多边形选区对象反序列化失败"));
}
} }
); });
} else if (objectType === "rect") { } else if (objectType === "rect") {
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => { fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
if (rect) { if (rect) {
@@ -794,30 +759,24 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
} }
}); });
} else if (objectType === "ellipse" || objectType === "circle") { } else if (objectType === "ellipse" || objectType === "circle") {
fabric.Ellipse.fromObject( fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
this.serializedSelectionObject, if (ellipse) {
(ellipse) => { console.log("椭圆选区对象反序列化成功");
if (ellipse) { resolve(ellipse);
console.log("椭圆选区对象反序列化成功"); } else {
resolve(ellipse); reject(new Error("椭圆选区对象反序列化失败"));
} else {
reject(new Error("椭圆选区对象反序列化失败"));
}
} }
); });
} else { } else {
// 通用对象反序列化 // 通用对象反序列化
fabric.util.enlivenObjects( fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
[this.serializedSelectionObject], if (objects && objects.length > 0) {
(objects) => { console.log("通用选区对象反序列化成功");
if (objects && objects.length > 0) { resolve(objects[0]);
console.log("通用选区对象反序列化成功"); } else {
resolve(objects[0]); reject(new Error("通用选区对象反序列化失败"));
} else {
reject(new Error("通用选区对象反序列化失败"));
}
} }
); });
} }
}); });
} catch (error) { } catch (error) {

View File

@@ -1,8 +1,4 @@
import { import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
createLayer,
findInChildLayers,
LayerType,
} from "../utils/layerHelper.js";
import { createRasterizedImage } from "../utils/selectionToImage.js"; import { createRasterizedImage } from "../utils/selectionToImage.js";
import { CompositeCommand, Command } from "./Command.js"; import { CompositeCommand, Command } from "./Command.js";
import { CreateImageLayerCommand } from "./LayerCommands.js"; import { CreateImageLayerCommand } from "./LayerCommands.js";
@@ -59,9 +55,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
const selectionObject = this.selectionManager.getSelectionObject(); const selectionObject = this.selectionManager.getSelectionObject();
if (selectionObject) { if (selectionObject) {
try { try {
this._clonedSelectionObject = await this._cloneObject( this._clonedSelectionObject = await this._cloneObject(selectionObject);
selectionObject
);
console.log("剪切选区:选区对象已克隆保存"); console.log("剪切选区:选区对象已克隆保存");
} catch (error) { } catch (error) {
console.error("剪切选区:克隆选区对象失败:", error); console.error("剪切选区:克隆选区对象失败:", error);
@@ -132,11 +126,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
} }
// 步骤2: 对原图层进行栅格化处理,移除选区内容 // 步骤2: 对原图层进行栅格化处理,移除选区内容
await this._rasterizeOriginalLayerWithCutout( await this._rasterizeOriginalLayerWithCutout(sourceLayer, sourceObjects, selectionObject);
sourceLayer,
sourceObjects,
selectionObject
);
// 步骤3: 创建图像图层命令 // 步骤3: 创建图像图层命令
const createImageLayerCmd = new CreateImageLayerCommand({ const createImageLayerCmd = new CreateImageLayerCommand({
@@ -246,10 +236,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
} }
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
`❌ 子命令撤销失败: ${command.constructor.name}`,
error
);
// 子命令撤销失败不中断整个撤销过程 // 子命令撤销失败不中断整个撤销过程
} }
} }
@@ -313,17 +300,14 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
await this._restoreObjectsFromBackup(sourceLayer); await this._restoreObjectsFromBackup(sourceLayer);
} else { } else {
// 备用方案恢复原始的fabricObjects数组 // 备用方案恢复原始的fabricObjects数组
sourceLayer.fabricObjects = sourceLayer.fabricObjects = this.originalLayerBackup.fabricObjects || [];
this.originalLayerBackup.fabricObjects || [];
if (sourceLayer.fabricObjects.length > 0) { if (sourceLayer.fabricObjects.length > 0) {
await this._restoreLayerObjects(sourceLayer); await this._restoreLayerObjects(sourceLayer);
} }
} }
console.log( console.log(`✅ 原图层状态恢复完成,恢复了 ${sourceLayer.fabricObjects.length} 个对象`);
`✅ 原图层状态恢复完成,恢复了 ${sourceLayer.fabricObjects.length} 个对象`
);
} catch (error) { } catch (error) {
console.error("恢复原图层状态失败:", error); console.error("恢复原图层状态失败:", error);
throw error; throw error;
@@ -337,78 +321,68 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
*/ */
async _restoreObjectsFromBackup(layer) { async _restoreObjectsFromBackup(layer) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if ( if (!this.originalLayerObjectsData || this.originalLayerObjectsData.length === 0) {
!this.originalLayerObjectsData ||
this.originalLayerObjectsData.length === 0
) {
console.warn("没有对象备份数据"); console.warn("没有对象备份数据");
resolve(); resolve();
return; return;
} }
try { try {
console.log( console.log(`开始从备份恢复 ${this.originalLayerObjectsData.length} 个对象...`);
`开始从备份恢复 ${this.originalLayerObjectsData.length} 个对象...`
);
// 使用fabric.util.enlivenObjects重建对象 // 使用fabric.util.enlivenObjects重建对象
fabric.util.enlivenObjects( fabric.util.enlivenObjects(this.originalLayerObjectsData, (restoredObjects) => {
this.originalLayerObjectsData, try {
(restoredObjects) => { let successCount = 0;
try {
let successCount = 0;
restoredObjects.forEach((obj, index) => { restoredObjects.forEach((obj, index) => {
if (obj) { if (obj) {
// 确保对象有正确的属性 // 确保对象有正确的属性
obj.set({ obj.set({
layerId: layer.id, layerId: layer.id,
layerName: layer.name, layerName: layer.name,
selectable: true, selectable: true,
evented: true, evented: true,
visible: true, visible: true,
}); });
// 添加到画布 // 添加到画布
this.canvas.add(obj); this.canvas.add(obj);
// 添加到图层的fabricObjects数组 // 添加到图层的fabricObjects数组
layer.fabricObjects.push( layer.fabricObjects.push(
obj.toObject([ obj.toObject([
"id", "id",
"layerId", "layerId",
"layerName", "layerName",
"parentId", "parentId",
"selectable", "selectable",
"evented", "evented",
"visible", "visible",
]) ])
); );
successCount++; successCount++;
console.log( console.log(
`恢复对象 ${index + 1}/${restoredObjects.length}: ${ `恢复对象 ${index + 1}/${restoredObjects.length}: ${obj.id || obj.type}`
obj.id || obj.type );
}` } else {
); console.warn(`对象 ${index + 1} 恢复失败`);
} else { }
console.warn(`对象 ${index + 1} 恢复失败`); });
}
});
// 重新渲染画布 // 重新渲染画布
this.canvas.renderAll(); this.canvas.renderAll();
console.log( console.log(
`✅ 成功恢复了 ${successCount}/${this.originalLayerObjectsData.length} 个对象` `✅ 成功恢复了 ${successCount}/${this.originalLayerObjectsData.length} 个对象`
); );
resolve(); resolve();
} catch (error) { } catch (error) {
console.error("处理恢复的对象时出错:", error); console.error("处理恢复的对象时出错:", error);
reject(error); reject(error);
}
} }
); });
} catch (error) { } catch (error) {
console.error("恢复对象时出错:", error); console.error("恢复对象时出错:", error);
reject(error); reject(error);
@@ -474,11 +448,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
* @returns {fabric.Image} 抠图结果的fabric图像对象 * @returns {fabric.Image} 抠图结果的fabric图像对象
* @private * @private
*/ */
async _performCutoutWithRasterized( async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
sourceObjects,
selectionObject,
selectionBounds
) {
try { try {
console.log("=== 开始使用createRasterizedImage执行剪切抠图 ==="); console.log("=== 开始使用createRasterizedImage执行剪切抠图 ===");
console.log(`源对象数量: ${sourceObjects.length}`); console.log(`源对象数量: ${sourceObjects.length}`);
@@ -488,10 +458,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
let scaleFactor = this.baseResolutionScale; let scaleFactor = this.baseResolutionScale;
if (this.highResolutionEnabled) { if (this.highResolutionEnabled) {
const currentZoom = this.canvas.getZoom?.() || 1; const currentZoom = this.canvas.getZoom?.() || 1;
scaleFactor = Math.max( scaleFactor = Math.max(scaleFactor || this.canvas?.getRetinaScaling?.(), currentZoom);
scaleFactor || this.canvas?.getRetinaScaling?.(),
currentZoom
);
scaleFactor = Math.min(scaleFactor, 3); scaleFactor = Math.min(scaleFactor, 3);
} }
@@ -535,15 +502,10 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
// 递归获取图层及其子图层的所有对象 // 递归获取图层及其子图层的所有对象
const collectLayerObjects = (currentLayer) => { const collectLayerObjects = (currentLayer) => {
// 处理图层的fabricObjects // 处理图层的fabricObjects
if ( if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
currentLayer.fabricObjects &&
Array.isArray(currentLayer.fabricObjects)
) {
currentLayer.fabricObjects.forEach((fabricObj) => { currentLayer.fabricObjects.forEach((fabricObj) => {
if (fabricObj && fabricObj.id) { if (fabricObj && fabricObj.id) {
const realObject = canvasObjects.find( const realObject = canvasObjects.find((canvasObj) => canvasObj.id === fabricObj.id);
(canvasObj) => canvasObj.id === fabricObj.id
);
if (realObject && realObject.visible !== false) { if (realObject && realObject.visible !== false) {
objects.push(realObject); objects.push(realObject);
} }
@@ -649,17 +611,14 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
} }
}); });
} else if (objectType === "polygon") { } else if (objectType === "polygon") {
fabric.Polygon.fromObject( fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
this.serializedSelectionObject, if (polygon) {
(polygon) => { console.log("多边形选区对象反序列化成功");
if (polygon) { resolve(polygon);
console.log("多边形选区对象反序列化成功"); } else {
resolve(polygon); reject(new Error("多边形选区对象反序列化失败"));
} else {
reject(new Error("多边形选区对象反序列化失败"));
}
} }
); });
} else if (objectType === "rect") { } else if (objectType === "rect") {
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => { fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
if (rect) { if (rect) {
@@ -670,30 +629,24 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
} }
}); });
} else if (objectType === "ellipse" || objectType === "circle") { } else if (objectType === "ellipse" || objectType === "circle") {
fabric.Ellipse.fromObject( fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
this.serializedSelectionObject, if (ellipse) {
(ellipse) => { console.log("椭圆选区对象反序列化成功");
if (ellipse) { resolve(ellipse);
console.log("椭圆选区对象反序列化成功"); } else {
resolve(ellipse); reject(new Error("椭圆选区对象反序列化失败"));
} else {
reject(new Error("椭圆选区对象反序列化失败"));
}
} }
); });
} else { } else {
// 通用对象反序列化 // 通用对象反序列化
fabric.util.enlivenObjects( fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
[this.serializedSelectionObject], if (objects && objects.length > 0) {
(objects) => { console.log("通用选区对象反序列化成功");
if (objects && objects.length > 0) { resolve(objects[0]);
console.log("通用选区对象反序列化成功"); } else {
resolve(objects[0]); reject(new Error("通用选区对象反序列化失败"));
} else {
reject(new Error("通用选区对象反序列化失败"));
}
} }
); });
} }
}); });
} catch (error) { } catch (error) {
@@ -761,9 +714,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
this.originalLayerRasterized = false; // 标记原图层是否已栅格化 this.originalLayerRasterized = false; // 标记原图层是否已栅格化
console.log( console.log(`✅ 原图层状态已备份,对象数量: ${this.originalLayerObjectsData.length}`);
`✅ 原图层状态已备份,对象数量: ${this.originalLayerObjectsData.length}`
);
} catch (error) { } catch (error) {
console.error("备份原图层状态失败:", error); console.error("备份原图层状态失败:", error);
throw error; throw error;
@@ -777,11 +728,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
* @param {Object} selectionObject 选区对象 * @param {Object} selectionObject 选区对象
* @private * @private
*/ */
async _rasterizeOriginalLayerWithCutout( async _rasterizeOriginalLayerWithCutout(sourceLayer, sourceObjects, selectionObject) {
sourceLayer,
sourceObjects,
selectionObject
) {
try { try {
console.log("=== 开始对原图层进行栅格化处理,移除选区内容 ==="); console.log("=== 开始对原图层进行栅格化处理,移除选区内容 ===");

View File

@@ -92,9 +92,6 @@ export class EraserCommand extends Command {
if (!this.layerManager) return; if (!this.layerManager) return;
const canvasObjects = this.canvas.getObjects(); const canvasObjects = this.canvas.getObjects();
restoreObjectLayerAssociations( restoreObjectLayerAssociations(this.layerManager?.layers?.value, canvasObjects);
this.layerManager?.layers?.value,
canvasObjects
);
} }
} }

View File

@@ -1,10 +1,5 @@
import { generateId } from "../utils/helper"; import { generateId } from "../utils/helper";
import { import { createLayer, findLayerRecursively, LayerType, OperationType } from "../utils/layerHelper";
createLayer,
findLayerRecursively,
LayerType,
OperationType,
} from "../utils/layerHelper";
import { Command } from "./Command"; import { Command } from "./Command";
import { import {
findObjectById, findObjectById,
@@ -33,14 +28,12 @@ export class MergeGroupLayerCommand extends Command {
this.originalObjects = [...this.canvas.getObjects()]; this.originalObjects = [...this.canvas.getObjects()];
this.flattenedLayer = null; this.flattenedLayer = null;
this.flattenedLayerId = this.flattenedLayerId =
generateId("flattened_") || generateId("flattened_") || `flattened_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
`flattened_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
this.existingGroupId = null; // 用于查找现有组对象 this.existingGroupId = null; // 用于查找现有组对象
// 组对象相关 // 组对象相关
this.newGroupId = this.newGroupId =
generateId("group") || generateId("group") || `group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
`group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
} }
async execute() { async execute() {
@@ -172,15 +165,11 @@ export class MergeGroupLayerCommand extends Command {
}); });
// 获取源图层在数组中的索引 // 获取源图层在数组中的索引
const sourceIndex = this.layers.value.findIndex( const sourceIndex = this.layers.value.findIndex((l) => l.id === this.layerId);
(l) => l.id === this.layerId
);
// 移除所有相关图层 // 移除所有相关图层
const layerIdsToRemove = layersToFlatten.map((layer) => layer.id); const layerIdsToRemove = layersToFlatten.map((layer) => layer.id);
this.layers.value = this.layers.value.filter( this.layers.value = this.layers.value.filter((layer) => !layerIdsToRemove.includes(layer.id));
(layer) => !layerIdsToRemove.includes(layer.id)
);
// 在原位置插入展平后的图层 // 在原位置插入展平后的图层
this.layers.value.splice(sourceIndex, 0, this.flattenedLayer); this.layers.value.splice(sourceIndex, 0, this.flattenedLayer);
@@ -202,11 +191,7 @@ export class MergeGroupLayerCommand extends Command {
} }
async undo() { async undo() {
if ( if (!this.flattenedLayer || !this.originalLayers || !this.originalObjectStates) {
!this.flattenedLayer ||
!this.originalLayers ||
!this.originalObjectStates
) {
console.warn("没有可撤销的数据"); console.warn("没有可撤销的数据");
return; return;
} }
@@ -324,10 +309,7 @@ export class MergeGroupLayerCommand extends Command {
groupObjects.forEach((obj) => { groupObjects.forEach((obj) => {
try { try {
// 计算对象的绝对位置 // 计算对象的绝对位置
const absolutePosition = this._calculateObjectAbsolutePosition( const absolutePosition = this._calculateObjectAbsolutePosition(obj, groupObject);
obj,
groupObject
);
// 使用removeWithUpdate移除对象这会自动恢复对象的独立状态 // 使用removeWithUpdate移除对象这会自动恢复对象的独立状态
groupObject.removeWithUpdate(obj); groupObject.removeWithUpdate(obj);
@@ -382,10 +364,7 @@ export class MergeGroupLayerCommand extends Command {
const objectPoint = new fabric.Point(obj.left || 0, obj.top || 0); const objectPoint = new fabric.Point(obj.left || 0, obj.top || 0);
// 应用组的变换矩阵计算绝对位置 // 应用组的变换矩阵计算绝对位置
const absolutePoint = fabric.util.transformPoint( const absolutePoint = fabric.util.transformPoint(objectPoint, groupTransform);
objectPoint,
groupTransform
);
// 计算缩放比例 // 计算缩放比例
const totalScaleX = (group.scaleX || 1) * (obj.scaleX || 1); const totalScaleX = (group.scaleX || 1) * (obj.scaleX || 1);
@@ -483,9 +462,7 @@ export class MergeGroupLayerCommand extends Command {
canvasObj.layerId = layer.id; canvasObj.layerId = layer.id;
canvasObj.layerName = layer.name; canvasObj.layerName = layer.name;
console.log( console.log(`🔗 恢复对象 ${canvasObj.id} 与图层 ${layer.name} 的关联`);
`🔗 恢复对象 ${canvasObj.id} 与图层 ${layer.name} 的关联`
);
} }
} }
}); });

View File

@@ -1,14 +1,7 @@
import { import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
createLayer,
findInChildLayers,
LayerType,
} from "../utils/layerHelper.js";
import { createRasterizedImage } from "../utils/selectionToImage.js"; import { createRasterizedImage } from "../utils/selectionToImage.js";
import { CompositeCommand, Command } from "./Command.js"; import { CompositeCommand, Command } from "./Command.js";
import { import { CreateImageLayerCommand, RemoveLayerCommand } from "./LayerCommands.js";
CreateImageLayerCommand,
RemoveLayerCommand,
} from "./LayerCommands.js";
import { fabric } from "fabric-with-all"; import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper.js"; import { generateId } from "../utils/helper.js";
@@ -65,9 +58,7 @@ export class LassoCutoutCommand extends CompositeCommand {
const selectionObject = this.selectionManager.getSelectionObject(); const selectionObject = this.selectionManager.getSelectionObject();
if (selectionObject) { if (selectionObject) {
try { try {
this._clonedSelectionObject = await this._cloneObject( this._clonedSelectionObject = await this._cloneObject(selectionObject);
selectionObject
);
console.log("套索抠图:选区对象已克隆保存"); console.log("套索抠图:选区对象已克隆保存");
} catch (error) { } catch (error) {
console.error("套索抠图:克隆选区对象失败:", error); console.error("套索抠图:克隆选区对象失败:", error);
@@ -103,14 +94,7 @@ export class LassoCutoutCommand extends CompositeCommand {
const sourceObjects = this._getLayerObjects(activeLayer); const sourceObjects = this._getLayerObjects(activeLayer);
this.originalCanvasObjects = sourceObjects; // 保存真实对象引用 this.originalCanvasObjects = sourceObjects; // 保存真实对象引用
this.originalFabricObjects = sourceObjects.map((obj) => this.originalFabricObjects = sourceObjects.map((obj) =>
obj.toObject([ obj.toObject(["id", "layerId", "layerName", "parentId", "type", "custom"])
"id",
"layerId",
"layerName",
"parentId",
"type",
"custom",
])
); );
console.log( console.log(
@@ -353,10 +337,7 @@ export class LassoCutoutCommand extends CompositeCommand {
await command.undo(); await command.undo();
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
`❌ 子命令撤销失败: ${command.constructor.name}`,
error
);
// 子命令撤销失败不中断整个撤销过程 // 子命令撤销失败不中断整个撤销过程
} }
} }
@@ -433,16 +414,11 @@ export class LassoCutoutCommand extends CompositeCommand {
// 递归获取图层及其子图层的所有对象 // 递归获取图层及其子图层的所有对象
const collectLayerObjects = (currentLayer) => { const collectLayerObjects = (currentLayer) => {
// 处理图层的fabricObjects // 处理图层的fabricObjects
if ( if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
currentLayer.fabricObjects &&
Array.isArray(currentLayer.fabricObjects)
) {
currentLayer.fabricObjects.forEach((fabricRef) => { currentLayer.fabricObjects.forEach((fabricRef) => {
if (fabricRef && fabricRef.id) { if (fabricRef && fabricRef.id) {
// 从画布中查找真实的对象 // 从画布中查找真实的对象
const realObject = canvasObjects.find( const realObject = canvasObjects.find((obj) => obj.id === fabricRef.id);
(obj) => obj.id === fabricRef.id
);
if (realObject && realObject.visible) { if (realObject && realObject.visible) {
objects.push(realObject); objects.push(realObject);
} }
@@ -452,9 +428,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 处理单个fabricObject背景图层等 // 处理单个fabricObject背景图层等
if (currentLayer.fabricObject && currentLayer.fabricObject.id) { if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
const realObject = canvasObjects.find( const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
(obj) => obj.id === currentLayer.fabricObject.id
);
if (realObject && realObject.visible) { if (realObject && realObject.visible) {
objects.push(realObject); objects.push(realObject);
} }
@@ -485,11 +459,7 @@ export class LassoCutoutCommand extends CompositeCommand {
* @returns {String} 抠图结果的DataURL * @returns {String} 抠图结果的DataURL
* @private * @private
*/ */
async _performCutoutWithRasterized( async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
sourceObjects,
selectionObject,
selectionBounds
) {
try { try {
console.log("=== 开始使用createRasterizedImage执行抠图 ==="); console.log("=== 开始使用createRasterizedImage执行抠图 ===");
console.log(`源对象数量: ${sourceObjects.length}`); console.log(`源对象数量: ${sourceObjects.length}`);
@@ -528,10 +498,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 如果createRasterizedImage失败回退到原始方法 // 如果createRasterizedImage失败回退到原始方法
console.log("⚠️ 回退到原始抠图方法..."); console.log("⚠️ 回退到原始抠图方法...");
return await this._performCutout( return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
{ fabricObjects: sourceObjects },
selectionObject
);
} }
} }
@@ -562,9 +529,7 @@ export class LassoCutoutCommand extends CompositeCommand {
try { try {
// 收集源图层中的可见对象 // 收集源图层中的可见对象
const visibleObjects = sourceLayer.fabricObjects.filter( const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
(obj) => obj.visible
);
if (visibleObjects.length === 0) { if (visibleObjects.length === 0) {
throw new Error("源图层没有可见对象"); throw new Error("源图层没有可见对象");
@@ -617,10 +582,7 @@ export class LassoCutoutCommand extends CompositeCommand {
let highResolutionScale = 1; let highResolutionScale = 1;
if (this.highResolutionEnabled) { if (this.highResolutionEnabled) {
const devicePixelRatio = window.devicePixelRatio || 1; const devicePixelRatio = window.devicePixelRatio || 1;
highResolutionScale = Math.max( highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
this.baseResolutionScale,
devicePixelRatio * 2
);
} }
// 创建用于导出的高分辨率canvas // 创建用于导出的高分辨率canvas
@@ -631,12 +593,8 @@ export class LassoCutoutCommand extends CompositeCommand {
colorSpace: "srgb", colorSpace: "srgb",
}); });
const actualWidth = Math.round( const actualWidth = Math.round(renderBounds.width * highResolutionScale);
renderBounds.width * highResolutionScale const actualHeight = Math.round(renderBounds.height * highResolutionScale);
);
const actualHeight = Math.round(
renderBounds.height * highResolutionScale
);
exportCanvas.width = actualWidth; exportCanvas.width = actualWidth;
exportCanvas.height = actualHeight; exportCanvas.height = actualHeight;
@@ -845,17 +803,14 @@ export class LassoCutoutCommand extends CompositeCommand {
} }
}); });
} else if (objectType === "polygon") { } else if (objectType === "polygon") {
fabric.Polygon.fromObject( fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
this.serializedSelectionObject, if (polygon) {
(polygon) => { console.log("多边形选区对象反序列化成功");
if (polygon) { resolve(polygon);
console.log("多边形选区对象反序列化成功"); } else {
resolve(polygon); reject(new Error("多边形选区对象反序列化失败"));
} else {
reject(new Error("多边形选区对象反序列化失败"));
}
} }
); });
} else if (objectType === "rect") { } else if (objectType === "rect") {
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => { fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
if (rect) { if (rect) {
@@ -866,30 +821,24 @@ export class LassoCutoutCommand extends CompositeCommand {
} }
}); });
} else if (objectType === "ellipse" || objectType === "circle") { } else if (objectType === "ellipse" || objectType === "circle") {
fabric.Ellipse.fromObject( fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
this.serializedSelectionObject, if (ellipse) {
(ellipse) => { console.log("椭圆选区对象反序列化成功");
if (ellipse) { resolve(ellipse);
console.log("椭圆选区对象反序列化成功"); } else {
resolve(ellipse); reject(new Error("椭圆选区对象反序列化失败"));
} else {
reject(new Error("椭圆选区对象反序列化失败"));
}
} }
); });
} else { } else {
// 通用对象反序列化 // 通用对象反序列化
fabric.util.enlivenObjects( fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
[this.serializedSelectionObject], if (objects && objects.length > 0) {
(objects) => { console.log("通用选区对象反序列化成功");
if (objects && objects.length > 0) { resolve(objects[0]);
console.log("通用选区对象反序列化成功"); } else {
resolve(objects[0]); reject(new Error("通用选区对象反序列化失败"));
} else {
reject(new Error("通用选区对象反序列化失败"));
}
} }
); });
} }
}); });
} catch (error) { } catch (error) {
@@ -913,11 +862,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 1. 恢复图层到原位置 // 1. 恢复图层到原位置
if (this.originalLayerIndex !== -1) { if (this.originalLayerIndex !== -1) {
this.layerManager.layers.value.splice( this.layerManager.layers.value.splice(this.originalLayerIndex, 0, this.originalLayer);
this.originalLayerIndex,
0,
this.originalLayer
);
} else { } else {
// 如果没有保存索引,添加到末尾 // 如果没有保存索引,添加到末尾
this.layerManager.layers.value.push(this.originalLayer); this.layerManager.layers.value.push(this.originalLayer);
@@ -925,9 +870,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 2. 恢复fabric对象到画布 // 2. 恢复fabric对象到画布
if (this.originalFabricObjects.length > 0) { if (this.originalFabricObjects.length > 0) {
console.log( console.log(`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`);
`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`
);
// 使用fabric.util.enlivenObjects批量反序列化对象 // 使用fabric.util.enlivenObjects批量反序列化对象
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
@@ -1076,12 +1019,7 @@ export class ClearSelectionCommand extends Command {
// 序列化选区对象和相关状态 // 序列化选区对象和相关状态
this.originalSelectionState = { this.originalSelectionState = {
// 选区对象的序列化数据 // 选区对象的序列化数据
selectionObjectData: selectionObject.toObject([ selectionObjectData: selectionObject.toObject(["id", "layerId", "layerName", "parentId"]),
"id",
"layerId",
"layerName",
"parentId",
]),
// 选区路径数据 // 选区路径数据
selectionPath: this.selectionManager.getSelectionPath(), selectionPath: this.selectionManager.getSelectionPath(),
// 羽化值 // 羽化值
@@ -1144,13 +1082,8 @@ export class ClearSelectionCommand extends Command {
return; return;
} }
const { const { selectionObjectData, featherAmount, selectionStyle, position, managerState } =
selectionObjectData, this.originalSelectionState;
featherAmount,
selectionStyle,
position,
managerState,
} = this.originalSelectionState;
// 根据选区对象类型进行反序列化 // 根据选区对象类型进行反序列化
const objectType = selectionObjectData.type; const objectType = selectionObjectData.type;
@@ -1196,9 +1129,7 @@ export class ClearSelectionCommand extends Command {
// 恢复阴影(羽化效果) // 恢复阴影(羽化效果)
if (selectionStyle.shadow) { if (selectionStyle.shadow) {
restoredObject.shadow = new fabric.Shadow( restoredObject.shadow = new fabric.Shadow(selectionStyle.shadow);
selectionStyle.shadow
);
} }
} }
@@ -1283,9 +1214,7 @@ export class ClearSelectionCommand extends Command {
// 验证基本属性 // 验证基本属性
const originalId = this.originalSelectionState.selectionId; const originalId = this.originalSelectionState.selectionId;
if (currentSelection.id !== originalId) { if (currentSelection.id !== originalId) {
console.warn( console.warn(`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`);
`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`
);
return false; return false;
} }
@@ -1293,9 +1222,7 @@ export class ClearSelectionCommand extends Command {
const currentFeather = this.selectionManager.getFeatherAmount(); const currentFeather = this.selectionManager.getFeatherAmount();
const originalFeather = this.originalSelectionState.featherAmount; const originalFeather = this.originalSelectionState.featherAmount;
if (currentFeather !== originalFeather) { if (currentFeather !== originalFeather) {
console.warn( console.warn(`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`);
`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`
);
return false; return false;
} }

View File

@@ -1,15 +1,7 @@
import { import { createLayer, findInChildLayers, LayerType, OperationType } from "../utils/layerHelper.js";
createLayer,
findInChildLayers,
LayerType,
OperationType,
} from "../utils/layerHelper.js";
import { createRasterizedImage } from "../utils/selectionToImage.js"; import { createRasterizedImage } from "../utils/selectionToImage.js";
import { CompositeCommand, Command } from "./Command.js"; import { CompositeCommand, Command } from "./Command.js";
import { import { CreateImageLayerCommand, RemoveLayerCommand } from "./LayerCommands.js";
CreateImageLayerCommand,
RemoveLayerCommand,
} from "./LayerCommands.js";
import { fabric } from "fabric-with-all"; import { fabric } from "fabric-with-all";
import { generateId } from "../utils/helper.js"; import { generateId } from "../utils/helper.js";
import { ToolCommand } from "./ToolCommands.js"; import { ToolCommand } from "./ToolCommands.js";
@@ -69,9 +61,7 @@ export class LassoCutoutCommand extends CompositeCommand {
const selectionObject = this.selectionManager.getSelectionObject(); const selectionObject = this.selectionManager.getSelectionObject();
if (selectionObject) { if (selectionObject) {
try { try {
this._clonedSelectionObject = await this._cloneObject( this._clonedSelectionObject = await this._cloneObject(selectionObject);
selectionObject
);
console.log("套索抠图:选区对象已克隆保存"); console.log("套索抠图:选区对象已克隆保存");
} catch (error) { } catch (error) {
console.error("套索抠图:克隆选区对象失败:", error); console.error("套索抠图:克隆选区对象失败:", error);
@@ -107,14 +97,7 @@ export class LassoCutoutCommand extends CompositeCommand {
const sourceObjects = this._getLayerObjects(activeLayer); const sourceObjects = this._getLayerObjects(activeLayer);
this.originalCanvasObjects = sourceObjects; // 保存真实对象引用 this.originalCanvasObjects = sourceObjects; // 保存真实对象引用
this.originalFabricObjects = sourceObjects.map((obj) => this.originalFabricObjects = sourceObjects.map((obj) =>
obj.toObject([ obj.toObject(["id", "layerId", "layerName", "parentId", "type", "custom"])
"id",
"layerId",
"layerName",
"parentId",
"type",
"custom",
])
); );
console.log( console.log(
@@ -385,10 +368,7 @@ export class LassoCutoutCommand extends CompositeCommand {
await command.undo(); await command.undo();
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
`❌ 子命令撤销失败: ${command.constructor.name}`,
error
);
// 子命令撤销失败不中断整个撤销过程 // 子命令撤销失败不中断整个撤销过程
} }
} }
@@ -465,16 +445,11 @@ export class LassoCutoutCommand extends CompositeCommand {
// 递归获取图层及其子图层的所有对象 // 递归获取图层及其子图层的所有对象
const collectLayerObjects = (currentLayer) => { const collectLayerObjects = (currentLayer) => {
// 处理图层的fabricObjects // 处理图层的fabricObjects
if ( if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
currentLayer.fabricObjects &&
Array.isArray(currentLayer.fabricObjects)
) {
currentLayer.fabricObjects.forEach((fabricRef) => { currentLayer.fabricObjects.forEach((fabricRef) => {
if (fabricRef && fabricRef.id) { if (fabricRef && fabricRef.id) {
// 从画布中查找真实的对象 // 从画布中查找真实的对象
const realObject = canvasObjects.find( const realObject = canvasObjects.find((obj) => obj.id === fabricRef.id);
(obj) => obj.id === fabricRef.id
);
if (realObject && realObject.visible) { if (realObject && realObject.visible) {
objects.push(realObject); objects.push(realObject);
} }
@@ -484,9 +459,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 处理单个fabricObject背景图层等 // 处理单个fabricObject背景图层等
if (currentLayer.fabricObject && currentLayer.fabricObject.id) { if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
const realObject = canvasObjects.find( const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
(obj) => obj.id === currentLayer.fabricObject.id
);
if (realObject && realObject.visible) { if (realObject && realObject.visible) {
objects.push(realObject); objects.push(realObject);
} }
@@ -517,11 +490,7 @@ export class LassoCutoutCommand extends CompositeCommand {
* @returns {String} 抠图结果的DataURL * @returns {String} 抠图结果的DataURL
* @private * @private
*/ */
async _performCutoutWithRasterized( async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
sourceObjects,
selectionObject,
selectionBounds
) {
try { try {
console.log("=== 开始使用createRasterizedImage执行抠图 ==="); console.log("=== 开始使用createRasterizedImage执行抠图 ===");
console.log(`源对象数量: ${sourceObjects.length}`); console.log(`源对象数量: ${sourceObjects.length}`);
@@ -560,10 +529,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 如果createRasterizedImage失败回退到原始方法 // 如果createRasterizedImage失败回退到原始方法
console.log("⚠️ 回退到原始抠图方法..."); console.log("⚠️ 回退到原始抠图方法...");
return await this._performCutout( return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
{ fabricObjects: sourceObjects },
selectionObject
);
} }
} }
@@ -594,9 +560,7 @@ export class LassoCutoutCommand extends CompositeCommand {
try { try {
// 收集源图层中的可见对象 // 收集源图层中的可见对象
const visibleObjects = sourceLayer.fabricObjects.filter( const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
(obj) => obj.visible
);
if (visibleObjects.length === 0) { if (visibleObjects.length === 0) {
throw new Error("源图层没有可见对象"); throw new Error("源图层没有可见对象");
@@ -649,10 +613,7 @@ export class LassoCutoutCommand extends CompositeCommand {
let highResolutionScale = 1; let highResolutionScale = 1;
if (this.highResolutionEnabled) { if (this.highResolutionEnabled) {
const devicePixelRatio = window.devicePixelRatio || 1; const devicePixelRatio = window.devicePixelRatio || 1;
highResolutionScale = Math.max( highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
this.baseResolutionScale,
devicePixelRatio * 2
);
} }
// 创建用于导出的高分辨率canvas // 创建用于导出的高分辨率canvas
@@ -663,12 +624,8 @@ export class LassoCutoutCommand extends CompositeCommand {
colorSpace: "srgb", colorSpace: "srgb",
}); });
const actualWidth = Math.round( const actualWidth = Math.round(renderBounds.width * highResolutionScale);
renderBounds.width * highResolutionScale const actualHeight = Math.round(renderBounds.height * highResolutionScale);
);
const actualHeight = Math.round(
renderBounds.height * highResolutionScale
);
exportCanvas.width = actualWidth; exportCanvas.width = actualWidth;
exportCanvas.height = actualHeight; exportCanvas.height = actualHeight;
@@ -877,17 +834,14 @@ export class LassoCutoutCommand extends CompositeCommand {
} }
}); });
} else if (objectType === "polygon") { } else if (objectType === "polygon") {
fabric.Polygon.fromObject( fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
this.serializedSelectionObject, if (polygon) {
(polygon) => { console.log("多边形选区对象反序列化成功");
if (polygon) { resolve(polygon);
console.log("多边形选区对象反序列化成功"); } else {
resolve(polygon); reject(new Error("多边形选区对象反序列化失败"));
} else {
reject(new Error("多边形选区对象反序列化失败"));
}
} }
); });
} else if (objectType === "rect") { } else if (objectType === "rect") {
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => { fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
if (rect) { if (rect) {
@@ -898,30 +852,24 @@ export class LassoCutoutCommand extends CompositeCommand {
} }
}); });
} else if (objectType === "ellipse" || objectType === "circle") { } else if (objectType === "ellipse" || objectType === "circle") {
fabric.Ellipse.fromObject( fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
this.serializedSelectionObject, if (ellipse) {
(ellipse) => { console.log("椭圆选区对象反序列化成功");
if (ellipse) { resolve(ellipse);
console.log("椭圆选区对象反序列化成功"); } else {
resolve(ellipse); reject(new Error("椭圆选区对象反序列化失败"));
} else {
reject(new Error("椭圆选区对象反序列化失败"));
}
} }
); });
} else { } else {
// 通用对象反序列化 // 通用对象反序列化
fabric.util.enlivenObjects( fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
[this.serializedSelectionObject], if (objects && objects.length > 0) {
(objects) => { console.log("通用选区对象反序列化成功");
if (objects && objects.length > 0) { resolve(objects[0]);
console.log("通用选区对象反序列化成功"); } else {
resolve(objects[0]); reject(new Error("通用选区对象反序列化失败"));
} else {
reject(new Error("通用选区对象反序列化失败"));
}
} }
); });
} }
}); });
} catch (error) { } catch (error) {
@@ -945,11 +893,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 1. 恢复图层到原位置 // 1. 恢复图层到原位置
if (this.originalLayerIndex !== -1) { if (this.originalLayerIndex !== -1) {
this.layerManager.layers.value.splice( this.layerManager.layers.value.splice(this.originalLayerIndex, 0, this.originalLayer);
this.originalLayerIndex,
0,
this.originalLayer
);
} else { } else {
// 如果没有保存索引,添加到末尾 // 如果没有保存索引,添加到末尾
this.layerManager.layers.value.push(this.originalLayer); this.layerManager.layers.value.push(this.originalLayer);
@@ -957,9 +901,7 @@ export class LassoCutoutCommand extends CompositeCommand {
// 2. 恢复fabric对象到画布 // 2. 恢复fabric对象到画布
if (this.originalFabricObjects.length > 0) { if (this.originalFabricObjects.length > 0) {
console.log( console.log(`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`);
`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`
);
// 使用fabric.util.enlivenObjects批量反序列化对象 // 使用fabric.util.enlivenObjects批量反序列化对象
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
@@ -1108,12 +1050,7 @@ export class ClearSelectionCommand extends Command {
// 序列化选区对象和相关状态 // 序列化选区对象和相关状态
this.originalSelectionState = { this.originalSelectionState = {
// 选区对象的序列化数据 // 选区对象的序列化数据
selectionObjectData: selectionObject.toObject([ selectionObjectData: selectionObject.toObject(["id", "layerId", "layerName", "parentId"]),
"id",
"layerId",
"layerName",
"parentId",
]),
// 选区路径数据 // 选区路径数据
selectionPath: this.selectionManager.getSelectionPath(), selectionPath: this.selectionManager.getSelectionPath(),
// 羽化值 // 羽化值
@@ -1176,13 +1113,8 @@ export class ClearSelectionCommand extends Command {
return; return;
} }
const { const { selectionObjectData, featherAmount, selectionStyle, position, managerState } =
selectionObjectData, this.originalSelectionState;
featherAmount,
selectionStyle,
position,
managerState,
} = this.originalSelectionState;
// 根据选区对象类型进行反序列化 // 根据选区对象类型进行反序列化
const objectType = selectionObjectData.type; const objectType = selectionObjectData.type;
@@ -1228,9 +1160,7 @@ export class ClearSelectionCommand extends Command {
// 恢复阴影(羽化效果) // 恢复阴影(羽化效果)
if (selectionStyle.shadow) { if (selectionStyle.shadow) {
restoredObject.shadow = new fabric.Shadow( restoredObject.shadow = new fabric.Shadow(selectionStyle.shadow);
selectionStyle.shadow
);
} }
} }
@@ -1315,9 +1245,7 @@ export class ClearSelectionCommand extends Command {
// 验证基本属性 // 验证基本属性
const originalId = this.originalSelectionState.selectionId; const originalId = this.originalSelectionState.selectionId;
if (currentSelection.id !== originalId) { if (currentSelection.id !== originalId) {
console.warn( console.warn(`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`);
`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`
);
return false; return false;
} }
@@ -1325,9 +1253,7 @@ export class ClearSelectionCommand extends Command {
const currentFeather = this.selectionManager.getFeatherAmount(); const currentFeather = this.selectionManager.getFeatherAmount();
const originalFeather = this.originalSelectionState.featherAmount; const originalFeather = this.originalSelectionState.featherAmount;
if (currentFeather !== originalFeather) { if (currentFeather !== originalFeather) {
console.warn( console.warn(`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`);
`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`
);
return false; return false;
} }

View File

@@ -1,16 +1,13 @@
import { Command } from "./Command"; import { Command } from "./Command";
import { import { createLayer, findLayerRecursively, LayerType, OperationType } from "../utils/layerHelper";
createLayer,
findLayerRecursively,
LayerType,
OperationType,
} from "../utils/layerHelper";
import { createStaticCanvas } from "../utils/canvasFactory"; import { createStaticCanvas } from "../utils/canvasFactory";
import { AddObjectToLayerCommand } from "./ObjectLayerCommands"; import { AddObjectToLayerCommand } from "./ObjectLayerCommands";
import { ToolCommand } from "./ToolCommands"; import { ToolCommand } from "./ToolCommands";
import { import {
findObjectById, findObjectById,
generateId, generateId,
getObjectZIndex,
insertObjectAtZIndex,
objectIsInCanvas, objectIsInCanvas,
optimizeCanvasRendering, optimizeCanvasRendering,
removeCanvasObjectByObject, removeCanvasObjectByObject,
@@ -112,15 +109,11 @@ export class AddLayerCommand extends Command {
parentLayer.children = parentLayer.children || []; parentLayer.children = parentLayer.children || [];
parentLayer.children.splice(insertIndex, 0, newLayer); parentLayer.children.splice(insertIndex, 0, newLayer);
console.log( console.log(`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`);
`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`
);
} else { } else {
// 当前激活图层是一级图层 // 当前激活图层是一级图层
// 在一级图层中,插入到激活图层之上 // 在一级图层中,插入到激活图层之上
const activeLayerIndex = layers.findIndex( const activeLayerIndex = layers.findIndex((layer) => layer.id === currentActiveLayerId);
(layer) => layer.id === currentActiveLayerId
);
insertIndex = Math.max(0, activeLayerIndex); insertIndex = Math.max(0, activeLayerIndex);
layers.splice(insertIndex, 0, newLayer); layers.splice(insertIndex, 0, newLayer);
@@ -273,9 +266,7 @@ export class PasteLayerCommand extends Command {
try { try {
objects.forEach((obj) => { objects.forEach((obj) => {
// 生成新的对象ID // 生成新的对象ID
const newObjId = `obj_${Date.now()}_${Math.floor( const newObjId = `obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
Math.random() * 1000
)}`;
obj.id = newObjId; obj.id = newObjId;
obj.layerId = this.newLayerId; obj.layerId = this.newLayerId;
obj.layerName = this.newLayer.name; obj.layerName = this.newLayer.name;
@@ -337,9 +328,7 @@ export class PasteLayerCommand extends Command {
if (!this.newLayer || !this.newLayerId) return; if (!this.newLayer || !this.newLayerId) return;
// 从图层列表删除该图层 // 从图层列表删除该图层
const index = this.layers.value.findIndex( const index = this.layers.value.findIndex((layer) => layer.id === this.newLayerId);
(layer) => layer.id === this.newLayerId
);
if (index !== -1) { if (index !== -1) {
this.layers.value.splice(index, 1); this.layers.value.splice(index, 1);
} }
@@ -405,9 +394,7 @@ export class RemoveLayerCommand extends Command {
this.activeLayerId = options.activeLayerId; this.activeLayerId = options.activeLayerId;
// 查找要删除的图层 // 查找要删除的图层
this.layerIndex = this.layers.value.findIndex( this.layerIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
(layer) => layer.id === this.layerId
);
this.removedLayer = this.layers.value[this.layerIndex]; this.removedLayer = this.layers.value[this.layerIndex];
this.isActiveLayer = this.layerId === this.activeLayerId.value; this.isActiveLayer = this.layerId === this.activeLayerId.value;
@@ -474,9 +461,7 @@ export class RemoveLayerCommand extends Command {
// 如果删除的是当前活动图层,需要更新活动图层 // 如果删除的是当前活动图层,需要更新活动图层
if (this.isActiveLayer) { if (this.isActiveLayer) {
// 查找最近的非背景层作为新的活动图层 // 查找最近的非背景层作为新的活动图层
const newActiveLayer = this.layers.value.find( const newActiveLayer = this.layers.value.find((layer) => !layer.isBackground);
(layer) => !layer.isBackground
);
if (newActiveLayer) { if (newActiveLayer) {
this.activeLayerId.value = newActiveLayer.id; this.activeLayerId.value = newActiveLayer.id;
} else { } else {
@@ -554,30 +539,22 @@ export class MoveLayerCommand extends Command {
this.parentLayer = null; // 父图层 this.parentLayer = null; // 父图层
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶 // 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
if (parent?.id) { if (parent?.id) {
// 查找子图层索引 // 查找子图层索引
this.layerIndex = parent?.children?.findIndex( this.layerIndex = parent?.children?.findIndex((layer) => layer.id === this.layerId);
(layer) => layer.id === this.layerId
);
this.parentLayer = parent; this.parentLayer = parent;
} else { } else {
// 查找图层索引 // 查找图层索引
this.layerIndex = this.layers.value.findIndex( this.layerIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
(layer) => layer.id === this.layerId
);
} }
this.layer = layer; this.layer = layer;
this.originalIndex = this.layerIndex; this.originalIndex = this.layerIndex;
// 目标位置 // 目标位置
this.targetIndex = this.targetIndex = options.direction === "up" ? this.layerIndex - 1 : this.layerIndex + 1;
options.direction === "up" ? this.layerIndex - 1 : this.layerIndex + 1;
} }
async execute() { async execute() {
@@ -668,9 +645,7 @@ export class ToggleLayerVisibilityCommand extends Command {
// 更新画布上图层对象的可见性 // 更新画布上图层对象的可见性
if (this.canvas) { if (this.canvas) {
const layerObjects = this.canvas const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
.getObjects()
.filter((obj) => obj.layerId === this.layerId);
layerObjects.forEach((obj) => { layerObjects.forEach((obj) => {
obj.visible = this.layer.visible; obj.visible = this.layer.visible;
}); });
@@ -711,10 +686,7 @@ export class ToggleChildLayerVisibilityCommand extends Command {
this.layerManager = options.layerManager; this.layerManager = options.layerManager;
// 查找父图层和子图层 // 查找父图层和子图层
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
this.parentLayer = parent; this.parentLayer = parent;
this.childLayer = layer; this.childLayer = layer;
@@ -731,9 +703,7 @@ export class ToggleChildLayerVisibilityCommand extends Command {
// 更新画布上图层对象的可见性 // 更新画布上图层对象的可见性
if (this.canvas) { if (this.canvas) {
const layerObjects = this.canvas const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
.getObjects()
.filter((obj) => obj.layerId === this.layerId);
layerObjects.forEach((obj) => { layerObjects.forEach((obj) => {
obj.visible = this.childLayer.visible; obj.visible = this.childLayer.visible;
@@ -792,9 +762,7 @@ export class RenameLayerCommand extends Command {
// 更新图层对象上的图层名称 // 更新图层对象上的图层名称
if (this.canvas) { if (this.canvas) {
const layerObjects = this.canvas const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
.getObjects()
.filter((obj) => obj.layerId === this.layerId);
layerObjects.forEach((obj) => { layerObjects.forEach((obj) => {
obj.layerName = this.newName; obj.layerName = this.newName;
@@ -811,9 +779,7 @@ export class RenameLayerCommand extends Command {
// 恢复图层对象上的图层名称 // 恢复图层对象上的图层名称
if (this.canvas) { if (this.canvas) {
const layerObjects = this.canvas const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
.getObjects()
.filter((obj) => obj.layerId === this.layerId);
layerObjects.forEach((obj) => { layerObjects.forEach((obj) => {
obj.layerName = this.oldName; obj.layerName = this.oldName;
@@ -848,10 +814,7 @@ export class LayerLockCommand extends Command {
// 查找图层(包括子图层) // 查找图层(包括子图层)
// 查找图层 // 查找图层
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
this.layer = layer; this.layer = layer;
this.parentLayer = parent || null; // 父图层 this.parentLayer = parent || null; // 父图层
this.oldLocked = this.layer ? this.layer.locked : null; this.oldLocked = this.layer ? this.layer.locked : null;
@@ -869,11 +832,7 @@ export class LayerLockCommand extends Command {
layer.locked = locked; layer.locked = locked;
// 如果是组图层,递归更新所有子图层 // 如果是组图层,递归更新所有子图层
if ( if (layer.type === "group" && layer.children && Array.isArray(layer.children)) {
layer.type === "group" &&
layer.children &&
Array.isArray(layer.children)
) {
layer.children.forEach((child) => { layer.children.forEach((child) => {
this._updateLayerLockState(child, locked); this._updateLayerLockState(child, locked);
}); });
@@ -895,11 +854,7 @@ export class LayerLockCommand extends Command {
// 更新画布上对象的可选择状态 // 更新画布上对象的可选择状态
await this.layerManager?.updateLayersObjectsInteractivity(); await this.layerManager?.updateLayersObjectsInteractivity();
console.log( console.log(`${newLocked ? "锁定" : "解锁"}图层: ${this.layer.name} (ID: ${this.layerId})`);
`${newLocked ? "锁定" : "解锁"}图层: ${this.layer.name} (ID: ${
this.layerId
})`
);
return true; return true;
} }
@@ -911,9 +866,7 @@ export class LayerLockCommand extends Command {
// 更新画布上对象的可选择状态 // 更新画布上对象的可选择状态
await this.layerManager?.updateLayersObjectsInteractivity(); await this.layerManager?.updateLayersObjectsInteractivity();
console.log( console.log(`恢复图层锁定状态: ${this.layer.name} (锁定: ${this.oldLocked})`);
`恢复图层锁定状态: ${this.layer.name} (锁定: ${this.oldLocked})`
);
} }
} }
@@ -961,9 +914,7 @@ export class SetLayerOpacityCommand extends Command {
// 更新画布上对象的不透明度 // 更新画布上对象的不透明度
if (this.canvas) { if (this.canvas) {
const layerObjects = this.canvas const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
.getObjects()
.filter((obj) => obj.layerId === this.layerId);
layerObjects.forEach((obj) => { layerObjects.forEach((obj) => {
obj.opacity = this.opacity; obj.opacity = this.opacity;
@@ -982,9 +933,7 @@ export class SetLayerOpacityCommand extends Command {
// 更新画布上对象的不透明度 // 更新画布上对象的不透明度
if (this.canvas) { if (this.canvas) {
const layerObjects = this.canvas const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
.getObjects()
.filter((obj) => obj.layerId === this.layerId);
layerObjects.forEach((obj) => { layerObjects.forEach((obj) => {
obj.opacity = this.oldOpacity; obj.opacity = this.oldOpacity;
@@ -1038,9 +987,7 @@ export class SetLayerBlendModeCommand extends Command {
// 更新画布上对象的混合模式 // 更新画布上对象的混合模式
if (this.canvas) { if (this.canvas) {
const layerObjects = this.canvas const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
.getObjects()
.filter((obj) => obj.layerId === this.layerId);
layerObjects.forEach((obj) => { layerObjects.forEach((obj) => {
obj.globalCompositeOperation = this.blendMode; obj.globalCompositeOperation = this.blendMode;
@@ -1059,9 +1006,7 @@ export class SetLayerBlendModeCommand extends Command {
// 更新画布上对象的混合模式 // 更新画布上对象的混合模式
if (this.canvas) { if (this.canvas) {
const layerObjects = this.canvas const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
.getObjects()
.filter((obj) => obj.layerId === this.layerId);
layerObjects.forEach((obj) => { layerObjects.forEach((obj) => {
obj.globalCompositeOperation = this.oldBlendMode; obj.globalCompositeOperation = this.oldBlendMode;
@@ -1101,17 +1046,11 @@ export class MergeLayersCommand extends Command {
// 备份原图层 // 备份原图层
this.originalLayers = [...this.layers.value]; this.originalLayers = [...this.layers.value];
// 新图层ID // 新图层ID
this.newLayerId = `merged_layer_${Date.now()}_${Math.floor( this.newLayerId = `merged_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
Math.random() * 1000
)}`;
} }
execute() { execute() {
if ( if (!this.layerIds || !Array.isArray(this.layerIds) || this.layerIds.length < 2) {
!this.layerIds ||
!Array.isArray(this.layerIds) ||
this.layerIds.length < 2
) {
console.error("合并图层至少需要两个图层"); console.error("合并图层至少需要两个图层");
return null; return null;
} }
@@ -1132,6 +1071,14 @@ export class MergeLayersCommand extends Command {
return null; return null;
} }
// 查找所有要组合的图层
const layersToGroup = this.layerIds
.map((id) => {
const tempLayer = this.layers.value.find((layer) => layer.id === id); // 需要给子图层加上parentId
return tempLayer ? { ...tempLayer, parentId: this.groupId } : false;
})
.filter(Boolean);
const topLayerIndex = Math.min( const topLayerIndex = Math.min(
...layersToGroup.map((layer) => ...layersToGroup.map((layer) =>
this.layers.value.findIndex((fLayer) => fLayer.id === layer.id) this.layers.value.findIndex((fLayer) => fLayer.id === layer.id)
@@ -1164,9 +1111,7 @@ export class MergeLayersCommand extends Command {
}); });
// 移除原图层 // 移除原图层
this.layers.value = this.layers.value.filter( this.layers.value = this.layers.value.filter((layer) => !this.layerIds.includes(layer.id));
(layer) => !this.layerIds.includes(layer.id)
);
// 插入新图层 // 插入新图层
this.layers.value.splice(topLayerIndex, 0, mergedLayer); this.layers.value.splice(topLayerIndex, 0, mergedLayer);
@@ -1227,18 +1172,13 @@ export class GroupLayersCommand extends Command {
this.originalLayers = [...this.layers.value]; this.originalLayers = [...this.layers.value];
// 新组ID // 新组ID
this.groupId = this.groupId =
generateId("group_layer_") || generateId("group_layer_") || `group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
`group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
this.originalActiveLayerId = this.activeLayerId.value; // 备份原活动图层ID this.originalActiveLayerId = this.activeLayerId.value; // 备份原活动图层ID
} }
async execute() { async execute() {
if ( if (!this.layerIds || !Array.isArray(this.layerIds) || this.layerIds.length < 2) {
!this.layerIds ||
!Array.isArray(this.layerIds) ||
this.layerIds.length < 2
) {
console.error("组合图层至少需要两个图层"); console.error("组合图层至少需要两个图层");
return null; return null;
} }
@@ -1282,9 +1222,7 @@ export class GroupLayersCommand extends Command {
}); });
// 移除原图层 // 移除原图层
this.layers.value = this.layers.value.filter( this.layers.value = this.layers.value.filter((layer) => !this.layerIds.includes(layer.id));
(layer) => !this.layerIds.includes(layer.id)
);
// 插入新组图层 // 插入新组图层
this.layers.value.splice(topLayerIndex, 0, groupLayer); this.layers.value.splice(topLayerIndex, 0, groupLayer);
@@ -1338,9 +1276,7 @@ export class UngroupLayersCommand extends Command {
async execute() { async execute() {
// 查找组图层 // 查找组图层
const groupIndex = this.layers.value.findIndex( const groupIndex = this.layers.value.findIndex((layer) => layer.id === this.groupId);
(layer) => layer.id === this.groupId
);
if (groupIndex === -1) { if (groupIndex === -1) {
console.error(`找不到组图层 ${this.groupId}`); console.error(`找不到组图层 ${this.groupId}`);
@@ -1361,11 +1297,11 @@ export class UngroupLayersCommand extends Command {
this.layers.value.splice( this.layers.value.splice(
groupIndex, groupIndex,
1, 1,
...groupLayer.children?.map((child) => { ...(groupLayer?.children?.map((child) => {
// 为子图层添加parentId // 为子图层添加parentId
delete child.parentId; // 删除parentId属性 delete child.parentId; // 删除parentId属性
return { ...child }; return { ...child };
}) }) || {})
); );
// 更新当前活动图层为第一个子图层 // 更新当前活动图层为第一个子图层
if (this.childLayerIds.length > 0 && this.activeLayerId) { if (this.childLayerIds.length > 0 && this.activeLayerId) {
@@ -1411,18 +1347,14 @@ export class MergeLayerObjectsCommand extends Command {
// 备份原始对象,用于撤销 // 备份原始对象,用于撤销
if (this.activeLayer && Array.isArray(this.activeLayer.fabricObjects)) { if (this.activeLayer && Array.isArray(this.activeLayer.fabricObjects)) {
this.originalObjects = this.originalObjects =
this.canvas this.canvas?.getObjects()?.filter((fItem) => fItem.layerId === this.activeLayer.id) || [];
?.getObjects()
?.filter((fItem) => fItem.layerId === this.activeLayer.id) || [];
} else { } else {
this.originalObjects = []; this.originalObjects = [];
} }
// 新合并图像对象 // 新合并图像对象
this.mergedImage = null; this.mergedImage = null;
this.newImageId = `merged_image_${Date.now()}_${Math.floor( this.newImageId = `merged_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
Math.random() * 1000
)}`;
} }
async execute() { async execute() {
@@ -1458,10 +1390,7 @@ export class MergeLayerObjectsCommand extends Command {
// 异步处理图像合并 // 异步处理图像合并
try { try {
const mergedImage = await this._createMergedImageAsync( const mergedImage = await this._createMergedImageAsync(objectsToMerge, bounds);
objectsToMerge,
bounds
);
this._setupMergedImage(mergedImage, bounds); this._setupMergedImage(mergedImage, bounds);
this._replaceObjects(mergedImage); this._replaceObjects(mergedImage);
@@ -1611,9 +1540,7 @@ export class MergeLayerObjectsCommand extends Command {
}); });
tempCanvas.add(clonedObj); tempCanvas.add(clonedObj);
console.log( console.log(`添加对象 ${index + 1}/${objects.length}: ${obj.type || "unknown"}`);
`添加对象 ${index + 1}/${objects.length}: ${obj.type || "unknown"}`
);
} catch (error) { } catch (error) {
console.error(`添加对象到临时画布时发生错误:`, error); console.error(`添加对象到临时画布时发生错误:`, error);
} }
@@ -1672,10 +1599,7 @@ export class MergeLayerObjectsCommand extends Command {
}); });
// 如果有新的图像对象,也要移除 // 如果有新的图像对象,也要移除
if ( if (this.fabricImage && this.canvas.getObjects().includes(this.fabricImage)) {
this.fabricImage &&
this.canvas.getObjects().includes(this.fabricImage)
) {
this.canvas.remove(this.fabricImage); this.canvas.remove(this.fabricImage);
} }
@@ -1691,9 +1615,7 @@ export class MergeLayerObjectsCommand extends Command {
// 更新缩略图 // 更新缩略图
if (this.canvas.thumbnailManager) { if (this.canvas.thumbnailManager) {
setTimeout(() => { setTimeout(() => {
this.canvas.thumbnailManager.generateLayerThumbnail( this.canvas.thumbnailManager.generateLayerThumbnail(this.activeLayer.id);
this.activeLayer.id
);
}, 100); }, 100);
} }
} }
@@ -1737,9 +1659,7 @@ export class MergeLayerObjectsCommand extends Command {
// 更新缩略图 // 更新缩略图
if (this.canvas.thumbnailManager) { if (this.canvas.thumbnailManager) {
setTimeout(() => { setTimeout(() => {
this.canvas.thumbnailManager.generateLayerThumbnail( this.canvas.thumbnailManager.generateLayerThumbnail(this.activeLayer.id);
this.activeLayer.id
);
}, 100); }, 100);
} }
} }
@@ -1827,8 +1747,7 @@ export class LayerObjectsToGroupCommand extends Command {
this.existingGroupId = null; this.existingGroupId = null;
this.groupObjectId = null; this.groupObjectId = null;
this.newGroupId = this.newGroupId =
generateId("group") || generateId("group") || `group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
`group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
this.wasGroupCreated = false; this.wasGroupCreated = false;
// 保存原始位置信息 // 保存原始位置信息
@@ -1889,10 +1808,7 @@ export class LayerObjectsToGroupCommand extends Command {
const newObjectsToAdd = []; const newObjectsToAdd = [];
if (this.fabricImage) { if (this.fabricImage) {
// 如果是重做,恢复新对象的原始位置 // 如果是重做,恢复新对象的原始位置
if ( if (!this.isFirstExecution && this.newObjectPositions.has(this.fabricImage.id)) {
!this.isFirstExecution &&
this.newObjectPositions.has(this.fabricImage.id)
) {
const savedPosition = this.newObjectPositions.get(this.fabricImage.id); const savedPosition = this.newObjectPositions.get(this.fabricImage.id);
this.fabricImage.set({ this.fabricImage.set({
left: savedPosition.left, left: savedPosition.left,
@@ -1905,9 +1821,7 @@ export class LayerObjectsToGroupCommand extends Command {
flipX: savedPosition.flipX, flipX: savedPosition.flipX,
flipY: savedPosition.flipY, flipY: savedPosition.flipY,
}); });
console.log( console.log(`🔄 重做时恢复新对象位置: (${savedPosition.left}, ${savedPosition.top})`);
`🔄 重做时恢复新对象位置: (${savedPosition.left}, ${savedPosition.top})`
);
} }
newObjectsToAdd.push(this.fabricImage); newObjectsToAdd.push(this.fabricImage);
@@ -1960,9 +1874,7 @@ export class LayerObjectsToGroupCommand extends Command {
if (!this.existingGroupId) { if (!this.existingGroupId) {
const groupObjects = const groupObjects =
this.activeLayer.fabricObjects.find( this.activeLayer.fabricObjects.find((obj) => obj && obj.type === "group") || null;
(obj) => obj && obj.type === "group"
) || null;
this.existingGroupId = groupObjects?.id || null; this.existingGroupId = groupObjects?.id || null;
} }
@@ -2062,9 +1974,7 @@ export class LayerObjectsToGroupCommand extends Command {
// 更新图层的对象列表 // 更新图层的对象列表
// this.activeLayer.fabricObjects = [groupObject]; // this.activeLayer.fabricObjects = [groupObject];
this.activeLayer.fabricObjects = [ this.activeLayer.fabricObjects = [groupObject.toObject(["id", "layerId", "layerName"])];
groupObject.toObject(["id", "layerId", "layerName"]),
];
} }
/** /**
@@ -2150,10 +2060,7 @@ export class LayerObjectsToGroupCommand extends Command {
restorePosition = { ...originalPosition }; restorePosition = { ...originalPosition };
} else { } else {
// 计算对象在画布中的当前绝对位置 // 计算对象在画布中的当前绝对位置
restorePosition = this._calculateObjectAbsolutePosition( restorePosition = this._calculateObjectAbsolutePosition(obj, groupObject);
obj,
groupObject
);
} }
objectsToRestore.push({ objectsToRestore.push({
@@ -2208,9 +2115,7 @@ export class LayerObjectsToGroupCommand extends Command {
restoredObjects.push(obj); restoredObjects.push(obj);
console.log( console.log(
`✅ 恢复原始对象 ${obj.id || obj.type} 到位置 (${position.left}, ${ `✅ 恢复原始对象 ${obj.id || obj.type} 到位置 (${position.left}, ${position.top})`
position.top
})`
); );
} catch (error) { } catch (error) {
console.error(`恢复对象 ${obj.id || obj.type} 时发生错误:`, error); console.error(`恢复对象 ${obj.id || obj.type} 时发生错误:`, error);
@@ -2270,10 +2175,7 @@ export class LayerObjectsToGroupCommand extends Command {
const objectPoint = new fabric.Point(obj.left || 0, obj.top || 0); const objectPoint = new fabric.Point(obj.left || 0, obj.top || 0);
// 应用组的变换矩阵计算绝对位置 // 应用组的变换矩阵计算绝对位置
const absolutePoint = fabric.util.transformPoint( const absolutePoint = fabric.util.transformPoint(objectPoint, groupTransform);
objectPoint,
groupTransform
);
// 计算缩放比例 // 计算缩放比例
const totalScaleX = (group.scaleX || 1) * (obj.scaleX || 1); const totalScaleX = (group.scaleX || 1) * (obj.scaleX || 1);
@@ -2441,8 +2343,7 @@ export class CreateImageLayerCommand extends Command {
// 存储执行过程中的结果 // 存储执行过程中的结果
this.newLayerId = this.newLayerId =
generateId("layer_image_") || generateId("layer_image_") || `layer_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
`layer_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
this.commands = []; this.commands = [];
this.executedCommands = []; this.executedCommands = [];
} }
@@ -2457,8 +2358,7 @@ export class CreateImageLayerCommand extends Command {
this.executedCommands = []; this.executedCommands = [];
// 生成图层名称 // 生成图层名称
const fileName = const fileName = this.layerName || `图片 ${new Date().toLocaleTimeString()}`;
this.layerName || `图片 ${new Date().toLocaleTimeString()}`;
this.fabricImage.set({ this.fabricImage.set({
id: this.imageId, id: this.imageId,
@@ -2529,9 +2429,7 @@ export class CreateImageLayerCommand extends Command {
return true; return true;
} }
console.log( console.log(`↩️ 开始撤销创建图片图层操作,共 ${this.executedCommands.length} 个子命令`);
`↩️ 开始撤销创建图片图层操作,共 ${this.executedCommands.length} 个子命令`
);
try { try {
// 逆序撤销已执行的命令 // 逆序撤销已执行的命令
@@ -2547,10 +2445,7 @@ export class CreateImageLayerCommand extends Command {
} }
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`); console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
`❌ 子命令撤销失败: ${command.constructor.name}`,
error
);
} }
} }
} }
@@ -2583,10 +2478,7 @@ export class CreateImageLayerCommand extends Command {
} }
console.log(`✅ 子命令回滚成功: ${command.constructor.name}`); console.log(`✅ 子命令回滚成功: ${command.constructor.name}`);
} catch (error) { } catch (error) {
console.error( console.error(`❌ 子命令回滚失败: ${command.constructor.name}`, error);
`❌ 子命令回滚失败: ${command.constructor.name}`,
error
);
} }
} }
} }
@@ -2721,9 +2613,7 @@ export class ReorderChildLayersCommand extends Command {
async execute() { async execute() {
// 查找父图层 // 查找父图层
const parentLayer = this.layers.value.find( const parentLayer = this.layers.value.find((layer) => layer.id === this.parentId);
(layer) => layer.id === this.parentId
);
if (!parentLayer) return false; if (!parentLayer) return false;
// 获取子图层 // 获取子图层
@@ -2873,15 +2763,11 @@ export class CutLayerCommand extends Command {
// 生成新图层ID和名称 // 生成新图层ID和名称
this.newLayerId = this.newLayerId =
generateId("layer_") || generateId("layer_") || `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
} }
async execute() { async execute() {
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
const sourceLayer = layer; const sourceLayer = layer;
const parentLayer = parent; const parentLayer = parent;
@@ -2908,9 +2794,7 @@ export class CutLayerCommand extends Command {
blendMode: sourceLayer.blendMode, blendMode: sourceLayer.blendMode,
fabricObjects: [], fabricObjects: [],
children: sourceLayer.children ? [...sourceLayer.children] : [], children: sourceLayer.children ? [...sourceLayer.children] : [],
layerProperties: sourceLayer.layerProperties layerProperties: sourceLayer.layerProperties ? { ...sourceLayer.layerProperties } : {},
? { ...sourceLayer.layerProperties }
: {},
metadata: sourceLayer.metadata ? { ...sourceLayer.metadata } : {}, metadata: sourceLayer.metadata ? { ...sourceLayer.metadata } : {},
parentId: parentLayer ? parentLayer.id : undefined, // 保持父图层关系 parentId: parentLayer ? parentLayer.id : undefined, // 保持父图层关系
}); });
@@ -2920,11 +2804,8 @@ export class CutLayerCommand extends Command {
if (parentLayer) { if (parentLayer) {
// 处理子图层在父图层的children数组中插入 // 处理子图层在父图层的children数组中插入
const sourceChildIndex = parentLayer.children.findIndex( const sourceChildIndex = parentLayer.children.findIndex((child) => child.id === this.layerId);
(child) => child.id === this.layerId insertIndex = this.insertIndex !== null ? this.insertIndex : sourceChildIndex + 1;
);
insertIndex =
this.insertIndex !== null ? this.insertIndex : sourceChildIndex + 1;
// 插入到父图层的children数组中 // 插入到父图层的children数组中
parentLayer.children.splice(insertIndex, 0, this.newLayer); parentLayer.children.splice(insertIndex, 0, this.newLayer);
@@ -2933,11 +2814,8 @@ export class CutLayerCommand extends Command {
this.childInsertIndex = insertIndex; this.childInsertIndex = insertIndex;
} else { } else {
// 处理主图层:在主图层数组中插入 // 处理主图层:在主图层数组中插入
const sourceIndex = this.layers.value.findIndex( const sourceIndex = this.layers.value.findIndex((l) => l.id === this.layerId);
(l) => l.id === this.layerId insertIndex = this.insertIndex !== null ? this.insertIndex : sourceIndex + 1;
);
insertIndex =
this.insertIndex !== null ? this.insertIndex : sourceIndex + 1;
// 插入到主图层数组中 // 插入到主图层数组中
this.layers.value.splice(insertIndex, 0, this.newLayer); this.layers.value.splice(insertIndex, 0, this.newLayer);
@@ -2956,9 +2834,7 @@ export class CutLayerCommand extends Command {
// 重新渲染画布 // 重新渲染画布
await this.layerManager?.updateLayersObjectsInteractivity(false); await this.layerManager?.updateLayersObjectsInteractivity(false);
console.log( console.log(`已复制图层:${newName} ${this.isChildLayer ? "(子图层)" : "(主图层)"}`);
`已复制图层:${newName} ${this.isChildLayer ? "(子图层)" : "(主图层)"}`
);
return this.newLayerId; return this.newLayerId;
} }
@@ -2975,9 +2851,7 @@ export class CutLayerCommand extends Command {
} }
} else { } else {
// 撤销主图层:从主图层数组中删除 // 撤销主图层:从主图层数组中删除
const index = this.layers.value.findIndex( const index = this.layers.value.findIndex((l) => l.id === this.newLayerId);
(l) => l.id === this.newLayerId
);
if (index !== -1) { if (index !== -1) {
this.layers.value.splice(index, 1); this.layers.value.splice(index, 1);
} }
@@ -3007,9 +2881,7 @@ export class CutLayerCommand extends Command {
fabric.util.enlivenObjects(serializedObjects, (objects) => { fabric.util.enlivenObjects(serializedObjects, (objects) => {
objects.forEach((obj) => { objects.forEach((obj) => {
// 生成新的对象ID // 生成新的对象ID
const newObjId = `obj_${Date.now()}_${Math.floor( const newObjId = `obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
Math.random() * 1000
)}`;
obj.id = newObjId; obj.id = newObjId;
obj.layerId = this.newLayerId; obj.layerId = this.newLayerId;
obj.layerName = this.newLayer.name; obj.layerName = this.newLayer.name;
@@ -3066,8 +2938,7 @@ export class CreateAdjustmentLayerCommand extends Command {
this.newLayer = null; this.newLayer = null;
// 生成新图层ID // 生成新图层ID
this.newLayerId = this.newLayerId =
generateId("adj_layer_") || generateId("adj_layer_") || `adj_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
`adj_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
} }
execute() { execute() {
@@ -3089,9 +2960,7 @@ export class CreateAdjustmentLayerCommand extends Command {
// 计算插入位置 // 计算插入位置
const insertIndex = const insertIndex =
this.insertIndex !== null this.insertIndex !== null ? this.insertIndex : this._getInsertIndexAboveActiveLayer();
? this.insertIndex
: this._getInsertIndexAboveActiveLayer();
// 插入新图层 // 插入新图层
this.layers.value.splice(insertIndex, 0, this.newLayer); this.layers.value.splice(insertIndex, 0, this.newLayer);
@@ -3155,9 +3024,7 @@ export class ApplyLayerStyleCommand extends Command {
return; return;
} }
this.layer = layer; this.layer = layer;
this.oldStyles = this.layer this.oldStyles = this.layer ? { ...(this.layer.layerProperties?.styles || {}) } : {};
? { ...(this.layer.layerProperties?.styles || {}) }
: {};
} }
execute() { execute() {
@@ -3296,9 +3163,7 @@ export class LayerClippingMaskCommand extends Command {
if (this.maskLayerId) { if (this.maskLayerId) {
// 创建剪贴蒙版 // 创建剪贴蒙版
const maskLayer = this.layers.value.find( const maskLayer = this.layers.value.find((l) => l.id === this.maskLayerId);
(l) => l.id === this.maskLayerId
);
if (!maskLayer) { if (!maskLayer) {
console.error(`蒙版图层 ${this.maskLayerId} 不存在`); console.error(`蒙版图层 ${this.maskLayerId} 不存在`);
return false; return false;
@@ -3525,19 +3390,14 @@ export class ChangeFixedImageCommand extends Command {
this.retryCount = attempt; this.retryCount = attempt;
if (attempt === this.maxRetries) { if (attempt === this.maxRetries) {
throw new Error( throw new Error(`图像加载失败,已重试${this.maxRetries}次: ${error.message}`);
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
);
} }
// 指数退避重试 // 指数退避重试
const delay = Math.pow(2, attempt) * 1000; const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay)); await new Promise((resolve) => setTimeout(resolve, delay));
console.warn( console.warn(`图像加载重试 ${attempt + 1}/${this.maxRetries}:`, error.message);
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
error.message
);
} }
} }
} }
@@ -3545,9 +3405,7 @@ export class ChangeFixedImageCommand extends Command {
loadImage() { loadImage() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
reject( reject(new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`));
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
);
}, this.timeoutMs); }, this.timeoutMs);
fabric.Image.fromURL( fabric.Image.fromURL(
@@ -3585,15 +3443,9 @@ export class ChangeFixedImageCommand extends Command {
// 移除旧对象(如果存在) // 移除旧对象(如果存在)
if (this.previousObjectId) { if (this.previousObjectId) {
const { object: oldObject } = findObjectById( const { object: oldObject } = findObjectById(this.canvas, this.previousObjectId);
this.canvas,
this.previousObjectId
);
if (oldObject) { if (oldObject) {
const removeSuccess = removeCanvasObjectByObject( const removeSuccess = removeCanvasObjectByObject(this.canvas, oldObject);
this.canvas,
oldObject
);
if (!removeSuccess) { if (!removeSuccess) {
console.warn("移除旧对象失败,但继续执行"); console.warn("移除旧对象失败,但继续执行");
} }
@@ -3650,97 +3502,75 @@ export class ChangeFixedImageCommand extends Command {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fabric.util.enlivenObjects( fabric.util.enlivenObjects([this.previousImage.objectData], async (objects) => {
[this.previousImage.objectData], try {
async (objects) => { const restoredImage = objects[0];
try {
const restoredImage = objects[0];
await optimizeCanvasRendering(this.canvas, async () => { await optimizeCanvasRendering(this.canvas, async () => {
// 移除当前对象 // 移除当前对象
if (this.newObjectId) { if (this.newObjectId) {
const { object: currentObject } = findObjectById( const { object: currentObject } = findObjectById(this.canvas, this.newObjectId);
this.canvas, if (currentObject) {
this.newObjectId removeCanvasObjectByObject(this.canvas, currentObject);
);
if (currentObject) {
removeCanvasObjectByObject(this.canvas, currentObject);
}
} }
}
// 恢复之前的变换 // 恢复之前的变换
if (this.previousTransform) { if (this.previousTransform) {
restoredImage.set(this.previousTransform); restoredImage.set(this.previousTransform);
} }
// 设置图层属性 // 设置图层属性
restoredImage.set({ restoredImage.set({
id: this.previousObjectId, id: this.previousObjectId,
layerId: this.targetLayer.id, layerId: this.targetLayer.id,
layerName: this.targetLayer.name, layerName: this.targetLayer.name,
isBackground: this.targetLayer.isBackground, isBackground: this.targetLayer.isBackground,
isFixed: this.targetLayer.isFixed, isFixed: this.targetLayer.isFixed,
});
// 使用帮助函数在正确的z-index位置恢复对象
if (
this.previousZIndex !== undefined &&
this.previousZIndex >= 0
) {
const insertSuccess = insertObjectAtZIndex(
this.canvas,
restoredImage,
this.previousZIndex,
false
);
if (insertSuccess) {
console.log(`恢复图像到z-index位置: ${this.previousZIndex}`);
} else {
// 如果插入失败,回退到普通添加
this.canvas.add(restoredImage);
console.log("z-index恢复失败恢复图像添加到顶层");
}
} else {
// 如果没有保存的z-index添加到顶层
this.canvas.add(restoredImage);
console.log("恢复图像添加到顶层");
}
restoredImage.setCoords();
// 更新引用
this.targetLayer.fabricObject = restoredImage.toObject([
"id",
"layerId",
"type",
]);
this.layerManager.updateLayerObject(
this.targetLayer.id,
restoredImage
);
}); });
resolve(); // 使用帮助函数在正确的z-index位置恢复对象
} catch (error) { if (this.previousZIndex !== undefined && this.previousZIndex >= 0) {
reject(error); const insertSuccess = insertObjectAtZIndex(
} this.canvas,
restoredImage,
this.previousZIndex,
false
);
if (insertSuccess) {
console.log(`恢复图像到z-index位置: ${this.previousZIndex}`);
} else {
// 如果插入失败,回退到普通添加
this.canvas.add(restoredImage);
console.log("z-index恢复失败恢复图像添加到顶层");
}
} else {
// 如果没有保存的z-index添加到顶层
this.canvas.add(restoredImage);
console.log("恢复图像添加到顶层");
}
restoredImage.setCoords();
// 更新引用
this.targetLayer.fabricObject = restoredImage.toObject(["id", "layerId", "type"]);
this.layerManager.updateLayerObject(this.targetLayer.id, restoredImage);
});
resolve();
} catch (error) {
reject(error);
} }
); });
}); });
} }
async removeCurrentImage() { async removeCurrentImage() {
await optimizeCanvasRendering(this.canvas, async () => { await optimizeCanvasRendering(this.canvas, async () => {
if (this.newObjectId) { if (this.newObjectId) {
const { object: currentObject } = findObjectById( const { object: currentObject } = findObjectById(this.canvas, this.newObjectId);
this.canvas,
this.newObjectId
);
if (currentObject) { if (currentObject) {
const removeSuccess = removeCanvasObjectByObject( const removeSuccess = removeCanvasObjectByObject(this.canvas, currentObject);
this.canvas,
currentObject
);
if (removeSuccess) { if (removeSuccess) {
this.targetLayer.fabricObject = null; this.targetLayer.fabricObject = null;
this.layerManager.updateLayerObject(this.targetLayer.id, null); this.layerManager.updateLayerObject(this.targetLayer.id, null);
@@ -3797,10 +3627,7 @@ export class AddImageToLayerCommand extends Command {
this.validateInputs(); this.validateInputs();
// 查找目标图层 // 查找目标图层
const { layer } = findLayerRecursively( const { layer } = findLayerRecursively(this.layerManager?.layers?.value || [], this.layerId);
this.layerManager?.layers?.value || [],
this.layerId
);
this.targetLayer = layer; this.targetLayer = layer;
@@ -3859,10 +3686,7 @@ export class AddImageToLayerCommand extends Command {
this.canvas.remove(this.addedObject); this.canvas.remove(this.addedObject);
// 从图层管理器中移除 // 从图层管理器中移除
this.layerManager.removeObjectFromLayer( this.layerManager.removeObjectFromLayer(this.addedObject.id, this.layerId);
this.addedObject.id,
this.layerId
);
this.isExecuted = false; this.isExecuted = false;
@@ -3925,19 +3749,14 @@ export class AddImageToLayerCommand extends Command {
this.retryCount = attempt; this.retryCount = attempt;
if (attempt === this.maxRetries) { if (attempt === this.maxRetries) {
throw new Error( throw new Error(`图像加载失败,已重试${this.maxRetries}次: ${error.message}`);
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
);
} }
// 指数退避重试 // 指数退避重试
const delay = Math.pow(2, attempt) * 1000; const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay)); await new Promise((resolve) => setTimeout(resolve, delay));
console.warn( console.warn(`图像加载重试 ${attempt + 1}/${this.maxRetries}:`, error.message);
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
error.message
);
} }
} }
} }
@@ -3945,9 +3764,7 @@ export class AddImageToLayerCommand extends Command {
loadImage() { loadImage() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
reject( reject(new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`));
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
);
}, this.timeoutMs); }, this.timeoutMs);
fabric.Image.fromURL( fabric.Image.fromURL(
@@ -4057,9 +3874,7 @@ export class RemoveChildLayerCommand extends Command {
this.parentLayer = parent; this.parentLayer = parent;
this.childIndex = this.childIndex =
this.parentLayer?.children?.findIndex( this.parentLayer?.children?.findIndex((child) => child.id === this.layerId) ?? -1;
(child) => child.id === this.layerId
) ?? -1;
this.removedChild = this.parentLayer?.children?.[this.childIndex]; this.removedChild = this.parentLayer?.children?.[this.childIndex];
this.isActiveLayer = this.layerId === this.activeLayerId.value; this.isActiveLayer = this.layerId === this.activeLayerId.value;
@@ -4074,10 +3889,7 @@ export class RemoveChildLayerCommand extends Command {
} }
// 从画布中移除子图层中的所有对象 // 从画布中移除子图层中的所有对象
if ( if (this.removedChild.fabricObjects && this.removedChild.fabricObjects.length > 0) {
this.removedChild.fabricObjects &&
this.removedChild.fabricObjects.length > 0
) {
this.removedChild.fabricObjects.forEach((obj) => { this.removedChild.fabricObjects.forEach((obj) => {
const { object } = findObjectById(this.canvas, obj.id); const { object } = findObjectById(this.canvas, obj.id);
if (object) { if (object) {
@@ -4096,9 +3908,7 @@ export class RemoveChildLayerCommand extends Command {
this.activeLayerId.value = this.parentLayer.children[0].id; this.activeLayerId.value = this.parentLayer.children[0].id;
} else { } else {
this.activeLayerId.value = this.activeLayerId.value =
this.layers.value.find( this.layers.value.find((layer) => !layer.isBackground || !layer.isFixed)?.[0]?.id || null;
(layer) => !layer.isBackground || !layer.isFixed
)?.[0]?.id || null;
} }
} }
@@ -4233,10 +4043,7 @@ export class ChildLayerLockCommand extends Command {
this.layerManager = options.layerManager; this.layerManager = options.layerManager;
// 查找父图层和子图层 // 查找父图层和子图层
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
this.parentLayer = parent; this.parentLayer = parent;
this.childLayer = layer; this.childLayer = layer;
this.oldLocked = this.childLayer ? this.childLayer.locked : null; this.oldLocked = this.childLayer ? this.childLayer.locked : null;

View File

@@ -21,9 +21,7 @@ export class LiquifyCommand extends Command {
constructor(options) { constructor(options) {
super({ super({
name: options.name || `液化操作: ${options.mode || "未知模式"}`, name: options.name || `液化操作: ${options.mode || "未知模式"}`,
description: description: options.description || `使用${options.mode || "未知模式"}模式进行液化操作`,
options.description ||
`使用${options.mode || "未知模式"}模式进行液化操作`,
}); });
this.canvas = options.canvas; this.canvas = options.canvas;
@@ -96,9 +94,7 @@ export class LiquifyCommand extends Command {
// 对于图像对象我们需要保存src和元数据 // 对于图像对象我们需要保存src和元数据
const state = { const state = {
src: this.targetObject.getSrc ? this.targetObject.getSrc() : null, src: this.targetObject.getSrc ? this.targetObject.getSrc() : null,
element: this.targetObject._element element: this.targetObject._element ? this.targetObject._element.cloneNode(true) : null,
? this.targetObject._element.cloneNode(true)
: null,
filters: this.targetObject.filters ? [...this.targetObject.filters] : [], filters: this.targetObject.filters ? [...this.targetObject.filters] : [],
originalData: this.originalData, originalData: this.originalData,
targetLayerId: this.targetLayerId, targetLayerId: this.targetLayerId,
@@ -172,10 +168,7 @@ export class LiquifyCommand extends Command {
// 确保图层引用更新 // 确保图层引用更新
const layer = this.layerManager.getLayerById(this.targetLayerId); const layer = this.layerManager.getLayerById(this.targetLayerId);
if (layer) { if (layer) {
if ( if (layer.type === "background" && layer.fabricObject === this.targetObject) {
layer.type === "background" &&
layer.fabricObject === this.targetObject
) {
layer.fabricObject = img; layer.fabricObject = img;
} else if (layer.fabricObjects) { } else if (layer.fabricObjects) {
const objIndex = layer.fabricObjects.indexOf(this.targetObject); const objIndex = layer.fabricObjects.indexOf(this.targetObject);
@@ -251,9 +244,7 @@ export class LiquifyDeformCommand extends LiquifyCommand {
super({ super({
...options, ...options,
name: `液化变形: ${options.mode || "未知模式"}`, name: `液化变形: ${options.mode || "未知模式"}`,
description: `在(${options.x}, ${options.y})应用${ description: `在(${options.x}, ${options.y})应用${options.mode || "未知模式"}变形`,
options.mode || "未知模式"
}变形`,
}); });
this.x = options.x; this.x = options.x;
@@ -343,9 +334,7 @@ export class LiquifyDeformCommand extends LiquifyCommand {
layer.fabricObject = img; layer.fabricObject = img;
} else if (layer.fabricObjects) { } else if (layer.fabricObjects) {
const objIndex = layer.fabricObjects.findIndex( const objIndex = layer.fabricObjects.findIndex(
(obj) => (obj) => obj === this.savedState?.originalObject || obj === this.targetObject
obj === this.savedState?.originalObject ||
obj === this.targetObject
); );
if (objIndex !== -1) { if (objIndex !== -1) {
layer.fabricObjects[objIndex] = img; layer.fabricObjects[objIndex] = img;
@@ -374,8 +363,7 @@ export class CompositeLiquifyCommand extends Command {
constructor(options) { constructor(options) {
super({ super({
name: options.name || "液化操作组合", name: options.name || "液化操作组合",
description: description: options.description || `包含${options.commands?.length || 0}个液化操作`,
options.description || `包含${options.commands?.length || 0}个液化操作`,
}); });
this.commands = options.commands || []; this.commands = options.commands || [];
@@ -551,10 +539,7 @@ export class LiquifyStateCommand extends Command {
} }
// 通过引用管理器更新图像数据 // 通过引用管理器更新图像数据
await this.refManager.updateObjectImageData( await this.refManager.updateObjectImageData(this.objectRefId, this.finalImageData);
this.objectRefId,
this.finalImageData
);
// 恢复液化管理器到最终状态 // 恢复液化管理器到最终状态
if (this.liquifyManager && this.finalLiquifyState) { if (this.liquifyManager && this.finalLiquifyState) {
@@ -585,10 +570,7 @@ export class LiquifyStateCommand extends Command {
} }
// 通过引用管理器恢复到初始快照 // 通过引用管理器恢复到初始快照
await this.refManager.restoreFromSnapshot( await this.refManager.restoreFromSnapshot(this.objectRefId, this.initialSnapshotId);
this.objectRefId,
this.initialSnapshotId
);
// 恢复液化管理器到初始状态 - 关键修复 // 恢复液化管理器到初始状态 - 关键修复
if (this.liquifyManager) { if (this.liquifyManager) {
@@ -604,18 +586,16 @@ export class LiquifyStateCommand extends Command {
// 强制设置原始图像数据为初始状态 // 强制设置原始图像数据为初始状态
if (this.liquifyManager.enhancedManager) { if (this.liquifyManager.enhancedManager) {
this.liquifyManager.enhancedManager.originalImageData = this.liquifyManager.enhancedManager.originalImageData = this.initialImageData;
this.initialImageData; this.liquifyManager.enhancedManager.currentImageData = this._cloneImageData(
this.liquifyManager.enhancedManager.currentImageData = this.initialImageData
this._cloneImageData(this.initialImageData); );
// 重置激活渲染器的数据 // 重置激活渲染器的数据
if (this.liquifyManager.enhancedManager.activeRenderer) { if (this.liquifyManager.enhancedManager.activeRenderer) {
const renderer = this.liquifyManager.enhancedManager.activeRenderer; const renderer = this.liquifyManager.enhancedManager.activeRenderer;
renderer.originalImageData = this.initialImageData; renderer.originalImageData = this.initialImageData;
renderer.currentImageData = this._cloneImageData( renderer.currentImageData = this._cloneImageData(this.initialImageData);
this.initialImageData
);
// 重置CPU渲染器的网格和变形历史 // 重置CPU渲染器的网格和变形历史
if (renderer.reset) { if (renderer.reset) {
@@ -741,8 +721,7 @@ export class LiquifyStateCommand extends Command {
timestamp: Date.now(), timestamp: Date.now(),
properties: this.refManager._captureObjectState(fabricObject), properties: this.refManager._captureObjectState(fabricObject),
imageData: this.initialImageData, imageData: this.initialImageData,
eventListeners: eventListeners: this.refManager.eventListeners.get(this.objectRefId) || {},
this.refManager.eventListeners.get(this.objectRefId) || {},
}; };
this.refManager.stateSnapshots.set(this.initialSnapshotId, snapshot); this.refManager.stateSnapshots.set(this.initialSnapshotId, snapshot);
@@ -772,8 +751,7 @@ export class LiquifyStateCommand extends Command {
timestamp: Date.now(), timestamp: Date.now(),
properties: this.refManager._captureObjectState(fabricObject), properties: this.refManager._captureObjectState(fabricObject),
imageData: this.finalImageData, imageData: this.finalImageData,
eventListeners: eventListeners: this.refManager.eventListeners.get(this.objectRefId) || {},
this.refManager.eventListeners.get(this.objectRefId) || {},
}; };
this.refManager.stateSnapshots.set(this.finalSnapshotId, snapshot); this.refManager.stateSnapshots.set(this.finalSnapshotId, snapshot);
@@ -804,8 +782,7 @@ export class LiquifyStateCommand extends Command {
// 捕获当前渲染器状态 // 捕获当前渲染器状态
activeRendererState: null, activeRendererState: null,
// 捕获目标对象引用 // 捕获目标对象引用
targetObjectRef: targetObjectRef: this.liquifyManager.targetObject ?? enhancedManager.targetObject,
this.liquifyManager.targetObject ?? enhancedManager.targetObject,
// 捕获初始化状态 // 捕获初始化状态
initialized: this.liquifyManager.initialized || false, initialized: this.liquifyManager.initialized || false,
}; };
@@ -832,13 +809,9 @@ export class LiquifyStateCommand extends Command {
params: { ...renderer.params }, params: { ...renderer.params },
currentMode: renderer.currentMode, currentMode: renderer.currentMode,
// 对于CPU渲染器还需要保存网格状态 // 对于CPU渲染器还需要保存网格状态
meshState: renderer.mesh meshState: renderer.mesh ? this._captureMeshState(renderer.mesh) : null,
? this._captureMeshState(renderer.mesh)
: null,
// 保存变形历史 // 保存变形历史
deformHistory: renderer.deformHistory deformHistory: renderer.deformHistory ? [...renderer.deformHistory] : [],
? [...renderer.deformHistory]
: [],
}; };
} }
} }
@@ -871,9 +844,7 @@ export class LiquifyStateCommand extends Command {
this.realtimeUpdater.targetObject = this.liquifyManager.targetObject; this.realtimeUpdater.targetObject = this.liquifyManager.targetObject;
// 如果有setTargetObject方法调用它 // 如果有setTargetObject方法调用它
if (typeof this.realtimeUpdater.setTargetObject === "function") { if (typeof this.realtimeUpdater.setTargetObject === "function") {
this.realtimeUpdater.setTargetObject( this.realtimeUpdater.setTargetObject(this.liquifyManager.targetObject);
this.liquifyManager.targetObject
);
} }
} }
@@ -911,10 +882,8 @@ export class LiquifyStateCommand extends Command {
} else if (renderer.mesh && renderer._initMesh) { } else if (renderer.mesh && renderer._initMesh) {
// 如果没有保存的网格状态,重新初始化网格 // 如果没有保存的网格状态,重新初始化网格
renderer._initMesh( renderer._initMesh(
rendererState.originalImageData?.width || rendererState.originalImageData?.width || renderer.originalImageData?.width,
renderer.originalImageData?.width, rendererState.originalImageData?.height || renderer.originalImageData?.height
rendererState.originalImageData?.height ||
renderer.originalImageData?.height
); );
} }
@@ -978,13 +947,7 @@ export class LiquifyStateCommand extends Command {
* @private * @private
*/ */
_restoreMeshState(mesh, meshState) { _restoreMeshState(mesh, meshState) {
if ( if (!mesh || !meshState || !Array.isArray(mesh) || !Array.isArray(meshState)) return;
!mesh ||
!meshState ||
!Array.isArray(mesh) ||
!Array.isArray(meshState)
)
return;
for (let i = 0; i < Math.min(mesh.length, meshState.length); i++) { for (let i = 0; i < Math.min(mesh.length, meshState.length); i++) {
for (let j = 0; j < Math.min(mesh[i].length, meshState[i].length); j++) { for (let j = 0; j < Math.min(mesh[i].length, meshState[i].length); j++) {
@@ -1043,8 +1006,7 @@ export class BatchLiquifyStateCommand extends Command {
constructor(options) { constructor(options) {
super({ super({
name: options.name || "批量液化操作", name: options.name || "批量液化操作",
description: description: options.description || `批量液化${options.commands?.length || 0}个对象`,
options.description || `批量液化${options.commands?.length || 0}个对象`,
}); });
this.commands = options.commands || []; this.commands = options.commands || [];
@@ -1117,9 +1079,7 @@ export class BatchLiquifyStateCommand extends Command {
* @returns {Boolean} 是否有效 * @returns {Boolean} 是否有效
*/ */
isValid() { isValid() {
return ( return this.commands.length > 0 && this.commands.every((cmd) => cmd.isValid());
this.commands.length > 0 && this.commands.every((cmd) => cmd.isValid())
);
} }
/** /**

View File

@@ -34,11 +34,7 @@ export class SetActiveLayerCommand extends Command {
} }
execute() { execute() {
const { layer } = findLayerRecursively( const { layer } = findLayerRecursively(this.layers.value, this.layerId, this.parentId);
this.layers.value,
this.layerId,
this.parentId
);
this.newLayer = layer; this.newLayer = layer;
if (!this.newLayer) { if (!this.newLayer) {
@@ -68,10 +64,7 @@ export class SetActiveLayerCommand extends Command {
this.canvas.discardActiveObject(); this.canvas.discardActiveObject();
// 设置为新的图层下的对象为激活,但需要确保对象存在于画布上 // 设置为新的图层下的对象为激活,但需要确保对象存在于画布上
if ( if (this.newLayer.fabricObjects && this.newLayer.fabricObjects.length > 0) {
this.newLayer.fabricObjects &&
this.newLayer.fabricObjects.length > 0
) {
const canvasObjects = this.canvas.getObjects(); const canvasObjects = this.canvas.getObjects();
const validObjects = this.newLayer.fabricObjects.filter( const validObjects = this.newLayer.fabricObjects.filter(
(obj) => obj && canvasObjects.includes(obj) (obj) => obj && canvasObjects.includes(obj)
@@ -153,9 +146,7 @@ export class AddObjectToLayerCommand extends Command {
this.layerManager = options.layerManager; this.layerManager = options.layerManager;
// 保存对象原始状态和ID // 保存对象原始状态和ID
this.objectId = this.objectId = this.fabricObject.id || `obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
this.fabricObject.id ||
`obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
this.fabricObject.set({ this.fabricObject.set({
id: this.objectId, id: this.objectId,
layerId: options.layerId, layerId: options.layerId,
@@ -163,11 +154,7 @@ export class AddObjectToLayerCommand extends Command {
}); });
// 保存完整的对象状态,包括位置和变换信息 // 保存完整的对象状态,包括位置和变换信息
this.originalObjectState = this.fabricObject.toObject([ this.originalObjectState = this.fabricObject.toObject(["id", "layerId", "layerName"]);
"id",
"layerId",
"layerName",
]);
// // 新增:保存详细的位置和变换信息,用于重做时恢复 // // 新增:保存详细的位置和变换信息,用于重做时恢复
// this.originalPosition = { // this.originalPosition = {
@@ -279,8 +266,7 @@ export class AddObjectToLayerCommand extends Command {
// 将对象添加到图层的fabricObjects数组 // 将对象添加到图层的fabricObjects数组
layer.fabricObjects = layer.fabricObjects || []; layer.fabricObjects = layer.fabricObjects || [];
layer.fabricObjects.push( layer.fabricObjects.push(
this.fabricObject?.toObject?.(["id", "layerId", "layerName"]) || this.fabricObject?.toObject?.(["id", "layerId", "layerName"]) || this.fabricObject
this.fabricObject
); );
await this.layerManager?.updateLayersObjectsInteractivity?.(false); await this.layerManager?.updateLayersObjectsInteractivity?.(false);
@@ -327,16 +313,12 @@ export class AddObjectToLayerCommand extends Command {
// 从图层的fabricObjects数组中移除对象 // 从图层的fabricObjects数组中移除对象
if (layer.fabricObjects) { if (layer.fabricObjects) {
layer.fabricObjects = layer.fabricObjects.filter( layer.fabricObjects = layer.fabricObjects.filter((obj) => obj.id !== this.objectId);
(obj) => obj.id !== this.objectId
);
} }
// 优化渲染 - 统一批处理 支持异步回调 // 优化渲染 - 统一批处理 支持异步回调
optimizeCanvasRendering(this.canvas, async () => { optimizeCanvasRendering(this.canvas, async () => {
// 从画布移除对象 // 从画布移除对象
const object = this.canvas const object = this.canvas.getObjects().find((obj) => obj.id === this.objectId);
.getObjects()
.find((obj) => obj.id === this.objectId);
if (object) { if (object) {
// 先丢弃活动对象,避免控制点渲染错误 // 先丢弃活动对象,避免控制点渲染错误
this.canvas.discardActiveObject(); this.canvas.discardActiveObject();
@@ -410,9 +392,7 @@ export class RemoveObjectFromLayerCommand extends Command {
// 从图层的fabricObjects数组移除对象 // 从图层的fabricObjects数组移除对象
if (layer.fabricObjects) { if (layer.fabricObjects) {
layer.fabricObjects = layer.fabricObjects.filter( layer.fabricObjects = layer.fabricObjects.filter((obj) => obj.id !== this.objectId);
(obj) => obj.id !== this.objectId
);
} }
// 更新画布 // 更新画布
@@ -639,19 +619,14 @@ export class ChangeFixedImageCommand extends Command {
this.retryCount = attempt; this.retryCount = attempt;
if (attempt === this.maxRetries) { if (attempt === this.maxRetries) {
throw new Error( throw new Error(`图像加载失败,已重试${this.maxRetries}次: ${error.message}`);
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
);
} }
// 指数退避重试 // 指数退避重试
const delay = Math.pow(2, attempt) * 1000; const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay)); await new Promise((resolve) => setTimeout(resolve, delay));
console.warn( console.warn(`图像加载重试 ${attempt + 1}/${this.maxRetries}:`, error.message);
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
error.message
);
} }
} }
} }
@@ -659,9 +634,7 @@ export class ChangeFixedImageCommand extends Command {
loadImage() { loadImage() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
reject( reject(new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`));
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
);
}, this.timeoutMs); }, this.timeoutMs);
fabric.Image.fromURL( fabric.Image.fromURL(
@@ -697,15 +670,9 @@ export class ChangeFixedImageCommand extends Command {
// 移除旧对象(如果存在) // 移除旧对象(如果存在)
if (this.previousObjectId) { if (this.previousObjectId) {
const { object: oldObject } = findObjectById( const { object: oldObject } = findObjectById(this.canvas, this.previousObjectId);
this.canvas,
this.previousObjectId
);
if (oldObject) { if (oldObject) {
const removeSuccess = removeCanvasObjectByObject( const removeSuccess = removeCanvasObjectByObject(this.canvas, oldObject);
this.canvas,
oldObject
);
if (!removeSuccess) { if (!removeSuccess) {
console.warn("移除旧对象失败,但继续执行"); console.warn("移除旧对象失败,但继续执行");
} }
@@ -771,93 +738,75 @@ export class ChangeFixedImageCommand extends Command {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fabric.util.enlivenObjects( fabric.util.enlivenObjects([this.previousImage.objectData], async (objects) => {
[this.previousImage.objectData], try {
async (objects) => { const restoredImage = objects[0];
try {
const restoredImage = objects[0];
await optimizeCanvasRendering(this.canvas, async () => { await optimizeCanvasRendering(this.canvas, async () => {
// 移除当前对象 // 移除当前对象
if (this.newObjectId) { if (this.newObjectId) {
const { object: currentObject } = findObjectById( const { object: currentObject } = findObjectById(this.canvas, this.newObjectId);
this.canvas, if (currentObject) {
this.newObjectId removeCanvasObjectByObject(this.canvas, currentObject);
);
if (currentObject) {
removeCanvasObjectByObject(this.canvas, currentObject);
}
} }
}
// 恢复之前的变换 // 恢复之前的变换
if (this.previousTransform) { if (this.previousTransform) {
restoredImage.set(this.previousTransform); restoredImage.set(this.previousTransform);
} }
// 设置图层属性 // 设置图层属性
restoredImage.set({ restoredImage.set({
id: this.previousObjectId, id: this.previousObjectId,
layerId: this.targetLayer.id, layerId: this.targetLayer.id,
layerName: this.targetLayer.name, layerName: this.targetLayer.name,
isBackground: this.targetLayer.isBackground, isBackground: this.targetLayer.isBackground,
isFixed: this.targetLayer.isFixed, isFixed: this.targetLayer.isFixed,
});
// 使用帮助函数在正确的z-index位置恢复对象
if (
this.previousZIndex !== undefined &&
this.previousZIndex >= 0
) {
const insertSuccess = insertObjectAtZIndex(
this.canvas,
restoredImage,
this.previousZIndex,
false
);
if (insertSuccess) {
console.log(`恢复图像到z-index位置: ${this.previousZIndex}`);
} else {
// 如果插入失败,回退到普通添加
this.canvas.add(restoredImage);
console.log("z-index恢复失败恢复图像添加到顶层");
}
} else {
// 如果没有保存的z-index添加到顶层
this.canvas.add(restoredImage);
console.log("恢复图像添加到顶层");
}
restoredImage.setCoords();
// 更新引用
this.targetLayer.fabricObject = restoredImage;
this.layerManager.updateLayerObject(
this.targetLayer.id,
restoredImage
);
}); });
resolve(); // 使用帮助函数在正确的z-index位置恢复对象
} catch (error) { if (this.previousZIndex !== undefined && this.previousZIndex >= 0) {
reject(error); const insertSuccess = insertObjectAtZIndex(
} this.canvas,
restoredImage,
this.previousZIndex,
false
);
if (insertSuccess) {
console.log(`恢复图像到z-index位置: ${this.previousZIndex}`);
} else {
// 如果插入失败,回退到普通添加
this.canvas.add(restoredImage);
console.log("z-index恢复失败恢复图像添加到顶层");
}
} else {
// 如果没有保存的z-index添加到顶层
this.canvas.add(restoredImage);
console.log("恢复图像添加到顶层");
}
restoredImage.setCoords();
// 更新引用
this.targetLayer.fabricObject = restoredImage;
this.layerManager.updateLayerObject(this.targetLayer.id, restoredImage);
});
resolve();
} catch (error) {
reject(error);
} }
); });
}); });
} }
async removeCurrentImage() { async removeCurrentImage() {
await optimizeCanvasRendering(this.canvas, async () => { await optimizeCanvasRendering(this.canvas, async () => {
if (this.newObjectId) { if (this.newObjectId) {
const { object: currentObject } = findObjectById( const { object: currentObject } = findObjectById(this.canvas, this.newObjectId);
this.canvas,
this.newObjectId
);
if (currentObject) { if (currentObject) {
const removeSuccess = removeCanvasObjectByObject( const removeSuccess = removeCanvasObjectByObject(this.canvas, currentObject);
this.canvas,
currentObject
);
if (removeSuccess) { if (removeSuccess) {
this.targetLayer.fabricObject = null; this.targetLayer.fabricObject = null;
this.layerManager.updateLayerObject(this.targetLayer.id, null); this.layerManager.updateLayerObject(this.targetLayer.id, null);
@@ -976,10 +925,7 @@ export class AddImageToLayerCommand extends Command {
this.canvas.remove(this.addedObject); this.canvas.remove(this.addedObject);
// 从图层管理器中移除 // 从图层管理器中移除
this.layerManager.removeObjectFromLayer( this.layerManager.removeObjectFromLayer(this.addedObject.id, this.layerId);
this.addedObject.id,
this.layerId
);
this.isExecuted = false; this.isExecuted = false;
@@ -1042,19 +988,14 @@ export class AddImageToLayerCommand extends Command {
this.retryCount = attempt; this.retryCount = attempt;
if (attempt === this.maxRetries) { if (attempt === this.maxRetries) {
throw new Error( throw new Error(`图像加载失败,已重试${this.maxRetries}次: ${error.message}`);
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
);
} }
// 指数退避重试 // 指数退避重试
const delay = Math.pow(2, attempt) * 1000; const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay)); await new Promise((resolve) => setTimeout(resolve, delay));
console.warn( console.warn(`图像加载重试 ${attempt + 1}/${this.maxRetries}:`, error.message);
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
error.message
);
} }
} }
} }
@@ -1062,9 +1003,7 @@ export class AddImageToLayerCommand extends Command {
loadImage() { loadImage() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
reject( reject(new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`));
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
);
}, this.timeoutMs); }, this.timeoutMs);
fabric.Image.fromURL( fabric.Image.fromURL(
@@ -1318,23 +1257,16 @@ export class MoveLayerToTopCommand extends Command {
this.parentLayer = null; // 父图层 this.parentLayer = null; // 父图层
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶 // 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
if (parent?.id) { if (parent?.id) {
// 查找子图层索引 // 查找子图层索引
this.layerIndex = parent?.children?.findIndex( this.layerIndex = parent?.children?.findIndex((layer) => layer.id === this.layerId);
(layer) => layer.id === this.layerId
);
this.parentLayer = parent; this.parentLayer = parent;
} else { } else {
// 查找图层索引 // 查找图层索引
this.layerIndex = this.layers.value.findIndex( this.layerIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
(layer) => layer.id === this.layerId
);
} }
this.layer = layer; this.layer = layer;
@@ -1416,23 +1348,16 @@ export class MoveLayerToBottomCommand extends Command {
this.parentLayer = null; // 父图层 this.parentLayer = null; // 父图层
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶 // 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
if (parent?.id) { if (parent?.id) {
// 查找子图层索引 // 查找子图层索引
this.layerIndex = parent?.children?.findIndex( this.layerIndex = parent?.children?.findIndex((layer) => layer.id === this.layerId);
(layer) => layer.id === this.layerId
);
this.parentLayer = parent; this.parentLayer = parent;
} else { } else {
// 查找图层索引 // 查找图层索引
this.layerIndex = this.layers.value.findIndex( this.layerIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
(layer) => layer.id === this.layerId
);
} }
this.layer = layer; this.layer = layer;
@@ -1473,9 +1398,7 @@ export class MoveLayerToBottomCommand extends Command {
async undo() { async undo() {
if (this.originalIndex !== -1 && this.layer) { if (this.originalIndex !== -1 && this.layer) {
// 获取当前位置 // 获取当前位置
const currentIndex = this.layers.value.findIndex( const currentIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
(layer) => layer.id === this.layerId
);
if (currentIndex !== -1) { if (currentIndex !== -1) {
// 移除当前位置 // 移除当前位置
@@ -1526,16 +1449,11 @@ export class MoveLayerToBottomCommand extends Command {
if (layer.isBackground && layer.fabricObject) { if (layer.isBackground && layer.fabricObject) {
// 背景图层 // 背景图层
const originalObj = canvasObjects.find( const originalObj = canvasObjects.find((o) => o.id === layer.fabricObject.id);
(o) => o.id === layer.fabricObject.id
);
if (originalObj) { if (originalObj) {
this.canvas.add(originalObj); this.canvas.add(originalObj);
} }
} else if ( } else if (Array.isArray(layer.fabricObjects) && layer.fabricObjects.length > 0) {
Array.isArray(layer.fabricObjects) &&
layer.fabricObjects.length > 0
) {
// 普通图层和固定图层 // 普通图层和固定图层
layer.fabricObjects.forEach((obj) => { layer.fabricObjects.forEach((obj) => {
const originalObj = canvasObjects.find((o) => o.id === obj.id); const originalObj = canvasObjects.find((o) => o.id === obj.id);

View File

@@ -1,11 +1,6 @@
import { Command } from "./Command"; import { Command } from "./Command";
import { fabric } from "fabric-with-all"; import { fabric } from "fabric-with-all";
import { import { LayerType, OperationType, createLayer, findLayerRecursively } from "../utils/layerHelper";
LayerType,
OperationType,
createLayer,
findLayerRecursively,
} from "../utils/layerHelper";
import { import {
generateId, generateId,
optimizeCanvasRendering, optimizeCanvasRendering,
@@ -39,10 +34,7 @@ export class RasterizeLayerCommand extends Command {
this.layerManager = options.layerManager; this.layerManager = options.layerManager;
// 查找目标图层 // 查找目标图层
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
this.layer = layer; this.layer = layer;
this.parentLayer = parent; this.parentLayer = parent;
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0; this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
@@ -102,9 +94,7 @@ export class RasterizeLayerCommand extends Command {
console.log(`✅ 图层 ${this.layer.name} 栅格化完成`); console.log(`✅ 图层 ${this.layer.name} 栅格化完成`);
this.canvas?.thumbnailManager?.generateLayerThumbnail?.( this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.rasterizedLayerId);
this.rasterizedLayerId
);
return this.rasterizedLayerId; return this.rasterizedLayerId;
} catch (error) { } catch (error) {
console.error("栅格化图层失败:", error); console.error("栅格化图层失败:", error);
@@ -350,10 +340,7 @@ export class RasterizeLayerCommand extends Command {
*/ */
_replaceLayerInStructure() { _replaceLayerInStructure() {
// 1.当前如果是子图层,则插入到子图层的位置 // 1.当前如果是子图层,则插入到子图层的位置
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
let insertIndex = 0; let insertIndex = 0;
// 说明是子图层 // 说明是子图层
@@ -374,11 +361,7 @@ export class RasterizeLayerCommand extends Command {
if (insertIndex !== -1) { if (insertIndex !== -1) {
if (parent) { if (parent) {
const pIndex = this.layers.value.findIndex((l) => l.id === parent.id); const pIndex = this.layers.value.findIndex((l) => l.id === parent.id);
this.layers.value[pIndex].children?.splice?.( this.layers.value[pIndex].children?.splice?.(insertIndex, 1, this.rasterizedLayer);
insertIndex,
1,
this.rasterizedLayer
);
} else { } else {
this.layers.value.splice(insertIndex, 1, this.rasterizedLayer); this.layers.value.splice(insertIndex, 1, this.rasterizedLayer);
} }
@@ -396,14 +379,9 @@ export class RasterizeLayerCommand extends Command {
async _getMaskObject() { async _getMaskObject() {
// 如果图层有clippingMask获取对应的fabric对象 // 如果图层有clippingMask获取对应的fabric对象
if (this.layer?.clippingMask) { if (this.layer?.clippingMask) {
const { object: maskObject } = findObjectById( const { object: maskObject } = findObjectById(this.canvas, this.layer.clippingMask.id);
this.canvas,
this.layer.clippingMask.id
);
if (maskObject) { if (maskObject) {
console.log( console.log(`📎 找到遮罩对象: ${maskObject.id}, 类型: ${maskObject.type}`);
`📎 找到遮罩对象: ${maskObject.id}, 类型: ${maskObject.type}`
);
return maskObject; return maskObject;
} }
return await restoreFabricObject(this.layer.clippingMask); return await restoreFabricObject(this.layer.clippingMask);
@@ -449,10 +427,7 @@ export class ExportLayerToImageCommand extends Command {
this.layerManager = options.layerManager; this.layerManager = options.layerManager;
// 查找目标图层 // 查找目标图层
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
this.layers.value,
this.layerId
);
this.layer = layer; this.layer = layer;
this.parentLayer = parent; this.parentLayer = parent;
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0; this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;

View File

@@ -1,8 +1,4 @@
import { import { findObjectById, generateId, optimizeCanvasRendering } from "../utils/helper.js";
findObjectById,
generateId,
optimizeCanvasRendering,
} from "../utils/helper.js";
import { imageModeHandler } from "../utils/imageHelper.js"; import { imageModeHandler } from "../utils/imageHelper.js";
import { LayerType, OperationType } from "../utils/layerHelper.js"; import { LayerType, OperationType } from "../utils/layerHelper.js";
import { Command, CompositeCommand } from "./Command.js"; import { Command, CompositeCommand } from "./Command.js";
@@ -58,9 +54,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
const layers = this.layerManager.layers?.value || []; const layers = this.layerManager.layers?.value || [];
const backgroundLayer = layers.find((layer) => layer.isBackground); const backgroundLayer = layers.find((layer) => layer.isBackground);
const fixedLayer = layers.find((layer) => layer.isFixed); const fixedLayer = layers.find((layer) => layer.isFixed);
const normalLayers = layers.filter( const normalLayers = layers.filter((layer) => !layer.isBackground && !layer.isFixed);
(layer) => !layer.isBackground && !layer.isFixed
);
if (!backgroundLayer || !fixedLayer || normalLayers.length === 0) { if (!backgroundLayer || !fixedLayer || normalLayers.length === 0) {
throw new Error("缺少必要的图层结构"); throw new Error("缺少必要的图层结构");
@@ -76,9 +70,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
} }
: null; : null;
this.originalFixedObjects = fixedLayer.fabricObject this.originalFixedObjects = fixedLayer.fabricObject ? [fixedLayer.fabricObject] : [];
? [fixedLayer.fabricObject]
: [];
this.originalNormalObjects = normalLayer.fabricObjects this.originalNormalObjects = normalLayer.fabricObjects
? [...normalLayer.fabricObjects] ? [...normalLayer.fabricObjects]
@@ -118,11 +110,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
await this._setupClothingImage(clothingImg, fixedLayer); await this._setupClothingImage(clothingImg, fixedLayer);
// 7. 设置红绿图到普通图层,位置和大小与衣服底图一致 // 7. 设置红绿图到普通图层,位置和大小与衣服底图一致
await this._setupRedGreenImage( await this._setupRedGreenImage(redGreenImg, normalLayer, this.clothingImage);
redGreenImg,
normalLayer,
this.clothingImage
);
// 4. 确保背景图层大小和衣服地图大小一致 // 4. 确保背景图层大小和衣服地图大小一致
const backgroundObject = await this._setupBackgroundLayer( const backgroundObject = await this._setupBackgroundLayer(
@@ -207,13 +195,9 @@ export class BatchInitializeRedGreenModeCommand extends Command {
async _createAndActivateEmptyLayer() { async _createAndActivateEmptyLayer() {
// 创建新的空白图层 // 创建新的空白图层
const newLayerName = "绘制图层"; const newLayerName = "绘制图层";
const newLayerId = await this.layerManager.createLayer( const newLayerId = await this.layerManager.createLayer(newLayerName, LayerType.BITMAP, {
newLayerName, undoable: false,
LayerType.BITMAP, });
{
undoable: false,
}
);
// 设置为活动图层 // 设置为活动图层
if (newLayerId) { if (newLayerId) {
@@ -228,19 +212,14 @@ export class BatchInitializeRedGreenModeCommand extends Command {
await optimizeCanvasRendering(this.canvas, async () => { await optimizeCanvasRendering(this.canvas, async () => {
// 1. 恢复画布背景 // 1. 恢复画布背景
if (this.originalCanvasBackground !== null) { if (this.originalCanvasBackground !== null) {
this.canvas.setBackgroundColor( this.canvas.setBackgroundColor(this.originalCanvasBackground, () => {});
this.originalCanvasBackground,
() => {}
);
} }
// 2. 恢复图层对象 // 2. 恢复图层对象
const layers = this.layerManager.layers?.value || []; const layers = this.layerManager.layers?.value || [];
const backgroundLayer = layers.find((layer) => layer.isBackground); const backgroundLayer = layers.find((layer) => layer.isBackground);
const fixedLayer = layers.find((layer) => layer.isFixed); const fixedLayer = layers.find((layer) => layer.isFixed);
const normalLayers = layers.filter( const normalLayers = layers.filter((layer) => !layer.isBackground && !layer.isFixed);
(layer) => !layer.isBackground && !layer.isFixed
);
// 移除当前添加的对象 // 移除当前添加的对象
if (this.clothingImage) { if (this.clothingImage) {
@@ -252,9 +231,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
// 移除新创建的空白图层 // 移除新创建的空白图层
if (this.newEmptyLayerId) { if (this.newEmptyLayerId) {
const emptyLayerIndex = layers.findIndex( const emptyLayerIndex = layers.findIndex((layer) => layer.id === this.newEmptyLayerId);
(layer) => layer.id === this.newEmptyLayerId
);
if (emptyLayerIndex !== -1) { if (emptyLayerIndex !== -1) {
layers.splice(emptyLayerIndex, 1); layers.splice(emptyLayerIndex, 1);
} }
@@ -270,9 +247,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
// 恢复固定图层 // 恢复固定图层
if (fixedLayer) { if (fixedLayer) {
fixedLayer.fabricObject = fixedLayer.fabricObject =
this.originalFixedObjects.length > 0 this.originalFixedObjects.length > 0 ? this.originalFixedObjects[0] : null;
? this.originalFixedObjects[0]
: null;
if (fixedLayer.fabricObject) { if (fixedLayer.fabricObject) {
this.canvas.add(fixedLayer.fabricObject); this.canvas.add(fixedLayer.fabricObject);
@@ -311,8 +286,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
// 4. 恢复工具状态 // 4. 恢复工具状态
if (this.toolManager && this.originalToolState) { if (this.toolManager && this.originalToolState) {
this.toolManager.isRedGreenMode = this.toolManager.isRedGreenMode = this.originalToolState.isRedGreenMode;
this.originalToolState.isRedGreenMode;
if (this.originalToolState.currentTool) { if (this.originalToolState.currentTool) {
this.toolManager.setTool(this.originalToolState.currentTool); this.toolManager.setTool(this.originalToolState.currentTool);
} }
@@ -434,10 +408,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
// 清除固定图层原有内容 // 清除固定图层原有内容
if (fixedLayer.fabricObject) { if (fixedLayer.fabricObject) {
const { object } = findObjectById( const { object } = findObjectById(this.canvas, fixedLayer.fabricObject.id);
this.canvas,
fixedLayer.fabricObject.id
);
if (object) this.canvas.remove(object); if (object) this.canvas.remove(object);
} }

View File

@@ -110,9 +110,7 @@ export class AddToSelectionCommand extends Command {
} }
// 添加到选区 // 添加到选区
const result = await this.selectionManager.addToSelection( const result = await this.selectionManager.addToSelection(this.newSelection);
this.newSelection
);
return result; return result;
} }
@@ -155,9 +153,7 @@ export class RemoveFromSelectionCommand extends Command {
} }
// 从选区中移除 // 从选区中移除
const result = await this.selectionManager.removeFromSelection( const result = await this.selectionManager.removeFromSelection(this.removeSelection);
this.removeSelection
);
return result; return result;
} }
@@ -203,9 +199,7 @@ export class FeatherSelectionCommand extends Command {
} }
// 应用羽化 // 应用羽化
const result = await this.selectionManager.featherSelection( const result = await this.selectionManager.featherSelection(this.featherAmount);
this.featherAmount
);
return result; return result;
} }
@@ -347,8 +341,7 @@ export class CopySelectionToNewLayerCommand extends CompositeCommand {
} }
// 确定源图层 // 确定源图层
const sourceId = const sourceId = this.sourceLayerId || this.layerManager.getActiveLayerId();
this.sourceLayerId || this.layerManager.getActiveLayerId();
const sourceLayer = this.layerManager.getLayerById(sourceId); const sourceLayer = this.layerManager.getLayerById(sourceId);
if (!sourceLayer || !sourceLayer.fabricObjects) { if (!sourceLayer || !sourceLayer.fabricObjects) {
console.error("无法复制选区:源图层无效或为空"); console.error("无法复制选区:源图层无效或为空");
@@ -356,10 +349,7 @@ export class CopySelectionToNewLayerCommand extends CompositeCommand {
} }
// 创建新图层 // 创建新图层
this.newLayerId = await this.layerManager.createLayer( this.newLayerId = await this.layerManager.createLayer(this.newLayerName, LayerType.EMPTY);
this.newLayerName,
LayerType.EMPTY
);
// 获取选区内的对象 // 获取选区内的对象
const objectsToCopy = sourceLayer.fabricObjects.filter((obj) => { const objectsToCopy = sourceLayer.fabricObjects.filter((obj) => {

View File

@@ -401,9 +401,7 @@ export class CreateTextCommand extends Command {
// 更新图层的对象列表 // 更新图层的对象列表
if (layer) { if (layer) {
layer.fabricObjects = layer.fabricObjects || []; layer.fabricObjects = layer.fabricObjects || [];
layer.fabricObjects.push( layer.fabricObjects.push(this.textObject.toObject(["id", "layerId", "layerName"]));
this.textObject.toObject(["id", "layerId", "layerName"])
);
} }
// 现在可以安全地设置为活动图层 // 现在可以安全地设置为活动图层
@@ -416,9 +414,7 @@ export class CreateTextCommand extends Command {
this.layerManager?.toolManager?.setTool?.(OperationType.SELECT); this.layerManager?.toolManager?.setTool?.(OperationType.SELECT);
}); });
console.log( console.log(`✅ 文本对象已创建: "${finalOptions.text}",位置: (${this.x}, ${this.y})`);
`✅ 文本对象已创建: "${finalOptions.text}",位置: (${this.x}, ${this.y})`
);
return this.textObject; return this.textObject;
} catch (error) { } catch (error) {
console.error("创建文本对象失败:", error); console.error("创建文本对象失败:", error);
@@ -467,15 +463,11 @@ export class CreateTextCommand extends Command {
parentLayer.children = parentLayer.children || []; parentLayer.children = parentLayer.children || [];
parentLayer.children.splice(insertIndex, 0, newLayer); parentLayer.children.splice(insertIndex, 0, newLayer);
console.log( console.log(`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`);
`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`
);
} else { } else {
// 当前激活图层是一级图层 // 当前激活图层是一级图层
// 在一级图层中,插入到激活图层之上 // 在一级图层中,插入到激活图层之上
const activeLayerIndex = layers.findIndex( const activeLayerIndex = layers.findIndex((layer) => layer.id === currentActiveLayerId);
(layer) => layer.id === currentActiveLayerId
);
insertIndex = Math.max(0, activeLayerIndex); insertIndex = Math.max(0, activeLayerIndex);
layers.splice(insertIndex, 0, newLayer); layers.splice(insertIndex, 0, newLayer);
@@ -551,9 +543,7 @@ export class CreateTextCommand extends Command {
// 恢复原活动图层 // 恢复原活动图层
if (this.oldActiveLayerId && this.layerManager) { if (this.oldActiveLayerId && this.layerManager) {
// 检查原活动图层是否还存在 // 检查原活动图层是否还存在
const originalLayer = this.layerManager.getLayerById( const originalLayer = this.layerManager.getLayerById(this.oldActiveLayerId);
this.oldActiveLayerId
);
if (originalLayer) { if (originalLayer) {
this.layerManager.setActiveLayer(this.oldActiveLayerId); this.layerManager.setActiveLayer(this.oldActiveLayerId);
} else { } else {
@@ -603,9 +593,7 @@ export class CreateTextCommand extends Command {
// 从子图层中移除 // 从子图层中移除
if (positionInfo.parent.children && positionInfo.index >= 0) { if (positionInfo.parent.children && positionInfo.index >= 0) {
positionInfo.parent.children.splice(positionInfo.index, 1); positionInfo.parent.children.splice(positionInfo.index, 1);
console.log( console.log(`已从子图层移除: ${this.layerId} (父图层: ${positionInfo.parent.name})`);
`已从子图层移除: ${this.layerId} (父图层: ${positionInfo.parent.name})`
);
} }
} else { } else {
// 从一级图层中移除 // 从一级图层中移除

View File

@@ -0,0 +1,244 @@
import { findLayerRecursively } from "../utils/layerHelper";
import { restoreFabricObject } from "../utils/objectHelper";
import { Command } from "./Command";
/**
* 更新组图层遮罩位置命令
* 当组图层的对象移动时,同步更新遮罩的位置
*/
export class UpdateGroupMaskPositionCommand extends Command {
constructor(options) {
super({
name: "更新组图层遮罩位置",
saveState: true,
});
this.canvas = options.canvas;
this.layers = options.layers;
this.layerManager = options.layerManager;
this.layerId = options.layerId;
this.deltaX = options.deltaX || 0;
this.deltaY = options.deltaY || 0;
this.maskInitialLeft = options.maskInitialLeft || 0;
this.maskInitialTop = options.maskInitialTop || 0;
this.isExecuteRealtime = options.isExecuteRealtime || false;
this.activeSelection = this.canvas.getActiveObject() || {};
this.isFristExecute = true;
// 查找目标图层
this.layer = findLayerRecursively(this.layers.value, this.layerId)?.layer;
if (!this.layer || !this.layer.clippingMask) {
console.warn(`图层 ${this.layerId} 不存在或没有遮罩`);
return false;
}
// 保存原始遮罩位置(用于撤销)
// 保存原始位置
this.originalMaskPosition = {
left: this.maskInitialLeft || 0,
top: this.maskInitialTop || 0,
};
// 收集当前选择的所有对象位置
this.originalObjectsPostion = this.activeSelection.getObjects().map((obj) => {
return {
left: obj.left || 0,
top: obj.top || 0,
id: obj.id,
};
});
this.originalSelectionPosition = {
left: this.activeSelection.left || 0,
top: this.activeSelection.top || 0,
};
console.log(
`🛠️ 初始化更新组图层遮罩位置命令: ${this.name}, 图层ID: ${this.layerId}, 初始位置: (${this.originalMaskPosition.left}, ${this.originalMaskPosition.top})`
);
this.newMaskPosition = null;
}
async execute() {
try {
// 计算新位置
const newLeft = this.maskInitialLeft + this.deltaX;
const newTop = this.maskInitialTop + this.deltaY;
// 更新遮罩位置
this.layer.clippingMask.left = newLeft;
this.layer.clippingMask.top = newTop;
this.newMaskPosition = {
left: newLeft,
top: newTop,
};
// 更新所有使用此遮罩的子图层对象
await this._updateChildObjectsClipPath(this.layer, false, true);
this.isFristExecute = false;
console.log(`✅ 组图层遮罩位置已更新: (${newLeft}, ${newTop})`);
return true;
} catch (error) {
console.error("更新组图层遮罩位置失败:", error);
return false;
}
}
async undo() {
try {
if (!this.originalMaskPosition) {
console.warn("没有原始遮罩位置数据,无法撤销");
return false;
}
// 查找目标图层
const layer = this.layers.value.find((l) => l.id === this.layerId);
if (!layer || !layer.clippingMask) {
console.warn(`图层 ${this.layerId} 不存在或没有遮罩`);
return false;
}
console.log(
`↶ 撤销更新组图层遮罩位置: ${this.originalMaskPosition.left}, 图层ID: ${this.originalMaskPosition.top}`
);
// 恢复原始位置
layer.clippingMask.left = this.originalMaskPosition.left;
layer.clippingMask.top = this.originalMaskPosition.top;
// 更新所有使用此遮罩的子图层对象
await this._updateChildObjectsClipPath(layer, true);
// await this.layerManager.updateLayersObjectsInteractivity();
// this.canvas.renderAll();
console.log(
`↶ 组图层遮罩位置已恢复: (${this.originalMaskPosition.left}, ${this.originalMaskPosition.top})`
);
return true;
} catch (error) {
console.error("撤销组图层遮罩位置更新失败:", error);
return false;
}
}
/**
* 实时执行(不记录到历史记录)
* 用于拖拽过程中的实时更新
*/
executeRealtime() {
try {
// 查找目标图层
const layer = this.layers.value.find((l) => l.id === this.layerId);
if (!layer || !layer.clippingMask) {
return false;
}
// 计算新位置
const newLeft = this.maskInitialLeft + this.deltaX;
const newTop = this.maskInitialTop + this.deltaY;
// 更新遮罩位置
layer.clippingMask.left = newLeft;
layer.clippingMask.top = newTop;
// 更新所有使用此遮罩的子图层对象(不需要等待)
this._updateChildObjectsClipPath(layer);
return true;
} catch (error) {
console.error("实时更新组图层遮罩位置失败:", error);
return false;
}
}
/**
* 更新子图层对象的裁剪路径
* @param {Object} layer 组图层对象
* @private
*/
async _updateChildObjectsClipPath(layer, isUndo = false, isExecute = false) {
if (!layer.children || layer.children.length === 0) {
return;
}
try {
// 重新创建遮罩对象
const clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas);
if (!clippingMaskFabricObject) {
console.warn("无法恢复遮罩对象");
return;
}
clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({
absolutePositioned: true,
});
clippingMaskFabricObject.dirty = true;
clippingMaskFabricObject.setCoords();
// 更新所有子图层对象的裁剪路径
layer.children.forEach((childLayer) => {
// 更新 fabricObjects 中的对象
childLayer.fabricObjects?.forEach((obj) => {
const fabricObject = this.canvas.getObjects().find((o) => o.id === obj.id);
if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true;
fabricObject.setCoords();
}
});
// 更新单个 fabricObject
if (childLayer.fabricObject) {
const fabricObject = this.canvas
.getObjects()
.find((o) => o.id === childLayer.fabricObject.id);
if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true;
fabricObject.setCoords();
}
}
});
if (isUndo) {
this.activeSelection.set({
left: this.originalSelectionPosition.left - this.deltaX,
top: this.originalSelectionPosition.top - this.deltaY,
});
this.activeSelection.dirty = true;
this.activeSelection.setCoords();
}
if (isExecute && !this.isFristExecute) {
this.activeSelection.set({
left: this.activeSelection.left + this.deltaX,
top: this.activeSelection.top + this.deltaY,
});
this.activeSelection.dirty = true;
this.activeSelection.setCoords();
}
// 触发画布重新渲染
this.canvas.renderAll();
} catch (error) {
console.error("更新子图层对象裁剪路径失败:", error);
}
}
getInfo() {
return {
name: this.name,
layerId: this.layerId,
deltaX: this.deltaX,
deltaY: this.deltaY,
originalPosition: this.originalMaskPosition,
newPosition: this.newMaskPosition,
};
}
}

View File

@@ -43,11 +43,7 @@
> >
+ +
</button> </button>
<button <button class="control-btn remove" @click="removeMemorizedSize" v-if="canRemoveSize">
class="control-btn remove"
@click="removeMemorizedSize"
v-if="canRemoveSize"
>
- -
</button> </button>
</div> </div>
@@ -57,16 +53,9 @@
<!-- 1.这里加上过渡动画 颜色选择器 - 仅在特定工具下显示 --> <!-- 1.这里加上过渡动画 颜色选择器 - 仅在特定工具下显示 -->
<transition name="color-picker-fade" mode="out-in"> <transition name="color-picker-fade" mode="out-in">
<div <div v-if="showColorPicker" class="color-picker-container" key="color-picker">
v-if="showColorPicker"
class="color-picker-container"
key="color-picker"
>
<label for="color-picker" class="current-color-label"> <label for="color-picker" class="current-color-label">
<div <div class="current-color" :style="{ backgroundColor: brushColor }"></div>
class="current-color"
:style="{ backgroundColor: brushColor }"
></div>
</label> </label>
<input <input
type="color" type="color"
@@ -115,9 +104,7 @@
></div> ></div>
</div> </div>
<div class="tooltip-content"> <div class="tooltip-content">
<div class="tooltip-text"> <div class="tooltip-text">{{ Math.round(brushOpacity * 100) }}%</div>
{{ Math.round(brushOpacity * 100) }}%
</div>
<div class="tooltip-controls"> <div class="tooltip-controls">
<button <button
class="control-btn add" class="control-btn add"
@@ -470,7 +457,9 @@ watch(
background: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.8);
border-radius: 5px; border-radius: 5px;
padding: 15px 3px; padding: 15px 3px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05); box-shadow:
0 4px 20px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(0, 0, 0, 0.05);
z-index: 8; z-index: 8;
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
color: #333; color: #333;
@@ -478,7 +467,9 @@ watch(
-webkit-user-select: none; -webkit-user-select: none;
// 添加高度过渡动画 // 添加高度过渡动画
transition: height 0.3s ease-out, min-height 0.3s ease-out; transition:
height 0.3s ease-out,
min-height 0.3s ease-out;
// overflow: hidden; // overflow: hidden;
transform: translate3d(0, -50%, 0); // 确保使用3D变换以提高性能 transform: translate3d(0, -50%, 0); // 确保使用3D变换以提高性能
@@ -492,8 +483,7 @@ watch(
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / 10px 10px;
10px 10px;
border-radius: 6px; border-radius: 6px;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
margin-bottom: 5px; margin-bottom: 5px;
@@ -521,8 +511,7 @@ watch(
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / 10px 10px;
10px 10px;
} }
.opacity-color { .opacity-color {
@@ -617,13 +606,17 @@ watch(
height: 32px; height: 32px;
border-radius: 8px; border-radius: 8px;
border: 2px solid #fff; border: 2px solid #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05); box-shadow:
0 2px 6px rgba(0, 0, 0, 0.15),
0 0 0 1px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease; transition: all 0.2s ease;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.1); box-shadow:
0 4px 8px rgba(0, 0, 0, 0.2),
0 0 0 1px rgba(0, 0, 0, 0.1);
} }
} }
@@ -683,7 +676,9 @@ watch(
// 淡入淡出动画 // 淡入淡出动画
.brush-control-canel-fade-enter-active, .brush-control-canel-fade-enter-active,
.brush-control-canel-fade-leave-active { .brush-control-canel-fade-leave-active {
transition: opacity 0.3s, transform 0.3s; transition:
opacity 0.3s,
transform 0.3s;
} }
.brush-control-canel-fade-enter-from, .brush-control-canel-fade-enter-from,
.brush-control-canel-fade-leave-to { .brush-control-canel-fade-leave-to {
@@ -694,7 +689,9 @@ watch(
// 颜色选择器过渡动画 // 颜色选择器过渡动画
.color-picker-fade-enter-active, .color-picker-fade-enter-active,
.color-picker-fade-leave-active { .color-picker-fade-leave-active {
transition: opacity 0.25s ease-out, transform 0.25s ease-out; transition:
opacity 0.25s ease-out,
transform 0.25s ease-out;
} }
.color-picker-fade-enter-from, .color-picker-fade-enter-from,
.color-picker-fade-leave-to { .color-picker-fade-leave-to {
@@ -705,7 +702,9 @@ watch(
// 透明度滑块过渡动画 // 透明度滑块过渡动画
.opacity-slider-fade-enter-active, .opacity-slider-fade-enter-active,
.opacity-slider-fade-leave-active { .opacity-slider-fade-leave-active {
transition: opacity 0.25s ease-out, transform 0.25s ease-out; transition:
opacity 0.25s ease-out,
transform 0.25s ease-out;
} }
.opacity-slider-fade-enter-from, .opacity-slider-fade-enter-from,
.opacity-slider-fade-leave-to { .opacity-slider-fade-leave-to {

View File

@@ -12,25 +12,16 @@
v-for="brush in brushStore.state.availableBrushes" v-for="brush in brushStore.state.availableBrushes"
:key="brush.id" :key="brush.id"
@click="setBrushTypeWithCommand(brush.id)" @click="setBrushTypeWithCommand(brush.id)"
:class="[ :class="['brush-type-item', { active: brushStore.state.type === brush.id }]"
'brush-type-item',
{ active: brushStore.state.type === brush.id },
]"
> >
<div <div class="brush-preview" :style="getBrushPreviewStyle(brush)"></div>
class="brush-preview"
:style="getBrushPreviewStyle(brush)"
></div>
<span class="brush-name">{{ brush.name }}</span> <span class="brush-name">{{ brush.name }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 动态渲染笔刷可配置属性 --> <!-- 动态渲染笔刷可配置属性 -->
<template <template v-for="(properties, category) in propertiesByCategory" :key="category">
v-for="(properties, category) in propertiesByCategory"
:key="category"
>
<div class="brush-section"> <div class="brush-section">
<div class="section-header"> <div class="section-header">
<span>{{ category }}</span> <span>{{ category }}</span>
@@ -43,11 +34,7 @@
<!-- 针对每个属性根据其类型渲染合适的控件 --> <!-- 针对每个属性根据其类型渲染合适的控件 -->
<div class="property-list"> <div class="property-list">
<div <div v-for="prop in properties" :key="prop.id" class="property-item">
v-for="prop in properties"
:key="prop.id"
class="property-item"
>
<!-- 滑块控件 --> <!-- 滑块控件 -->
<template v-if="prop.type === 'slider'"> <template v-if="prop.type === 'slider'">
<div class="slider-property"> <div class="slider-property">
@@ -61,13 +48,7 @@
<input <input
type="range" type="range"
:value="prop.value" :value="prop.value"
@input=" @input="(e) => handlePropertyChange(prop.id, parseFloat(e.target.value))"
(e) =>
handlePropertyChange(
prop.id,
parseFloat(e.target.value)
)
"
:min="prop.min || 0" :min="prop.min || 0"
:max="prop.max || 100" :max="prop.max || 100"
:step="prop.step || 1" :step="prop.step || 1"
@@ -93,25 +74,19 @@
<div class="color-property"> <div class="color-property">
<div class="color-header"> <div class="color-header">
<span>{{ prop.name }}</span> <span>{{ prop.name }}</span>
<div <div class="color-preview" :style="{ backgroundColor: prop.value }"></div>
class="color-preview"
:style="{ backgroundColor: prop.value }"
></div>
</div> </div>
<div class="color-row"> <div class="color-row">
<input <input
type="color" type="color"
:value="prop.value" :value="prop.value"
@input=" @input="(e) => handlePropertyChange(prop.id, e.target.value)"
(e) => handlePropertyChange(prop.id, e.target.value)
"
class="color-picker" class="color-picker"
/> />
<!-- 如果是主颜色显示最近使用的颜色 --> <!-- 如果是主颜色显示最近使用的颜色 -->
<div v-if="prop.id === 'color'" class="recent-colors"> <div v-if="prop.id === 'color'" class="recent-colors">
<div <div
v-for="(color, index) in brushStore.state v-for="(color, index) in brushStore.state.recentColors"
.recentColors"
:key="index" :key="index"
class="color-item" class="color-item"
:style="{ backgroundColor: color }" :style="{ backgroundColor: color }"
@@ -130,9 +105,7 @@
<input <input
type="checkbox" type="checkbox"
:checked="prop.value" :checked="prop.value"
@change=" @change="(e) => handlePropertyChange(prop.id, e.target.checked)"
(e) => handlePropertyChange(prop.id, e.target.checked)
"
:id="`toggle-${prop.id}`" :id="`toggle-${prop.id}`"
/> />
<label :for="`toggle-${prop.id}`"></label> <label :for="`toggle-${prop.id}`"></label>
@@ -146,9 +119,7 @@
<span>{{ prop.name }}</span> <span>{{ prop.name }}</span>
<select <select
:value="prop.value" :value="prop.value"
@change=" @change="(e) => handlePropertyChange(prop.id, e.target.value)"
(e) => handlePropertyChange(prop.id, e.target.value)
"
class="property-select" class="property-select"
> >
<option <option
@@ -225,10 +196,7 @@
<!-- 上传的纹理缓存区域 --> <!-- 上传的纹理缓存区域 -->
<!-- 自定义纹理上传按钮 --> <!-- 自定义纹理上传按钮 -->
<div <div class="texture-item upload-item" @click="triggerTextureUpload">
class="texture-item upload-item"
@click="triggerTextureUpload"
>
<div class="upload-icon"> <div class="upload-icon">
<span>+</span> <span>+</span>
</div> </div>
@@ -275,9 +243,7 @@
<input <input
type="text" type="text"
:value="prop.value" :value="prop.value"
@input=" @input="(e) => handlePropertyChange(prop.id, e.target.value)"
(e) => handlePropertyChange(prop.id, e.target.value)
"
class="property-input" class="property-input"
/> />
</div> </div>
@@ -360,13 +326,7 @@
<input <input
type="checkbox" type="checkbox"
:checked="brushStore.state.shadowEnabled" :checked="brushStore.state.shadowEnabled"
@change=" @change="(e) => handleShadowPropertyChange('shadowEnabled', e.target.checked)"
(e) =>
handleShadowPropertyChange(
'shadowEnabled',
e.target.checked
)
"
id="shadow-enabled" id="shadow-enabled"
/> />
<label for="shadow-enabled"></label> <label for="shadow-enabled"></label>
@@ -390,13 +350,7 @@
<input <input
type="color" type="color"
:value="brushStore.state.shadowColor" :value="brushStore.state.shadowColor"
@input=" @input="(e) => handleShadowPropertyChange('shadowColor', e.target.value)"
(e) =>
handleShadowPropertyChange(
'shadowColor',
e.target.value
)
"
class="color-picker" class="color-picker"
/> />
</div> </div>
@@ -408,20 +362,14 @@
<div class="slider-property"> <div class="slider-property">
<div class="slider-header"> <div class="slider-header">
<span>{{ $t("阴影宽度") }}</span> <span>{{ $t("阴影宽度") }}</span>
<span class="slider-value" <span class="slider-value">{{ brushStore.state.shadowWidth }}px</span>
>{{ brushStore.state.shadowWidth }}px</span
>
</div> </div>
<div class="slider-container"> <div class="slider-container">
<input <input
type="range" type="range"
:value="brushStore.state.shadowWidth" :value="brushStore.state.shadowWidth"
@input=" @input="
(e) => (e) => handleShadowPropertyChange('shadowWidth', parseFloat(e.target.value))
handleShadowPropertyChange(
'shadowWidth',
parseFloat(e.target.value)
)
" "
:min="0" :min="0"
:max="50" :max="50"
@@ -435,8 +383,7 @@
:key="preset" :key="preset"
@click="handleShadowPropertyChange('shadowWidth', preset)" @click="handleShadowPropertyChange('shadowWidth', preset)"
:class="{ :class="{
active: active: Math.abs(brushStore.state.shadowWidth - preset) < 0.1,
Math.abs(brushStore.state.shadowWidth - preset) < 0.1,
}" }"
> >
{{ preset }} {{ preset }}
@@ -450,9 +397,7 @@
<div class="slider-property"> <div class="slider-property">
<div class="slider-header"> <div class="slider-header">
<span>{{ $t("阴影X偏移") }}</span> <span>{{ $t("阴影X偏移") }}</span>
<span class="slider-value" <span class="slider-value">{{ brushStore.state.shadowOffsetX }}px</span>
>{{ brushStore.state.shadowOffsetX }}px</span
>
</div> </div>
<div class="slider-container"> <div class="slider-container">
<input <input
@@ -460,10 +405,7 @@
:value="brushStore.state.shadowOffsetX" :value="brushStore.state.shadowOffsetX"
@input=" @input="
(e) => (e) =>
handleShadowPropertyChange( handleShadowPropertyChange('shadowOffsetX', parseFloat(e.target.value))
'shadowOffsetX',
parseFloat(e.target.value)
)
" "
:min="-50" :min="-50"
:max="50" :max="50"
@@ -475,13 +417,9 @@
<button <button
v-for="preset in [-10, -5, 0, 5, 10]" v-for="preset in [-10, -5, 0, 5, 10]"
:key="preset" :key="preset"
@click=" @click="handleShadowPropertyChange('shadowOffsetX', preset)"
handleShadowPropertyChange('shadowOffsetX', preset)
"
:class="{ :class="{
active: active: Math.abs(brushStore.state.shadowOffsetX - preset) < 0.1,
Math.abs(brushStore.state.shadowOffsetX - preset) <
0.1,
}" }"
> >
{{ preset }} {{ preset }}
@@ -495,9 +433,7 @@
<div class="slider-property"> <div class="slider-property">
<div class="slider-header"> <div class="slider-header">
<span>{{ $t("阴影Y偏移") }}</span> <span>{{ $t("阴影Y偏移") }}</span>
<span class="slider-value" <span class="slider-value">{{ brushStore.state.shadowOffsetY }}px</span>
>{{ brushStore.state.shadowOffsetY }}px</span
>
</div> </div>
<div class="slider-container"> <div class="slider-container">
<input <input
@@ -505,10 +441,7 @@
:value="brushStore.state.shadowOffsetY" :value="brushStore.state.shadowOffsetY"
@input=" @input="
(e) => (e) =>
handleShadowPropertyChange( handleShadowPropertyChange('shadowOffsetY', parseFloat(e.target.value))
'shadowOffsetY',
parseFloat(e.target.value)
)
" "
:min="-50" :min="-50"
:max="50" :max="50"
@@ -520,13 +453,9 @@
<button <button
v-for="preset in [-10, -5, 0, 5, 10]" v-for="preset in [-10, -5, 0, 5, 10]"
:key="preset" :key="preset"
@click=" @click="handleShadowPropertyChange('shadowOffsetY', preset)"
handleShadowPropertyChange('shadowOffsetY', preset)
"
:class="{ :class="{
active: active: Math.abs(brushStore.state.shadowOffsetY - preset) < 0.1,
Math.abs(brushStore.state.shadowOffsetY - preset) <
0.1,
}" }"
> >
{{ preset }} {{ preset }}
@@ -544,14 +473,8 @@
class="shadow-preview-element" class="shadow-preview-element"
:style="{ :style="{
backgroundColor: brushStore.state.color, backgroundColor: brushStore.state.color,
width: `${Math.max( width: `${Math.max(20, Math.min(60, brushStore.state.size))}px`,
20, height: `${Math.max(20, Math.min(60, brushStore.state.size))}px`,
Math.min(60, brushStore.state.size)
)}px`,
height: `${Math.max(
20,
Math.min(60, brushStore.state.size)
)}px`,
boxShadow: brushStore.state.shadowEnabled boxShadow: brushStore.state.shadowEnabled
? `${brushStore.state.shadowOffsetX}px ${brushStore.state.shadowOffsetY}px ${brushStore.state.shadowWidth}px ${brushStore.state.shadowColor}` ? `${brushStore.state.shadowOffsetX}px ${brushStore.state.shadowOffsetY}px ${brushStore.state.shadowWidth}px ${brushStore.state.shadowColor}`
: 'none', : 'none',
@@ -567,11 +490,7 @@
<div class="brush-section"> <div class="brush-section">
<div class="section-header"> <div class="section-header">
<span>笔刷预设</span> <span>笔刷预设</span>
<button <button class="save-preset-btn" @click="saveCurrentAsPreset" title="保存当前设置为预设">
class="save-preset-btn"
@click="saveCurrentAsPreset"
title="保存当前设置为预设"
>
<i class="save-icon">+</i> <i class="save-icon">+</i>
</button> </button>
</div> </div>
@@ -694,9 +613,7 @@ const filteredTextures = computed(() => {
if (selectedCategory.value === "全部") { if (selectedCategory.value === "全部") {
return textureLibrary; return textureLibrary;
} }
return textureLibrary.filter( return textureLibrary.filter((texture) => texture.category === selectedCategory.value);
(texture) => texture.category === selectedCategory.value
);
}); });
// 从材质库选择材质 // 从材质库选择材质
@@ -909,8 +826,7 @@ function getBrushPreviewStyle(brush) {
case "spray": case "spray":
return { return {
...baseStyle, ...baseStyle,
backgroundImage: backgroundImage: "radial-gradient(circle, currentColor 1px, transparent 1px)",
"radial-gradient(circle, currentColor 1px, transparent 1px)",
backgroundSize: "4px 4px", backgroundSize: "4px 4px",
backgroundPosition: "center", backgroundPosition: "center",
opacity: 0.8, opacity: 0.8,
@@ -931,8 +847,7 @@ function getBrushPreviewStyle(brush) {
case "rainbow": case "rainbow":
return { return {
...baseStyle, ...baseStyle,
background: background: "linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet)",
"linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet)",
height: "3px", height: "3px",
}; };
case "texture": case "texture":
@@ -957,10 +872,7 @@ function applyPresetWithCommand(presetIndex) {
// 保存当前设置为预设 // 保存当前设置为预设
function saveCurrentAsPreset() { function saveCurrentAsPreset() {
// 简单实现,可以后续优化为弹窗输入名称 // 简单实现,可以后续优化为弹窗输入名称
const name = prompt( const name = prompt("请输入预设名称:", `预设 ${BrushStore.state.presets.length + 1}`);
"请输入预设名称:",
`预设 ${BrushStore.state.presets.length + 1}`
);
if (name) { if (name) {
const presetIndex = BrushStore.saveCurrentAsPreset(name); const presetIndex = BrushStore.saveCurrentAsPreset(name);
// 应用新创建的预设(可选) // 应用新创建的预设(可选)
@@ -1740,7 +1652,9 @@ const brushStore = BrushStore;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05); box-shadow:
0 8px 32px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(0, 0, 0, 0.05);
animation: modalSlideUp 0.3s ease; animation: modalSlideUp 0.3s ease;
} }
@@ -1930,12 +1844,7 @@ const brushStore = BrushStore;
content: ""; content: "";
flex: 1; flex: 1;
height: 1px; height: 1px;
background: linear-gradient( background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.08), transparent);
to right,
transparent,
rgba(0, 0, 0, 0.08),
transparent
);
} }
.uploaded-textures-divider span { .uploaded-textures-divider span {
@@ -2022,8 +1931,7 @@ const brushStore = BrushStore;
} }
.shadow-preview-box { .shadow-preview-box {
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / 10px 10px;
10px 10px;
border-radius: 8px; border-radius: 8px;
padding: 30px; padding: 30px;
display: flex; display: flex;

View File

@@ -1,13 +1,5 @@
<script setup> <script setup>
import { import { inject, ref, provide, onMounted, computed, watch, onUnmounted } from "vue";
inject,
ref,
provide,
onMounted,
computed,
watch,
onUnmounted,
} from "vue";
import { OperationType } from "../utils/layerHelper"; import { OperationType } from "../utils/layerHelper";
import BrushPanel from "./BrushPanel.vue"; import BrushPanel from "./BrushPanel.vue";
import { BrushStore } from "../store/BrushStore"; import { BrushStore } from "../store/BrushStore";
@@ -50,9 +42,7 @@ const lastColor = ref("#ffffff");
// return props.activeTool === OperationType.DRAW; // return props.activeTool === OperationType.DRAW;
// }); // });
function updateCanvasSize( function updateCanvasSize({ width, height } = { width: props.width, height: props.height }) {
{ width, height } = { width: props.width, height: props.height }
) {
if (!layerManager) { if (!layerManager) {
console.warn("LayerManager 未初始化,无法调整背景层尺寸"); console.warn("LayerManager 未初始化,无法调整背景层尺寸");
return; return;
@@ -366,11 +356,7 @@ onMounted(() => {
<!-- 绘图工具设置 --> <!-- 绘图工具设置 -->
<div class="canvas-settings gap-20" v-if="!props.enabledRedGreenMode"> <div class="canvas-settings gap-20" v-if="!props.enabledRedGreenMode">
<div <div class="btn" :class="{ active: showBrushPanel }" @click="toggleBrushPanel">
class="btn"
:class="{ active: showBrushPanel }"
@click="toggleBrushPanel"
>
<!-- <span class="setting-label">笔刷:</span>/ --> <!-- <span class="setting-label">笔刷:</span>/ -->
<div class="brush-selector"> <div class="brush-selector">
<SvgIcon name="CBrushTop" size="22"></SvgIcon> <SvgIcon name="CBrushTop" size="22"></SvgIcon>
@@ -385,11 +371,7 @@ onMounted(() => {
<!-- <span class="brush-dropdown"></span> --> <!-- <span class="brush-dropdown"></span> -->
</div> </div>
<!-- 笔刷面板 --> <!-- 笔刷面板 -->
<div <div v-if="showBrushPanel" class="brush-panel-container" ref="brushPanelRef">
v-if="showBrushPanel"
class="brush-panel-container"
ref="brushPanelRef"
>
<Teleport to="body"> <Teleport to="body">
<BrushPanel /> <BrushPanel />
</Teleport> </Teleport>

View File

@@ -16,9 +16,7 @@ onMounted(() => {
platform.value = { platform.value = {
isMac: keyboardManager.platform === "mac", isMac: keyboardManager.platform === "mac",
isIOS: keyboardManager.platform === "ios", isIOS: keyboardManager.platform === "ios",
isIPad: isIPad: keyboardManager.platform === "ios" && /iPad/.test(window.navigator.userAgent),
keyboardManager.platform === "ios" &&
/iPad/.test(window.navigator.userAgent),
isTouchDevice: keyboardManager.isTouchDevice, isTouchDevice: keyboardManager.isTouchDevice,
isWindows: keyboardManager.platform === "windows", isWindows: keyboardManager.platform === "windows",
isAndroid: keyboardManager.platform === "android", isAndroid: keyboardManager.platform === "android",
@@ -58,10 +56,10 @@ function convertShortcuts(managerShortcuts) {
increaseBrushSize: "增加笔触大小", increaseBrushSize: "增加笔触大小",
decreaseBrushSize: "减小笔触大小", decreaseBrushSize: "减小笔触大小",
toggleTempTool: "临时切换工具", toggleTempTool: "临时切换工具",
newLayer: "新建图层", // newLayer: "新建图层",
groupLayers: "组合图层", // groupLayers: "组合图层",
ungroupLayers: "取消组合", // ungroupLayers: "取消组合",
mergeLayers: "合并图层", // mergeLayers: "合并图层",
}; };
// 工具ID到显示名称的映射 // 工具ID到显示名称的映射
@@ -69,11 +67,11 @@ function convertShortcuts(managerShortcuts) {
select: "选择模式", select: "选择模式",
draw: "绘画模式", draw: "绘画模式",
eraser: "橡皮擦模式", eraser: "橡皮擦模式",
eyedropper: "吸色工具", // eyedropper: "吸色工具",
pan: "移动画布", pan: "移动画布",
lasso: "套索工具", lasso: "套索工具",
area_custom: "自由选区工具", // area_custom: "自由选区工具",
wave: "波浪工具", // wave: "波浪工具",
liquify: "液化工具", liquify: "液化工具",
}; };
@@ -82,11 +80,7 @@ function convertShortcuts(managerShortcuts) {
let actionDisplay = actionDisplayMap[shortcut.action] || shortcut.action; let actionDisplay = actionDisplayMap[shortcut.action] || shortcut.action;
// 特殊处理工具选择 // 特殊处理工具选择
if ( if (shortcut.action === "selectTool" && shortcut.param && toolDisplayMap[shortcut.param]) {
shortcut.action === "selectTool" &&
shortcut.param &&
toolDisplayMap[shortcut.param]
) {
actionDisplay = toolDisplayMap[shortcut.param]; actionDisplay = toolDisplayMap[shortcut.param];
} }
@@ -144,12 +138,12 @@ function generateDefaultShortcuts() {
mac: "Delete 或 ⌫", mac: "Delete 或 ⌫",
touch: "长按选中元素后点击删除", touch: "长按选中元素后点击删除",
}, },
{ // {
action: "全选", // action: "全选",
windows: "Ctrl+A", // windows: "Ctrl+A",
mac: "⌘+A", // mac: "⌘+A",
touch: "无", // touch: "无",
}, // },
{ {
action: "复制", action: "复制",
windows: "Ctrl+C", windows: "Ctrl+C",
@@ -198,12 +192,12 @@ function generateDefaultShortcuts() {
mac: "E", mac: "E",
touch: "点击橡皮擦工具", touch: "点击橡皮擦工具",
}, },
{ // {
action: "吸色工具", // action: "吸色工具",
windows: "I", // windows: "I",
mac: "I", // mac: "I",
touch: "点击吸色工具", // touch: "点击吸色工具",
}, // },
{ {
action: "增加笔触大小", action: "增加笔触大小",
windows: "]", windows: "]",
@@ -251,39 +245,24 @@ function getShortcutForCurrentPlatform(shortcut) {
// 按分类获取快捷键 // 按分类获取快捷键
function getShortcutsByCategory(category) { function getShortcutsByCategory(category) {
const categoryMap = { const categoryMap = {
basic: [ // basic: ["撤销", "重做", "全选", "复制", "粘贴", "剪切", "删除选中元素", "上传图片"],
"撤销", basic: ["撤销", "重做", "复制", "粘贴", "剪切", "删除选中元素", "上传图片"],
"重做",
"全选",
"复制",
"粘贴",
"剪切",
"删除选中元素",
"上传图片",
],
view: ["缩放画布", "移动画布"], view: ["缩放画布", "移动画布"],
tools: [ tools: [
"绘画模式", "绘画模式",
"选择模式", "选择模式",
"橡皮擦模式", "橡皮擦模式",
"吸色工具", // "吸色工具",
"套索工具", "套索工具",
"自由选区工具", // "自由选区工具",
"波浪工具", // "波浪工具",
"液化工具", "液化工具",
], ],
brush: [ brush: ["增加笔触大小", "减小笔触大小", "增加材质图片大小", "减小材质图片大小"],
"增加笔触大小", // layer: ["新建图层", "组合图层", "取消组合", "合并图层"],
"减小笔触大小",
"增加材质图片大小",
"减小材质图片大小",
],
layer: ["新建图层", "组合图层", "取消组合", "合并图层"],
}; };
return shortcuts.value.filter((s) => return shortcuts.value.filter((s) => categoryMap[category]?.includes(s.action));
categoryMap[category]?.includes(s.action)
);
} }
</script> </script>
@@ -313,10 +292,7 @@ function getShortcutsByCategory(category) {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr v-for="item in getShortcutsByCategory('basic')" :key="item.action">
v-for="item in getShortcutsByCategory('basic')"
:key="item.action"
>
<td>{{ item.action }}</td> <td>{{ item.action }}</td>
<td>{{ getShortcutForCurrentPlatform(item) }}</td> <td>{{ getShortcutForCurrentPlatform(item) }}</td>
</tr> </tr>
@@ -334,10 +310,7 @@ function getShortcutsByCategory(category) {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr v-for="item in getShortcutsByCategory('view')" :key="item.action">
v-for="item in getShortcutsByCategory('view')"
:key="item.action"
>
<td>{{ item.action }}</td> <td>{{ item.action }}</td>
<td>{{ getShortcutForCurrentPlatform(item) }}</td> <td>{{ getShortcutForCurrentPlatform(item) }}</td>
</tr> </tr>
@@ -355,10 +328,7 @@ function getShortcutsByCategory(category) {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr v-for="item in getShortcutsByCategory('tools')" :key="item.action">
v-for="item in getShortcutsByCategory('tools')"
:key="item.action"
>
<td>{{ item.action }}</td> <td>{{ item.action }}</td>
<td>{{ getShortcutForCurrentPlatform(item) }}</td> <td>{{ getShortcutForCurrentPlatform(item) }}</td>
</tr> </tr>
@@ -376,10 +346,7 @@ function getShortcutsByCategory(category) {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr v-for="item in getShortcutsByCategory('brush')" :key="item.action">
v-for="item in getShortcutsByCategory('brush')"
:key="item.action"
>
<td>{{ item.action }}</td> <td>{{ item.action }}</td>
<td>{{ getShortcutForCurrentPlatform(item) }}</td> <td>{{ getShortcutForCurrentPlatform(item) }}</td>
</tr> </tr>
@@ -387,7 +354,7 @@ function getShortcutsByCategory(category) {
</table> </table>
</div> </div>
<div class="shortcuts-category"> <!-- <div class="shortcuts-category">
<h3>图层操作</h3> <h3>图层操作</h3>
<table class="shortcuts-table"> <table class="shortcuts-table">
<thead> <thead>
@@ -397,16 +364,13 @@ function getShortcutsByCategory(category) {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr v-for="item in getShortcutsByCategory('layer')" :key="item.action">
v-for="item in getShortcutsByCategory('layer')"
:key="item.action"
>
<td>{{ item.action }}</td> <td>{{ item.action }}</td>
<td>{{ getShortcutForCurrentPlatform(item) }}</td> <td>{{ getShortcutForCurrentPlatform(item) }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div> -->
<div class="touch-tips" v-if="platform.isTouchDevice"> <div class="touch-tips" v-if="platform.isTouchDevice">
<h3>触控设备提示</h3> <h3>触控设备提示</h3>

View File

@@ -145,11 +145,7 @@ const handleItemMouseEnter = (item, index, event) => {
// 处理鼠标在菜单项内移动 // 处理鼠标在菜单项内移动
const handleItemMouseMove = (item, index, event) => { const handleItemMouseMove = (item, index, event) => {
// 如果当前菜单项有子菜单但子菜单未显示,则显示子菜单 // 如果当前菜单项有子菜单但子菜单未显示,则显示子菜单
if ( if (item.children && item.children.length > 0 && hoveredItem.value !== index) {
item.children &&
item.children.length > 0 &&
hoveredItem.value !== index
) {
const element = event.target.closest(".context-menu-item"); const element = event.target.closest(".context-menu-item");
showSubmenu(item, index, element); showSubmenu(item, index, element);
} }
@@ -261,10 +257,7 @@ onUnmounted(() => {
> >
<template v-for="(item, index) in items" :key="index"> <template v-for="(item, index) in items" :key="index">
<!-- 分隔线 --> <!-- 分隔线 -->
<div <div v-if="item.type === 'divider'" class="context-menu-divider"></div>
v-if="item.type === 'divider'"
class="context-menu-divider"
></div>
<!-- 菜单项 --> <!-- 菜单项 -->
<div <div
@@ -294,38 +287,24 @@ onUnmounted(() => {
<span class="context-menu-shortcut" v-if="item.shortcut"> <span class="context-menu-shortcut" v-if="item.shortcut">
{{ item.shortcut }} {{ item.shortcut }}
</span> </span>
<span <span class="context-menu-arrow" v-if="item.children && item.children.length > 0">
class="context-menu-arrow"
v-if="item.children && item.children.length > 0"
>
<SvgIcon name="CRight" size="12" /> <SvgIcon name="CRight" size="12" />
</span> </span>
<!-- 子菜单 --> <!-- 子菜单 -->
<transition name="context-submenu"> <transition name="context-submenu">
<div <div
v-if=" v-if="item.children && item.children.length > 0 && hoveredItem === index"
item.children &&
item.children.length > 0 &&
hoveredItem === index
"
class="context-submenu" class="context-submenu"
:class="{ :class="{
'submenu-left': 'submenu-left': submenuPositions.get(index)?.direction === 'left',
submenuPositions.get(index)?.direction === 'left',
}" }"
@mouseenter="handleSubmenuMouseEnter(index)" @mouseenter="handleSubmenuMouseEnter(index)"
@mouseleave="handleSubmenuMouseLeave" @mouseleave="handleSubmenuMouseLeave"
> >
<template <template v-for="(subItem, subIndex) in item.children" :key="subIndex">
v-for="(subItem, subIndex) in item.children"
:key="subIndex"
>
<!-- 子菜单分隔线 --> <!-- 子菜单分隔线 -->
<div <div v-if="subItem.type === 'divider'" class="context-menu-divider"></div>
v-if="subItem.type === 'divider'"
class="context-menu-divider"
></div>
<!-- 子菜单项 --> <!-- 子菜单项 -->
<div <div
@@ -342,9 +321,7 @@ onUnmounted(() => {
:name="subItem.icon" :name="subItem.icon"
size="14" size="14"
:style="{ :style="{
transform: subItem.inverIcon transform: subItem.inverIcon ? `rotate(90deg)` : 'none',
? `rotate(90deg)`
: 'none',
}" }"
/> />
</span> </span>

View File

@@ -250,13 +250,7 @@ function handleTouchEnd(event) {
function handleUpdateChildLayers(newChildren) { function handleUpdateChildLayers(newChildren) {
// 更新当前组图层的children数组 // 更新当前组图层的children数组
console.log( console.log("更新子图层顺序:", "父图层ID:", props.layer.id, "新顺序:", newChildren);
"更新子图层顺序:",
"父图层ID:",
props.layer.id,
"新顺序:",
newChildren
);
emit("update-child-layers", props.layer.id, newChildren); emit("update-child-layers", props.layer.id, newChildren);
} }
@@ -383,21 +377,13 @@ function findParentLayerId() {
> >
<!-- 拖拽手柄 --> <!-- 拖拽手柄 -->
<div class="layer-drag-handle" :title="$t('拖拽排序')"> <div class="layer-drag-handle" :title="$t('拖拽排序')">
<SvgIcon <SvgIcon v-if="!isHidenDragHandle" :name="isChild ? 'CSort' : 'CSort'" :size="32"></SvgIcon>
v-if="!isHidenDragHandle"
:name="isChild ? 'CSort' : 'CSort'"
:size="32"
></SvgIcon>
</div> </div>
<!-- 图层头部 --> <!-- 图层头部 -->
<div class="layer-header"> <div class="layer-header">
<!-- 多选复选框 --> <!-- 多选复选框 -->
<div <div v-if="isMultiSelectMode && !isChild" class="layer-checkbox" @click.stop>
v-if="isMultiSelectMode && !isChild"
class="layer-checkbox"
@click.stop
>
<Checkbox :checked="isSelected" @change="handleCheckboxChange" /> <Checkbox :checked="isSelected" @change="handleCheckboxChange" />
</div> </div>
@@ -453,12 +439,7 @@ function findParentLayerId() {
> >
<SvgIcon name="CLock" :size="18"></SvgIcon> <SvgIcon name="CLock" :size="18"></SvgIcon>
</span> </span>
<span <span v-else class="status-icon" :title="$t('未锁定')" @click.stop="handleToggleLock">
v-else
class="status-icon"
:title="$t('未锁定')"
@click.stop="handleToggleLock"
>
<SvgIcon name="CUnLock" :size="18"></SvgIcon> <SvgIcon name="CUnLock" :size="18"></SvgIcon>
</span> </span>

View File

@@ -96,8 +96,7 @@ const handleRootLayersSort = (event) => {
// 获取被拖拽的图层ID // 获取被拖拽的图层ID
const draggedLayerId = const draggedLayerId =
item.getAttribute("data-layer-id") || item.getAttribute("data-layer-id") || props.sortableRootLayers[oldIndex]?.id;
props.sortableRootLayers[oldIndex]?.id;
if (!draggedLayerId) { if (!draggedLayerId) {
console.error("❌ 无法获取被拖拽的图层ID"); console.error("❌ 无法获取被拖拽的图层ID");
@@ -150,25 +149,14 @@ const handleRootLayersSort = (event) => {
if (props.isChild) { if (props.isChild) {
// 子图层事件处理 // 子图层事件处理
// 确保排序只影响当前组图层的children而不是全局layers // 确保排序只影响当前组图层的children而不是全局layers
emit( emit("child-layers-sort", event, props.sortableRootLayers, props.parentLayerId);
"child-layers-sort",
event,
props.sortableRootLayers,
props.parentLayerId
);
} else { } else {
emit("root-layers-sort", event); emit("root-layers-sort", event);
} }
}; };
// 验证跨层级移动的有效性 // 验证跨层级移动的有效性
const validateCrossLevelMove = ( const validateCrossLevelMove = (layerId, fromType, toType, fromParentId, toParentId) => {
layerId,
fromType,
toType,
fromParentId,
toParentId
) => {
// 查找图层 // 查找图层
const layer = findLayerInHierarchy(layerId, fromType, fromParentId); const layer = findLayerInHierarchy(layerId, fromType, fromParentId);
@@ -250,9 +238,7 @@ const handleDragRemove = (event) => {
const canDeleteComputed = computed(() => { const canDeleteComputed = computed(() => {
// 如果是子图层,检查父图层是否可以删除 // 如果是子图层,检查父图层是否可以删除
if (props.isChild) { if (props.isChild) {
const parentLayer = props.layers.find( const parentLayer = props.layers.find((layer) => layer.id === props.parentLayerId);
(layer) => layer.id === props.parentLayerId
);
return parentLayer?.children?.length > 1; return parentLayer?.children?.length > 1;
} }
// 否则直接返回根图层的可删除状态 // 否则直接返回根图层的可删除状态
@@ -305,23 +291,14 @@ const canDeleteComputed = computed(() => {
:is-editing="editingLayerId === layer.id" :is-editing="editingLayerId === layer.id"
:editing-name="editingLayerName" :editing-name="editingLayerName"
:can-delete=" :can-delete="
canDeleteComputed && canDeleteComputed && !layer.isBackground && !layer.isFixed && !layer.locked
!layer.isBackground &&
!layer.isFixed &&
!layer.locked
" "
:expanded-group-ids="expandedGroupIds" :expanded-group-ids="expandedGroupIds"
@click="(...args) => forwardEvent('layer-click', ...args)" @click="(...args) => forwardEvent('layer-click', ...args)"
@double-click=" @double-click="(...args) => forwardEvent('layer-double-click', ...args)"
(...args) => forwardEvent('layer-double-click', ...args)
"
@context-menu="(...args) => forwardEvent('context-menu', ...args)" @context-menu="(...args) => forwardEvent('context-menu', ...args)"
@checkbox-change=" @checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
(...args) => forwardEvent('checkbox-change', ...args) @toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
"
@toggle-visibility="
(...args) => forwardEvent('toggle-visibility', ...args)
"
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)" @toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
@delete="(...args) => forwardEvent('delete', ...args)" @delete="(...args) => forwardEvent('delete', ...args)"
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)" @edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
@@ -330,18 +307,10 @@ const canDeleteComputed = computed(() => {
@touch-start="(...args) => forwardEvent('touch-start', ...args)" @touch-start="(...args) => forwardEvent('touch-start', ...args)"
@touch-move="(...args) => forwardEvent('touch-move', ...args)" @touch-move="(...args) => forwardEvent('touch-move', ...args)"
@touch-end="(...args) => forwardEvent('touch-end', ...args)" @touch-end="(...args) => forwardEvent('touch-end', ...args)"
@update:editing-name=" @update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
(...args) => forwardEvent('update:editing-name', ...args) @toggle-group-expanded="(...args) => forwardEvent('toggle-group-expanded', ...args)"
" @toggle-child-visibility="(...args) => forwardEvent('toggle-child-visibility', ...args)"
@toggle-group-expanded=" @toggle-child-lock="(...args) => forwardEvent('toggle-child-lock', ...args)"
(...args) => forwardEvent('toggle-group-expanded', ...args)
"
@toggle-child-visibility="
(...args) => forwardEvent('toggle-child-visibility', ...args)
"
@toggle-child-lock="
(...args) => forwardEvent('toggle-child-lock', ...args)
"
@delete-child="(...args) => forwardEvent('delete-child', ...args)" @delete-child="(...args) => forwardEvent('delete-child', ...args)"
@rename-child="(...args) => forwardEvent('rename-child', ...args)" @rename-child="(...args) => forwardEvent('rename-child', ...args)"
/> />
@@ -370,16 +339,10 @@ const canDeleteComputed = computed(() => {
:parentLayerId="layer.id" :parentLayerId="layer.id"
:group-name="groupName" :group-name="groupName"
@layer-click="(...args) => forwardEvent('layer-click', ...args)" @layer-click="(...args) => forwardEvent('layer-click', ...args)"
@layer-double-click=" @layer-double-click="(...args) => forwardEvent('layer-double-click', ...args)"
(...args) => forwardEvent('layer-double-click', ...args)
"
@context-menu="(...args) => forwardEvent('context-menu', ...args)" @context-menu="(...args) => forwardEvent('context-menu', ...args)"
@checkbox-change=" @checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
(...args) => forwardEvent('checkbox-change', ...args) @toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
"
@toggle-visibility="
(...args) => forwardEvent('toggle-visibility', ...args)
"
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)" @toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
@delete="(...args) => forwardEvent('delete', ...args)" @delete="(...args) => forwardEvent('delete', ...args)"
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)" @edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
@@ -388,33 +351,17 @@ const canDeleteComputed = computed(() => {
@touch-start="(...args) => forwardEvent('touch-start', ...args)" @touch-start="(...args) => forwardEvent('touch-start', ...args)"
@touch-move="(...args) => forwardEvent('touch-move', ...args)" @touch-move="(...args) => forwardEvent('touch-move', ...args)"
@touch-end="(...args) => forwardEvent('touch-end', ...args)" @touch-end="(...args) => forwardEvent('touch-end', ...args)"
@update:editing-name=" @update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
(...args) => forwardEvent('update:editing-name', ...args) @root-layers-sort="(...args) => forwardEvent('root-layers-sort', ...args)"
" @child-layers-sort="(...args) => forwardEvent('child-layers-sort', ...args)"
@root-layers-sort=" @cross-level-move="(...args) => forwardEvent('cross-level-move', ...args)"
(...args) => forwardEvent('root-layers-sort', ...args) @select-child-layer="(...args) => forwardEvent('select-child-layer', ...args)"
" @start-child-layer-edit="(...args) => forwardEvent('start-child-layer-edit', ...args)"
@child-layers-sort=" @child-context-menu="(...args) => forwardEvent('child-context-menu', ...args)"
(...args) => forwardEvent('child-layers-sort', ...args)
"
@cross-level-move="
(...args) => forwardEvent('cross-level-move', ...args)
"
@select-child-layer="
(...args) => forwardEvent('select-child-layer', ...args)
"
@start-child-layer-edit="
(...args) => forwardEvent('start-child-layer-edit', ...args)
"
@child-context-menu="
(...args) => forwardEvent('child-context-menu', ...args)
"
@toggle-child-visibility=" @toggle-child-visibility="
(...args) => forwardEvent('toggle-child-visibility', ...args) (...args) => forwardEvent('toggle-child-visibility', ...args)
" "
@toggle-child-lock=" @toggle-child-lock="(...args) => forwardEvent('toggle-child-lock', ...args)"
(...args) => forwardEvent('toggle-child-lock', ...args)
"
@finish-child-layer-edit=" @finish-child-layer-edit="
(...args) => forwardEvent('finish-child-layer-edit', ...args) (...args) => forwardEvent('finish-child-layer-edit', ...args)
" "
@@ -424,9 +371,7 @@ const canDeleteComputed = computed(() => {
@child-layer-edit-keydown=" @child-layer-edit-keydown="
(...args) => forwardEvent('child-layer-edit-keydown', ...args) (...args) => forwardEvent('child-layer-edit-keydown', ...args)
" "
@toggle-group-expanded=" @toggle-group-expanded="(...args) => forwardEvent('toggle-group-expanded', ...args)"
(...args) => forwardEvent('toggle-group-expanded', ...args)
"
@delete-child="(...args) => forwardEvent('delete-child', ...args)" @delete-child="(...args) => forwardEvent('delete-child', ...args)"
@rename-child="(...args) => forwardEvent('rename-child', ...args)" @rename-child="(...args) => forwardEvent('rename-child', ...args)"
/> />
@@ -517,7 +462,9 @@ const canDeleteComputed = computed(() => {
// 跨层级拖拽目标区域高亮 // 跨层级拖拽目标区域高亮
.sortable-layers { .sortable-layers {
position: relative; position: relative;
transition: background-color 0.2s ease, border-color 0.2s ease; transition:
background-color 0.2s ease,
border-color 0.2s ease;
border-radius: 4px; border-radius: 4px;
min-height: 40px; // 确保空组也有足够的拖拽区域 min-height: 40px; // 确保空组也有足够的拖拽区域

View File

@@ -72,26 +72,21 @@ const contextMenuItems = ref([]);
// 计算属性:可排序的根级图层(排除背景层和固定层) // 计算属性:可排序的根级图层(排除背景层和固定层)
const sortableRootLayers = computed(() => { const sortableRootLayers = computed(() => {
if (!layers) return []; if (!layers) return [];
return layers.value.filter( return layers.value.filter((layer) => !layer.parentId && !layer.isFixed && !layer.isBackground);
(layer) => !layer.parentId && !layer.isFixed && !layer.isBackground
);
}); });
// 计算属性:不可排序的固定图层(背景层和固定层) // 计算属性:不可排序的固定图层(背景层和固定层)
const fixedLayers = computed(() => { const fixedLayers = computed(() => {
if (!layers) return []; if (!layers) return [];
return layers.value.filter((layer) => { return layers.value.filter((layer) => {
if (props.showFixedLayer) if (props.showFixedLayer) return !layer.parentId && (layer.isFixed || layer.isBackground);
return !layer.parentId && (layer.isFixed || layer.isBackground);
return !layer.parentId && layer.isBackground; // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特 return !layer.parentId && layer.isBackground; // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特
}); });
}); });
// 计算属性:获取当前选中的图层 // 计算属性:获取当前选中的图层
const selectedLayers = computed(() => { const selectedLayers = computed(() => {
return sortableRootLayers.value.filter((layer) => return sortableRootLayers.value.filter((layer) => selectedLayerIds.value.includes(layer.id));
selectedLayerIds.value.includes(layer.id)
);
}); });
// 计算属性:获取当前是否激活子图层 // 计算属性:获取当前是否激活子图层
@@ -224,9 +219,7 @@ function toggleLayerSelection(layer, event) {
} }
const isShift = event.shiftKey; const isShift = event.shiftKey;
const layerIndex = sortableRootLayers.value.findIndex( const layerIndex = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
(l) => l.id === layer.id
);
console.log("isShift", isShift); console.log("isShift", isShift);
if (isShift && lastSelectedIndex.value !== -1) { if (isShift && lastSelectedIndex.value !== -1) {
@@ -386,9 +379,7 @@ async function ungroupSelectedLayer() {
try { try {
const childLayerIds = await layerManager?.ungroupLayers(groupLayer.id); const childLayerIds = await layerManager?.ungroupLayers(groupLayer.id);
console.log( console.log(`✅ 成功解组图层组: ${groupLayer.name}, 子图层: ${childLayerIds}`);
`✅ 成功解组图层组: ${groupLayer.name}, 子图层: ${childLayerIds}`
);
// 清除选择状态 // 清除选择状态
clearSelection(); clearSelection();
@@ -406,9 +397,7 @@ function deleteSelectedLayers() {
} }
// 检查是否包含不能删除的图层 // 检查是否包含不能删除的图层
const undeletableLayers = selectedLayers.filter( const undeletableLayers = selectedLayers.filter((layer) => layer.isBackground || layer.isFixed);
(layer) => layer.isBackground || layer.isFixed
);
if (undeletableLayers.length > 0) { if (undeletableLayers.length > 0) {
console.warn("选择的图层中包含背景层或固定层,无法删除"); console.warn("选择的图层中包含背景层或固定层,无法删除");
@@ -417,10 +406,7 @@ function deleteSelectedLayers() {
// 检查删除后是否还有足够的普通图层 // 检查删除后是否还有足够的普通图层
const remainingNormalLayers = layers.value.filter( const remainingNormalLayers = layers.value.filter(
(layer) => (layer) => !layer.isBackground && !layer.isFixed && !selectedLayerIds.value.includes(layer.id)
!layer.isBackground &&
!layer.isFixed &&
!selectedLayerIds.value.includes(layer.id)
).length; ).length;
if (remainingNormalLayers < 1) { if (remainingNormalLayers < 1) {
@@ -460,9 +446,7 @@ function startEditing(layer) {
// 下一帧聚焦输入框并选中文本 // 下一帧聚焦输入框并选中文本
nextTick(() => { nextTick(() => {
const inputElement = document.querySelector( const inputElement = document.querySelector(`input[data-layer-id="${layer.id}"]`);
`input[data-layer-id="${layer.id}"]`
);
if (inputElement) { if (inputElement) {
inputElement.focus(); inputElement.focus();
inputElement.select(); inputElement.select();
@@ -494,41 +478,41 @@ function handleEditKeydown(event) {
} }
} }
// 获取图层缩略图URL // 获取图层缩略图URL - 弃用
function getLayerThumbnail(layerId) { // function getLayerThumbnail(layerId) {
if (props.thumbnailManager) { // if (props.thumbnailManager) {
return props.thumbnailManager.getLayerThumbnail(layerId); // return props.thumbnailManager.getLayerThumbnail(layerId);
} // }
return null; // return null;
} // }
// 获取图层类型图标 // 获取图层类型图标
function getLayerTypeIcon(layer) { // function getLayerTypeIcon(layer) {
if (!layer) return "🖼️"; // if (!layer) return "🖼️";
if (isGroupLayer(layer)) { // if (isGroupLayer(layer)) {
return "📁"; // return "📁";
} // }
if (layer.fabricObject) { // if (layer.fabricObject) {
switch (layer.fabricObject.type) { // switch (layer.fabricObject.type) {
case "image": // case "image":
return "🖼️"; // return "🖼️";
case "text": // case "text":
return "📝"; // return "📝";
case "rect": // case "rect":
return "▢"; // return "▢";
case "circle": // case "circle":
return "⬤"; // return "⬤";
case "path": // case "path":
return "✎"; // return "✎";
default: // default:
return "⬤"; // return "⬤";
} // }
} // }
return "🖼️"; // return "🖼️";
} // }
// 获取图层的子图层 // 获取图层的子图层
function getChildLayers(parentId) { function getChildLayers(parentId) {
@@ -557,22 +541,22 @@ const toggleGroupExpanded = (groupId) => {
expandedGroupIds.value = new Set(expandedGroupIds.value); expandedGroupIds.value = new Set(expandedGroupIds.value);
}; };
// 渲染单个图层项(递归组件) // // 渲染单个图层项(递归组件)
function renderLayerItem(layer, index) { // function renderLayerItem(layer, index) {
if (!layer) return null; // if (!layer) return null;
const isGroup = isGroupLayerType(layer); // const isGroup = isGroupLayerType(layer);
const children = isGroup ? getChildLayers(layer.id) : []; // const children = isGroup ? getChildLayers(layer.id) : [];
return { // return {
id: layer.id, // id: layer.id,
name: layer.name, // name: layer.name,
isGroup: isGroup, // isGroup: isGroup,
children: children, // children: children,
fabricObject: layer.fabricObject, // fabricObject: layer.fabricObject,
visible: layer.visible, // visible: layer.visible,
}; // };
} // }
// 处理图层点击事件 // 处理图层点击事件
function handleLayerClick(layer, event) { function handleLayerClick(layer, event) {
@@ -580,12 +564,7 @@ function handleLayerClick(layer, event) {
event.stopPropagation(); event.stopPropagation();
// 如果按住修饰键,执行多选逻辑 // 如果按住修饰键,执行多选逻辑
if ( if (event.ctrlKey || event.metaKey || event.shiftKey || isMultiSelectMode.value) {
event.ctrlKey ||
event.metaKey ||
event.shiftKey ||
isMultiSelectMode.value
) {
toggleLayerSelection(layer, event); toggleLayerSelection(layer, event);
} else { } else {
// 普通点击:进入单选模式 // 普通点击:进入单选模式
@@ -595,11 +574,7 @@ function handleLayerClick(layer, event) {
// 如果不是多选模式,才可激活图层 // 如果不是多选模式,才可激活图层
// 1.如果是组,则设置组下的第一个子图层为活动图层 // 1.如果是组,则设置组下的第一个子图层为活动图层
// 2.否则直接设置活动图层 // 2.否则直接设置活动图层
if ( if (isGroupLayerType(layer) && layer.children && layer.children.length > 0) {
isGroupLayerType(layer) &&
layer.children &&
layer.children.length > 0
) {
// 如果是组图层,设置第一个子图层为活动图层 // 如果是组图层,设置第一个子图层为活动图层
layerManager?.setAllActiveGroupLayerCanvasObject?.(layer); layerManager?.setAllActiveGroupLayerCanvasObject?.(layer);
setActiveLayer(layer.children[0].id, { parentId: layer.id }); setActiveLayer(layer.children[0].id, { parentId: layer.id });
@@ -609,9 +584,7 @@ function handleLayerClick(layer, event) {
layerManager?.updateLayersObjectsInteractivity(); layerManager?.updateLayersObjectsInteractivity();
} }
} }
lastSelectedIndex.value = sortableRootLayers.value.findIndex( lastSelectedIndex.value = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
(l) => l.id === layer.id
);
} }
} }
@@ -675,10 +648,7 @@ function buildContextMenuItems(layer) {
{ {
label: "栅格化图层", label: "栅格化图层",
icon: "CPicture", icon: "CPicture",
disabled: disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
layer.isBackground ||
layer.isFixed ||
!layerManager?.canRasterizeLayer?.(layer.id),
action: () => { action: () => {
rasterizeLayer(layer.id); rasterizeLayer(layer.id);
hideContextMenu(); hideContextMenu();
@@ -688,10 +658,7 @@ function buildContextMenuItems(layer) {
{ {
label: "导出图层", label: "导出图层",
icon: "CExport", icon: "CExport",
disabled: disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
layer.isBackground ||
layer.isFixed ||
!layerManager?.canRasterizeLayer?.(layer.id),
action: () => { action: () => {
exportLayerToImage(layer.id); exportLayerToImage(layer.id);
hideContextMenu(); hideContextMenu();
@@ -755,10 +722,7 @@ function buildContextMenuItems(layer) {
label: "置顶", label: "置顶",
icon: "CBottom", icon: "CBottom",
inverIcon: true, // 倒置图标 inverIcon: true, // 倒置图标
disabled: disabled: layer.isBackground || layer.isFixed || !layerManager?.canMoveToTop?.(layer.id),
layer.isBackground ||
layer.isFixed ||
!layerManager?.canMoveToTop?.(layer.id),
action: () => { action: () => {
moveLayerToTop(layer.id); moveLayerToTop(layer.id);
hideContextMenu(); hideContextMenu();
@@ -767,10 +731,7 @@ function buildContextMenuItems(layer) {
{ {
label: "向上移动", label: "向上移动",
icon: "CUp", icon: "CUp",
disabled: disabled: layer.isBackground || layer.isFixed || !layerManager?.canMoveToTop?.(layer.id),
layer.isBackground ||
layer.isFixed ||
!layerManager?.canMoveToTop?.(layer.id),
action: () => { action: () => {
moveLayerUp(layer.id); moveLayerUp(layer.id);
hideContextMenu(); hideContextMenu();
@@ -780,9 +741,7 @@ function buildContextMenuItems(layer) {
label: "向下移动", label: "向下移动",
icon: "CDown", icon: "CDown",
disabled: disabled:
layer.isBackground || layer.isBackground || layer.isFixed || !layerManager?.canMoveToBottom?.(layer.id),
layer.isFixed ||
!layerManager?.canMoveToBottom?.(layer.id),
action: () => { action: () => {
moveLayerDown(layer.id); moveLayerDown(layer.id);
hideContextMenu(); hideContextMenu();
@@ -792,9 +751,7 @@ function buildContextMenuItems(layer) {
label: "置底", label: "置底",
icon: "CBottom", icon: "CBottom",
disabled: disabled:
layer.isBackground || layer.isBackground || layer.isFixed || !layerManager?.canMoveToBottom?.(layer.id),
layer.isFixed ||
!layerManager?.canMoveToBottom?.(layer.id),
action: () => { action: () => {
moveLayerToBottom(layer.id); moveLayerToBottom(layer.id);
hideContextMenu(); hideContextMenu();
@@ -856,18 +813,13 @@ function canDeleteLayers() {
if (selectedLayers.length === 0) return false; if (selectedLayers.length === 0) return false;
// 检查是否包含不能删除的图层 // 检查是否包含不能删除的图层
const undeletableLayers = selectedLayers.filter( const undeletableLayers = selectedLayers.filter((layer) => layer.isBackground || layer.isFixed);
(layer) => layer.isBackground || layer.isFixed
);
if (undeletableLayers.length > 0) return false; if (undeletableLayers.length > 0) return false;
// 检查删除后是否还有足够的普通图层 // 检查删除后是否还有足够的普通图层
const remainingNormalLayers = layers.value.filter( const remainingNormalLayers = layers.value.filter(
(layer) => (layer) => !layer.isBackground && !layer.isFixed && !selectedLayerIds.value.includes(layer.id)
!layer.isBackground &&
!layer.isFixed &&
!selectedLayerIds.value.includes(layer.id)
).length; ).length;
return remainingNormalLayers >= 1; return remainingNormalLayers >= 1;
@@ -904,9 +856,7 @@ function startChildLayerEdit(childLayer) {
childLayer.tempName = childLayer.name; childLayer.tempName = childLayer.name;
nextTick(() => { nextTick(() => {
const inputElement = document.querySelector( const inputElement = document.querySelector(`input[data-child-layer-id="${childLayer.id}"]`);
`input[data-child-layer-id="${childLayer.id}"]`
);
if (inputElement) { if (inputElement) {
inputElement.focus(); inputElement.focus();
inputElement.select(); inputElement.select();
@@ -943,8 +893,7 @@ function handleChildLayerEditKeydown(event, childLayer) {
// 选中子图层 // 选中子图层
function selectChildLayer(layerId, parentId) { function selectChildLayer(layerId, parentId) {
if (!isMultiSelectMode.value) if (!isMultiSelectMode.value) layerManager?.setActiveLayer(layerId, { parentId });
layerManager?.setActiveLayer(layerId, { parentId });
} }
// 子图层右键菜单处理 // 子图层右键菜单处理
@@ -991,8 +940,7 @@ function buildChildLayerContextMenuItems(childLayer) {
{ {
label: childLayer.visible ? "隐藏图层" : "显示图层", label: childLayer.visible ? "隐藏图层" : "显示图层",
icon: childLayer.visible ? "CUnEye" : "CEye", icon: childLayer.visible ? "CUnEye" : "CEye",
action: () => action: () => toggleChildLayerVisibility(childLayer.id, childLayer.parentId),
toggleChildLayerVisibility(childLayer.id, childLayer.parentId),
}, },
{ type: "divider" }, { type: "divider" },
// 移出组 // 移出组
@@ -1045,9 +993,7 @@ async function handleRootLayersSort(event) {
try { try {
layerManager?.reorderLayers(oldIndex, newIndex, layerId); layerManager?.reorderLayers(oldIndex, newIndex, layerId);
console.log( console.log(`✅ 图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`);
`✅ 图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`
);
} catch (error) { } catch (error) {
console.error("❌ 图层排序命令执行失败:", error); console.error("❌ 图层排序命令执行失败:", error);
emit("reorder-layers", { emit("reorder-layers", {
@@ -1085,12 +1031,7 @@ async function handleChildLayersSort(event, childrenLayers, parentId) {
const layerToMove = childrenLayers[oldIndex]; const layerToMove = childrenLayers[oldIndex];
if (!layerToMove) { if (!layerToMove) {
console.error( console.error("❌ 找不到要移动的子图层oldIndex:", oldIndex, "childLayers:", childrenLayers);
"❌ 找不到要移动的子图层oldIndex:",
oldIndex,
"childLayers:",
childrenLayers
);
return; return;
} }
@@ -1105,9 +1046,7 @@ async function handleChildLayersSort(event, childrenLayers, parentId) {
try { try {
layerManager?.moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }); layerManager?.moveLayerToIndex({ parentId, oldIndex, newIndex, layerId });
console.log( console.log(`✅ 子图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`);
`✅ 子图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`
);
} catch (error) { } catch (error) {
console.error("❌ 子图层排序命令执行失败:", error); console.error("❌ 子图层排序命令执行失败:", error);
emit("reorder-child-layers", { emit("reorder-child-layers", {
@@ -1168,11 +1107,7 @@ async function mergeGroupLayer(groupId) {
const childLayerId = await layerManager.mergeGroupLayers(groupId); const childLayerId = await layerManager.mergeGroupLayers(groupId);
if (childLayerId) { if (childLayerId) {
const groupLayer = layers.value.find((l) => l.id === groupId); const groupLayer = layers.value.find((l) => l.id === groupId);
console.log( console.log(`✅ 成功合并组图层: ${groupLayer?.name || groupId}, 生成 ${childLayerId} 图层`);
`✅ 成功合并组图层: ${
groupLayer?.name || groupId
}, 生成 ${childLayerId} 图层`
);
} else { } else {
console.warn("合并组图层失败"); console.warn("合并组图层失败");
} }
@@ -1284,8 +1219,7 @@ async function handleCrossLevelMove(moveData) {
目标图层不是组图层: "只能将图层移动到组图层中", 目标图层不是组图层: "只能将图层移动到组图层中",
}; };
const userMessage = const userMessage = errorMessages[error.message] || `移动失败: ${error.message}`;
errorMessages[error.message] || `移动失败: ${error.message}`;
// 这里可以触发一个全局的错误提示组件 // 这里可以触发一个全局的错误提示组件
// 暂时使用console.warn实际项目中应该替换为适当的提示方式 // 暂时使用console.warn实际项目中应该替换为适当的提示方式
@@ -1315,9 +1249,7 @@ async function executeDirectMove(moveData) {
} else if (fromContainerType === "child" && fromParentId) { } else if (fromContainerType === "child" && fromParentId) {
sourceParent = layers.value.find((layer) => layer.id === fromParentId); sourceParent = layers.value.find((layer) => layer.id === fromParentId);
if (sourceParent && sourceParent.children) { if (sourceParent && sourceParent.children) {
draggedLayer = sourceParent.children.find( draggedLayer = sourceParent.children.find((child) => child.id === layerId);
(child) => child.id === layerId
);
} }
} }
@@ -1385,9 +1317,7 @@ async function moveRootToGroup(draggedLayer, toParentId, newIndex) {
} }
// 从顶级图层数组中移除 // 从顶级图层数组中移除
const rootIndex = layers.value.findIndex( const rootIndex = layers.value.findIndex((layer) => layer.id === draggedLayer.id);
(layer) => layer.id === draggedLayer.id
);
if (rootIndex !== -1) { if (rootIndex !== -1) {
layers.value.splice(rootIndex, 1); layers.value.splice(rootIndex, 1);
} }
@@ -1432,9 +1362,7 @@ async function moveGroupToRoot(draggedLayer, fromParentId, newIndex) {
} else { } else {
// 回退方案:手动更新图层关系 // 回退方案:手动更新图层关系
// 从源父图层的children中移除 // 从源父图层的children中移除
const childIndex = sourceParent.children.findIndex( const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
(child) => child.id === draggedLayer.id
);
if (childIndex !== -1) { if (childIndex !== -1) {
sourceParent.children.splice(childIndex, 1); sourceParent.children.splice(childIndex, 1);
} }
@@ -1461,12 +1389,7 @@ async function moveGroupToRoot(draggedLayer, fromParentId, newIndex) {
} }
// 在不同组之间移动 // 在不同组之间移动
async function moveGroupToGroup( async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex) {
draggedLayer,
fromParentId,
toParentId,
newIndex
) {
console.log("🔄 在不同组间移动:", { console.log("🔄 在不同组间移动:", {
layerId: draggedLayer.id, layerId: draggedLayer.id,
fromParentId, fromParentId,
@@ -1488,18 +1411,11 @@ async function moveGroupToGroup(
// 使用 layerManager 的方法在组间移动 // 使用 layerManager 的方法在组间移动
if (layerManager?.moveLayerBetweenGroups) { if (layerManager?.moveLayerBetweenGroups) {
await layerManager.moveLayerBetweenGroups( await layerManager.moveLayerBetweenGroups(draggedLayer.id, fromParentId, toParentId, newIndex);
draggedLayer.id,
fromParentId,
toParentId,
newIndex
);
} else { } else {
// 回退方案:手动更新图层关系 // 回退方案:手动更新图层关系
// 从源父图层中移除 // 从源父图层中移除
const childIndex = sourceParent.children.findIndex( const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
(child) => child.id === draggedLayer.id
);
if (childIndex !== -1) { if (childIndex !== -1) {
sourceParent.children.splice(childIndex, 1); sourceParent.children.splice(childIndex, 1);
@@ -1585,19 +1501,11 @@ async function moveGroupToGroup(
> >
<SvgIcon name="CPlusTop" size="16"></SvgIcon> <SvgIcon name="CPlusTop" size="16"></SvgIcon>
</div> </div>
<div <div class="add-layer-btn action-btn" @click="addLayer" :title="$t('添加图层')">
class="add-layer-btn action-btn"
@click="addLayer"
:title="$t('添加图层')"
>
<SvgIcon name="CPlus" size="16"></SvgIcon> <SvgIcon name="CPlus" size="16"></SvgIcon>
</div> </div>
<div <div class="select-all-btn action-btn" @click="selectAllLayers" :title="$t('全选图层')">
class="select-all-btn action-btn"
@click="selectAllLayers"
:title="$t('全选图层')"
>
<SvgIcon name="CCheckbox" size="16"></SvgIcon> <SvgIcon name="CCheckbox" size="16"></SvgIcon>
</div> </div>
</div> </div>
@@ -1668,15 +1576,11 @@ async function moveGroupToGroup(
:editing-name="editingLayerName" :editing-name="editingLayerName"
:can-delete="false" :can-delete="false"
:isHidenDragHandle="true" :isHidenDragHandle="true"
@toggle-visibility=" @toggle-visibility="(...args) => forwardEvent('toggle-layer-visibility', ...args)"
(...args) => forwardEvent('toggle-layer-visibility', ...args)
"
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)" @edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
@edit-cancel="(...args) => forwardEvent('edit-cancel', ...args)" @edit-cancel="(...args) => forwardEvent('edit-cancel', ...args)"
@edit-keydown="(...args) => forwardEvent('edit-keydown', ...args)" @edit-keydown="(...args) => forwardEvent('edit-keydown', ...args)"
@update:editing-name=" @update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
(...args) => forwardEvent('update:editing-name', ...args)
"
/> />
</div> </div>

View File

@@ -374,10 +374,7 @@ onMounted(() => {
}); });
// 创建状态管理器 // 创建状态管理器
stateManager.value = new LiquifyStateManager( stateManager.value = new LiquifyStateManager(props.canvas, realtimeUpdater.value);
props.canvas,
realtimeUpdater.value
);
} }
// 监听画布事件 // 监听画布事件
@@ -446,12 +443,7 @@ function showPanel(event) {
return; return;
} }
console.log( console.log("显示液化面板,目标对象:", targetObj.type, "图层ID:", targetLayerIdValue);
"显示液化面板,目标对象:",
targetObj.type,
"图层ID:",
targetLayerIdValue
);
// 更新面板状态,但保持当前模式不变 // 更新面板状态,但保持当前模式不变
// 只有在首次显示面板或面板已关闭时才重置模式 // 只有在首次显示面板或面板已关闭时才重置模式
@@ -521,8 +513,8 @@ function showPanel(event) {
const status = props.liquifyManager.getStatus const status = props.liquifyManager.getStatus
? props.liquifyManager.getStatus() ? props.liquifyManager.getStatus()
: props.liquifyManager.enhancedManager : props.liquifyManager.enhancedManager
? props.liquifyManager.enhancedManager.getStatus() ? props.liquifyManager.enhancedManager.getStatus()
: {}; : {};
webglAvailable.value = status.isWebGLAvailable || false; webglAvailable.value = status.isWebGLAvailable || false;
@@ -553,8 +545,8 @@ function showPanel(event) {
const status = props.liquifyManager.getStatus const status = props.liquifyManager.getStatus
? props.liquifyManager.getStatus() ? props.liquifyManager.getStatus()
: props.liquifyManager.enhancedManager : props.liquifyManager.enhancedManager
? props.liquifyManager.enhancedManager.getStatus() ? props.liquifyManager.enhancedManager.getStatus()
: {}; : {};
webglAvailable.value = status.isWebGLAvailable || false; webglAvailable.value = status.isWebGLAvailable || false;
@@ -613,11 +605,7 @@ function setTargetObject(obj) {
// 确保对象有唯一ID // 确保对象有唯一ID
if (!obj.id && !obj.objectId && !obj.uid) { if (!obj.id && !obj.objectId && !obj.uid) {
obj.id = obj.id = "liquify_target_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
"liquify_target_" +
Date.now() +
"_" +
Math.random().toString(36).substr(2, 9);
} }
targetObject.value = obj; targetObject.value = obj;
@@ -685,9 +673,7 @@ function updateParam(paramName, value) {
`set${paramName.charAt(0).toUpperCase() + paramName.slice(1)}` `set${paramName.charAt(0).toUpperCase() + paramName.slice(1)}`
] === "function" ] === "function"
) { ) {
props.liquifyManager[ props.liquifyManager[`set${paramName.charAt(0).toUpperCase() + paramName.slice(1)}`](value);
`set${paramName.charAt(0).toUpperCase() + paramName.slice(1)}`
](value);
} else { } else {
console.warn(`❌ 液化管理器不支持设置参数: ${paramName}`); console.warn(`❌ 液化管理器不支持设置参数: ${paramName}`);
} }
@@ -770,19 +756,9 @@ async function getCurrentImageData(targetObject) {
tempCtx.drawImage(element, 0, 0, tempCanvas.width, tempCanvas.height); tempCtx.drawImage(element, 0, 0, tempCanvas.width, tempCanvas.height);
// 获取ImageData // 获取ImageData
const imageData = tempCtx.getImageData( const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log( console.log("✅ 成功获取当前图像状态,尺寸:", imageData.width, "x", imageData.height);
"✅ 成功获取当前图像状态,尺寸:",
imageData.width,
"x",
imageData.height
);
return imageData; return imageData;
} catch (error) { } catch (error) {
console.warn("获取当前图像数据失败:", error); console.warn("获取当前图像数据失败:", error);
@@ -861,9 +837,7 @@ async function handleMouseDown(event) {
const imageCoords = _convertFabricCoordsToImageCoords(pointer.x, pointer.y); const imageCoords = _convertFabricCoordsToImageCoords(pointer.x, pointer.y);
if (imageCoords) { if (imageCoords) {
props.liquifyManager.startLiquifyOperation(imageCoords.x, imageCoords.y); props.liquifyManager.startLiquifyOperation(imageCoords.x, imageCoords.y);
console.log( console.log(`开始液化操作,图像坐标: (${imageCoords.x}, ${imageCoords.y})`);
`开始液化操作,图像坐标: (${imageCoords.x}, ${imageCoords.y})`
);
} }
} }
@@ -959,27 +933,15 @@ async function handleMouseUp() {
// 尝试从实时更新器获取当前图像数据 // 尝试从实时更新器获取当前图像数据
if (realtimeUpdater.value) { if (realtimeUpdater.value) {
try { try {
const currentImageElement = const currentImageElement = realtimeUpdater.value.getTargetObject()?._element;
realtimeUpdater.value.getTargetObject()?._element;
if (currentImageElement) { if (currentImageElement) {
// 从当前图像元素创建ImageData // 从当前图像元素创建ImageData
const tempCanvas = document.createElement("canvas"); const tempCanvas = document.createElement("canvas");
tempCanvas.width = initialImageData.value.width; tempCanvas.width = initialImageData.value.width;
tempCanvas.height = initialImageData.value.height; tempCanvas.height = initialImageData.value.height;
const tempCtx = tempCanvas.getContext("2d"); const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage( tempCtx.drawImage(currentImageElement, 0, 0, tempCanvas.width, tempCanvas.height);
currentImageElement, finalImageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
0,
0,
tempCanvas.width,
tempCanvas.height
);
finalImageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log( console.log(
"✅ 从实时更新器获取最终图像数据成功,尺寸:", "✅ 从实时更新器获取最终图像数据成功,尺寸:",
@@ -1000,19 +962,8 @@ async function handleMouseUp() {
tempCanvas.width = initialImageData.value.width; tempCanvas.width = initialImageData.value.width;
tempCanvas.height = initialImageData.value.height; tempCanvas.height = initialImageData.value.height;
const tempCtx = tempCanvas.getContext("2d"); const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage( tempCtx.drawImage(currentTarget._element, 0, 0, tempCanvas.width, tempCanvas.height);
currentTarget._element, finalImageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
0,
0,
tempCanvas.width,
tempCanvas.height
);
finalImageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log( console.log(
"✅ 从目标对象获取最终图像数据成功,尺寸:", "✅ 从目标对象获取最终图像数据成功,尺寸:",
@@ -1097,14 +1048,7 @@ async function applyLiquifyAtPoint(x, y) {
return; return;
} }
console.log( console.log("原始坐标:", x, y, "转换后图像坐标:", imageCoords.x, imageCoords.y);
"原始坐标:",
x,
y,
"转换后图像坐标:",
imageCoords.x,
imageCoords.y
);
// 获取当前参数 // 获取当前参数
const params = { const params = {
@@ -1169,8 +1113,7 @@ async function applyLiquifyAtPoint(x, y) {
targetObject.value = updatedObject; targetObject.value = updatedObject;
// 如果对象有新的ID也要更新ID // 如果对象有新的ID也要更新ID
if (updatedObject.id || updatedObject.objectId || updatedObject.uid) { if (updatedObject.id || updatedObject.objectId || updatedObject.uid) {
targetObjectId.value = targetObjectId.value = updatedObject.id || updatedObject.objectId || updatedObject.uid;
updatedObject.id || updatedObject.objectId || updatedObject.uid;
} }
} }
} }
@@ -1184,8 +1127,7 @@ async function applyLiquifyAtPoint(x, y) {
if (avgOperationTime.value === 0) { if (avgOperationTime.value === 0) {
avgOperationTime.value = operationTime; avgOperationTime.value = operationTime;
} else { } else {
avgOperationTime.value = avgOperationTime.value = 0.7 * avgOperationTime.value + 0.3 * operationTime;
0.7 * avgOperationTime.value + 0.3 * operationTime;
} }
// 将性能指标传递给状态管理器 // 将性能指标传递给状态管理器
@@ -1265,7 +1207,7 @@ async function _updateImageOnCanvas(imageData) {
} }
} else { } else {
// 拖拽结束后进行完整的对象替换 // 拖拽结束后进行完整的对象替换
await _replaceTargetObjectWithNewImage(dataURL); // await _replaceTargetObjectWithNewImage(dataURL);
} }
} catch (error) { } catch (error) {
console.error("更新画布图像时出错:", error); console.error("更新画布图像时出错:", error);
@@ -1325,9 +1267,7 @@ async function _replaceTargetWithNewImage(dataURL) {
if (layer.type === "background" || layer.isBackground) { if (layer.type === "background" || layer.isBackground) {
layer.fabricObject = img; layer.fabricObject = img;
} else if (layer.fabricObjects) { } else if (layer.fabricObjects) {
const objIndex = layer.fabricObjects.findIndex( const objIndex = layer.fabricObjects.findIndex((obj) => obj.id === img.id);
(obj) => obj.id === img.id
);
if (objIndex !== -1) { if (objIndex !== -1) {
layer.fabricObjects[objIndex] = img; layer.fabricObjects[objIndex] = img;
} }
@@ -1364,10 +1304,7 @@ function _convertFabricCoordsToImageCoords(fabricX, fabricY) {
const matrix = fabric.util.invertTransform(transform); const matrix = fabric.util.invertTransform(transform);
// 应用逆变换,将画布坐标转换为对象本地坐标 // 应用逆变换,将画布坐标转换为对象本地坐标
const localPoint = fabric.util.transformPoint( const localPoint = fabric.util.transformPoint(new fabric.Point(fabricX, fabricY), matrix);
new fabric.Point(fabricX, fabricY),
matrix
);
// 获取图像的原始尺寸(未缩放前) // 获取图像的原始尺寸(未缩放前)
const imageWidth = originalImageData.value.width; const imageWidth = originalImageData.value.width;
@@ -1403,12 +1340,7 @@ function _convertFabricCoordsToImageCoords(fabricX, fabricY) {
); );
// 检查坐标是否在图像范围内 // 检查坐标是否在图像范围内
if ( if (imageX < 0 || imageX >= imageWidth || imageY < 0 || imageY >= imageHeight) {
imageX < 0 ||
imageX >= imageWidth ||
imageY < 0 ||
imageY >= imageHeight
) {
console.warn( console.warn(
`坐标超出图像范围: (${imageX}, ${imageY}), 图像尺寸: ${imageWidth}x${imageHeight}` `坐标超出图像范围: (${imageX}, ${imageY}), 图像尺寸: ${imageWidth}x${imageHeight}`
); );
@@ -1607,7 +1539,9 @@ function stopPressTimer() {
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.3s, transform 0.3s; transition:
opacity 0.3s,
transform 0.3s;
} }
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {

View File

@@ -68,9 +68,7 @@ onBeforeUnmount(() => {
<div class="minimap-container"> <div class="minimap-container">
<div class="minimap-header"> <div class="minimap-header">
<span>画布小地图</span> <span>画布小地图</span>
<button class="minimap-refresh" @click="forceRefresh" title="刷新小地图"> <button class="minimap-refresh" @click="forceRefresh" title="刷新小地图"></button>
</button>
</div> </div>
<div class="minimap-content" ref="minimapContainerRef"> <div class="minimap-content" ref="minimapContainerRef">
<!-- 不再需要直接提供canvas引用由MinimapManager内部创建 --> <!-- 不再需要直接提供canvas引用由MinimapManager内部创建 -->

View File

@@ -10,30 +10,21 @@
<div class="tool-types"> <div class="tool-types">
<div <div
:class="[ :class="['tool-btn', { active: selectionType === OperationType.LASSO }]"
'tool-btn',
{ active: selectionType === OperationType.LASSO },
]"
@click="setSelectionType(OperationType.LASSO)" @click="setSelectionType(OperationType.LASSO)"
> >
<svg-icon name="CFree" size="26" /> <svg-icon name="CFree" size="26" />
<span>{{ $t("手绘") }}</span> <span>{{ $t("手绘") }}</span>
</div> </div>
<div <div
:class="[ :class="['tool-btn', { active: selectionType === OperationType.LASSO_RECTANGLE }]"
'tool-btn',
{ active: selectionType === OperationType.LASSO_RECTANGLE },
]"
@click="setSelectionType(OperationType.LASSO_RECTANGLE)" @click="setSelectionType(OperationType.LASSO_RECTANGLE)"
> >
<svg-icon name="CRectangle" size="32" /> <svg-icon name="CRectangle" size="32" />
<span>{{ $t("矩形") }}</span> <span>{{ $t("矩形") }}</span>
</div> </div>
<div <div
:class="[ :class="['tool-btn', { active: selectionType === OperationType.LASSO_ELLIPSE }]"
'tool-btn',
{ active: selectionType === OperationType.LASSO_ELLIPSE },
]"
@click="setSelectionType(OperationType.LASSO_ELLIPSE)" @click="setSelectionType(OperationType.LASSO_ELLIPSE)"
> >
<svg-icon name="CEllipse" size="30" /> <svg-icon name="CEllipse" size="30" />
@@ -159,9 +150,7 @@
<div class="dialog-container"> <div class="dialog-container">
<div class="dialog-header"> <div class="dialog-header">
<h3>{{ $t("选择填充颜色") }}</h3> <h3>{{ $t("选择填充颜色") }}</h3>
<button class="close-dialog-btn" @click="cancelColorPicker"> <button class="close-dialog-btn" @click="cancelColorPicker">×</button>
×
</button>
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
<input type="color" v-model="fillColor" class="color-picker" /> <input type="color" v-model="fillColor" class="color-picker" />
@@ -249,6 +238,7 @@ onMounted(() => {
checkSelectionStatus(); checkSelectionStatus();
// 设置选区状态变化的回调 // 设置选区状态变化的回调
// eslint-disable-next-line vue/no-mutating-props
props.selectionManager.onSelectionChanged = () => { props.selectionManager.onSelectionChanged = () => {
checkSelectionStatus(); checkSelectionStatus();
}; };
@@ -321,8 +311,7 @@ function setSelectionType(type) {
*/ */
function checkSelectionStatus() { function checkSelectionStatus() {
hasSelection.value = hasSelection.value =
props.selectionManager && props.selectionManager && props.selectionManager.getSelectionObject() !== null;
props.selectionManager.getSelectionObject() !== null;
// 同步羽化值 // 同步羽化值
if (hasSelection.value) { if (hasSelection.value) {
@@ -836,7 +825,9 @@ function confirmColorPicker() {
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.3s, transform 0.3s; transition:
opacity 0.3s,
transform 0.3s;
} }
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {

View File

@@ -1,10 +1,6 @@
<!-- filepath: /Users/aaron/work/pc/air/canvasEdit/src/components/CanvasEditor/components/TextEditorPanel.vue --> <!-- filepath: /Users/aaron/work/pc/air/canvasEdit/src/components/CanvasEditor/components/TextEditorPanel.vue -->
<template> <template>
<div <div v-if="visible" class="text-editor-panel" :class="{ 'is-active': visible }">
v-if="visible"
class="text-editor-panel"
:class="{ 'is-active': visible }"
>
<div class="text-editor-panel-header"> <div class="text-editor-panel-header">
<div class="header-btn import-btn">编辑文本样式</div> <div class="header-btn import-btn">编辑文本样式</div>
<div class="header-actions"> <div class="header-actions">
@@ -185,10 +181,7 @@
<div class="bg-header">字体色</div> <div class="bg-header">字体色</div>
<div class="bg-options"> <div class="bg-options">
<div class="style-btn color-btn" @click="openColorPicker('text')"> <div class="style-btn color-btn" @click="openColorPicker('text')">
<div <div class="style-icon color-icon" :style="{ backgroundColor: textColor }"></div>
class="style-icon color-icon"
:style="{ backgroundColor: textColor }"
></div>
</div> </div>
</div> </div>
</div> </div>
@@ -196,22 +189,15 @@
<div class="background-controls"> <div class="background-controls">
<div class="bg-header">背景色</div> <div class="bg-header">背景色</div>
<div class="bg-options"> <div class="bg-options">
<div <div class="style-btn color-btn" @click="openColorPicker('background')">
class="style-btn color-btn"
@click="openColorPicker('background')"
>
<div <div
class="style-icon color-icon" class="style-icon color-icon"
:style="{ :style="{
backgroundColor: hasTransparentBg backgroundColor: hasTransparentBg ? 'transparent' : backgroundColor,
? 'transparent'
: backgroundColor,
backgroundImage: hasTransparentBg backgroundImage: hasTransparentBg
? 'linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc), linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc)' ? 'linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc), linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc)'
: 'none', : 'none',
backgroundSize: hasTransparentBg backgroundSize: hasTransparentBg ? '8px 8px, 8px 8px' : 'auto',
? '8px 8px, 8px 8px'
: 'auto',
backgroundPosition: hasTransparentBg ? '0 0, 4px 4px' : '0 0', backgroundPosition: hasTransparentBg ? '0 0, 4px 4px' : '0 0',
}" }"
></div> ></div>
@@ -232,18 +218,11 @@
<div v-if="showColorPicker" class="color-picker-modal"> <div v-if="showColorPicker" class="color-picker-modal">
<div class="color-picker-container"> <div class="color-picker-container">
<div class="color-picker-header"> <div class="color-picker-header">
<span>{{ <span>{{ colorPickerMode === "text" ? "选择文字颜色" : "选择背景颜色" }}</span>
colorPickerMode === "text" ? "选择文字颜色" : "选择背景颜色"
}}</span>
<div class="close-color-picker" @click="closeColorPicker">×</div> <div class="close-color-picker" @click="closeColorPicker">×</div>
</div> </div>
<div class="color-picker-content"> <div class="color-picker-content">
<input <input type="color" v-model="currentColor" @change="updateColor" class="color-input" />
type="color"
v-model="currentColor"
@change="updateColor"
class="color-input"
/>
<div class="color-presets"> <div class="color-presets">
<div <div
v-for="(color, index) in colorPresets" v-for="(color, index) in colorPresets"
@@ -253,9 +232,7 @@
@click="selectPresetColor(color)" @click="selectPresetColor(color)"
></div> ></div>
</div> </div>
<div class="confirm-color-btn" @click="confirmColorSelection"> <div class="confirm-color-btn" @click="confirmColorSelection">确定</div>
确定
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -316,7 +293,7 @@ export default {
// 新增属性 // 新增属性
const charSpacingPercent = ref(0); const charSpacingPercent = ref(0);
const textSpacing = ref(0); const textSpacing = ref(0);
const baseline = ref(0); // const baseline = ref(0);
const showColorPicker = ref(false); const showColorPicker = ref(false);
const colorPickerMode = ref("text"); // 'text' 或 'background' const colorPickerMode = ref("text"); // 'text' 或 'background'
const currentColor = ref("#000000"); const currentColor = ref("#000000");
@@ -406,8 +383,7 @@ export default {
textColor.value = textObject.value.fill || "#000000"; textColor.value = textObject.value.fill || "#000000";
backgroundColor.value = textObject.value.textBackgroundColor || "#ffffff"; backgroundColor.value = textObject.value.textBackgroundColor || "#ffffff";
hasTransparentBg.value = !textObject.value.textBackgroundColor; hasTransparentBg.value = !textObject.value.textBackgroundColor;
opacity.value = opacity.value = textObject.value.opacity !== undefined ? textObject.value.opacity : 1;
textObject.value.opacity !== undefined ? textObject.value.opacity : 1;
// 样式 // 样式
fontWeight.value = textObject.value.fontWeight || "normal"; fontWeight.value = textObject.value.fontWeight || "normal";
@@ -422,7 +398,7 @@ export default {
// 转换字符间距为百分比显示 // 转换字符间距为百分比显示
charSpacingPercent.value = charSpacing.value / 10; charSpacingPercent.value = charSpacing.value / 10;
textSpacing.value = charSpacingPercent.value; // 暂用相同值 textSpacing.value = charSpacingPercent.value; // 暂用相同值
baseline.value = 0; // Fabric.js没有直接支持基线偏移用0初始化 // baseline.value = 0; // Fabric.js没有直接支持基线偏移用0初始化
}; };
const selectFont = (fontName) => { const selectFont = (fontName) => {
@@ -459,8 +435,7 @@ export default {
// 颜色选择器相关功能 // 颜色选择器相关功能
const openColorPicker = (mode) => { const openColorPicker = (mode) => {
colorPickerMode.value = mode; colorPickerMode.value = mode;
currentColor.value = currentColor.value = mode === "text" ? textColor.value : backgroundColor.value;
mode === "text" ? textColor.value : backgroundColor.value;
showColorPicker.value = true; showColorPicker.value = true;
}; };
@@ -602,12 +577,12 @@ export default {
executeCommand(command); executeCommand(command);
}; };
// 基线更新 (Fabric.js没有直接支持可能需要自定义实现) // // 基线更新 (Fabric.js没有直接支持可能需要自定义实现)
const updateBaseline = () => { // const updateBaseline = () => {
console.log("基线调整功能待实现", baseline.value); // // console.log("基线调整功能待实现", baseline.value);
// 注意Fabric.js 5没有直接支持基线调整 // // 注意Fabric.js 5没有直接支持基线调整
// 可能需要通过自定义处理或CSS方式实现 // // 可能需要通过自定义处理或CSS方式实现
}; // };
// 透明度更新 // 透明度更新
const updateOpacity = () => { const updateOpacity = () => {
@@ -662,7 +637,7 @@ export default {
lineHeight, lineHeight,
charSpacingPercent, charSpacingPercent,
textSpacing, textSpacing,
baseline, // baseline,
showColorPicker, showColorPicker,
colorPickerMode, colorPickerMode,
currentColor, currentColor,
@@ -690,7 +665,7 @@ export default {
updateCharSpacing, updateCharSpacing,
updateTextSpacing, updateTextSpacing,
updateLineHeight, updateLineHeight,
updateBaseline, // updateBaseline,
updateOpacity, updateOpacity,
executeCommand, executeCommand,
}; };

View File

@@ -259,10 +259,7 @@ function handleKeyDown(event) {
const key = event.key.toUpperCase(); const key = event.key.toUpperCase();
// 当处于输入状态时不触发快捷键 // 当处于输入状态时不触发快捷键
if ( if (event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA") {
event.target.tagName === "INPUT" ||
event.target.tagName === "TEXTAREA"
) {
return; return;
} }
@@ -312,10 +309,7 @@ const handleToolClick = (tool) => {
/> />
<!-- 自定义工具栏按钮插槽 --> <!-- 自定义工具栏按钮插槽 -->
<slot <slot name="customTools" :tool-button-props="{ activeTool, canUndo, canRedo }" />
name="customTools"
:tool-button-props="{ activeTool, canUndo, canRedo }"
/>
</div> </div>
</template> </template>

View File

@@ -7,10 +7,7 @@
@touchstart.prevent="startTouchSliding" @touchstart.prevent="startTouchSliding"
@click="handleClick" @click="handleClick"
> >
<div <div class="slider-fill" :style="{ height: `${displayPercentage}%` }"></div>
class="slider-fill"
:style="{ height: `${displayPercentage}%` }"
></div>
<div <div
class="slider-thumb" class="slider-thumb"
:style="{ :style="{
@@ -154,9 +151,7 @@ function clearHideTooltipTimer() {
// 计算tooltip的可见性优先使用props中的showTooltip否则使用内部状态 // 计算tooltip的可见性优先使用props中的showTooltip否则使用内部状态
const tooltipVisible = computed(() => { const tooltipVisible = computed(() => {
return props.showTooltip !== undefined return props.showTooltip !== undefined ? props.showTooltip : internalShowTooltip.value;
? props.showTooltip
: internalShowTooltip.value;
}); });
// 更新tooltip状态的方法 // 更新tooltip状态的方法
@@ -250,8 +245,7 @@ function updateValueFromMousePosition(event) {
} }
} else { } else {
// 检查是否可以进入吸附状态 // 检查是否可以进入吸附状态
const snapPercentage = const snapPercentage = (props.snapThreshold / trackHeight) * (props.max - props.min);
(props.snapThreshold / trackHeight) * (props.max - props.min);
// 检查是否接近预设值,增加吸附判定范围 // 检查是否接近预设值,增加吸附判定范围
for (const preset of props.presets) { for (const preset of props.presets) {
@@ -417,8 +411,7 @@ function updateValueFromTouchPosition(event) {
} }
} else { } else {
// 检查是否可以进入吸附状态 // 检查是否可以进入吸附状态
const snapPercentage = const snapPercentage = (props.snapThreshold / trackHeight) * (props.max - props.min);
(props.snapThreshold / trackHeight) * (props.max - props.min);
// 检查是否接近预设值,增加吸附判定范围 // 检查是否接近预设值,增加吸附判定范围
for (const preset of props.presets) { for (const preset of props.presets) {
@@ -500,9 +493,7 @@ onMounted(() => {
// 添加全局点击事件监听 // 添加全局点击事件监听
if (props.closeOnOutsideClick) { if (props.closeOnOutsideClick) {
// 使用 setTimeout 确保点击事件在其他处理程序之后执行 // 使用 setTimeout 确保点击事件在其他处理程序之后执行
window.addEventListener("click", (e) => window.addEventListener("click", (e) => setTimeout(() => handleOutsideClick(e), 0));
setTimeout(() => handleOutsideClick(e), 0)
);
} }
}); });
@@ -616,7 +607,9 @@ onBeforeUnmount(() => {
background: rgba(255, 255, 255, 0.95); background: rgba(255, 255, 255, 0.95);
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05); box-shadow:
0 3px 12px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(0, 0, 0, 0.05);
min-width: 120px; min-width: 120px;
z-index: 10; z-index: 10;
@@ -664,7 +657,9 @@ onBeforeUnmount(() => {
// 淡入淡出动画 // 淡入淡出动画
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.3s, transform 0.3s; transition:
opacity 0.3s,
transform 0.3s;
} }
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {

View File

@@ -34,14 +34,10 @@ import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面
import { LayerType, OperationType } from "./utils/layerHelper.js"; import { LayerType, OperationType } from "./utils/layerHelper.js";
import { ToolManager } from "./managers/toolManager.js"; import { ToolManager } from "./managers/toolManager.js";
import { fabric } from "fabric-with-all"; import { fabric } from "fabric-with-all";
import { import { uploadImageAndCreateLayer, loadImageUrlToLayer, loadImage } from "./utils/imageHelper.js";
uploadImageAndCreateLayer,
loadImageUrlToLayer,
loadImage,
} from "./utils/imageHelper.js";
// import MinimapPanel from "./components/MinimapPanel.vue"; // import MinimapPanel from "./components/MinimapPanel.vue";
const KeyboardShortcutHelp = defineAsyncComponent(() => const KeyboardShortcutHelp = defineAsyncComponent(
import("./components/KeyboardShortcutHelp.vue") () => import("./components/KeyboardShortcutHelp.vue")
); );
const emit = defineEmits([ const emit = defineEmits([
@@ -318,11 +314,7 @@ onMounted(async () => {
await layerManager.initializeLayers(); await layerManager.initializeLayers();
} }
if ( if (props.enabledRedGreenMode && props.clothingImageUrl && props.redGreenImageUrl) {
props.enabledRedGreenMode &&
props.clothingImageUrl &&
props.redGreenImageUrl
) {
canvasManager.canvas.fill = "#fff"; // 设置画布背景色为白色 // 初始化红绿图模式管理器 canvasManager.canvas.fill = "#fff"; // 设置画布背景色为白色 // 初始化红绿图模式管理器
redGreenModeManager = new RedGreenModeManager({ redGreenModeManager = new RedGreenModeManager({
canvas: canvasManager.canvas, canvas: canvasManager.canvas,
@@ -369,10 +361,7 @@ onMounted(async () => {
console.error("更换底图失败:", error); console.error("更换底图失败:", error);
} }
canvasManager?.centerBackgroundLayer?.( canvasManager?.centerBackgroundLayer?.(canvasManager.canvas.width, canvasManager.canvas.height);
canvasManager.canvas.width,
canvasManager.canvas.height
);
} }
// // 设置固定图层是否可擦除 // // 设置固定图层是否可擦除
@@ -395,7 +384,7 @@ onMounted(async () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
setTimeout(() => { setTimeout(() => {
// 初始状态下生成所有预览图 // 初始状态下生成所有预览图
canvasManager.updateAllThumbnails(); canvasManager?.updateAllThumbnails?.();
}, 300); }, 300);
}); });
}); });
@@ -559,9 +548,7 @@ function moveLayerDown(layerId) {
function removeLayer(layerId) { function removeLayer(layerId) {
// Check if this is the last layer - prevent deletion // Check if this is the last layer - prevent deletion
if (layers.value.length <= 2) { if (layers.value.length <= 2) {
console.warn( console.warn("Cannot delete the last layer. At least one layer must remain.");
"Cannot delete the last layer. At least one layer must remain."
);
return; return;
} }
@@ -667,9 +654,7 @@ function handleLayersReorder(reorderData) {
const success = layerManager.reorderLayers(oldIndex, newIndex, layerId); const success = layerManager.reorderLayers(oldIndex, newIndex, layerId);
if (success) { if (success) {
console.log( console.log(`图层 ${layerId} 已从位置 ${oldIndex} 移动到位置 ${newIndex}`);
`图层 ${layerId} 已从位置 ${oldIndex} 移动到位置 ${newIndex}`
);
// 更新画布渲染顺序 // 更新画布渲染顺序
if (canvasManager) { if (canvasManager) {
@@ -686,12 +671,7 @@ function handleChildLayersReorder(reorderData) {
const { parentId, oldIndex, newIndex, layerId } = reorderData; const { parentId, oldIndex, newIndex, layerId } = reorderData;
if (layerManager && layerManager.reorderChildLayers) { if (layerManager && layerManager.reorderChildLayers) {
const success = layerManager.reorderChildLayers( const success = layerManager.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
parentId,
oldIndex,
newIndex,
layerId
);
if (success) { if (success) {
console.log( console.log(
@@ -851,8 +831,7 @@ defineExpose({
* @returns {Object} 优化结果统计 * @returns {Object} 优化结果统计
*/ */
optimizeLayerStructure() { optimizeLayerStructure() {
if (!layerManager) if (!layerManager) return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 };
return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 };
return layerManager.optimizeLayerStructure(); return layerManager.optimizeLayerStructure();
}, },
@@ -965,10 +944,7 @@ defineExpose({
<!-- <MinimapPanel v-if="minimapEnabled" :minimapManager="minimapManager" /> --> <!-- <MinimapPanel v-if="minimapEnabled" :minimapManager="minimapManager" /> -->
<!-- 笔刷控制面板 --> <!-- 笔刷控制面板 -->
<BrushControlPanel <BrushControlPanel v-if="canvasManagerLoaded" :activeTool="activeTool" />
v-if="canvasManagerLoaded"
:activeTool="activeTool"
/>
<!-- 文本编辑面板 --> <!-- 文本编辑面板 -->
<TextEditorPanel <TextEditorPanel
@@ -1001,11 +977,7 @@ defineExpose({
<div class="zoom-info"> <div class="zoom-info">
缩放: {{ currentZoom }}% 缩放: {{ currentZoom }}%
<button class="reset-zoom" @click="resetZoom">重置视图</button> <button class="reset-zoom" @click="resetZoom">重置视图</button>
<button <button class="help-btn" @click="toggleShortcutHelp" title="查看快捷键和触控操作">
class="help-btn"
@click="toggleShortcutHelp"
title="查看快捷键和触控操作"
>
? ?
</button> </button>
</div> </div>
@@ -1045,11 +1017,7 @@ defineExpose({
</div> --> </div> -->
<!-- 快捷键帮助模态框 --> <!-- 快捷键帮助模态框 -->
<div <div v-if="showShortcutHelp" class="modal-overlay" @click="showShortcutHelp = false">
v-if="showShortcutHelp"
class="modal-overlay"
@click="showShortcutHelp = false"
>
<div class="modal-content" @click.stop> <div class="modal-content" @click.stop>
<button class="close-modal" @click="showShortcutHelp = false">×</button> <button class="close-modal" @click="showShortcutHelp = false">×</button>
<KeyboardShortcutHelp /> <KeyboardShortcutHelp />
@@ -1126,30 +1094,20 @@ defineExpose({
--offsetY: 0px; --offsetY: 0px;
--size: 8px; --size: 8px;
--color: #dedcdc; --color: #dedcdc;
background-image: -webkit-linear-gradient( background-image:
-webkit-linear-gradient(
45deg, 45deg,
var(--color) 25%, var(--color) 25%,
transparent 0, transparent 0,
transparent 75%, transparent 75%,
var(--color) 0 var(--color) 0
), ),
-webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent -webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent 75%, var(--color) 0);
75%, var(--color) 0); background-image:
background-image: linear-gradient( linear-gradient(45deg, var(--color) 25%, transparent 0, transparent 75%, var(--color) 0),
45deg, linear-gradient(45deg, var(--color) 25%, transparent 0, transparent 75%, var(--color) 0);
var(--color) 25%, background-position:
transparent 0, var(--offsetX) var(--offsetY),
transparent 75%,
var(--color) 0
),
linear-gradient(
45deg,
var(--color) 25%,
transparent 0,
transparent 75%,
var(--color) 0
);
background-position: var(--offsetX) var(--offsetY),
calc(var(--size) + var(--offsetX)) calc(var(--size) + var(--offsetY)); calc(var(--size) + var(--offsetX)) calc(var(--size) + var(--offsetY));
background-size: calc(var(--size) * 2) calc(var(--size) * 2); background-size: calc(var(--size) * 2) calc(var(--size) * 2);
} }
@@ -1369,7 +1327,9 @@ button:hover {
// 淡入淡出动画 // 淡入淡出动画
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.3s, transform 0.3s; transition:
opacity 0.3s,
transform 0.3s;
} }
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {

View File

@@ -97,8 +97,7 @@ export class BrushIndicator {
if (!this.staticCanvas || !this.canvas) return; if (!this.staticCanvas || !this.canvas) return;
// 检查是否为笔刷或橡皮擦模式,非相关模式直接返回 // 检查是否为笔刷或橡皮擦模式,非相关模式直接返回
const isBrushMode = const isBrushMode = this.canvas.isDrawingMode && this.canvas.freeDrawingBrush;
this.canvas.isDrawingMode && this.canvas.freeDrawingBrush;
const isEraserMode = const isEraserMode =
this.canvas.isDrawingMode && this.canvas.isDrawingMode &&
this.canvas.freeDrawingBrush && this.canvas.freeDrawingBrush &&
@@ -136,10 +135,7 @@ export class BrushIndicator {
const currentVpt = this.canvas.viewportTransform; const currentVpt = this.canvas.viewportTransform;
if ( if (
currentVpt && currentVpt &&
!this._areViewportTransformsEqual( !this._areViewportTransformsEqual(currentVpt, this._lastCanvasState.viewportTransform)
currentVpt,
this._lastCanvasState.viewportTransform
)
) { ) {
this.staticCanvas.setViewportTransform([...currentVpt]); this.staticCanvas.setViewportTransform([...currentVpt]);
this._lastCanvasState.viewportTransform = [...currentVpt]; this._lastCanvasState.viewportTransform = [...currentVpt];

View File

@@ -1,7 +1,5 @@
import { fabric } from "fabric-with-all"; import { fabric } from "fabric-with-all";
import initAligningGuidelines, { import initAligningGuidelines, { initCenteringGuidelines } from "../utils/helperLine";
initCenteringGuidelines,
} from "../utils/helperLine";
import { ThumbnailManager } from "./ThumbnailManager"; import { ThumbnailManager } from "./ThumbnailManager";
import { ExportManager } from "./ExportManager"; import { ExportManager } from "./ExportManager";
import { import {
@@ -16,11 +14,7 @@ import { CanvasEventManager } from "./events/CanvasEventManager";
import CanvasConfig from "../config/canvasConfig"; import CanvasConfig from "../config/canvasConfig";
import { RedGreenModeManager } from "./RedGreenModeManager"; import { RedGreenModeManager } from "./RedGreenModeManager";
import { EraserStateManager } from "./EraserStateManager"; import { EraserStateManager } from "./EraserStateManager";
import { import { deepClone, generateId, optimizeCanvasRendering } from "../utils/helper";
deepClone,
generateId,
optimizeCanvasRendering,
} from "../utils/helper";
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands"; import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
import { isFunction } from "lodash-es"; import { isFunction } from "lodash-es";
import { import {
@@ -155,10 +149,7 @@ export class CanvasManager {
return false; return false;
}; };
this.eraserStateManager = new EraserStateManager( this.eraserStateManager = new EraserStateManager(this.canvas, this.layerManager);
this.canvas,
this.layerManager
);
// 监听擦除开始事件 // 监听擦除开始事件
this.canvas.on("erasing:start", () => { this.canvas.on("erasing:start", () => {
@@ -179,17 +170,12 @@ export class CanvasManager {
} }
// 更新交互性 // 更新交互性
command && command && (await this.layerManager?.updateLayersObjectsInteractivity?.());
(await this.layerManager?.updateLayersObjectsInteractivity?.());
this.thumbnailManager?.generateLayerThumbnail( this.thumbnailManager?.generateLayerThumbnail(this.layerManager?.activeLayerId?.value);
this.layerManager?.activeLayerId?.value
);
// 固定图层 的擦除也需要重新生成缩略图 要判断 当前固定图层是否锁定 // 固定图层 的擦除也需要重新生成缩略图 要判断 当前固定图层是否锁定
const fixedLayer = this.layers?.value?.find( const fixedLayer = this.layers?.value?.find((layer) => layer.isFixed && !layer.locked);
(layer) => layer.isFixed && !layer.locked
);
// 如果有固定图层且未锁定,则生成缩略图 // 如果有固定图层且未锁定,则生成缩略图
fixedLayer && fixedLayer &&
this.isFixedErasable && this.isFixedErasable &&
@@ -378,9 +364,7 @@ export class CanvasManager {
// 设置固定图层的可擦除状态 // 设置固定图层的可擦除状态
layer.locked = flag; layer.locked = flag;
// 更新画布对象的erasable属性 // 更新画布对象的erasable属性
const fabricObject = this.canvas const fabricObject = this.canvas.getObjects().find((obj) => obj.id === layer.id);
.getObjects()
.find((obj) => obj.id === layer.id);
if (fabricObject) { if (fabricObject) {
fabricObject.erasable = flag; fabricObject.erasable = flag;
fabricObject.set("erasable", flag); fabricObject.set("erasable", flag);
@@ -492,9 +476,7 @@ export class CanvasManager {
const deltaY = backgroundObject.top - backgroundOldTop; const deltaY = backgroundObject.top - backgroundOldTop;
// 将相同的偏移量应用到所有其他对象上 // 将相同的偏移量应用到所有其他对象上
const otherObjects = visibleObjects.filter( const otherObjects = visibleObjects.filter((obj) => obj !== backgroundObject);
(obj) => obj !== backgroundObject
);
otherObjects.forEach((obj) => { otherObjects.forEach((obj) => {
obj.set({ obj.set({
@@ -597,8 +579,7 @@ export class CanvasManager {
if (!backgroundLayerObject) return; if (!backgroundLayerObject) return;
const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX; const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX;
const bgHeight = const bgHeight = backgroundLayerObject.height * backgroundLayerObject.scaleY;
backgroundLayerObject.height * backgroundLayerObject.scaleY;
const left = backgroundLayerObject.left; const left = backgroundLayerObject.left;
const top = backgroundLayerObject.top; const top = backgroundLayerObject.top;
@@ -659,9 +640,7 @@ export class CanvasManager {
return obj.isBackground || obj.id === backgroundLayerId; return obj.isBackground || obj.id === backgroundLayerId;
}); });
if (!backgroundLayerByBgLayer) { if (!backgroundLayerByBgLayer) {
console.warn( console.warn("CanvasManager.js = >getBackgroundLayer 方法没有找到背景层");
"CanvasManager.js = >getBackgroundLayer 方法没有找到背景层"
);
} }
return backgroundLayerByBgLayer; return backgroundLayerByBgLayer;
@@ -671,8 +650,7 @@ export class CanvasManager {
* @param {Object} backgroundLayerObject 背景层对象 * @param {Object} backgroundLayerObject 背景层对象
*/ */
updateMaskPosition(backgroundLayerObject) { updateMaskPosition(backgroundLayerObject) {
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath) if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath) return;
return;
const left = backgroundLayerObject.left; const left = backgroundLayerObject.left;
const top = backgroundLayerObject.top; const top = backgroundLayerObject.top;
@@ -737,8 +715,7 @@ export class CanvasManager {
...options, ...options,
}); });
command.undoable = command.undoable = options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
const result = (await command?.execute?.()) || { const result = (await command?.execute?.()) || {
success: false, success: false,
@@ -785,9 +762,7 @@ export class CanvasManager {
...options, ...options,
// 如果没有明确指定,则根据当前模式自动设置 // 如果没有明确指定,则根据当前模式自动设置
restoreOpacityInRedGreen: restoreOpacityInRedGreen:
options.restoreOpacityInRedGreen !== undefined options.restoreOpacityInRedGreen !== undefined ? options.restoreOpacityInRedGreen : true, // 默认在红绿图模式下恢复透明度
? options.restoreOpacityInRedGreen
: true, // 默认在红绿图模式下恢复透明度
}; };
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层 // 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
@@ -801,9 +776,7 @@ export class CanvasManager {
// 获取所有非背景、非固定的普通图层ID // 获取所有非背景、非固定的普通图层ID
const normalLayerIds = const normalLayerIds =
this.layers?.value this.layers?.value
?.filter( ?.filter((layer) => !layer.isBackground && !layer.isFixed && layer.visible)
(layer) => !layer.isBackground && !layer.isFixed && layer.visible
)
?.map((layer) => layer.id) || []; ?.map((layer) => layer.id) || [];
if (normalLayerIds.length > 0) { if (normalLayerIds.length > 0) {
@@ -912,9 +885,11 @@ export class CanvasManager {
// }); // });
// }; // };
try { try {
const simplifyLayersData = simplifyLayers( // 清除画布中选中状态
JSON.parse(JSON.stringify(this.layers.value)) this.canvas.discardActiveObject();
); this.canvas.renderAll();
const simplifyLayersData = simplifyLayers(JSON.parse(JSON.stringify(this.layers.value)));
console.log("获取画布JSON数据...", simplifyLayersData); console.log("获取画布JSON数据...", simplifyLayersData);
return JSON.stringify({ return JSON.stringify({
canvas: this.canvas.toJSON([ canvas: this.canvas.toJSON([
@@ -962,6 +937,7 @@ export class CanvasManager {
this.canvasHeight.value = parsedJson.canvasHeight || this.height; this.canvasHeight.value = parsedJson.canvasHeight || this.height;
this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor; this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const tempLayers = JSON.parse(parsedJson?.layers) || []; const tempLayers = JSON.parse(parsedJson?.layers) || [];
const canvasData = parsedJson?.canvas; const canvasData = parsedJson?.canvas;
@@ -1023,10 +999,7 @@ export class CanvasManager {
// 重置画布数据 // 重置画布数据
this.setCanvasSize(this.canvas.width, this.canvas.height); this.setCanvasSize(this.canvas.width, this.canvas.height);
// 重新构建对象关系 // 重新构建对象关系
restoreObjectLayerAssociations( restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
this.layers.value,
this.canvas.getObjects()
);
// 验证图层关联关系 - 稳定后可以注释 // 验证图层关联关系 - 稳定后可以注释
const isValidate = validateLayerAssociations( const isValidate = validateLayerAssociations(
this.layers.value, this.layers.value,
@@ -1038,8 +1011,7 @@ export class CanvasManager {
// 使用LayerSort工具重新排列画布对象如果可用 // 使用LayerSort工具重新排列画布对象如果可用
await this?.layerManager?.layerSort?.rearrangeObjects(); await this?.layerManager?.layerSort?.rearrangeObjects();
this.layerManager.activeLayerId.value = this.layers.value[0] this.layerManager.activeLayerId.value = this.layers.value[0].children?.length
.children?.length
? this.layers.value[0].children[0].id ? this.layers.value[0].children[0].id
: this.layers.value[0]?.id || parsedJson?.activeLayerId || null; : this.layers.value[0]?.id || parsedJson?.activeLayerId || null;
@@ -1052,9 +1024,7 @@ export class CanvasManager {
await calllBack?.(); await calllBack?.();
// 确保所有对象的交互性正确设置 // 确保所有对象的交互性正确设置
await this.layerManager?.updateLayersObjectsInteractivity?.( await this.layerManager?.updateLayersObjectsInteractivity?.(false);
false
);
console.log(this.layerManager.layers.value); console.log(this.layerManager.layers.value);
// 更新所有缩略图 // 更新所有缩略图
@@ -1211,9 +1181,7 @@ export class CanvasManager {
if (!this.layers || !this.layers.value) return []; if (!this.layers || !this.layers.value) return [];
// 查找所有非背景、非固定的普通图层 // 查找所有非背景、非固定的普通图层
const normalLayers = this.layers.value.filter( const normalLayers = this.layers.value.filter((layer) => !layer.isBackground && !layer.isFixed);
(layer) => !layer.isBackground && !layer.isFixed
);
// 收集所有普通图层中的对象 // 收集所有普通图层中的对象
const objects = []; const objects = [];
@@ -1250,15 +1218,9 @@ export class CanvasManager {
// 比较尺寸允许5%的误差) // 比较尺寸允许5%的误差)
const sizeMatch = const sizeMatch =
Math.abs( Math.abs(obj.width * obj.scaleX - fixedLayerObject.width * fixedLayerObject.scaleX) <
obj.width * obj.scaleX -
fixedLayerObject.width * fixedLayerObject.scaleX
) <
fixedLayerObject.width * fixedLayerObject.scaleX * 0.05 && fixedLayerObject.width * fixedLayerObject.scaleX * 0.05 &&
Math.abs( Math.abs(obj.height * obj.scaleY - fixedLayerObject.height * fixedLayerObject.scaleY) <
obj.height * obj.scaleY -
fixedLayerObject.height * fixedLayerObject.scaleY
) <
fixedLayerObject.height * fixedLayerObject.scaleY * 0.05; fixedLayerObject.height * fixedLayerObject.scaleY * 0.05;
// 比较位置(允许一定的偏差) // 比较位置(允许一定的偏差)

View File

@@ -86,12 +86,7 @@ export class ExportManager {
* @returns {String} 图片数据URL * @returns {String} 图片数据URL
* @private * @private
*/ */
_exportSpecificLayer( _exportSpecificLayer(layerId, expPicType, isRedGreenMode, restoreOpacityInRedGreen) {
layerId,
expPicType,
isRedGreenMode,
restoreOpacityInRedGreen
) {
if (!this.layerManager) { if (!this.layerManager) {
throw new Error("图层管理器未初始化"); throw new Error("图层管理器未初始化");
} }
@@ -115,19 +110,11 @@ export class ExportManager {
// 红绿图模式下使用固定尺寸和裁剪 // 红绿图模式下使用固定尺寸和裁剪
if (isRedGreenMode) { if (isRedGreenMode) {
return this._exportWithRedGreenMode( return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
objectsToExport,
expPicType,
restoreOpacityInRedGreen
);
} }
// 普通模式使用画布尺寸 // 普通模式使用画布尺寸
return this._exportWithCanvasSize( return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
objectsToExport,
expPicType,
restoreOpacityInRedGreen
);
} }
/** /**
@@ -168,19 +155,11 @@ export class ExportManager {
// 红绿图模式下使用固定尺寸和裁剪 // 红绿图模式下使用固定尺寸和裁剪
if (isRedGreenMode) { if (isRedGreenMode) {
return this._exportWithRedGreenMode( return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
objectsToExport,
expPicType,
restoreOpacityInRedGreen
);
} }
// 普通模式使用画布尺寸 // 普通模式使用画布尺寸
return this._exportWithCanvasSize( return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
objectsToExport,
expPicType,
restoreOpacityInRedGreen
);
} }
/** /**
@@ -215,22 +194,14 @@ export class ExportManager {
// 红绿图模式下使用固定尺寸和裁剪 // 红绿图模式下使用固定尺寸和裁剪
if (isRedGreenMode) { if (isRedGreenMode) {
return this._exportWithRedGreenMode( return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
objectsToExport,
expPicType,
restoreOpacityInRedGreen
);
} }
let canvasClipPath = this.canvas.clipPath; let canvasClipPath = this.canvas.clipPath;
if (isCropByBg) { if (isCropByBg) {
const cropWidth = const cropWidth =
this.canvasManager?.canvasWidth?.value || this.canvasManager?.canvasWidth?.value || this.canvas?.canvasWidth || this.canvas.width;
this.canvas?.canvasWidth ||
this.canvas.width;
const cropHeight = const cropHeight =
this.canvasManager?.canvasHeight?.value || this.canvasManager?.canvasHeight?.value || this.canvas?.canvasHeight || this.canvas.height;
this.canvas?.canvasHeight ||
this.canvas.height;
canvasClipPath = new fabric.Rect({ canvasClipPath = new fabric.Rect({
left: this.canvas.width / 2, left: this.canvas.width / 2,
top: this.canvas.height / 2, top: this.canvas.height / 2,
@@ -331,27 +302,14 @@ export class ExportManager {
* @returns {String} 图片数据URL * @returns {String} 图片数据URL
* @private * @private
*/ */
async _exportObject( async _exportObject(obj, expPicType, isRedGreenMode, restoreOpacityInRedGreen) {
obj,
expPicType,
isRedGreenMode,
restoreOpacityInRedGreen
) {
// 红绿图模式下使用固定尺寸和裁剪 // 红绿图模式下使用固定尺寸和裁剪
if (isRedGreenMode) { if (isRedGreenMode) {
return this._exportWithRedGreenMode( return this._exportWithRedGreenMode([obj], expPicType, restoreOpacityInRedGreen);
[obj],
expPicType,
restoreOpacityInRedGreen
);
} }
// 普通模式使用画布尺寸 // 普通模式使用画布尺寸
return this._exportWithCanvasSize( return this._exportWithCanvasSize([obj], expPicType, restoreOpacityInRedGreen);
[obj],
expPicType,
restoreOpacityInRedGreen
);
} }
/** /**
@@ -374,8 +332,7 @@ export class ExportManager {
if (layerIdArray && !layerIdArray.includes(layer.id)) continue; if (layerIdArray && !layerIdArray.includes(layer.id)) continue;
// 检查图层类型过滤条件 // 检查图层类型过滤条件
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed)) if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed)) continue;
continue;
if (layer.visible) { if (layer.visible) {
const layerObjects = this._collectObjectsFromLayer(layer); const layerObjects = this._collectObjectsFromLayer(layer);
@@ -514,21 +471,12 @@ export class ExportManager {
* @returns {String} 图片数据URL * @returns {String} 图片数据URL
* @private * @private
*/ */
async _exportWithRedGreenMode( async _exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen) {
objectsToExport,
expPicType,
restoreOpacityInRedGreen
) {
// 获取固定图层对象(衣服底图)作为参考 // 获取固定图层对象(衣服底图)作为参考
const fixedLayerObject = const fixedLayerObject = this._getFixedLayerObject() ?? this.canvas.clipPath;
this._getFixedLayerObject() ?? this.canvas.clipPath;
if (!fixedLayerObject) { if (!fixedLayerObject) {
console.warn("红绿图模式下未找到固定图层对象,使用画布尺寸"); console.warn("红绿图模式下未找到固定图层对象,使用画布尺寸");
return this._exportWithCanvasSize( return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
objectsToExport,
expPicType,
restoreOpacityInRedGreen
);
} }
// 获取固定图层对象的边界矩形(包含位置、尺寸、缩放等信息) // 获取固定图层对象的边界矩形(包含位置、尺寸、缩放等信息)
@@ -565,10 +513,7 @@ export class ExportManager {
// 克隆并添加所有对象到临时画布,需要调整位置相对于固定图层 // 克隆并添加所有对象到临时画布,需要调整位置相对于固定图层
for (let i = 0; i < objectsToExport.length; i++) { for (let i = 0; i < objectsToExport.length; i++) {
const obj = objectsToExport[i]; const obj = objectsToExport[i];
const cloned = await this._cloneObjectForExport( const cloned = await this._cloneObjectForExport(obj, restoreOpacityInRedGreen && true);
obj,
restoreOpacityInRedGreen && true
);
if (cloned) { if (cloned) {
// 调整对象位置:将原画布坐标转换为以固定图层为原点的相对坐标 // 调整对象位置:将原画布坐标转换为以固定图层为原点的相对坐标
cloned.set({ cloned.set({
@@ -606,12 +551,7 @@ export class ExportManager {
* @returns {String} 图片数据URL * @returns {String} 图片数据URL
* @private * @private
*/ */
async _exportWithCanvasSize( async _exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen, maskObject) {
objectsToExport,
expPicType,
restoreOpacityInRedGreen,
maskObject
) {
// 使用当前画布尺寸 // 使用当前画布尺寸
// const canvasWidth = // const canvasWidth =
// this.canvasManager?.canvasWidth?.value || this.canvas.width; // this.canvasManager?.canvasWidth?.value || this.canvas.width;
@@ -634,44 +574,44 @@ export class ExportManager {
console.log("导出图片数据URL:", dataURL); console.log("导出图片数据URL:", dataURL);
return dataURL; return dataURL;
// 创建与画布相同尺寸的临时画布 // // 创建与画布相同尺寸的临时画布
const scaleFactor = 2; // 高清导出 // const scaleFactor = 2; // 高清导出
const tempCanvas = document.createElement("canvas"); // const tempCanvas = document.createElement("canvas");
tempCanvas.width = canvasWidth * scaleFactor; // tempCanvas.width = canvasWidth * scaleFactor;
tempCanvas.height = canvasHeight * scaleFactor; // tempCanvas.height = canvasHeight * scaleFactor;
tempCanvas.style.width = canvasWidth + "px"; // tempCanvas.style.width = canvasWidth + "px";
tempCanvas.style.height = canvasHeight + "px"; // tempCanvas.style.height = canvasHeight + "px";
const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, { // const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
width: canvasWidth, // width: canvasWidth,
height: canvasHeight, // height: canvasHeight,
backgroundColor: null, // backgroundColor: null,
}); // });
tempFabricCanvas.enableRetinaScaling = true; // tempFabricCanvas.enableRetinaScaling = true;
tempFabricCanvas.imageSmoothingEnabled = true; // tempFabricCanvas.imageSmoothingEnabled = true;
tempFabricCanvas.setZoom(1); // tempFabricCanvas.setZoom(1);
try { // try {
// 克隆并添加所有对象到临时画布 // // 克隆并添加所有对象到临时画布
for (const obj of objectsToExport) { // for (const obj of objectsToExport) {
const cloned = await this._cloneObjectForExport( // const cloned = await this._cloneObjectForExport(
obj, // obj,
restoreOpacityInRedGreen && false // 普通模式不强制恢复透明度 // restoreOpacityInRedGreen && false, // 普通模式不强制恢复透明度
); // );
if (cloned) { // if (cloned) {
tempFabricCanvas.add(cloned); // tempFabricCanvas.add(cloned);
} // }
} // }
// 渲染画布 // // 渲染画布
tempFabricCanvas.renderAll(); // tempFabricCanvas.renderAll();
// 生成图片 // // 生成图片
return this._generateHighQualityDataURL(tempCanvas, expPicType); // return this._generateHighQualityDataURL(tempCanvas, expPicType);
} finally { // } finally {
this._cleanupTempCanvas(tempFabricCanvas); // this._cleanupTempCanvas(tempFabricCanvas);
} // }
} }
/** /**
@@ -703,10 +643,7 @@ export class ExportManager {
* @returns {Promise<fabric.Object>} 克隆的对象 * @returns {Promise<fabric.Object>} 克隆的对象
* @private * @private
*/ */
_cloneObjectAsync( _cloneObjectAsync(obj, propertiesToInclude = ["id", "layerId", "layerName", "name"]) {
obj,
propertiesToInclude = ["id", "layerId", "layerName", "name"]
) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!obj) { if (!obj) {
resolve(null); resolve(null);
@@ -736,11 +673,7 @@ export class ExportManager {
* @returns {Promise<Object>} 克隆的对象 * @returns {Promise<Object>} 克隆的对象
* @private * @private
*/ */
async _cloneObjectForExport( async _cloneObjectForExport(obj, forceRestoreOpacity = false, removeClipPath = true) {
obj,
forceRestoreOpacity = false,
removeClipPath = true
) {
if (!obj) return null; if (!obj) return null;
try { try {
@@ -874,11 +807,7 @@ export class ExportManager {
} }
// 克隆对象作为裁剪路径 // 克隆对象作为裁剪路径
const clonedClipPath = await this._cloneObjectForExport( const clonedClipPath = await this._cloneObjectForExport(clipObject, false, false);
clipObject,
false,
false
);
if (!clonedClipPath) { if (!clonedClipPath) {
console.warn("无法克隆裁剪对象"); console.warn("无法克隆裁剪对象");

View File

@@ -51,24 +51,16 @@ import {
} from "../commands/RasterizeLayerCommand"; } from "../commands/RasterizeLayerCommand";
// 导入图层排序相关类和混入 // 导入图层排序相关类和混入
import { import { LayerSort, createLayerSort, LayerSortMixin, LayerSortUtils } from "../utils/LayerSort";
LayerSort,
createLayerSort,
LayerSortMixin,
LayerSortUtils,
} from "../utils/LayerSort";
import CanvasConfig from "../config/canvasConfig"; import CanvasConfig from "../config/canvasConfig";
import { isBoolean, template } from "lodash-es"; import { isBoolean, template } from "lodash-es";
import { import { findObjectById, generateId, optimizeCanvasRendering } from "../utils/helper";
findObjectById,
generateId,
optimizeCanvasRendering,
} from "../utils/helper";
import { message } from "ant-design-vue"; import { message } from "ant-design-vue";
import { fabric } from "fabric-with-all"; import { fabric } from "fabric-with-all";
import { getOriginObjectInfo } from "../utils/layerUtils"; import { getOriginObjectInfo } from "../utils/layerUtils";
import { restoreFabricObject } from "../utils/objectHelper"; import { restoreFabricObject } from "../utils/objectHelper";
import { UpdateGroupMaskPositionCommand } from "../commands/UpdateGroupMaskPositionCommand";
/** /**
* 图层管理器 - 负责管理画布上的所有图层 * 图层管理器 - 负责管理画布上的所有图层
@@ -219,11 +211,7 @@ export class LayerManager {
// 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限 // 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限
// 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除 // 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除
obj.erasable = obj.erasable =
isInActiveLayer && isInActiveLayer && layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
layer.visible &&
!layer.locked &&
!layer.isBackground &&
!layer.isFixed;
// 图层状态决定交互性 // 图层状态决定交互性
if (layer.isBackground || obj.isBackground || layer.isFixed) { if (layer.isBackground || obj.isBackground || layer.isFixed) {
@@ -270,11 +258,7 @@ export class LayerManager {
if (this.isRedGreenMode) { if (this.isRedGreenMode) {
// 红绿图模式下 所有普通图层都可擦除 // 红绿图模式下 所有普通图层都可擦除
obj.erasable = obj.erasable = layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
layer.visible &&
!layer.locked &&
!layer.isBackground &&
!layer.isFixed;
} }
// 平移模式下,禁用多选和擦除 // 平移模式下,禁用多选和擦除
@@ -325,9 +309,7 @@ export class LayerManager {
// 设置子图层对象的交互性 // 设置子图层对象的交互性
layer?.childLayer?.forEach(async (childLayer) => { layer?.childLayer?.forEach(async (childLayer) => {
const childObj = this.canvas const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
.getObjects()
.find((o) => o.layerId === childLayer.id);
if (childObj) { if (childObj) {
await this._setObjectInteractivity(childObj, childLayer, editorMode); await this._setObjectInteractivity(childObj, childLayer, editorMode);
} }
@@ -339,10 +321,7 @@ export class LayerManager {
let clippingMaskFabricObject = null; let clippingMaskFabricObject = null;
if (layer.clippingMask) { if (layer.clippingMask) {
// 反序列化 clippingMask // 反序列化 clippingMask
clippingMaskFabricObject = await restoreFabricObject( clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas);
layer.clippingMask,
this.canvas
);
clippingMaskFabricObject.clipPath = null; clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({ clippingMaskFabricObject.set({
@@ -354,9 +333,7 @@ export class LayerManager {
// 如果是组图层 则给所有子对象设置裁剪对象 // 如果是组图层 则给所有子对象设置裁剪对象
if (layer.type === LayerType.GROUP || layer.children?.length > 0) { if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
layer.children.forEach((childLayer) => { layer.children.forEach((childLayer) => {
const childObj = this.canvas const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
.getObjects()
.find((o) => o.layerId === childLayer.id);
if (childObj) { if (childObj) {
childObj.clipPath = clippingMaskFabricObject; childObj.clipPath = clippingMaskFabricObject;
childObj.dirty = true; // 标记为脏对象 childObj.dirty = true; // 标记为脏对象
@@ -365,9 +342,7 @@ export class LayerManager {
}); });
} else { } else {
layer.fabricObjects?.forEach((obj) => { layer.fabricObjects?.forEach((obj) => {
const fabricObject = this.canvas const fabricObject = this.canvas.getObjects().find((o) => o.id === obj.id);
.getObjects()
.find((o) => o.id === obj.id);
if (fabricObject) { if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject; fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true; // 标记为脏对象 fabricObject.dirty = true; // 标记为脏对象
@@ -447,9 +422,7 @@ export class LayerManager {
*/ */
createBackgroundLayer(name = "背景") { createBackgroundLayer(name = "背景") {
// 检查是否已有背景图层 // 检查是否已有背景图层
const hasBackgroundLayer = this.layers.value.some( const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
(layer) => layer.isBackground
);
if (hasBackgroundLayer) { if (hasBackgroundLayer) {
console.warn("已存在背景层,不再创建新的背景层"); console.warn("已存在背景层,不再创建新的背景层");
@@ -502,9 +475,7 @@ export class LayerManager {
} }
// 生成唯一ID // 生成唯一ID
const layerId = `fixed_layer_${Date.now()}_${Math.floor( const layerId = `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
Math.random() * 1000
)}`;
// 创建固定图层 // 创建固定图层
const fixedLayer = createFixedLayer({ const fixedLayer = createFixedLayer({
@@ -564,9 +535,7 @@ export class LayerManager {
await this.createLayer("图层 1"); await this.createLayer("图层 1");
} else { } else {
// 检查是否已有背景层 // 检查是否已有背景层
const hasBackgroundLayer = this.layers.value.some( const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
(layer) => layer.isBackground
);
if (!hasBackgroundLayer) { if (!hasBackgroundLayer) {
this.createBackgroundLayer(); this.createBackgroundLayer();
@@ -612,10 +581,7 @@ export class LayerManager {
} }
// 验证目标图层是否存在 // 验证目标图层是否存在
const { layer: targetLayer } = findLayerRecursively( const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
this.layers.value,
targetLayerId
);
if (!targetLayer) { if (!targetLayer) {
console.error(`目标图层 ${targetLayerId} 不存在`); console.error(`目标图层 ${targetLayerId} 不存在`);
return null; return null;
@@ -669,8 +635,7 @@ export class LayerManager {
*/ */
removeObjectFromLayer(objectOrId) { removeObjectFromLayer(objectOrId) {
// 获取对象ID // 获取对象ID
const objectId = const objectId = typeof objectOrId === "string" ? objectOrId : objectOrId.id;
typeof objectOrId === "string" ? objectOrId : objectOrId.id;
if (!objectId) { if (!objectId) {
console.error("无效的对象ID"); console.error("无效的对象ID");
@@ -729,9 +694,7 @@ export class LayerManager {
*/ */
getActiveLayer() { getActiveLayer() {
if (!this.activeLayerId.value) { if (!this.activeLayerId.value) {
console.warn( console.warn("没有活动图层ID无法获取活动图层 ==== 默认设置第一个图层为活动图层");
"没有活动图层ID无法获取活动图层 ==== 默认设置第一个图层为活动图层"
);
this.activeLayerId.value = this.layers.value[0]?.id || null; this.activeLayerId.value = this.layers.value[0]?.id || null;
} }
@@ -836,13 +799,8 @@ export class LayerManager {
} }
const tempFabricObject = const tempFabricObject =
fabricObject?.toObject?.([ fabricObject?.toObject?.(["id", "layerId", "layerName", "isBackgroud", "isFixed"]) ||
"id", fabricObject;
"layerId",
"layerName",
"isBackgroud",
"isFixed",
]) || fabricObject;
if (layer.isFixed || layer.isBackground) { if (layer.isFixed || layer.isBackground) {
layer.fabricObject = tempFabricObject; layer.fabricObject = tempFabricObject;
} else { } else {
@@ -868,9 +826,7 @@ export class LayerManager {
// 如果是背景层或固定层,不允许移动 // 如果是背景层或固定层,不允许移动
if (layer && (layer.isBackground || layer.isFixed)) { if (layer && (layer.isBackground || layer.isFixed)) {
console.warn( console.warn(layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动"));
layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动")
);
return false; return false;
} }
@@ -1000,6 +956,9 @@ export class LayerManager {
// 设置活动选择组的属性 // 设置活动选择组的属性
this.canvas.setActiveObject(activeSelection); this.canvas.setActiveObject(activeSelection);
// 为活动选择组添加移动事件监听器,用于同步更新遮罩位置
this._setupGroupMaskMovementSync(activeSelection, layer);
activeSelection = null; // 清理引用,避免内存泄漏 activeSelection = null; // 清理引用,避免内存泄漏
// 确保选择组正确渲染 // 确保选择组正确渲染
// activeSelection.setCoords(); // activeSelection.setCoords();
@@ -1234,11 +1193,7 @@ export class LayerManager {
async ungroupLayers(groupId) { async ungroupLayers(groupId) {
// 查找组图层 // 查找组图层
const groupLayer = this.layers.value.find((l) => l.id === groupId); const groupLayer = this.layers.value.find((l) => l.id === groupId);
if ( if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
!groupLayer ||
!groupLayer.children ||
groupLayer.children.length === 0
) {
console.error(`${groupId} 不是有效的组图层或不包含子图层`); console.error(`${groupId} 不是有效的组图层或不包含子图层`);
return null; return null;
} }
@@ -1295,9 +1250,7 @@ export class LayerManager {
*/ */
resizeCanvasWithScale(width, height, options = {}) { resizeCanvasWithScale(width, height, options = {}) {
// 检查是否有除背景层外的其他元素 // 检查是否有除背景层外的其他元素
const hasOtherElements = this.canvas const hasOtherElements = this.canvas.getObjects().some((obj) => !obj.isBackground);
.getObjects()
.some((obj) => !obj.isBackground);
if (hasOtherElements) { if (hasOtherElements) {
// 有其他元素时使用带缩放的命令 // 有其他元素时使用带缩放的命令
@@ -1366,9 +1319,7 @@ export class LayerManager {
if (!this.canvas) return; if (!this.canvas) return;
// 获取画布上的所有对象 // 获取画布上的所有对象
const canvasObjects = [ const canvasObjects = [...this.canvas.getObjects(["id", "layerId", "layerName"])];
...this.canvas.getObjects(["id", "layerId", "layerName"]),
];
// 清空画布 // 清空画布
this.canvas.clear(); this.canvas.clear();
@@ -1383,19 +1334,13 @@ export class LayerManager {
if (layer.isBackground && layer.fabricObject) { if (layer.isBackground && layer.fabricObject) {
// 背景图层 // 背景图层
const originalObj = canvasObjects.find( const originalObj = canvasObjects.find((o) => o.id === layer.fabricObject.id);
(o) => o.id === layer.fabricObject.id
);
if (originalObj) { if (originalObj) {
this.canvas.add(originalObj); this.canvas.add(originalObj);
} else { } else {
this.canvas.add(layer.fabricObject); this.canvas.add(layer.fabricObject);
} }
} else if ( } else if (layer.isFixed && layer.fabricObjects && layer.fabricObjects.length > 0) {
layer.isFixed &&
layer.fabricObjects &&
layer.fabricObjects.length > 0
) {
// 固定图层 // 固定图层
layer.fabricObjects.forEach((obj) => { layer.fabricObjects.forEach((obj) => {
const originalObj = canvasObjects.find((o) => o.id === obj.id); const originalObj = canvasObjects.find((o) => o.id === obj.id);
@@ -1405,10 +1350,7 @@ export class LayerManager {
this.canvas.add(obj); this.canvas.add(obj);
} }
}); });
} else if ( } else if (Array.isArray(layer.fabricObjects) && layer.fabricObjects.length > 0) {
Array.isArray(layer.fabricObjects) &&
layer.fabricObjects.length > 0
) {
// 普通图层添加所有fabricObjects // 普通图层添加所有fabricObjects
layer.fabricObjects.forEach((obj) => { layer.fabricObjects.forEach((obj) => {
const originalObj = canvasObjects.find((o) => o.id === obj.id); const originalObj = canvasObjects.find((o) => o.id === obj.id);
@@ -1437,9 +1379,7 @@ export class LayerManager {
if (layer.isBackground) { if (layer.isBackground) {
// 背景图层处理 // 背景图层处理
if (layer.fabricObject) { if (layer.fabricObject) {
const existsOnCanvas = canvasObjects.some( const existsOnCanvas = canvasObjects.some((obj) => obj.id === layer.fabricObject.id);
(obj) => obj.id === layer.fabricObject.id
);
if (!existsOnCanvas) { if (!existsOnCanvas) {
this.canvas.add(layer.fabricObject); this.canvas.add(layer.fabricObject);
} }
@@ -1549,9 +1489,7 @@ export class LayerManager {
if (layer.fabricObjects && layer.fabricObjects.length > 0) { if (layer.fabricObjects && layer.fabricObjects.length > 0) {
layerCopy.serializedObjects = layer.fabricObjects layerCopy.serializedObjects = layer.fabricObjects
.map((obj) => .map((obj) =>
typeof obj.toObject === "function" typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
? obj.toObject(["id", "layerId", "layerName"])
: null
) )
.filter(Boolean); .filter(Boolean);
} }
@@ -1596,9 +1534,7 @@ export class LayerManager {
if (layer.fabricObjects && layer.fabricObjects.length > 0) { if (layer.fabricObjects && layer.fabricObjects.length > 0) {
layerCopy.serializedObjects = layer.fabricObjects layerCopy.serializedObjects = layer.fabricObjects
.map((obj) => .map((obj) =>
typeof obj.toObject === "function" typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
? obj.toObject(["id", "layerId", "layerName"])
: null
) )
.filter(Boolean); .filter(Boolean);
} }
@@ -1719,8 +1655,7 @@ export class LayerManager {
// 查找第一个非背景、非锁定的图层,排除指定的图层 // 查找第一个非背景、非锁定的图层,排除指定的图层
return ( return (
this.layers.value.find( this.layers.value.find(
(layer) => (layer) => layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
) || null ) || null
); );
} }
@@ -1855,9 +1790,7 @@ export class LayerManager {
* @param {string} backgroundColor 背景颜色 * @param {string} backgroundColor 背景颜色
*/ */
updateBackgroundColor(backgroundColor, options = {}) { updateBackgroundColor(backgroundColor, options = {}) {
const backgroundLayer = this.layers.value.find( const backgroundLayer = this.layers.value.find((layer) => layer.isBackground);
(layer) => layer.isBackground
);
if (!backgroundLayer) { if (!backgroundLayer) {
console.warn("没有找到背景图层"); console.warn("没有找到背景图层");
@@ -2057,8 +1990,7 @@ export class LayerManager {
if (!this.canvas || !textObject) return null; if (!this.canvas || !textObject) return null;
// 确保对象有ID // 确保对象有ID
textObject.id = textObject.id = textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// 创建文本图层 // 创建文本图层
const layerName = options.name || "文本图层"; const layerName = options.name || "文本图层";
@@ -2075,9 +2007,7 @@ export class LayerManager {
overline: options.overline || textObject.overline || false, overline: options.overline || textObject.overline || false,
fill: options.fill || textObject.fill || "#000000", fill: options.fill || textObject.fill || "#000000",
textBackgroundColor: textBackgroundColor:
options.textBackgroundColor || options.textBackgroundColor || textObject.textBackgroundColor || "transparent",
textObject.textBackgroundColor ||
"transparent",
lineHeight: options.lineHeight || textObject.lineHeight || 1.16, lineHeight: options.lineHeight || textObject.lineHeight || 1.16,
charSpacing: options.charSpacing || textObject.charSpacing || 0, charSpacing: options.charSpacing || textObject.charSpacing || 0,
}, },
@@ -2088,9 +2018,7 @@ export class LayerManager {
textObject.layerName = layerName; textObject.layerName = layerName;
// 添加到画布,如果还未添加 // 添加到画布,如果还未添加
const isOnCanvas = this.canvas const isOnCanvas = this.canvas.getObjects().some((obj) => obj.id === textObject.id);
.getObjects()
.some((obj) => obj.id === textObject.id);
if (!isOnCanvas) { if (!isOnCanvas) {
this.canvas.add(textObject); this.canvas.add(textObject);
} }
@@ -2099,9 +2027,7 @@ export class LayerManager {
const layer = this.getLayerById(layerId); const layer = this.getLayerById(layerId);
if (layer) { if (layer) {
layer.fabricObjects = layer.fabricObjects || []; layer.fabricObjects = layer.fabricObjects || [];
layer.fabricObjects.push( layer.fabricObjects.push(textObject.toObject(["id", "layerId", "layerName"]));
textObject.toObject(["id", "layerId", "layerName"])
);
} }
// 设置此图层为活动图层 // 设置此图层为活动图层
@@ -2134,9 +2060,7 @@ export class LayerManager {
// 检查普通图层 // 检查普通图层
if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) { if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) {
const foundObject = layer.fabricObjects.find( const foundObject = layer.fabricObjects.find((obj) => obj.id === fabricObject.id);
(obj) => obj.id === fabricObject.id
);
if (foundObject) { if (foundObject) {
return layer; return layer;
} }
@@ -2217,12 +2141,7 @@ export class LayerManager {
* @returns {boolean} 是否排序成功 * @returns {boolean} 是否排序成功
*/ */
reorderChildLayers(parentId, oldIndex, newIndex, layerId) { reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
return this.layerSort?.reorderChildLayers( return this.layerSort?.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
parentId,
oldIndex,
newIndex,
layerId
);
} }
/** /**
@@ -2403,19 +2322,13 @@ export class LayerManager {
const layersCount = this.layers?.value?.length || 0; const layersCount = this.layers?.value?.length || 0;
const shouldUseAsync = const shouldUseAsync =
async !== null async !== null ? async : LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
? async
: LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
if (shouldUseAsync) { if (shouldUseAsync) {
console.log( console.log(`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
);
return this.layerSort.rearrangeObjectsAsync(); return this.layerSort.rearrangeObjectsAsync();
} else { } else {
console.log( console.log(`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
);
this.layerSort.rearrangeObjects(); this.layerSort.rearrangeObjects();
} }
} }
@@ -2524,10 +2437,7 @@ export class LayerManager {
moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) { moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) {
if (!this.layerSort) { if (!this.layerSort) {
console.warn("图层排序工具未初始化,使用基础移动方法"); console.warn("图层排序工具未初始化,使用基础移动方法");
return this.moveLayer( return this.moveLayer(layerId, newIndex > this.getLayerIndex(layerId) ? "down" : "up");
layerId,
newIndex > this.getLayerIndex(layerId) ? "down" : "up"
);
} }
const result = this.layerSort.moveLayerToIndex({ const result = this.layerSort.moveLayerToIndex({
@@ -2538,9 +2448,7 @@ export class LayerManager {
}); });
if (result) { if (result) {
console.log( console.log(`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`);
`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`
);
// 更新对象交互性 // 更新对象交互性
// this.updateLayersObjectsInteractivity(); // this.updateLayersObjectsInteractivity();
} }
@@ -2584,9 +2492,7 @@ export class LayerManager {
return -1; return -1;
} }
let layerIndex = this.layers.value.findIndex( let layerIndex = this.layers.value.findIndex((layer) => layer.id === layerId);
(layer) => layer.id === layerId
);
if (layerIndex >= 0) return layerIndex; if (layerIndex >= 0) return layerIndex;
// 如果未找到,尝试在子图层中查找 // 如果未找到,尝试在子图层中查找
const { parent } = findLayerRecursively(this.layers.value, layerId); const { parent } = findLayerRecursively(this.layers.value, layerId);
@@ -2617,9 +2523,7 @@ export class LayerManager {
const result = this.layerSort.reorderLayers(oldIndex, newIndex, layerId); const result = this.layerSort.reorderLayers(oldIndex, newIndex, layerId);
if (result) { if (result) {
console.log( console.log(`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`);
`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`
);
// 更新对象交互性 // 更新对象交互性
this.updateLayersObjectsInteractivity(); this.updateLayersObjectsInteractivity();
} }
@@ -2636,12 +2540,7 @@ export class LayerManager {
* @returns {boolean} 是否排序成功 * @returns {boolean} 是否排序成功
*/ */
advancedReorderChildLayers(parentId, oldIndex, newIndex, layerId) { advancedReorderChildLayers(parentId, oldIndex, newIndex, layerId) {
const result = this.reorderChildLayers( const result = this.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
parentId,
oldIndex,
newIndex,
layerId
);
if (result) { if (result) {
console.log( console.log(
@@ -2758,11 +2657,7 @@ export class LayerManager {
async mergeGroupLayers(groupId) { async mergeGroupLayers(groupId) {
// 查找组图层 // 查找组图层
const groupLayer = this.layers.value.find((l) => l.id === groupId); const groupLayer = this.layers.value.find((l) => l.id === groupId);
if ( if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
!groupLayer ||
!groupLayer.children ||
groupLayer.children.length === 0
) {
console.warn($t("找不到有效的组图层或组图层为空")); console.warn($t("找不到有效的组图层或组图层为空"));
return []; return [];
} }
@@ -2803,10 +2698,7 @@ export class LayerManager {
// 查找目标图层 // 查找目标图层
// const targetLayer = this.getLayerById(targetLayerId); // const targetLayer = this.getLayerById(targetLayerId);
const { layer: targetLayer } = findLayerRecursively( const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
this.layers.value,
targetLayerId
);
if (!targetLayer) { if (!targetLayer) {
console.error($t("图层不存在", { layerId: targetLayerId })); console.error($t("图层不存在", { layerId: targetLayerId }));
@@ -2864,9 +2756,7 @@ export class LayerManager {
return ( return (
layer.children && layer.children &&
layer.children.length > 0 && layer.children.length > 0 &&
layer.children.some( layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0)
(child) => child.fabricObjects && child.fabricObjects.length > 0
)
); );
} else { } else {
// 普通图层:检查是否有对象 // 普通图层:检查是否有对象
@@ -2888,10 +2778,7 @@ export class LayerManager {
// 查找目标图层 // 查找目标图层
// const targetLayer = this.getLayerById(targetLayerId); // const targetLayer = this.getLayerById(targetLayerId);
const { layer: targetLayer } = findLayerRecursively( const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
this.layers.value,
targetLayerId
);
if (!targetLayer) { if (!targetLayer) {
console.error($t("图层不存在", { layerId: targetLayerId })); console.error($t("图层不存在", { layerId: targetLayerId }));
@@ -2926,15 +2813,162 @@ export class LayerManager {
} }
/** /**
* 更新图层缩略图 * 为组图层的活动选择组设置遮罩移动同步(修复版)
* @param {string} layerId 图层ID * @param {fabric.ActiveSelection} activeSelection 活动选择组
* @param {Object} layer 组图层对象
* @private * @private
*/ */
_updateLayerThumbnail(layerId) { _setupGroupMaskMovementSync(activeSelection, layer) {
if (this.canvas && this.canvas.thumbnailManager) { if (!activeSelection || !layer || !layer.clippingMask) {
setTimeout(() => { return;
this.canvas.thumbnailManager.generateLayerThumbnail(layerId);
}, 100);
} }
// 记录初始位置
let initialLeft = activeSelection.left;
let initialTop = activeSelection.top;
// 记录遮罩初始位置
let maskInitialLeft = layer.clippingMask.left || 0;
let maskInitialTop = layer.clippingMask.top || 0;
// 用于节流和状态管理的变量
let isUpdating = false;
let lastUpdateTime = 0;
let hasMoved = false; // 追踪是否实际发生了移动
const UPDATE_THRESHOLD = 16; // 约60fps
// 移动开始事件处理
const handleMovingStart = (e) => {
if (e.target === activeSelection) {
hasMoved = false; // 重置移动状态
console.log("🎯 开始移动组选择对象");
// 记录遮罩初始位置
console.log(
"🖼️ 记录遮罩初始位置",
`${layer.clippingMask.left || 0}, ${layer.clippingMask.top || 0}`
);
// 记录初始位置
initialLeft = activeSelection.left;
initialTop = activeSelection.top;
maskInitialLeft = layer.clippingMask.left || 0;
maskInitialTop = layer.clippingMask.top || 0;
}
};
// 移动中事件处理函数(带节流)
const handleMoving = (e) => {
const target = e.target;
if (target === activeSelection) {
hasMoved = true; // 标记发生了移动
const now = Date.now();
// 节流处理,避免过于频繁的更新
if (now - lastUpdateTime < UPDATE_THRESHOLD) {
return;
}
if (isUpdating) {
return;
}
isUpdating = true;
lastUpdateTime = now;
// 使用 requestAnimationFrame 优化渲染
requestAnimationFrame(() => {
try {
// 计算移动距离
const deltaX = target.left - initialLeft;
const deltaY = target.top - initialTop;
// 创建更新遮罩位置的命令
const command = new UpdateGroupMaskPositionCommand({
canvas: this.canvas,
layerManager: this,
layers: this.layers,
layerId: layer.id,
deltaX: deltaX,
deltaY: deltaY,
maskInitialLeft: maskInitialLeft,
maskInitialTop: maskInitialTop,
isExecuteRealtime: true,
});
// 执行实时更新
command.executeRealtime();
} finally {
isUpdating = false;
}
});
}
};
// 修改事件处理函数 - 使用 object:modified 替代 object:moved
const handleModified = (e) => {
const target = e.target;
if (target === activeSelection && hasMoved) {
console.log("✅ 组选择对象移动完成");
// 计算最终移动距离
const deltaX = target.left - initialLeft;
const deltaY = target.top - initialTop;
// 如果有实际移动,创建可撤销的命令
if (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1) {
const command = new UpdateGroupMaskPositionCommand({
canvas: this.canvas,
layers: this.layers,
layerManager: this,
layerId: layer.id,
deltaX: deltaX,
deltaY: deltaY,
maskInitialLeft: maskInitialLeft,
maskInitialTop: maskInitialTop,
activeSelection,
});
// 执行可撤销的命令
if (this.commandManager) {
this.commandManager.execute(command);
} else {
command.execute();
}
}
hasMoved = false; // 重置移动状态
}
};
// 鼠标抬起事件处理 - 备用方案
const handleMouseUp = (e) => {
if (hasMoved && this.canvas.getActiveObject() === activeSelection) {
console.log("🖱️ 鼠标抬起 - 备用移动完成处理");
handleModified(e);
}
};
// 清理事件监听器的函数
const cleanup = () => {
this.canvas.off("object:moving", handleMoving);
this.canvas.off("object:modified", handleModified);
this.canvas.off("mouse:down", handleMovingStart);
this.canvas.off("mouse:up", handleMouseUp);
this.canvas.off("selection:cleared", cleanup);
this.canvas.off("selection:updated", cleanup);
console.log("🧹 清理组遮罩移动同步事件监听器");
};
// 绑定事件监听器
this.canvas.on("mouse:down", handleMovingStart);
this.canvas.on("object:moving", handleMoving);
this.canvas.on("object:modified", handleModified); // 使用 modified 替代 moved
this.canvas.on("mouse:up", handleMouseUp); // 备用方案
// 当选择被清除或更新时清理事件监听器
this.canvas.on("selection:cleared", cleanup);
this.canvas.on("selection:updated", cleanup);
console.log("🎨 已设置组遮罩移动同步 - 使用 object:modified 事件");
} }
} }

View File

@@ -141,9 +141,7 @@ export class LiquifyReferenceManager {
const snapshot = this.stateSnapshots.get(snapshotId); const snapshot = this.stateSnapshots.get(snapshotId);
if (!fabricObject || !snapshot) { if (!fabricObject || !snapshot) {
throw new Error( throw new Error(`无法恢复快照: 对象或快照不存在 (${refId}, ${snapshotId})`);
`无法恢复快照: 对象或快照不存在 (${refId}, ${snapshotId})`
);
} }
// 恢复图像数据 // 恢复图像数据
@@ -175,10 +173,7 @@ export class LiquifyReferenceManager {
for (const update of updates) { for (const update of updates) {
try { try {
const result = await this.updateObjectImageData( const result = await this.updateObjectImageData(update.refId, update.imageData);
update.refId,
update.imageData
);
results.push({ refId: update.refId, success: true, result }); results.push({ refId: update.refId, success: true, result });
} catch (error) { } catch (error) {
console.error(`批量更新失败 ${update.refId}:`, error); console.error(`批量更新失败 ${update.refId}:`, error);
@@ -253,19 +248,10 @@ export class LiquifyReferenceManager {
const listeners = {}; const listeners = {};
// 备份常见的事件监听器 // 备份常见的事件监听器
const eventTypes = [ const eventTypes = ["mousedown", "mouseup", "mousemove", "mouseout", "mouseover"];
"mousedown",
"mouseup",
"mousemove",
"mouseout",
"mouseover",
];
eventTypes.forEach((eventType) => { eventTypes.forEach((eventType) => {
if ( if (fabricObject.__eventListeners && fabricObject.__eventListeners[eventType]) {
fabricObject.__eventListeners &&
fabricObject.__eventListeners[eventType]
) {
listeners[eventType] = [...fabricObject.__eventListeners[eventType]]; listeners[eventType] = [...fabricObject.__eventListeners[eventType]];
} }
}); });
@@ -378,11 +364,7 @@ export class LiquifyReferenceManager {
*/ */
_captureImageData(fabricObject) { _captureImageData(fabricObject) {
try { try {
if ( if (fabricObject._element && fabricObject._element.width && fabricObject._element.height) {
fabricObject._element &&
fabricObject._element.width &&
fabricObject._element.height
) {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = fabricObject._element.width; canvas.width = fabricObject._element.width;
canvas.height = fabricObject._element.height; canvas.height = fabricObject._element.height;

View File

@@ -55,14 +55,10 @@ export class RedGreenModeManager {
if (typeof options.normalLayerOpacity === "number") { if (typeof options.normalLayerOpacity === "number") {
if (options.normalLayerOpacity > 1) { if (options.normalLayerOpacity > 1) {
// 如果大于1认为是百分比值(0-100) // 如果大于1认为是百分比值(0-100)
this.normalLayerOpacity = this.normalLayerOpacity = Math.max(0, Math.min(100, options.normalLayerOpacity)) / 100;
Math.max(0, Math.min(100, options.normalLayerOpacity)) / 100;
} else { } else {
// 如果小于等于1认为是小数值(0-1) // 如果小于等于1认为是小数值(0-1)
this.normalLayerOpacity = Math.max( this.normalLayerOpacity = Math.max(0, Math.min(1, options.normalLayerOpacity));
0,
Math.min(1, options.normalLayerOpacity)
);
} }
} }
@@ -97,10 +93,7 @@ export class RedGreenModeManager {
this.registerRedGreenMouseUpEvent(); this.registerRedGreenMouseUpEvent();
// 启用图层管理器的红绿图模式 // 启用图层管理器的红绿图模式
if ( if (this.layerManager && typeof this.layerManager.enableRedGreenMode === "function") {
this.layerManager &&
typeof this.layerManager.enableRedGreenMode === "function"
) {
this.layerManager.enableRedGreenMode(); this.layerManager.enableRedGreenMode();
} }
@@ -177,26 +170,24 @@ export class RedGreenModeManager {
normalizedOpacity = Math.max(0, Math.min(1, opacity)); normalizedOpacity = Math.max(0, Math.min(1, opacity));
} }
// 创建透明度更新命令 // // 创建透明度更新命令
const opacityCommand = new UpdateNormalLayerOpacityCommand({ // const opacityCommand = new UpdateNormalLayerOpacityCommand({
canvas: this.canvas, // canvas: this.canvas,
layerManager: this.layerManager, // layerManager: this.layerManager,
opacity: normalizedOpacity, // opacity: normalizedOpacity,
}); // });
// 执行命令 // // 执行命令
if (this.commandManager) { // if (this.commandManager) {
this.commandManager.execute(opacityCommand); // this.commandManager.execute(opacityCommand);
} else { // } else {
opacityCommand.execute(); // opacityCommand.execute();
} // }
// 更新内部状态 // 更新内部状态
this.normalLayerOpacity = normalizedOpacity; this.normalLayerOpacity = normalizedOpacity;
console.log( console.log(`普通图层透明度已更新为: ${Math.round(normalizedOpacity * 100)}%`);
`普通图层透明度已更新为: ${Math.round(normalizedOpacity * 100)}%`
);
return true; return true;
} catch (error) { } catch (error) {
console.error("更新普通图层透明度失败:", error); console.error("更新普通图层透明度失败:", error);
@@ -280,9 +271,7 @@ export class RedGreenModeManager {
const layers = this.layerManager.layers.value || []; const layers = this.layerManager.layers.value || [];
const backgroundLayer = layers.find((layer) => layer.isBackground); const backgroundLayer = layers.find((layer) => layer.isBackground);
const fixedLayer = layers.find((layer) => layer.isFixed); const fixedLayer = layers.find((layer) => layer.isFixed);
const normalLayers = layers.filter( const normalLayers = layers.filter((layer) => !layer.isBackground && !layer.isFixed);
(layer) => !layer.isBackground && !layer.isFixed
);
return { return {
backgroundLayer: backgroundLayer:
@@ -318,10 +307,7 @@ export class RedGreenModeManager {
cleanup() { cleanup() {
try { try {
// 禁用图层管理器的红绿图模式 // 禁用图层管理器的红绿图模式
if ( if (this.layerManager && typeof this.layerManager.disableRedGreenMode === "function") {
this.layerManager &&
typeof this.layerManager.disableRedGreenMode === "function"
) {
this.layerManager.disableRedGreenMode(); this.layerManager.disableRedGreenMode();
} }

View File

@@ -11,7 +11,8 @@ export class ThumbnailManager {
this.layers = options.layers || []; // 图层管理器 this.layers = options.layers || []; // 图层管理器
this.layerThumbSize = options.layerThumbSize || { width: 48, height: 48 }; this.layerThumbSize = options.layerThumbSize || { width: 48, height: 48 };
this.layerThumbnails = new Map(); // 图层缩略图缓存 // this.layerThumbnails = new Map(); // 图层缩略图缓存 - 改成使用图层对象的thumbnailUrl属性
// 使用图层对象的thumbnailUrl属性来存储缩略图URL
this.defaultThumbnail = this.defaultThumbnail =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="; // 1x1 透明图 "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="; // 1x1 透明图
} }
@@ -23,17 +24,24 @@ export class ThumbnailManager {
async generateLayerThumbnail(layerId) { async generateLayerThumbnail(layerId) {
const fabricObjects = this._collectLayersAndObjects(layerId); const fabricObjects = this._collectLayersAndObjects(layerId);
if (!fabricObjects || fabricObjects.length === 0) {
console.warn("⚠️ 无法生成缩略图:没有可栅格化的对象 返回空缩略图");
// 如果没有对象,返回默认缩略图
const { layer } = findLayerRecursively(this.layers.value, layerId);
if (layer) {
layer.thumbnailUrl = this.defaultThumbnail; // 更新图层对象的缩略图
}
return this.defaultThumbnail;
}
// 延迟执行避免阻塞UI // 延迟执行避免阻塞UI
fabricObjects.length > 0 && fabricObjects.length > 0 &&
requestIdleCallback(() => { requestIdleCallback(() => {
setTimeout(async () => { setTimeout(async () => {
const base64 = await this._generateLayerThumbnailNow(fabricObjects); const base64 = await this._generateLayerThumbnailNow(fabricObjects);
this.layerThumbnails.set(layerId, base64); // this.layerThumbnails.set(layerId, base64);
try { try {
const { layer, parent } = findLayerRecursively( const { layer, parent } = findLayerRecursively(this.layers.value, layerId);
this.layers.value,
layerId
);
if (layer) { if (layer) {
layer.thumbnailUrl = base64; // 更新图层对象的缩略图 layer.thumbnailUrl = base64; // 更新图层对象的缩略图
} }
@@ -201,7 +209,7 @@ export class ThumbnailManager {
*/ */
getLayerThumbnail(layerId) { getLayerThumbnail(layerId) {
if (!layerId) return null; if (!layerId) return null;
return this.layerThumbnails.get(layerId) || null; // return this.layerThumbnails.get(layerId) || null;
} }
/** /**
@@ -210,7 +218,7 @@ export class ThumbnailManager {
*/ */
clearLayerThumbnail(layerId) { clearLayerThumbnail(layerId) {
if (layerId && this.layerThumbnails.has(layerId)) { if (layerId && this.layerThumbnails.has(layerId)) {
this.layerThumbnails.delete(layerId); // this.layerThumbnails.delete(layerId);
} }
} }
@@ -218,7 +226,7 @@ export class ThumbnailManager {
* 清除所有缩略图 * 清除所有缩略图
*/ */
clearAllThumbnails() { clearAllThumbnails() {
this.layerThumbnails.clear(); // this.layerThumbnails.clear();
} }
/** /**

View File

@@ -258,10 +258,7 @@ export class ToolManager {
const shiftKey = event.shiftKey; const shiftKey = event.shiftKey;
// 当处于输入状态时不触发快捷键 // 当处于输入状态时不触发快捷键
if ( if (event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA") {
event.target.tagName === "INPUT" ||
event.target.tagName === "TEXTAREA"
) {
return; return;
} }
@@ -286,10 +283,7 @@ export class ToolManager {
} }
// 在红绿图模式下检查工具可用性 // 在红绿图模式下检查工具可用性
if ( if (this.isRedGreenMode && !this.isToolAvailableInRedGreenMode(toolId)) {
this.isRedGreenMode &&
!this.isToolAvailableInRedGreenMode(toolId)
) {
continue; continue;
} }
@@ -564,9 +558,7 @@ export class ToolManager {
// 通知选区管理器切换到矩形套索工具 // 通知选区管理器切换到矩形套索工具
if (this.canvasManager && this.canvasManager.selectionManager) { if (this.canvasManager && this.canvasManager.selectionManager) {
this.canvasManager.selectionManager.setCurrentTool( this.canvasManager.selectionManager.setCurrentTool(OperationType.LASSO_RECTANGLE);
OperationType.LASSO_RECTANGLE
);
} }
} }
@@ -581,9 +573,7 @@ export class ToolManager {
// 通知选区管理器切换到椭圆套索工具 // 通知选区管理器切换到椭圆套索工具
if (this.canvasManager && this.canvasManager.selectionManager) { if (this.canvasManager && this.canvasManager.selectionManager) {
this.canvasManager.selectionManager.setCurrentTool( this.canvasManager.selectionManager.setCurrentTool(OperationType.LASSO_ELLIPSE);
OperationType.LASSO_ELLIPSE
);
} }
} }
@@ -598,9 +588,7 @@ export class ToolManager {
// 通知选区管理器切换到椭圆套索工具 // 通知选区管理器切换到椭圆套索工具
if (this.canvasManager && this.canvasManager.selectionManager) { if (this.canvasManager && this.canvasManager.selectionManager) {
this.canvasManager.selectionManager.setCurrentTool( this.canvasManager.selectionManager.setCurrentTool(OperationType.AREA_CUSTOM);
OperationType.AREA_CUSTOM
);
} }
} }
@@ -618,9 +606,7 @@ export class ToolManager {
console.log("矩形选区工具已激活"); console.log("矩形选区工具已激活");
if (this.canvasManager && this.canvasManager.selectionManager) { if (this.canvasManager && this.canvasManager.selectionManager) {
this.canvasManager.selectionManager.setCurrentTool( this.canvasManager.selectionManager.setCurrentTool(OperationType.AREA_RECTANGLE);
OperationType.AREA_RECTANGLE
);
} }
} }
@@ -677,10 +663,7 @@ export class ToolManager {
if (layer) { if (layer) {
if (layer.isBackground || layer.type === "background") { if (layer.isBackground || layer.type === "background") {
panelDetail.targetObject = layer.fabricObject; panelDetail.targetObject = layer.fabricObject;
} else if ( } else if (layer.fabricObjects && layer.fabricObjects.length > 0) {
layer.fabricObjects &&
layer.fabricObjects.length > 0
) {
panelDetail.targetObject = layer.fabricObjects[0]; panelDetail.targetObject = layer.fabricObjects[0];
} }
@@ -906,9 +889,7 @@ export class ToolManager {
}); });
// 准备液化操作,获取原始图像数据 // 准备液化操作,获取原始图像数据
const prepareResult = await liquifyManager.prepareForLiquify( const prepareResult = await liquifyManager.prepareForLiquify(targetObject);
targetObject
);
// 创建和初始化命令 // 创建和初始化命令
const initCommand = new InitLiquifyToolCommand({ const initCommand = new InitLiquifyToolCommand({
@@ -1110,9 +1091,7 @@ export class ToolManager {
const target = e.target; const target = e.target;
if ( if (
target && target &&
(target.type === "text" || (target.type === "text" || target.type === "i-text" || target.type === "textbox")
target.type === "i-text" ||
target.type === "textbox")
) { ) {
// 获取对应的图层 // 获取对应的图层
const layer = this.layerManager.getLayerById(target.layerId); const layer = this.layerManager.getLayerById(target.layerId);
@@ -1380,11 +1359,7 @@ export class ToolManager {
} }
// 从画布笔刷获取 // 从画布笔刷获取
if ( if (this.canvas && this.canvas.freeDrawingBrush && this.canvas.freeDrawingBrush.width) {
this.canvas &&
this.canvas.freeDrawingBrush &&
this.canvas.freeDrawingBrush.width
) {
return this.canvas.freeDrawingBrush.width; return this.canvas.freeDrawingBrush.width;
} }
@@ -1409,11 +1384,7 @@ export class ToolManager {
} }
// 从画布笔刷获取 // 从画布笔刷获取
if ( if (this.canvas && this.canvas.freeDrawingBrush && this.canvas.freeDrawingBrush.color) {
this.canvas &&
this.canvas.freeDrawingBrush &&
this.canvas.freeDrawingBrush.color
) {
return this.canvas.freeDrawingBrush.color; return this.canvas.freeDrawingBrush.color;
} }

View File

@@ -87,8 +87,7 @@ export class AnimationManager {
// 计算过渡动画持续时间 - 根据当前值到目标值的距离比例 // 计算过渡动画持续时间 - 根据当前值到目标值的距离比例
const progressRatio = const progressRatio =
Math.abs(targetZoom - currentZoomValue) / Math.abs(targetZoom - currentZoomValue) / Math.abs(targetZoom - currentZoom);
Math.abs(targetZoom - currentZoom);
const duration = options.duration || 0.3 * progressRatio; const duration = options.duration || 0.3 * progressRatio;
// 计算缩放后目标位置需要的修正,保持缩放点不变 // 计算缩放后目标位置需要的修正,保持缩放点不变
@@ -425,17 +424,17 @@ export class AnimationManager {
const now = Date.now(); const now = Date.now();
// Mac设备的轻量防抖检查 - 进一步减少冷却时间,确保响应性 // Mac设备的轻量防抖检查 - 进一步减少冷却时间,确保响应性
if ( if (this._isMac && now - this._lastMacAnimationTime < this._macAnimationCooldown) {
this._isMac &&
now - this._lastMacAnimationTime < this._macAnimationCooldown
) {
// 如果距离上次动画时间太短,只延迟很短时间,不阻塞太久 // 如果距离上次动画时间太短,只延迟很短时间,不阻塞太久
if (this._wheelAccumulationTimeout) { if (this._wheelAccumulationTimeout) {
clearTimeout(this._wheelAccumulationTimeout); clearTimeout(this._wheelAccumulationTimeout);
} }
this._wheelAccumulationTimeout = setTimeout(() => { this._wheelAccumulationTimeout = setTimeout(
this._processAccumulatedWheel(lastOpt); () => {
}, Math.min(this._macAnimationCooldown, 3)); // 最多延迟3ms this._processAccumulatedWheel(lastOpt);
},
Math.min(this._macAnimationCooldown, 3)
); // 最多延迟3ms
return; return;
} }
@@ -513,28 +512,16 @@ export class AnimationManager {
// Mac设备使用平衡的动画时间控制 // Mac设备使用平衡的动画时间控制
if (speedFactor > 2) { if (speedFactor > 2) {
// 快速操作:快速但平滑 // 快速操作:快速但平滑
duration = Math.min( duration = Math.min(0.18, Math.max(0.08, (zoomRatio * 0.3) / Math.sqrt(speedFactor)));
0.18,
Math.max(0.08, (zoomRatio * 0.3) / Math.sqrt(speedFactor))
);
} else if (speedFactor > 1.2) { } else if (speedFactor > 1.2) {
// 中等速度:标准响应 // 中等速度:标准响应
duration = Math.min( duration = Math.min(0.25, Math.max(0.1, (zoomRatio * 0.4) / Math.sqrt(speedFactor)));
0.25,
Math.max(0.1, (zoomRatio * 0.4) / Math.sqrt(speedFactor))
);
} else { } else {
// 慢速精确操作:确保平滑 // 慢速精确操作:确保平滑
duration = Math.min( duration = Math.min(0.3, Math.max(0.12, (zoomRatio * 0.5) / Math.sqrt(speedFactor)));
0.3,
Math.max(0.12, (zoomRatio * 0.5) / Math.sqrt(speedFactor))
);
} }
} else { } else {
duration = Math.min( duration = Math.min(0.5, Math.max(0.15, (zoomRatio * 0.8) / Math.sqrt(speedFactor)));
0.5,
Math.max(0.15, (zoomRatio * 0.8) / Math.sqrt(speedFactor))
);
} }
// 根据滚动速度选择不同的缓动效果 // 根据滚动速度选择不同的缓动效果

View File

@@ -125,9 +125,7 @@ export class BaseBrush {
_createRGBAColor(color, opacity) { _createRGBAColor(color, opacity) {
// 如果已经是rgba颜色先提取RGB部分 // 如果已经是rgba颜色先提取RGB部分
if (color.startsWith("rgba")) { if (color.startsWith("rgba")) {
const rgbaMatch = color.match( const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/
);
if (rgbaMatch) { if (rgbaMatch) {
const [, r, g, b] = rgbaMatch; const [, r, g, b] = rgbaMatch;
return `rgba(${r}, ${g}, ${b}, ${opacity})`; return `rgba(${r}, ${g}, ${b}, ${opacity})`;
@@ -258,10 +256,7 @@ export class BaseBrush {
// 过滤掉同名属性(子类优先) // 过滤掉同名属性(子类优先)
const basePropsFiltered = baseProperties.filter( const basePropsFiltered = baseProperties.filter(
(baseProp) => (baseProp) => !specificProperties.some((specificProp) => specificProp.id === baseProp.id)
!specificProperties.some(
(specificProp) => specificProp.id === baseProp.id
)
); );
return [...basePropsFiltered, ...specificProperties]; return [...basePropsFiltered, ...specificProperties];
@@ -334,10 +329,7 @@ export class BaseBrush {
* @returns {Array} 选项数组 * @returns {Array} 选项数组
*/ */
getDynamicOptions(property, currentValues) { getDynamicOptions(property, currentValues) {
if ( if (property.dynamicOptions && typeof property.dynamicOptions === "function") {
property.dynamicOptions &&
typeof property.dynamicOptions === "function"
) {
return property.dynamicOptions(currentValues); return property.dynamicOptions(currentValues);
} }
return property.options || []; return property.options || [];

View File

@@ -206,9 +206,7 @@ export class TexturePresetManager {
* @returns {Object|null} 材质对象 * @returns {Object|null} 材质对象
*/ */
getTextureById(textureId) { getTextureById(textureId) {
return ( return this.getAllTextures().find((texture) => texture.id === textureId) || null;
this.getAllTextures().find((texture) => texture.id === textureId) || null
);
} }
/** /**
@@ -217,9 +215,7 @@ export class TexturePresetManager {
* @returns {Array} 材质数组 * @returns {Array} 材质数组
*/ */
getTexturesByCategory(category) { getTexturesByCategory(category) {
return this.getAllTextures().filter( return this.getAllTextures().filter((texture) => texture.category === category);
(texture) => texture.category === category
);
} }
/** /**
@@ -241,8 +237,7 @@ export class TexturePresetManager {
*/ */
addCustomTexture(textureData) { addCustomTexture(textureData) {
const textureId = const textureId =
textureData.id || textureData.id || `custom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
`custom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const texture = { const texture = {
id: textureId, id: textureId,
@@ -286,9 +281,7 @@ export class TexturePresetManager {
* @returns {Boolean} 是否删除成功 * @returns {Boolean} 是否删除成功
*/ */
removeCustomTexture(textureId) { removeCustomTexture(textureId) {
const index = this.customTextures.findIndex( const index = this.customTextures.findIndex((texture) => texture.id === textureId);
(texture) => texture.id === textureId
);
if (index === -1) { if (index === -1) {
return false; return false;
} }
@@ -430,10 +423,7 @@ export class TexturePresetManager {
file: null, file: null,
})); }));
localStorage.setItem( localStorage.setItem("canvasEditor_customTextures", JSON.stringify(customTexturesData));
"canvasEditor_customTextures",
JSON.stringify(customTexturesData)
);
} catch (error) { } catch (error) {
console.error("保存自定义材质失败:", error); console.error("保存自定义材质失败:", error);
} }
@@ -472,9 +462,7 @@ export class TexturePresetManager {
* @returns {String} 预设ID * @returns {String} 预设ID
*/ */
createTexturePreset(name, settings) { createTexturePreset(name, settings) {
const presetId = `preset_${Date.now()}_${Math.random() const presetId = `preset_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
.toString(36)
.substr(2, 9)}`;
const preset = { const preset = {
id: presetId, id: presetId,

View File

@@ -17,6 +17,7 @@ import { MarkerBrush } from "./types/MarkerBrush";
import { CustomPenBrush } from "./types/CustomPenBrush"; import { CustomPenBrush } from "./types/CustomPenBrush";
import { RibbonBrush } from "./types/RibbonBrush"; import { RibbonBrush } from "./types/RibbonBrush";
import { ShadedBrush } from "./types/ShadedBrush"; import { ShadedBrush } from "./types/ShadedBrush";
import { EraserStateManager } from "../EraserStateManager";
// import { SketchyBrush } from "./types/SketchyBrush"; // import { SketchyBrush } from "./types/SketchyBrush";
// import { SpraypaintBrush } from "./types/SpraypaintBrush"; // import { SpraypaintBrush } from "./types/SpraypaintBrush";
@@ -379,27 +380,23 @@ export class BrushManager {
} }
// 创建新笔刷实例 // 创建新笔刷实例
try { try {
const brushInstance = brushRegistry.createBrushInstance( const brushInstance = brushRegistry.createBrushInstance(brushId, this.canvas, {
brushId, color: brushId === "eraser" ? this.brushStore.state.color : undefined,
this.canvas, width: this.brushStore.state.size,
{ opacity: this.brushStore.state.opacity,
color: brushId === "eraser" ? this.brushStore.state.color : undefined,
width: this.brushStore.state.size,
opacity: this.brushStore.state.opacity,
// 阴影相关配置 // 阴影相关配置
shadowEnabled: this.brushStore.state.shadowEnabled, shadowEnabled: this.brushStore.state.shadowEnabled,
shadowColor: this.brushStore.state.shadowColor, shadowColor: this.brushStore.state.shadowColor,
shadowWidth: this.brushStore.state.shadowWidth, shadowWidth: this.brushStore.state.shadowWidth,
shadowOffsetX: this.brushStore.state.shadowOffsetX, shadowOffsetX: this.brushStore.state.shadowOffsetX,
shadowOffsetY: this.brushStore.state.shadowOffsetY, shadowOffsetY: this.brushStore.state.shadowOffsetY,
// 材质笔刷特有配置 // 材质笔刷特有配置
textureEnabled: this.brushStore.state.textureEnabled, textureEnabled: this.brushStore.state.textureEnabled,
texturePath: this.brushStore.state.texturePath, texturePath: this.brushStore.state.texturePath,
textureScale: this.brushStore.state.textureScale, textureScale: this.brushStore.state.textureScale,
} });
);
if (brushInstance) { if (brushInstance) {
// 创建笔刷 // 创建笔刷
@@ -648,48 +645,7 @@ export class BrushManager {
// 初始化橡皮擦状态管理器 // 初始化橡皮擦状态管理器
if (this.canvas && this.layerManager) { if (this.canvas && this.layerManager) {
this.eraserStateManager = new EraserStateManager( this.eraserStateManager = new EraserStateManager(this.canvas, this.layerManager);
this.canvas,
this.layerManager
);
}
}
/**
* 创建橡皮擦
* @param {Object} options 橡皮擦选项
*/
createEraser(options = {}) {
if (!this.canvas) {
console.error("画布未初始化");
return null;
}
try {
// 直接使用 fabric-with-erasing 库提供的 EraserBrush
this.brush = new fabric.EraserBrush(this.canvas);
// 应用配置
this.configure(this.brush, {
width: this.brushSize.value,
color: this.brushColor.value,
opacity: this.brushOpacity.value,
inverted: options.inverted || false,
...options,
});
// 设置画布为绘图模式
this.canvas.isDrawingMode = true;
this.canvas.freeDrawingBrush = this.brush;
// 绑定橡皮擦事件处理器
// this._bindEraserEvents();
console.log("橡皮擦创建成功");
return this.brush;
} catch (error) {
console.error("创建橡皮擦失败:", error);
return null;
} }
} }
@@ -866,6 +822,44 @@ export class BrushManager {
} }
} }
// /**
// * 创建橡皮擦
// * @param {Object} options 橡皮擦选项
// */
// createEraser(options = {}) {
// if (!this.canvas) {
// console.error("画布未初始化");
// return null;
// }
// try {
// // 直接使用 fabric-with-erasing 库提供的 EraserBrush
// this.brush = new fabric.EraserBrush(this.canvas);
// // 应用配置
// this.configure(this.brush, {
// width: this.brushSize.value,
// color: this.brushColor.value,
// opacity: this.brushOpacity.value,
// inverted: options.inverted || false,
// ...options,
// });
// // 设置画布为绘图模式
// this.canvas.isDrawingMode = true;
// this.canvas.freeDrawingBrush = this.brush;
// // 绑定橡皮擦事件处理器
// // this._bindEraserEvents();
// console.log("橡皮擦创建成功");
// return this.brush;
// } catch (error) {
// console.error("创建橡皮擦失败:", error);
// return null;
// }
// }
/** /**
* 创建橡皮擦 * 创建橡皮擦
* @returns {Object} 橡皮擦笔刷 * @returns {Object} 橡皮擦笔刷
@@ -891,12 +885,7 @@ export class BrushManager {
const imageData = ctx.getImageData(pointer.x, pointer.y, 1, 1).data; const imageData = ctx.getImageData(pointer.x, pointer.y, 1, 1).data;
// 将RGB转换为十六进制颜色 // 将RGB转换为十六进制颜色
const color = `#${( const color = `#${((1 << 24) + (imageData[0] << 16) + (imageData[1] << 8) + imageData[2])
(1 << 24) +
(imageData[0] << 16) +
(imageData[1] << 8) +
imageData[2]
)
.toString(16) .toString(16)
.slice(1)}`; .slice(1)}`;

View File

@@ -97,9 +97,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
} }
if (color.indexOf("rgb") === -1) { if (color.indexOf("rgb") === -1) {
// convert named colors // convert named colors
var tempElem = document.body.appendChild( var tempElem = document.body.appendChild(document.createElement("fictum")); // intentionally use unknown tag to lower chances of css rule override with !important
document.createElement("fictum")
); // intentionally use unknown tag to lower chances of css rule override with !important
var flag = "rgb(1, 2, 3)"; // this flag tested on chrome 59, ff 53, ie9, ie10, ie11, edge 14 var flag = "rgb(1, 2, 3)"; // this flag tested on chrome 59, ff 53, ie9, ie10, ie11, edge 14
tempElem.style.color = flag; tempElem.style.color = flag;
if (tempElem.style.color !== flag) { if (tempElem.style.color !== flag) {
@@ -116,7 +114,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
if (color.indexOf("rgba") === -1) { if (color.indexOf("rgba") === -1) {
color += ",1"; // convert 'rgb(R,G,B)' to 'rgb(R,G,B)A' which looks awful but will pass the regxep below color += ",1"; // convert 'rgb(R,G,B)' to 'rgb(R,G,B)A' which looks awful but will pass the regxep below
} }
return color.match(/[\.\d]+/g).map(function (a) { return color.match(/[.\d]+/g).map(function (a) {
return +a; return +a;
}); });
} }
@@ -269,13 +267,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
draw: function () { draw: function () {
var ctx = this.ctx; var ctx = this.ctx;
ctx.save(); ctx.save();
this.line( this.line(ctx, this._lastPoint, this._point, this.color, this._currentLineWidth);
ctx,
this._lastPoint,
this._point,
this.color,
this._currentLineWidth
);
ctx.restore(); ctx.restore();
}, },
@@ -319,10 +311,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this.canvas.contextTop.globalAlpha = this.opacity; this.canvas.contextTop.globalAlpha = this.opacity;
this._size = this.width / 2 + this._baseWidth; this._size = this.width / 2 + this._baseWidth;
this._drawn = false; this._drawn = false;
@@ -330,10 +320,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this.update(pointer); this.update(pointer);
this.draw(this.canvas.contextTop); this.draw(this.canvas.contextTop);
}, },
@@ -358,9 +346,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
update: function (p) { update: function (p) {
this.set(p); this.set(p);
this._latestStrokeLength = this._point this._latestStrokeLength = this._point.subtract(this._latest).distanceFrom({ x: 0, y: 0 });
.subtract(this._latest)
.distanceFrom({ x: 0, y: 0 });
}, },
draw: function (ctx) { draw: function (ctx) {
@@ -372,12 +358,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
v.normalize(s); v.normalize(s);
dotSize = dotSize =
this._sep * this._sep * fabric.util.clamp((this._inkAmount / this._latestStrokeLength) * 3, 1, 0.5);
fabric.util.clamp(
(this._inkAmount / this._latestStrokeLength) * 3,
1,
0.5
);
dotNum = Math.ceil(this._size * this._sep); dotNum = Math.ceil(this._size * this._sep);
range = this._size / 2; range = this._size / 2;
@@ -428,10 +409,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
// 添加坐标转换处理画布缩放和偏移 // 添加坐标转换处理画布缩放和偏移
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points = [pointer]; this._points = [pointer];
this._count = 0; this._count = 0;
@@ -440,15 +419,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
color = fabric.util.colorValues(this.color); color = fabric.util.colorValues(this.color);
ctx.strokeStyle = ctx.strokeStyle =
"rgba(" + "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.1 * this.opacity + ")";
color[0] +
"," +
color[1] +
"," +
color[2] +
"," +
0.1 * this.opacity +
")";
ctx.lineWidth = this.width; ctx.lineWidth = this.width;
this._points.push(pointer); this._points.push(pointer);
@@ -456,10 +427,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
// 添加坐标转换处理画布缩放和偏移 // 添加坐标转换处理画布缩放和偏移
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points.push(pointer); this._points.push(pointer);
@@ -528,10 +497,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
_render: function (pointer) { _render: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
var len, var len,
i, i,
point = this.setPointer(pointer), point = this.setPointer(pointer),
@@ -579,21 +546,11 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
r = fabric.util.getRandom(range, 1); r = fabric.util.getRandom(range, 1);
c = fabric.util.getRandom(Math.PI * 2); c = fabric.util.getRandom(Math.PI * 2);
point = new fabric.Point( point = new fabric.Point(pointer.x + r * Math.sin(c), pointer.y + r * Math.cos(c));
pointer.x + r * Math.sin(c),
pointer.y + r * Math.cos(c)
);
ctx.fillStyle = color; ctx.fillStyle = color;
ctx.beginPath(); ctx.beginPath();
ctx.arc( ctx.arc(point.x, point.y, fabric.util.getRandom(maxSize) / 2, 0, Math.PI * 2, false);
point.x,
point.y,
fabric.util.getRandom(maxSize) / 2,
0,
Math.PI * 2,
false
);
ctx.fill(); ctx.fill();
} }
ctx.restore(); ctx.restore();
@@ -609,10 +566,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
_resetTip: function (pointer) { _resetTip: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
var len, var len,
i, i,
point = this.setPointer(pointer); point = this.setPointer(pointer);
@@ -655,10 +610,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points = [pointer]; this._points = [pointer];
this._count = 0; this._count = 0;
@@ -667,23 +620,13 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
//ctx.globalCompositeOperation = 'source-over'; //ctx.globalCompositeOperation = 'source-over';
ctx.strokeStyle = ctx.strokeStyle =
"rgba(" + "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.05 * this.opacity + ")";
color[0] +
"," +
color[1] +
"," +
color[2] +
"," +
0.05 * this.opacity +
")";
ctx.lineWidth = this.width; ctx.lineWidth = this.width;
}, },
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points.push(pointer); this._points.push(pointer);
var i, var i,
@@ -772,10 +715,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
this._lastPoint.x - lineWidthDiff + num, this._lastPoint.x - lineWidthDiff + num,
this._lastPoint.y + lineWidthDiff - num this._lastPoint.y + lineWidthDiff - num
); );
ctx.lineTo( ctx.lineTo(pointer.x - lineWidthDiff + num, pointer.y + lineWidthDiff - num);
pointer.x - lineWidthDiff + num,
pointer.y + lineWidthDiff - num
);
ctx.stroke(); ctx.stroke();
} }
@@ -783,10 +723,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._lastPoint = pointer; this._lastPoint = pointer;
this.canvas.contextTop.strokeStyle = this.color; this.canvas.contextTop.strokeStyle = this.color;
this.canvas.contextTop.lineWidth = this._lineWidth; this.canvas.contextTop.lineWidth = this._lineWidth;
@@ -795,10 +733,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
if (this.canvas._isCurrentlyDrawing) { if (this.canvas._isCurrentlyDrawing) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._render(pointer); this._render(pointer);
} }
}, },
@@ -852,10 +788,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
this._lastPoint.x + lineWidthDiff - num, this._lastPoint.x + lineWidthDiff - num,
this._lastPoint.y + lineWidthDiff - num this._lastPoint.y + lineWidthDiff - num
); );
ctx.lineTo( ctx.lineTo(pointer.x + lineWidthDiff - num, pointer.y + lineWidthDiff - num);
pointer.x + lineWidthDiff - num,
pointer.y + lineWidthDiff - num
);
ctx.stroke(); ctx.stroke();
} }
@@ -863,10 +796,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._lastPoint = pointer; this._lastPoint = pointer;
this.canvas.contextTop.strokeStyle = this.color; this.canvas.contextTop.strokeStyle = this.color;
this.canvas.contextTop.lineWidth = this._lineWidth; this.canvas.contextTop.lineWidth = this._lineWidth;
@@ -875,10 +806,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
if (this.canvas._isCurrentlyDrawing) { if (this.canvas._isCurrentlyDrawing) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._render(pointer); this._render(pointer);
} }
}, },
@@ -923,10 +852,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points = []; this._points = [];
this._points.push(["M", pointer.x, pointer.y]); this._points.push(["M", pointer.x, pointer.y]);
@@ -938,10 +865,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
if (this.canvas._isCurrentlyDrawing) { if (this.canvas._isCurrentlyDrawing) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._render(pointer); this._render(pointer);
} }
}, },
@@ -1010,10 +935,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
// 添加坐标转换处理画布缩放和偏移 // 添加坐标转换处理画布缩放和偏移
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._lastPoint = pointer; this._lastPoint = pointer;
this.canvas.contextTop.strokeStyle = this.color; this.canvas.contextTop.strokeStyle = this.color;
@@ -1024,10 +947,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
if (this.canvas._isCurrentlyDrawing) { if (this.canvas._isCurrentlyDrawing) {
// 添加坐标转换处理画布缩放和偏移 // 添加坐标转换处理画布缩放和偏移
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._render(pointer); this._render(pointer);
} }
@@ -1093,10 +1014,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._lastPoint = pointer; this._lastPoint = pointer;
this.canvas.contextTop.lineWidth = this._lineWidth; this.canvas.contextTop.lineWidth = this._lineWidth;
this._size = this.width + this._baseWidth; this._size = this.width + this._baseWidth;
@@ -1104,10 +1023,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
if (this.canvas._isCurrentlyDrawing) { if (this.canvas._isCurrentlyDrawing) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._render(pointer); this._render(pointer);
} }
}, },
@@ -1158,12 +1075,10 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(painters[i].dx, painters[i].dy); ctx.moveTo(painters[i].dx, painters[i].dy);
painters[i].dx -= painters[i].ax = painters[i].dx -= painters[i].ax =
(painters[i].ax + (painters[i].ax + (painters[i].dx - this._lastPoint.x) * painters[i].div) *
(painters[i].dx - this._lastPoint.x) * painters[i].div) *
painters[i].ease; painters[i].ease;
painters[i].dy -= painters[i].ay = painters[i].dy -= painters[i].ay =
(painters[i].ay + (painters[i].ay + (painters[i].dy - this._lastPoint.y) * painters[i].div) *
(painters[i].dy - this._lastPoint.y) * painters[i].div) *
painters[i].ease; painters[i].ease;
ctx.lineTo(painters[i].dx, painters[i].dy); ctx.lineTo(painters[i].dx, painters[i].dy);
ctx.stroke(); ctx.stroke();
@@ -1185,26 +1100,16 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
ease: Math.random() * 0.2 + 0.6, ease: Math.random() * 0.2 + 0.6,
}); });
} }
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._lastPoint = pointer; this._lastPoint = pointer;
//ctx.globalCompositeOperation = 'source-over'; //ctx.globalCompositeOperation = 'source-over';
ctx.strokeStyle = ctx.strokeStyle =
"rgba(" + "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.05 * this.opacity + ")";
color[0] +
"," +
color[1] +
"," +
color[2] +
"," +
0.05 * this.opacity +
")";
ctx.lineWidth = this.width; ctx.lineWidth = this.width;
for (var i = 0; i < this._nrPainters; i++) { for (let i = 0; i < this._nrPainters; i++) {
this._painters[i].dx = pointer.x; this._painters[i].dx = pointer.x;
this._painters[i].dy = pointer.y; this._painters[i].dy = pointer.y;
} }
@@ -1216,10 +1121,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._lastPoint = pointer; this._lastPoint = pointer;
}, },
@@ -1254,34 +1157,22 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points = [pointer]; this._points = [pointer];
var ctx = this.canvas.contextTop, var ctx = this.canvas.contextTop,
color = fabric.util.colorValues(this.color); color = fabric.util.colorValues(this.color);
ctx.strokeStyle = ctx.strokeStyle =
"rgba(" + "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + this.opacity + ")";
color[0] +
"," +
color[1] +
"," +
color[2] +
"," +
this.opacity +
")";
ctx.lineWidth = this.width; ctx.lineWidth = this.width;
ctx.lineJoin = ctx.lineCap = "round"; ctx.lineJoin = ctx.lineCap = "round";
}, },
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points.push(pointer); this._points.push(pointer);
var ctx = this.canvas.contextTop, var ctx = this.canvas.contextTop,
@@ -1346,10 +1237,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
this.canvas.contextTop.globalAlpha = this.opacity; this.canvas.contextTop.globalAlpha = this.opacity;
// 坐标转换 // 坐标转换
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points = [pointer]; this._points = [pointer];
this._count = 0; this._count = 0;
@@ -1361,23 +1250,13 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
ctx.lineWidth = this.width; ctx.lineWidth = this.width;
ctx.strokeStyle = ctx.strokeStyle =
"rgba(" + "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.3 * this.opacity + ")";
color[0] +
"," +
color[1] +
"," +
color[2] +
"," +
0.3 * this.opacity +
")";
}, },
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
// 坐标转换 // 坐标转换
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points.push(pointer); this._points.push(pointer);
@@ -1396,15 +1275,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
// 增加透明度设置 // 增加透明度设置
var color = fabric.util.colorValues(this.color); var color = fabric.util.colorValues(this.color);
ctx.strokeStyle = ctx.strokeStyle =
"rgba(" + "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.2 * this.opacity + ")";
color[0] +
"," +
color[1] +
"," +
color[2] +
"," +
0.2 * this.opacity +
")";
// 修改循环逻辑,确保在有点时能画出效果 // 修改循环逻辑,确保在有点时能画出效果
if (this._count > 0) { if (this._count > 0) {
@@ -1416,10 +1287,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
if (d < 4000 && Math.random() > d / 2000) { if (d < 4000 && Math.random() > d / 2000) {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo( ctx.moveTo(points[this._count].x + dx * factor, points[this._count].y + dy * factor);
points[this._count].x + dx * factor,
points[this._count].y + dy * factor
);
ctx.lineTo(points[i].x - dx * factor, points[i].y - dy * factor); ctx.lineTo(points[i].x - dx * factor, points[i].y - dy * factor);
ctx.stroke(); ctx.stroke();
this._drawn = true; this._drawn = true;
@@ -1494,10 +1362,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
this.canvas.contextTop.globalAlpha = this.opacity; this.canvas.contextTop.globalAlpha = this.opacity;
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._point = new fabric.Point(pointer.x, pointer.y); this._point = new fabric.Point(pointer.x, pointer.y);
this._lastPoint = this._point; this._lastPoint = this._point;
@@ -1510,10 +1376,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._lastPoint = this._point; this._lastPoint = this._point;
this._point = new fabric.Point(pointer.x, pointer.y); this._point = new fabric.Point(pointer.x, pointer.y);
}, },
@@ -1542,13 +1406,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
x = self._lastPoint.x + Math.sin(angle) - self.size / 2; x = self._lastPoint.x + Math.sin(angle) - self.size / 2;
y = self._lastPoint.y + Math.cos(angle) - self.size / 2; y = self._lastPoint.y + Math.cos(angle) - self.size / 2;
self.canvas.contextTop.drawImage( self.canvas.contextTop.drawImage(self.brush._element, x, y, self.size, self.size);
self.brush._element,
x,
y,
self.size,
self.size
);
if (self.canvas._isCurrentlyDrawing) { if (self.canvas._isCurrentlyDrawing) {
setTimeout(draw, self._interval); setTimeout(draw, self._interval);
@@ -1594,43 +1452,22 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
var ctx = this.canvas.contextTop, var ctx = this.canvas.contextTop,
color = fabric.util.colorValues(this.color), color = fabric.util.colorValues(this.color),
bgColor = fabric.util.colorValues(this.bgColor); bgColor = fabric.util.colorValues(this.bgColor);
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._lastPoint = pointer; this._lastPoint = pointer;
this._drawn = false; this._drawn = false;
//ctx.globalCompositeOperation = 'source-over'; //ctx.globalCompositeOperation = 'source-over';
this.canvas.contextTop.globalAlpha = this.opacity; this.canvas.contextTop.globalAlpha = this.opacity;
ctx.fillStyle = ctx.fillStyle =
"rgba(" + "rgba(" + bgColor[0] + "," + bgColor[1] + "," + bgColor[2] + "," + bgColor[3] + ")";
bgColor[0] + ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ")";
"," +
bgColor[1] +
"," +
bgColor[2] +
"," +
bgColor[3] +
")";
ctx.strokeStyle =
"rgba(" +
color[0] +
"," +
color[1] +
"," +
color[2] +
"," +
color[3] +
")";
ctx.lineWidth = this.width; ctx.lineWidth = this.width;
}, },
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
var ctx = this.canvas.contextTop, var ctx = this.canvas.contextTop,
dx = pointer.x - this._lastPoint.x, dx = pointer.x - this._lastPoint.x,
dy = pointer.y - this._lastPoint.y, dy = pointer.y - this._lastPoint.y,
@@ -1683,20 +1520,16 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
}, },
onMouseDown: function (pointer) { onMouseDown: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points = [pointer]; this._points = [pointer];
this._count = 0; this._count = 0;
this._colorValues = fabric.util.colorValues(this.color); this._colorValues = fabric.util.colorValues(this.color);
}, },
onMouseMove: function (pointer) { onMouseMove: function (pointer) {
pointer.x = pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
pointer.y =
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
this._points.push(pointer); this._points.push(pointer);
var ctx = this.canvas.contextTop, var ctx = this.canvas.contextTop,

View File

@@ -350,9 +350,7 @@ export class RainbowBrush extends BaseBrush {
]; ];
// 移除基础属性中的颜色选择器,因为彩虹笔不需要颜色选择 // 移除基础属性中的颜色选择器,因为彩虹笔不需要颜色选择
const filteredBaseProps = baseProperties.filter( const filteredBaseProps = baseProperties.filter((prop) => prop.id !== "color");
(prop) => prop.id !== "color"
);
// 合并并返回所有属性 // 合并并返回所有属性
return [...filteredBaseProps, ...rainbowProperties]; return [...filteredBaseProps, ...rainbowProperties];
@@ -432,11 +430,7 @@ export class CustomBrushExample extends BaseBrush {
this.dashPattern = options.dashPattern || [0, 0]; // 实线 this.dashPattern = options.dashPattern || [0, 0]; // 实线
this.noiseAmount = options.noiseAmount || 0; this.noiseAmount = options.noiseAmount || 0;
this.gradientEnabled = options.gradientEnabled || false; this.gradientEnabled = options.gradientEnabled || false;
this.gradientColors = options.gradientColors || [ this.gradientColors = options.gradientColors || ["#ff0000", "#ffff00", "#00ff00"];
"#ff0000",
"#ffff00",
"#00ff00",
];
this.angle = options.angle || 0; this.angle = options.angle || 0;
// 材质相关属性 // 材质相关属性
@@ -885,8 +879,7 @@ export class CustomBrushExample extends BaseBrush {
description: "选择图案类型", description: "选择图案类型",
category: "特效设置", category: "特效设置",
order: 130, order: 130,
visibleWhen: (values) => visibleWhen: (values) => values.effectType === "pattern" || values.patternType === "dashed",
values.effectType === "pattern" || values.patternType === "dashed",
}, },
// 虚线特有属性 // 虚线特有属性
@@ -1184,7 +1177,7 @@ export class CustomBrushExample extends BaseBrush {
return true; return true;
case "dashPattern1": case "dashPattern1":
case "dashPattern2": case "dashPattern2": {
// 更新虚线模式 // 更新虚线模式
const idx = propId === "dashPattern1" ? 0 : 1; const idx = propId === "dashPattern1" ? 0 : 1;
this.dashPattern[idx] = value; this.dashPattern[idx] = value;
@@ -1192,6 +1185,7 @@ export class CustomBrushExample extends BaseBrush {
this.brush.strokeDashArray = this.dashPattern; this.brush.strokeDashArray = this.dashPattern;
} }
return true; return true;
}
case "noiseAmount": case "noiseAmount":
this.noiseAmount = value; this.noiseAmount = value;
@@ -1206,13 +1200,14 @@ export class CustomBrushExample extends BaseBrush {
case "gradientColor1": case "gradientColor1":
case "gradientColor2": case "gradientColor2":
case "gradientColor3": case "gradientColor3": {
const colorIdx = parseInt(propId.slice(-1)) - 1; const colorIdx = parseInt(propId.slice(-1)) - 1;
this.gradientColors[colorIdx] = value; this.gradientColors[colorIdx] = value;
if (this.gradientEnabled && this.brush) { if (this.gradientEnabled && this.brush) {
this._configureGradient(this.brush); this._configureGradient(this.brush);
} }
return true; return true;
}
case "angle": case "angle":
this.angle = value; this.angle = value;

View File

@@ -24,8 +24,7 @@ export class InkBrush extends BaseBrush {
this._baseWidth = options._baseWidth || 15; this._baseWidth = options._baseWidth || 15;
this._inkAmount = options._inkAmount || 7; this._inkAmount = options._inkAmount || 7;
this._range = options._range || 10; this._range = options._range || 10;
this.splashEnabled = this.splashEnabled = options.splashEnabled !== undefined ? options.splashEnabled : true;
options.splashEnabled !== undefined ? options.splashEnabled : true;
this.splashSize = options.splashSize || 5; this.splashSize = options.splashSize || 5;
this.splashDistance = options.splashDistance || 30; this.splashDistance = options.splashDistance || 30;
} }

View File

@@ -26,9 +26,7 @@ export class LongfurBrush extends BaseBrush {
this.furFlowFactor = options.furFlowFactor || 0.5; this.furFlowFactor = options.furFlowFactor || 0.5;
this.furCurvature = options.furCurvature || 0.3; this.furCurvature = options.furCurvature || 0.3;
this.randomizeDirection = this.randomizeDirection =
options.randomizeDirection !== undefined options.randomizeDirection !== undefined ? options.randomizeDirection : true;
? options.randomizeDirection
: true;
} }
/** /**

View File

@@ -39,9 +39,7 @@ export class PencilBrush extends BaseBrush {
this.brush = new fabric.PencilBrush(this.canvas); this.brush = new fabric.PencilBrush(this.canvas);
// 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象 // 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind( const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(this.brush);
this.brush
);
const self = this; // 保存外部this引用 const self = this; // 保存外部this引用
this.brush._finalizeAndAddPath = function () { this.brush._finalizeAndAddPath = function () {
@@ -54,10 +52,7 @@ export class PencilBrush extends BaseBrush {
this._points = this.decimatePoints(this._points, this.decimate); this._points = this.decimatePoints(this._points, this.decimate);
} }
console.log( console.log("PencilBrush: points count =", this._points ? this._points.length : 0);
"PencilBrush: points count =",
this._points ? this._points.length : 0
);
// 检查是否有有效的路径数据 // 检查是否有有效的路径数据
if (!this._points || this._points.length < 2) { if (!this._points || this._points.length < 2) {
@@ -97,9 +92,7 @@ export class PencilBrush extends BaseBrush {
// 恢复透明度 // 恢复透明度
ctx.globalAlpha = currentAlpha; ctx.globalAlpha = currentAlpha;
} else { } else {
console.warn( console.warn("convertToImg method not found, falling back to original behavior");
"convertToImg method not found, falling back to original behavior"
);
// 如果没有convertToImg方法回退到原始行为 // 如果没有convertToImg方法回退到原始行为
this.canvas.add(path); this.canvas.add(path);
this.canvas.fire("path:created", { path: path }); this.canvas.fire("path:created", { path: path });
@@ -135,11 +128,7 @@ export class PencilBrush extends BaseBrush {
const command = pathData[i]; const command = pathData[i];
if (command[0] === "M") { if (command[0] === "M") {
moveCount++; moveCount++;
} else if ( } else if (command[0] === "L" || command[0] === "Q" || command[0] === "C") {
command[0] === "L" ||
command[0] === "Q" ||
command[0] === "C"
) {
hasDrawing = true; hasDrawing = true;
break; break;
} }
@@ -210,9 +199,7 @@ export class PencilBrush extends BaseBrush {
_createRGBAColor(color, opacity) { _createRGBAColor(color, opacity) {
// 如果已经是rgba颜色先提取RGB部分 // 如果已经是rgba颜色先提取RGB部分
if (color.startsWith("rgba")) { if (color.startsWith("rgba")) {
const rgbaMatch = color.match( const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/
);
if (rgbaMatch) { if (rgbaMatch) {
const [, r, g, b] = rgbaMatch; const [, r, g, b] = rgbaMatch;
return `rgba(${r}, ${g}, ${b}, ${opacity})`; return `rgba(${r}, ${g}, ${b}, ${opacity})`;

View File

@@ -75,10 +75,7 @@ export class RibbonBrush extends BaseBrush {
brush.opacity = options.opacity; brush.opacity = options.opacity;
// 如果启用渐变,更新渐变的第一个颜色 // 如果启用渐变,更新渐变的第一个颜色
if (this.gradient && this.gradientColors.length > 0) { if (this.gradient && this.gradientColors.length > 0) {
this.gradientColors[0] = this._createRGBAColor( this.gradientColors[0] = this._createRGBAColor(brush.color, options.opacity);
brush.color,
options.opacity
);
this.updateGradient(); this.updateGradient();
} }
brush.canvas.freeDrawingBrush.opacity = options.opacity; brush.canvas.freeDrawingBrush.opacity = options.opacity;

View File

@@ -23,8 +23,7 @@ export class SpraypaintBrush extends BaseBrush {
// 喷漆笔刷特有属性 // 喷漆笔刷特有属性
this.density = options.density || 20; this.density = options.density || 20;
this.sprayRadius = options.sprayRadius || 10; this.sprayRadius = options.sprayRadius || 10;
this.randomOpacity = this.randomOpacity = options.randomOpacity !== undefined ? options.randomOpacity : true;
options.randomOpacity !== undefined ? options.randomOpacity : true;
this.dotSize = options.dotSize || 1; this.dotSize = options.dotSize || 1;
this.dotShape = options.dotShape || "circle"; this.dotShape = options.dotShape || "circle";
} }

View File

@@ -71,9 +71,7 @@ export class TextureBrush extends BaseBrush {
this.brush = new fabric.PatternBrush(this.canvas); this.brush = new fabric.PatternBrush(this.canvas);
// 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象 // 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind( const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(this.brush);
this.brush
);
const self = this; // 保存外部this引用 const self = this; // 保存外部this引用
this.brush._finalizeAndAddPath = function () { this.brush._finalizeAndAddPath = function () {
@@ -86,10 +84,7 @@ export class TextureBrush extends BaseBrush {
this._points = this.decimatePoints(this._points, this.decimate); this._points = this.decimatePoints(this._points, this.decimate);
} }
console.log( console.log("TextureBrush: points count =", this._points ? this._points.length : 0);
"TextureBrush: points count =",
this._points ? this._points.length : 0
);
// 检查是否有有效的路径数据 // 检查是否有有效的路径数据
if (!this._points || this._points.length < 2) { if (!this._points || this._points.length < 2) {
@@ -122,9 +117,7 @@ export class TextureBrush extends BaseBrush {
this.convertToImg(); this.convertToImg();
console.log("TextureBrush: convertToImg called successfully"); console.log("TextureBrush: convertToImg called successfully");
} else { } else {
console.warn( console.warn("convertToImg method not found, falling back to original behavior");
"convertToImg method not found, falling back to original behavior"
);
// 如果没有convertToImg方法回退到原始行为 // 如果没有convertToImg方法回退到原始行为
this.canvas.add(path); this.canvas.add(path);
this.canvas.fire("path:created", { path: path }); this.canvas.fire("path:created", { path: path });
@@ -254,10 +247,7 @@ export class TextureBrush extends BaseBrush {
this._applyTextureToPatternBrush(img); this._applyTextureToPatternBrush(img);
resolve(img); resolve(img);
}); });
} else if ( } else if (source instanceof Image || source instanceof HTMLCanvasElement) {
source instanceof Image ||
source instanceof HTMLCanvasElement
) {
// 如果已经是Image或Canvas对象直接使用 // 如果已经是Image或Canvas对象直接使用
this._applyTextureToPatternBrush(source); this._applyTextureToPatternBrush(source);
resolve(source); resolve(source);
@@ -425,9 +415,7 @@ export class TextureBrush extends BaseBrush {
if (textures.length === 0) return Promise.resolve(); if (textures.length === 0) return Promise.resolve();
this.currentTextureIndex = this.currentTextureIndex =
this.currentTextureIndex === 0 this.currentTextureIndex === 0 ? textures.length - 1 : this.currentTextureIndex - 1;
? textures.length - 1
: this.currentTextureIndex - 1;
const prevTexture = textures[this.currentTextureIndex]; const prevTexture = textures[this.currentTextureIndex];
return this.setTextureById(prevTexture.id); return this.setTextureById(prevTexture.id);
@@ -943,11 +931,7 @@ export class TextureBrush extends BaseBrush {
const command = pathData[i]; const command = pathData[i];
if (command[0] === "M") { if (command[0] === "M") {
moveCount++; moveCount++;
} else if ( } else if (command[0] === "L" || command[0] === "Q" || command[0] === "C") {
command[0] === "L" ||
command[0] === "Q" ||
command[0] === "C"
) {
hasDrawing = true; hasDrawing = true;
break; break;
} }

View File

@@ -25,9 +25,7 @@ export class WritingBrush extends BaseBrush {
this.inkAmount = options.inkAmount || 20; this.inkAmount = options.inkAmount || 20;
this.brushTaperFactor = options.brushTaperFactor || 0.6; this.brushTaperFactor = options.brushTaperFactor || 0.6;
this.enableInkDripping = this.enableInkDripping =
options.enableInkDripping !== undefined options.enableInkDripping !== undefined ? options.enableInkDripping : true;
? options.enableInkDripping
: true;
} }
/** /**

View File

@@ -61,14 +61,10 @@ export class PerformanceManager {
: 0; : 0;
const avgUndoTime = const avgUndoTime =
this.stats.totalUndos > 0 this.stats.totalUndos > 0 ? this.stats.totalUndoTime / this.stats.totalUndos : 0;
? this.stats.totalUndoTime / this.stats.totalUndos
: 0;
const avgRedoTime = const avgRedoTime =
this.stats.totalRedos > 0 this.stats.totalRedos > 0 ? this.stats.totalRedoTime / this.stats.totalRedos : 0;
? this.stats.totalRedoTime / this.stats.totalRedos
: 0;
return { return {
overview: { overview: {
@@ -79,26 +75,18 @@ export class PerformanceManager {
avgUndoTime: Number(avgUndoTime.toFixed(2)), avgUndoTime: Number(avgUndoTime.toFixed(2)),
avgRedoTime: Number(avgRedoTime.toFixed(2)), avgRedoTime: Number(avgRedoTime.toFixed(2)),
}, },
commandBreakdown: Array.from(this.stats.commandStats.entries()).map( commandBreakdown: Array.from(this.stats.commandStats.entries()).map(([name, stats]) => ({
([name, stats]) => ({ commandName: name,
commandName: name, executions: stats.executions,
executions: stats.executions, undos: stats.undos,
undos: stats.undos, redos: stats.redos,
redos: stats.redos, avgExecutionTime:
avgExecutionTime: stats.executions > 0
stats.executions > 0 ? Number((stats.totalExecutionTime / stats.executions).toFixed(2))
? Number((stats.totalExecutionTime / stats.executions).toFixed(2)) : 0,
: 0, avgUndoTime: stats.undos > 0 ? Number((stats.totalUndoTime / stats.undos).toFixed(2)) : 0,
avgUndoTime: avgRedoTime: stats.redos > 0 ? Number((stats.totalRedoTime / stats.redos).toFixed(2)) : 0,
stats.undos > 0 })),
? Number((stats.totalUndoTime / stats.undos).toFixed(2))
: 0,
avgRedoTime:
stats.redos > 0
? Number((stats.totalRedoTime / stats.redos).toFixed(2))
: 0,
})
),
recentOperations: this.stats.recentOperations.slice(-20), // 最近20个操作 recentOperations: this.stats.recentOperations.slice(-20), // 最近20个操作
}; };
} }
@@ -110,10 +98,8 @@ export class PerformanceManager {
const slowCommands = []; const slowCommands = [];
for (const [name, stats] of this.stats.commandStats.entries()) { for (const [name, stats] of this.stats.commandStats.entries()) {
const avgExecTime = const avgExecTime = stats.executions > 0 ? stats.totalExecutionTime / stats.executions : 0;
stats.executions > 0 ? stats.totalExecutionTime / stats.executions : 0; const avgUndoTime = stats.undos > 0 ? stats.totalUndoTime / stats.undos : 0;
const avgUndoTime =
stats.undos > 0 ? stats.totalUndoTime / stats.undos : 0;
if (avgExecTime > threshold || avgUndoTime > threshold) { if (avgExecTime > threshold || avgUndoTime > threshold) {
slowCommands.push({ slowCommands.push({
@@ -128,8 +114,7 @@ export class PerformanceManager {
return slowCommands.sort( return slowCommands.sort(
(a, b) => (a, b) =>
Math.max(b.avgExecutionTime, b.avgUndoTime) - Math.max(b.avgExecutionTime, b.avgUndoTime) - Math.max(a.avgExecutionTime, a.avgUndoTime)
Math.max(a.avgExecutionTime, a.avgUndoTime)
); );
} }

View File

@@ -68,14 +68,9 @@ export class CanvasEventManager {
// 让动画管理器自行处理冲突,避免过度干预 // 让动画管理器自行处理冲突,避免过度干预
} else { } else {
// 非 Mac 设备的标准处理 // 非 Mac 设备的标准处理
if ( if (this.animationManager._panAnimation || this.animationManager._zoomAnimation) {
this.animationManager._panAnimation || this.animationManager._wasPanning = !!this.animationManager._panAnimation;
this.animationManager._zoomAnimation this.animationManager._wasZooming = !!this.animationManager._zoomAnimation;
) {
this.animationManager._wasPanning =
!!this.animationManager._panAnimation;
this.animationManager._wasZooming =
!!this.animationManager._zoomAnimation;
this.animationManager.smoothStopAnimations({ duration: 0.1 }); this.animationManager.smoothStopAnimations({ duration: 0.1 });
} }
} }
@@ -127,8 +122,7 @@ export class CanvasEventManager {
// Mac 设备优化:触控板滚动通常比鼠标滚轮更敏感 // Mac 设备优化:触控板滚动通常比鼠标滚轮更敏感
if (this.deviceInfo.isMac) { if (this.deviceInfo.isMac) {
const isMacTrackpadScroll = const isMacTrackpadScroll = Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0;
Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0;
if (isMacTrackpadScroll) { if (isMacTrackpadScroll) {
// Mac 触控板滚动更细腻,需要调整滚动因子 // Mac 触控板滚动更细腻,需要调整滚动因子
scrollFactor *= 0.8; // 降低滚动敏感度 scrollFactor *= 0.8; // 降低滚动敏感度
@@ -183,11 +177,7 @@ export class CanvasEventManager {
// 平滑停止任何正在进行的惯性动画 // 平滑停止任何正在进行的惯性动画
this.stopInertiaAnimation(true); this.stopInertiaAnimation(true);
if ( if (opt.e.altKey || opt.e.which === 2 || this.editorMode === OperationType.PAN) {
opt.e.altKey ||
opt.e.which === 2 ||
this.editorMode === OperationType.PAN
) {
this.canvas.isDragging = true; this.canvas.isDragging = true;
this.canvas.lastPosX = opt.e.clientX; this.canvas.lastPosX = opt.e.clientX;
this.canvas.lastPosY = opt.e.clientY; this.canvas.lastPosY = opt.e.clientY;
@@ -247,10 +237,8 @@ export class CanvasEventManager {
if (opt.e.touches && opt.e.touches.length === 2) { if (opt.e.touches && opt.e.touches.length === 2) {
this.canvas.isDragging = true; this.canvas.isDragging = true;
this.canvas.lastPosX = this.canvas.lastPosX = (opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2;
(opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2; this.canvas.lastPosY = (opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2;
this.canvas.lastPosY =
(opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2;
// 重置触摸位置历史 // 重置触摸位置历史
this.dragStartTime = Date.now(); this.dragStartTime = Date.now();
@@ -290,10 +278,8 @@ export class CanvasEventManager {
if (!this.canvas.isDragging) return; if (!this.canvas.isDragging) return;
if (opt.e.touches && opt.e.touches.length === 2) { if (opt.e.touches && opt.e.touches.length === 2) {
const currentX = const currentX = (opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2;
(opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2; const currentY = (opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2;
const currentY =
(opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2;
const vpt = this.canvas.viewportTransform; const vpt = this.canvas.viewportTransform;
vpt[4] += currentX - this.canvas.lastPosX; vpt[4] += currentX - this.canvas.lastPosX;
@@ -321,8 +307,7 @@ export class CanvasEventManager {
// 单指拖动更新 // 单指拖动更新
this.canvas.on("touch:drag:update", (opt) => { this.canvas.on("touch:drag:update", (opt) => {
if (!this.canvas.isDragging || this.editorMode !== OperationType.PAN) if (!this.canvas.isDragging || this.editorMode !== OperationType.PAN) return;
return;
const currentX = opt.e.touches[0].clientX; const currentX = opt.e.touches[0].clientX;
const currentY = opt.e.touches[0].clientY; const currentY = opt.e.touches[0].clientY;
@@ -367,10 +352,7 @@ export class CanvasEventManager {
if (this.canvas.isDragging) { if (this.canvas.isDragging) {
// 使用动画管理器处理惯性效果 // 使用动画管理器处理惯性效果
if (this.lastMousePositions.length > 1 && opt && opt.e) { if (this.lastMousePositions.length > 1 && opt && opt.e) {
this.animationManager.applyInertiaEffect( this.animationManager.applyInertiaEffect(this.lastMousePositions, isTouch);
this.lastMousePositions,
isTouch
);
} }
} }
@@ -405,22 +387,10 @@ export class CanvasEventManager {
}); });
// 添加对象开始变换时的状态捕获 // 添加对象开始变换时的状态捕获
this.canvas.on( this.canvas.on("object:moving", this._captureInitialTransformState.bind(this));
"object:moving", this.canvas.on("object:scaling", this._captureInitialTransformState.bind(this));
this._captureInitialTransformState.bind(this) this.canvas.on("object:rotating", this._captureInitialTransformState.bind(this));
); this.canvas.on("object:skewing", this._captureInitialTransformState.bind(this));
this.canvas.on(
"object:scaling",
this._captureInitialTransformState.bind(this)
);
this.canvas.on(
"object:rotating",
this._captureInitialTransformState.bind(this)
);
this.canvas.on(
"object:skewing",
this._captureInitialTransformState.bind(this)
);
this.canvas.on("object:modified", (e) => { this.canvas.on("object:modified", (e) => {
// 移除调试日志 // 移除调试日志
@@ -592,8 +562,7 @@ export class CanvasEventManager {
} }
// 验证是否需要合并 // 验证是否需要合并
const hasExistingObjects = const hasExistingObjects =
Array.isArray(activeLayer.fabricObjects) && Array.isArray(activeLayer.fabricObjects) && activeLayer.fabricObjects.length > 0;
activeLayer.fabricObjects.length > 0;
const hasNewImage = !!fabricImage; const hasNewImage = !!fabricImage;
if (!hasExistingObjects && !hasNewImage) { if (!hasExistingObjects && !hasNewImage) {
@@ -611,10 +580,7 @@ export class CanvasEventManager {
try { try {
console.log(`开始合并图层 ${activeLayer.name} 中的对象为组...`); console.log(`开始合并图层 ${activeLayer.name} 中的对象为组...`);
const command = await this.layerManager.LayerObjectsToGroup( const command = await this.layerManager.LayerObjectsToGroup(activeLayer, fabricImage);
activeLayer,
fabricImage
);
// 设置命令的撤销状态 // 设置命令的撤销状态
if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销 if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销
@@ -660,9 +626,7 @@ export class CanvasEventManager {
// 查找对应的图层(现在元素就是图层) // 查找对应的图层(现在元素就是图层)
const layer = this.layers.value.find( const layer = this.layers.value.find(
(l) => (l) => l.fabricObjects && l.fabricObjects?.[0]?.id === layerId
l.id === elementId ||
(l.fabricObjects && l.fabricObjects?.[0]?.id === layerId)
); );
if (layer) { if (layer) {
@@ -745,8 +709,7 @@ export class CanvasEventManager {
_detectDeviceType() { _detectDeviceType() {
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
const platform = navigator.platform.toLowerCase(); const platform = navigator.platform.toLowerCase();
const hasTouchSupport = const hasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0;
"ontouchstart" in window || navigator.maxTouchPoints > 0;
// 检测操作系统 // 检测操作系统
const isMac = /mac|darwin/.test(platform) || /macintosh/.test(userAgent); const isMac = /mac|darwin/.test(platform) || /macintosh/.test(userAgent);

View File

@@ -92,9 +92,7 @@ export class KeyboardManager {
*/ */
detectTouchDevice() { detectTouchDevice() {
return ( return (
"ontouchstart" in window || "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
); );
} }
@@ -214,9 +212,7 @@ export class KeyboardManager {
this.container.addEventListener("touchcancel", this._handleTouchEnd); this.container.addEventListener("touchcancel", this._handleTouchEnd);
} }
console.log( console.log(`键盘管理器已初始化,平台: ${this.platform}, 触摸设备: ${this.isTouchDevice}`);
`键盘管理器已初始化,平台: ${this.platform}, 触摸设备: ${this.isTouchDevice}`
);
} }
/** /**
@@ -262,10 +258,7 @@ export class KeyboardManager {
// 调用自定义处理程序 // 调用自定义处理程序
const key = event.key.toLowerCase(); const key = event.key.toLowerCase();
if ( if (this.customHandlers[key] && typeof this.customHandlers[key].onKeyUp === "function") {
this.customHandlers[key] &&
typeof this.customHandlers[key].onKeyUp === "function"
) {
this.customHandlers[key].onKeyUp(event); this.customHandlers[key].onKeyUp(event);
} }
} }
@@ -281,15 +274,11 @@ export class KeyboardManager {
if (touches.length === 2) { if (touches.length === 2) {
// 双指触摸 - 可用于缩放或调整画笔大小 // 双指触摸 - 可用于缩放或调整画笔大小
this.touchState.isTwoFingerTouch = true; this.touchState.isTwoFingerTouch = true;
this.touchState.pinchStartDistance = this.getDistanceBetweenTouches( this.touchState.pinchStartDistance = this.getDistanceBetweenTouches(touches[0], touches[1]);
touches[0],
touches[1]
);
// 如果有画笔管理器,记录起始画笔大小 // 如果有画笔管理器,记录起始画笔大小
if (this.toolManager && this.toolManager.brushManager) { if (this.toolManager && this.toolManager.brushManager) {
this.touchState.pinchStartBrushSize = this.touchState.pinchStartBrushSize = this.toolManager.brushManager.brushSize.value;
this.toolManager.brushManager.brushSize.value;
} }
} else if (touches.length === 3) { } else if (touches.length === 3) {
// 三指触摸 - 可用于撤销/重做 // 三指触摸 - 可用于撤销/重做
@@ -311,10 +300,7 @@ export class KeyboardManager {
// 双指缩放处理 - 调整画笔大小 // 双指缩放处理 - 调整画笔大小
if (touches.length === 2 && this.touchState.isTwoFingerTouch) { if (touches.length === 2 && this.touchState.isTwoFingerTouch) {
const currentDistance = this.getDistanceBetweenTouches( const currentDistance = this.getDistanceBetweenTouches(touches[0], touches[1]);
touches[0],
touches[1]
);
const scale = currentDistance / this.touchState.pinchStartDistance; const scale = currentDistance / this.touchState.pinchStartDistance;
// 调整画笔大小 // 调整画笔大小
@@ -587,10 +573,7 @@ export class KeyboardManager {
let shortcutKey = ""; let shortcutKey = "";
// 统一处理Mac和PC的修饰键 // 统一处理Mac和PC的修饰键
if ( if ((this.platform === "mac" && event.metaKey) || (this.platform !== "mac" && event.ctrlKey)) {
(this.platform === "mac" && event.metaKey) ||
(this.platform !== "mac" && event.ctrlKey)
) {
shortcutKey += `${this.modifierKeys.cmdOrCtrl}+`; shortcutKey += `${this.modifierKeys.cmdOrCtrl}+`;
} else if (event.ctrlKey) { } else if (event.ctrlKey) {
shortcutKey += "ctrl+"; shortcutKey += "ctrl+";

View File

@@ -216,9 +216,7 @@ export class EnhancedLiquifyManager {
// 计算图像大小 // 计算图像大小
const pixelCount = imageData.width * imageData.height; const pixelCount = imageData.width * imageData.height;
console.log( console.log(`液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}`);
`液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}`
);
// 默认使用CPU渲染器 // 默认使用CPU渲染器
this.activeRenderer = this.cpuRenderer; this.activeRenderer = this.cpuRenderer;
@@ -249,11 +247,7 @@ export class EnhancedLiquifyManager {
this.activeRenderer = this.webglRenderer; this.activeRenderer = this.webglRenderer;
this.renderMode = "webgl"; this.renderMode = "webgl";
} else { } else {
console.log( console.log(`液化功能: 使用CPU渲染模式${!this.isWebGLAvailable ? " (WebGL不可用)" : ""}`);
`液化功能: 使用CPU渲染模式${
!this.isWebGLAvailable ? " (WebGL不可用)" : ""
}`
);
} }
} }
@@ -318,16 +312,11 @@ export class EnhancedLiquifyManager {
this.params[param] = value; this.params[param] = value;
// 同步更新当前渲染器 - 关键修复:确保参数正确传递 // 同步更新当前渲染器 - 关键修复:确保参数正确传递
if ( if (this.activeRenderer && typeof this.activeRenderer.setParam === "function") {
this.activeRenderer &&
typeof this.activeRenderer.setParam === "function"
) {
console.log(`EnhancedLiquifyManager 设置参数: ${param}=${value}`); console.log(`EnhancedLiquifyManager 设置参数: ${param}=${value}`);
this.activeRenderer.setParam(param, value); this.activeRenderer.setParam(param, value);
} else { } else {
console.warn( console.warn(`EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪`);
`EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪`
);
} }
return true; return true;
@@ -382,25 +371,17 @@ export class EnhancedLiquifyManager {
* @param {Number} y 初始Y坐标 * @param {Number} y 初始Y坐标
*/ */
startLiquifyOperation(x, y) { startLiquifyOperation(x, y) {
if ( if (this.activeRenderer && typeof this.activeRenderer.startDeformation === "function") {
this.activeRenderer &&
typeof this.activeRenderer.startDeformation === "function"
) {
this.activeRenderer.startDeformation(x, y); this.activeRenderer.startDeformation(x, y);
} }
console.log( console.log(`开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})`);
`开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})`
);
} }
/** /**
* 结束液化操作 * 结束液化操作
*/ */
endLiquifyOperation() { endLiquifyOperation() {
if ( if (this.activeRenderer && typeof this.activeRenderer.endDeformation === "function") {
this.activeRenderer &&
typeof this.activeRenderer.endDeformation === "function"
) {
this.activeRenderer.endDeformation(); this.activeRenderer.endDeformation();
} }
console.log(`结束液化操作,渲染模式=${this.renderMode}`); console.log(`结束液化操作,渲染模式=${this.renderMode}`);
@@ -443,9 +424,7 @@ export class EnhancedLiquifyManager {
// 坐标边界检查 // 坐标边界检查
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) { if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) {
console.warn( console.warn(`液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}`);
`液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}`
);
return null; return null;
} }
@@ -510,9 +489,7 @@ export class EnhancedLiquifyManager {
console.log( console.log(
`液化性能数据: 模式=${this.renderMode}, 平均耗时=${avgTime.toFixed( `液化性能数据: 模式=${this.renderMode}, 平均耗时=${avgTime.toFixed(
2 2
)}ms, 图像尺寸=${this.originalImageData?.width}x${ )}ms, 图像尺寸=${this.originalImageData?.width}x${this.originalImageData?.height}`
this.originalImageData?.height
}`
); );
} }
@@ -604,8 +581,7 @@ export class EnhancedLiquifyManager {
const singleObject = objectsToCheck.length === 1; const singleObject = objectsToCheck.length === 1;
const isImage = const isImage =
singleObject && singleObject &&
(objectsToCheck[0].type === "image" || (objectsToCheck[0].type === "image" || objectsToCheck[0].type === "rasterized-layer");
objectsToCheck[0].type === "rasterized-layer");
// 检查是否为组 // 检查是否为组
const isGroup = const isGroup =
@@ -655,19 +631,14 @@ export class EnhancedLiquifyManager {
tempCanvas.height = fabricObject.height; tempCanvas.height = fabricObject.height;
const tempCtx = tempCanvas.getContext("2d"); const tempCtx = tempCanvas.getContext("2d");
console.log( console.log(`创建临时Canvas尺寸: ${tempCanvas.width}x${tempCanvas.height}`);
`创建临时Canvas尺寸: ${tempCanvas.width}x${tempCanvas.height}`
);
// 处理不同的图像源 // 处理不同的图像源
if (fabricObject._element) { if (fabricObject._element) {
console.log("使用 _element 绘制图像"); console.log("使用 _element 绘制图像");
// 检查_element是否有效 // 检查_element是否有效
if ( if (!fabricObject._element.complete && fabricObject._element.tagName === "IMG") {
!fabricObject._element.complete &&
fabricObject._element.tagName === "IMG"
) {
console.log("图像未加载完成,等待加载..."); console.log("图像未加载完成,等待加载...");
fabricObject._element.onload = () => { fabricObject._element.onload = () => {
try { try {
@@ -678,12 +649,7 @@ export class EnhancedLiquifyManager {
fabricObject.width, fabricObject.width,
fabricObject.height fabricObject.height
); );
const imageData = tempCtx.getImageData( const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log("✅ 图像加载完成后获取数据成功"); console.log("✅ 图像加载完成后获取数据成功");
resolve(imageData); resolve(imageData);
} catch (error) { } catch (error) {
@@ -698,17 +664,8 @@ export class EnhancedLiquifyManager {
} }
// 直接绘制已加载的图像 // 直接绘制已加载的图像
tempCtx.drawImage( tempCtx.drawImage(fabricObject._element, 0, 0, fabricObject.width, fabricObject.height);
fabricObject._element, } else if (fabricObject.getSrc && typeof fabricObject.getSrc === "function") {
0,
0,
fabricObject.width,
fabricObject.height
);
} else if (
fabricObject.getSrc &&
typeof fabricObject.getSrc === "function"
) {
console.log("使用 getSrc() 方法获取图像源"); console.log("使用 getSrc() 方法获取图像源");
// 通过URL创建图像 // 通过URL创建图像
@@ -717,24 +674,11 @@ export class EnhancedLiquifyManager {
img.onload = () => { img.onload = () => {
try { try {
console.log( console.log(`图像加载成功,原始尺寸: ${img.naturalWidth}x${img.naturalHeight}`);
`图像加载成功,原始尺寸: ${img.naturalWidth}x${img.naturalHeight}`
);
tempCtx.drawImage( tempCtx.drawImage(img, 0, 0, fabricObject.width, fabricObject.height);
img,
0,
0,
fabricObject.width,
fabricObject.height
);
const imageData = tempCtx.getImageData( const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log("✅ 通过URL获取图像数据成功"); console.log("✅ 通过URL获取图像数据成功");
resolve(imageData); resolve(imageData);
@@ -762,20 +706,9 @@ export class EnhancedLiquifyManager {
img.onload = () => { img.onload = () => {
try { try {
tempCtx.drawImage( tempCtx.drawImage(img, 0, 0, fabricObject.width, fabricObject.height);
img,
0,
0,
fabricObject.width,
fabricObject.height
);
const imageData = tempCtx.getImageData( const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log("✅ 通过src属性获取图像数据成功"); console.log("✅ 通过src属性获取图像数据成功");
resolve(imageData); resolve(imageData);
@@ -795,20 +728,13 @@ export class EnhancedLiquifyManager {
return; return;
} else { } else {
console.error("无法找到有效的图像源"); console.error("无法找到有效的图像源");
reject( reject(new Error("图像对象缺少有效的图像源_element, getSrc, 或 src"));
new Error("图像对象缺少有效的图像源_element, getSrc, 或 src")
);
return; return;
} }
// 如果走到这里说明使用了_element直接绘制 // 如果走到这里说明使用了_element直接绘制
try { try {
const imageData = tempCtx.getImageData( const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log( console.log(
`✅ 获取图像数据成功: 对象尺寸=${fabricObject.width}x${fabricObject.height}, ` + `✅ 获取图像数据成功: 对象尺寸=${fabricObject.width}x${fabricObject.height}, ` +

View File

@@ -178,10 +178,7 @@ export class HybridLiquifyManager {
const timeFactor = Math.min(this.pressDuration / 1000, 5.0); const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
const baseRotationSpeed = 0.015; const baseRotationSpeed = 0.015;
const rotationAngle = const rotationAngle =
(clockwise ? 1 : -1) * (clockwise ? 1 : -1) * baseRotationSpeed * strength * (1.0 + timeFactor * 0.3);
baseRotationSpeed *
strength *
(1.0 + timeFactor * 0.3);
this.accumulatedRotation += rotationAngle; this.accumulatedRotation += rotationAngle;
@@ -209,13 +206,7 @@ export class HybridLiquifyManager {
const sourceY = centerY + Math.sin(newAngle) * distance; const sourceY = centerY + Math.sin(newAngle) * distance;
// 双线性插值采样 // 双线性插值采样
const color = this._bilinearSample( const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
srcData,
width,
height,
sourceX,
sourceY
);
if (color) { if (color) {
const targetIdx = (y * width + x) * 4; const targetIdx = (y * width + x) * 4;
@@ -232,16 +223,7 @@ export class HybridLiquifyManager {
/** /**
* 像素级水晶效果 * 像素级水晶效果
*/ */
_applyPixelCrystal( _applyPixelCrystal(srcData, dstData, width, height, centerX, centerY, radius, strength) {
srcData,
dstData,
width,
height,
centerX,
centerY,
radius,
strength
) {
const timeFactor = Math.min(this.pressDuration / 1000, 3.0); const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
const distortionStrength = strength * (1.0 + timeFactor * 0.5); const distortionStrength = strength * (1.0 + timeFactor * 0.5);
@@ -266,25 +248,16 @@ export class HybridLiquifyManager {
// 多层波浪扭曲 // 多层波浪扭曲
const wave1 = Math.sin(angle * 8 + this.pressDuration * 0.005) * 0.6; const wave1 = Math.sin(angle * 8 + this.pressDuration * 0.005) * 0.6;
const wave2 = Math.cos(angle * 12 + this.pressDuration * 0.003) * 0.4; const wave2 = Math.cos(angle * 12 + this.pressDuration * 0.003) * 0.4;
const waveAngle = const waveAngle = angle + (wave1 + wave2) * distortionStrength * falloff;
angle + (wave1 + wave2) * distortionStrength * falloff;
const radialMod = const radialMod =
1 + 1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) *
0.3;
const modDistance = distance * radialMod; const modDistance = distance * radialMod;
const sourceX = centerX + Math.cos(waveAngle) * modDistance; const sourceX = centerX + Math.cos(waveAngle) * modDistance;
const sourceY = centerY + Math.sin(waveAngle) * modDistance; const sourceY = centerY + Math.sin(waveAngle) * modDistance;
const color = this._bilinearSample( const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
srcData,
width,
height,
sourceX,
sourceY
);
if (color) { if (color) {
const targetIdx = (y * width + x) * 4; const targetIdx = (y * width + x) * 4;
@@ -311,16 +284,7 @@ export class HybridLiquifyManager {
/** /**
* 像素级边缘效果 * 像素级边缘效果
*/ */
_applyPixelEdge( _applyPixelEdge(srcData, dstData, width, height, centerX, centerY, radius, strength) {
srcData,
dstData,
width,
height,
centerX,
centerY,
radius,
strength
) {
const timeFactor = Math.min(this.pressDuration / 1000, 2.5); const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
const edgeStrength = strength * (1.0 + timeFactor * 0.4); const edgeStrength = strength * (1.0 + timeFactor * 0.4);
@@ -354,13 +318,7 @@ export class HybridLiquifyManager {
const sourceX = x + offsetX; const sourceX = x + offsetX;
const sourceY = y + offsetY; const sourceY = y + offsetY;
const color = this._bilinearSample( const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
srcData,
width,
height,
sourceX,
sourceY
);
if (color) { if (color) {
const targetIdx = (y * width + x) * 4; const targetIdx = (y * width + x) * 4;

View File

@@ -79,12 +79,7 @@ export class LiquifyCPUManager {
this.canvas.width = imageSource.width; this.canvas.width = imageSource.width;
this.canvas.height = imageSource.height; this.canvas.height = imageSource.height;
this.ctx.drawImage(imageSource, 0, 0); this.ctx.drawImage(imageSource, 0, 0);
this.originalImageData = this.ctx.getImageData( this.originalImageData = this.ctx.getImageData(0, 0, imageSource.width, imageSource.height);
0,
0,
imageSource.width,
imageSource.height
);
} else { } else {
throw new Error("不支持的图像类型"); throw new Error("不支持的图像类型");
} }
@@ -95,10 +90,7 @@ export class LiquifyCPUManager {
this.originalImageData.height this.originalImageData.height
); );
this._initMesh( this._initMesh(this.originalImageData.width, this.originalImageData.height);
this.originalImageData.width,
this.originalImageData.height
);
this.initialized = true; this.initialized = true;
return true; return true;
} catch (error) { } catch (error) {
@@ -268,13 +260,7 @@ export class LiquifyCPUManager {
* @param {number} strength 强度 * @param {number} strength 强度
* @param {boolean} isClockwise 是否顺时针旋转 * @param {boolean} isClockwise 是否顺时针旋转
*/ */
_applyEnhancedRotationDeformation( _applyEnhancedRotationDeformation(centerX, centerY, radius, strength, isClockwise) {
centerX,
centerY,
radius,
strength,
isClockwise
) {
if (!this.currentImageData) return; if (!this.currentImageData) return;
const data = this.currentImageData.data; const data = this.currentImageData.data;
@@ -287,11 +273,7 @@ export class LiquifyCPUManager {
const timeFactor = Math.min(this.pressDuration / 1000, 5.0); const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
const baseRotationSpeed = 0.02; // 使用与测试文件相同的速度 const baseRotationSpeed = 0.02; // 使用与测试文件相同的速度
const rotationAngle = const rotationAngle =
(isClockwise ? 1 : -1) * (isClockwise ? 1 : -1) * baseRotationSpeed * pressure * power * (1.0 + timeFactor * 0.5);
baseRotationSpeed *
pressure *
power *
(1.0 + timeFactor * 0.5);
// 累积旋转角度 - 关键:这确保了持续旋转效果 // 累积旋转角度 - 关键:这确保了持续旋转效果
this.accumulatedRotation += rotationAngle; this.accumulatedRotation += rotationAngle;
@@ -322,13 +304,7 @@ export class LiquifyCPUManager {
const sourceY = centerY + Math.sin(newAngle) * distance; const sourceY = centerY + Math.sin(newAngle) * distance;
// 双线性插值采样 - 确保像素连续性 // 双线性插值采样 - 确保像素连续性
const color = this._bilinearSample( const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
tempData,
width,
height,
sourceX,
sourceY
);
if (color) { if (color) {
const targetIdx = (y * width + x) * 4; const targetIdx = (y * width + x) * 4;
@@ -389,13 +365,7 @@ export class LiquifyCPUManager {
const sourceY = centerY + dy * scale; const sourceY = centerY + dy * scale;
// 双线性插值采样 // 双线性插值采样
const color = this._bilinearSample( const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
tempData,
width,
height,
sourceX,
sourceY
);
if (color) { if (color) {
const targetIdx = (y * width + x) * 4; const targetIdx = (y * width + x) * 4;
@@ -461,13 +431,7 @@ export class LiquifyCPUManager {
const sourceX = x - pushX; const sourceX = x - pushX;
const sourceY = y - pushY; const sourceY = y - pushY;
const color = this._bilinearSample( const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
tempData,
width,
height,
sourceX,
sourceY
);
if (color) { if (color) {
const targetIdx = (y * width + x) * 4; const targetIdx = (y * width + x) * 4;
@@ -504,13 +468,7 @@ export class LiquifyCPUManager {
const sourceX = x - offsetX; const sourceX = x - offsetX;
const sourceY = y - offsetY; const sourceY = y - offsetY;
const color = this._bilinearSample( const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
tempData,
width,
height,
sourceX,
sourceY
);
if (color) { if (color) {
const targetIdx = (y * width + x) * 4; const targetIdx = (y * width + x) * 4;
@@ -561,7 +519,7 @@ export class LiquifyCPUManager {
this._applyEnhancedPushDeformation(x, y, radius, strength); this._applyEnhancedPushDeformation(x, y, radius, strength);
break; break;
default: default: {
// 对于其他模式,使用原有的网格算法 // 对于其他模式,使用原有的网格算法
if (!this.mesh) return; if (!this.mesh) return;
@@ -569,20 +527,14 @@ export class LiquifyCPUManager {
const timeFactor = Math.min(this.pressDuration / 1000, 4.0); const timeFactor = Math.min(this.pressDuration / 1000, 4.0);
const finalStrength = baseStrength * (1.0 + timeFactor * 0.5); const finalStrength = baseStrength * (1.0 + timeFactor * 0.5);
this._applyDeformation( this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
x,
y,
radius,
finalStrength,
mode,
this.params.distortion
);
if (this.config.smoothingIterations > 0) { if (this.config.smoothingIterations > 0) {
this._lightSmoothing(); this._lightSmoothing();
} }
return this._applyMeshToImage(); return this._applyMeshToImage();
}
} }
// 对于像素算法,直接返回当前图像数据 // 对于像素算法,直接返回当前图像数据
@@ -592,96 +544,96 @@ export class LiquifyCPUManager {
/** /**
* 应用液化变形 - 主要入口,集成增强算法 * 应用液化变形 - 主要入口,集成增强算法
*/ */
applyDeformation(x, y) { // applyDeformation(x, y) {
if (!this.initialized || !this.originalImageData) { // if (!this.initialized || !this.originalImageData) {
console.warn("液化管理器未初始化或缺少必要数据"); // console.warn("液化管理器未初始化或缺少必要数据");
return this.currentImageData; // return this.currentImageData;
} // }
// 更新鼠标位置 // // 更新鼠标位置
this.currentMouseX = x; // this.currentMouseX = x;
this.currentMouseY = y; // this.currentMouseY = y;
// 计算拖拽参数 // // 计算拖拽参数
const deltaX = this.currentMouseX - this.initialMouseX; // const deltaX = this.currentMouseX - this.initialMouseX;
const deltaY = this.currentMouseY - this.initialMouseY; // const deltaY = this.currentMouseY - this.initialMouseY;
this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
this.dragAngle = Math.atan2(deltaY, deltaX); // this.dragAngle = Math.atan2(deltaY, deltaX);
// 获取当前参数 // // 获取当前参数
const { size, pressure, power } = this.params; // const { size, pressure, power } = this.params;
const mode = this.currentMode; // const mode = this.currentMode;
const radius = size; // const radius = size;
const strength = pressure * power; // const strength = pressure * power;
// 根据模式选择算法 // // 根据模式选择算法
const pixelModes = [ // const pixelModes = [
this.modes.CLOCKWISE, // this.modes.CLOCKWISE,
this.modes.COUNTERCLOCKWISE, // this.modes.COUNTERCLOCKWISE,
this.modes.PINCH, // this.modes.PINCH,
this.modes.EXPAND, // this.modes.EXPAND,
this.modes.PUSH, // this.modes.PUSH,
]; // ];
if (pixelModes.includes(mode)) { // if (pixelModes.includes(mode)) {
// 使用增强的像素算法 // // 使用增强的像素算法
switch (mode) { // switch (mode) {
case this.modes.CLOCKWISE: // case this.modes.CLOCKWISE:
this._applyEnhancedRotationDeformation(x, y, radius, strength, false); // this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
break; // break;
case this.modes.COUNTERCLOCKWISE: // case this.modes.COUNTERCLOCKWISE:
this._applyEnhancedRotationDeformation(x, y, radius, strength, true); // this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
break; // break;
case this.modes.PINCH: // case this.modes.PINCH:
this._applyEnhancedPinchDeformation(x, y, radius, strength, true); // this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
break; // break;
case this.modes.EXPAND: // case this.modes.EXPAND:
this._applyEnhancedPinchDeformation(x, y, radius, strength, false); // this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
break; // break;
case this.modes.PUSH: // case this.modes.PUSH:
this._applyEnhancedPushDeformation(x, y, radius, strength); // this._applyEnhancedPushDeformation(x, y, radius, strength);
break; // break;
} // }
// 更新最后应用时间 // // 更新最后应用时间
this.lastApplyTime = Date.now(); // this.lastApplyTime = Date.now();
this.isFirstApply = false; // this.isFirstApply = false;
return this.currentImageData; // return this.currentImageData;
} else { // } else {
// 使用原有的网格算法处理其他模式 // // 使用原有的网格算法处理其他模式
if (!this.mesh) { // if (!this.mesh) {
console.warn("网格未初始化"); // console.warn("网格未初始化");
return this.currentImageData; // return this.currentImageData;
} // }
const finalStrength = (strength * this.config.maxStrength) / 100; // const finalStrength = (strength * this.config.maxStrength) / 100;
// 应用变形 // // 应用变形
this._applyDeformation( // this._applyDeformation(
x, // x,
y, // y,
radius, // radius,
finalStrength, // finalStrength,
mode, // mode,
this.params.distortion // this.params.distortion,
); // );
// 平滑处理 // // 平滑处理
if (this.config.smoothingIterations > 0) { // if (this.config.smoothingIterations > 0) {
this._smoothMesh(); // this._smoothMesh();
} // }
// 更新图像数据 // // 更新图像数据
const result = this._applyMeshToImage(); // const result = this._applyMeshToImage();
// 更新最后应用时间 // // 更新最后应用时间
this.lastApplyTime = Date.now(); // this.lastApplyTime = Date.now();
this.isFirstApply = false; // this.isFirstApply = false;
return result; // return result;
} // }
} // }
/** /**
* 双线性插值采样 - 用于像素级算法 * 双线性插值采样 - 用于像素级算法
@@ -773,22 +725,14 @@ export class LiquifyCPUManager {
const baseDistortion = Math.max(distortion, 0.3); const baseDistortion = Math.max(distortion, 0.3);
const timeFactor = Math.min(this.pressDuration / 1000, 2.0); const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
const timeEnhancedDistortion = const timeEnhancedDistortion = baseDistortion * (1.0 + timeFactor * 0.3);
baseDistortion * (1.0 + timeFactor * 0.3);
const wave1 = const wave1 = Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6;
Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6; const wave2 = Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
const wave2 = const waveAngle = crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
const waveAngle =
crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
const radialMod = const radialMod =
1 + 1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
Math.sin(
crystalRadius * Math.PI * 2 + this.pressDuration * 0.002
) *
0.3;
const modDistance = distance * radialMod; const modDistance = distance * radialMod;
const crystalX = x + Math.cos(waveAngle) * modDistance; const crystalX = x + Math.cos(waveAngle) * modDistance;
@@ -809,8 +753,7 @@ export class LiquifyCPUManager {
const baseEdgeDistortion = Math.max(distortion, 0.5); const baseEdgeDistortion = Math.max(distortion, 0.5);
const timeFactor = Math.min(this.pressDuration / 1000, 2.5); const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
const timeEnhancedDistortion = const timeEnhancedDistortion = baseEdgeDistortion * (1.0 + timeFactor * 0.4);
baseEdgeDistortion * (1.0 + timeFactor * 0.4);
const edgeWave = const edgeWave =
Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) * Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) *
@@ -887,11 +830,7 @@ export class LiquifyCPUManager {
const points = this.mesh.deformedPoints; const points = this.mesh.deformedPoints;
const tempPoints = points.map((p) => ({ x: p.x, y: p.y })); const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
for ( for (let iteration = 0; iteration < this.config.smoothingIterations; iteration++) {
let iteration = 0;
iteration < this.config.smoothingIterations;
iteration++
) {
for (let y = 1; y < rows; y++) { for (let y = 1; y < rows; y++) {
for (let x = 1; x < cols; x++) { for (let x = 1; x < cols; x++) {
const idx = y * (cols + 1) + x; const idx = y * (cols + 1) + x;
@@ -996,19 +935,8 @@ export class LiquifyCPUManager {
for (let x = 0; x < width; x += step) { for (let x = 0; x < width; x += step) {
const srcPos = this._mapPointBack(x, y); const srcPos = this._mapPointBack(x, y);
if ( if (srcPos.x >= 0 && srcPos.x < width && srcPos.y >= 0 && srcPos.y < height) {
srcPos.x >= 0 && const color = this._bilinearInterpolate(srcData, width, height, srcPos.x, srcPos.y);
srcPos.x < width &&
srcPos.y >= 0 &&
srcPos.y < height
) {
const color = this._bilinearInterpolate(
srcData,
width,
height,
srcPos.x,
srcPos.y
);
// 如果使用步长采样,需要填充相邻像素 // 如果使用步长采样,需要填充相邻像素
for (let dy = 0; dy < step && y + dy < height; dy++) { for (let dy = 0; dy < step && y + dy < height; dy++) {
@@ -1070,22 +998,14 @@ export class LiquifyCPUManager {
// 根据推拉模式和拖拽距离动态调整强度 // 根据推拉模式和拖拽距离动态调整强度
let strength; let strength;
if (mode === this.modes.PUSH) { if (mode === this.modes.PUSH) {
const baseStrength = const baseStrength = (pressure * power * this.config.maxStrength) / 100;
(pressure * power * this.config.maxStrength) / 100;
const distanceFactor = Math.min(this.dragDistance / radius, 2.0); const distanceFactor = Math.min(this.dragDistance / radius, 2.0);
strength = baseStrength * distanceFactor * 0.3; // 批量处理时降低强度 strength = baseStrength * distanceFactor * 0.3; // 批量处理时降低强度
} else { } else {
strength = (pressure * power * this.config.maxStrength) / 100; strength = (pressure * power * this.config.maxStrength) / 100;
} }
this._applyDeformation( this._applyDeformation(pos.x, pos.y, radius, strength, mode, distortion);
pos.x,
pos.y,
radius,
strength,
mode,
distortion
);
}); });
// 结束拖拽操作 // 结束拖拽操作
@@ -1331,14 +1251,7 @@ export class LiquifyCPUManager {
const finalStrength = (strength * this.config.maxStrength) / 100; const finalStrength = (strength * this.config.maxStrength) / 100;
// 应用变形 // 应用变形
this._applyDeformation( this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
x,
y,
radius,
finalStrength,
mode,
this.params.distortion
);
// 平滑处理 // 平滑处理
if (this.config.smoothingIterations > 0) { if (this.config.smoothingIterations > 0) {

View File

@@ -130,32 +130,18 @@ export class LiquifyManager {
return null; return null;
} }
console.log( console.log(`LiquifyManager.applyLiquify: 模式=${mode}, 坐标=(${x}, ${y}), 参数=`, params);
`LiquifyManager.applyLiquify: 模式=${mode}, 坐标=(${x}, ${y}), 参数=`,
params
);
try { try {
// 直接调用EnhancedLiquifyManager的applyLiquify方法 // 直接调用EnhancedLiquifyManager的applyLiquify方法
// 避免重复设置参数让EnhancedLiquifyManager处理参数设置 // 避免重复设置参数让EnhancedLiquifyManager处理参数设置
const resultData = await this.enhancedManager.applyLiquify( const resultData = await this.enhancedManager.applyLiquify(targetObject, mode, params, x, y);
targetObject,
mode,
params,
x,
y
);
// 确保返回结果数据 // 确保返回结果数据
if (!resultData) { if (!resultData) {
console.warn("液化变形没有返回结果数据"); console.warn("液化变形没有返回结果数据");
} else { } else {
console.log( console.log("✅ 液化变形成功,返回图像数据尺寸:", resultData.width, "x", resultData.height);
"✅ 液化变形成功,返回图像数据尺寸:",
resultData.width,
"x",
resultData.height
);
} }
return resultData; return resultData;

View File

@@ -159,9 +159,7 @@ export class LiquifyRealTimeUpdater {
// }); // });
// } // }
} else { } else {
console.warn( console.warn("=================快速更新液化效果时,图像数据未变化,跳过更新");
"=================快速更新液化效果时,图像数据未变化,跳过更新"
);
} }
} catch (error) { } catch (error) {
console.error("快速更新液化效果失败:", error); console.error("快速更新液化效果失败:", error);
@@ -184,6 +182,8 @@ export class LiquifyRealTimeUpdater {
*/ */
async _fullUpdate(imageData) { async _fullUpdate(imageData) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 临时禁用画布自动渲染
const oldRenderOnAddRemove = this.canvas.renderOnAddRemove;
try { try {
// 使用高质量canvas进行最终渲染 // 使用高质量canvas进行最终渲染
this.highQualityCtx.putImageData(imageData, 0, 0); this.highQualityCtx.putImageData(imageData, 0, 0);
@@ -233,9 +233,6 @@ export class LiquifyRealTimeUpdater {
selected: false, selected: false,
evented: originalObj.evented, evented: originalObj.evented,
}); });
// 临时禁用画布自动渲染
const oldRenderOnAddRemove = this.canvas.renderOnAddRemove;
this.canvas.renderOnAddRemove = false; this.canvas.renderOnAddRemove = false;
// 智能查找和替换canvas上的对象 // 智能查找和替换canvas上的对象
@@ -244,9 +241,7 @@ export class LiquifyRealTimeUpdater {
// 如果直接查找失败尝试通过ID查找 // 如果直接查找失败尝试通过ID查找
if (targetIndex === -1 && originalObjId) { if (targetIndex === -1 && originalObjId) {
targetIndex = allObjects.findIndex( targetIndex = allObjects.findIndex((obj) => obj.id === originalObjId);
(obj) => obj.id === originalObjId
);
if (targetIndex !== -1) { if (targetIndex !== -1) {
console.log(`通过ID找到目标对象: ${originalObjId}`); console.log(`通过ID找到目标对象: ${originalObjId}`);
// 更新目标对象引用 // 更新目标对象引用
@@ -256,9 +251,7 @@ export class LiquifyRealTimeUpdater {
// 如果通过ID查找仍然失败尝试通过图层ID查找 // 如果通过ID查找仍然失败尝试通过图层ID查找
if (targetIndex === -1 && originalObjLayerId) { if (targetIndex === -1 && originalObjLayerId) {
targetIndex = allObjects.findIndex( targetIndex = allObjects.findIndex((obj) => obj.layerId === originalObjLayerId);
(obj) => obj.layerId === originalObjLayerId
);
if (targetIndex !== -1) { if (targetIndex !== -1) {
console.log(`通过图层ID找到目标对象: ${originalObjLayerId}`); console.log(`通过图层ID找到目标对象: ${originalObjLayerId}`);
// 更新目标对象引用 // 更新目标对象引用
@@ -284,9 +277,7 @@ export class LiquifyRealTimeUpdater {
resolve(newImg); resolve(newImg);
} else { } else {
// 如果在画布中找不到对象,可能对象已被移除或引用已更新 // 如果在画布中找不到对象,可能对象已被移除或引用已更新
console.warn( console.warn("在画布中找不到目标对象,可能已被其他操作移除或替换");
"在画布中找不到目标对象,可能已被其他操作移除或替换"
);
// 恢复自动渲染设置 // 恢复自动渲染设置
this.canvas.renderOnAddRemove = oldRenderOnAddRemove; this.canvas.renderOnAddRemove = oldRenderOnAddRemove;
@@ -333,24 +324,6 @@ export class LiquifyRealTimeUpdater {
} }
} }
/**
* 清理资源
*/
dispose() {
this.targetObject = null;
this.cachedDataURL = null;
this.pendingImageData = null;
this.updateQueue.length = 0;
// 清理临时canvas
if (this.tempCanvas) {
this.tempCanvas.width = 0;
this.tempCanvas.height = 0;
this.tempCanvas = null;
this.tempCtx = null;
}
}
/** /**
* 获取当前目标对象 * 获取当前目标对象
* @returns {Object} 当前的fabric对象 * @returns {Object} 当前的fabric对象
@@ -388,13 +361,6 @@ export class LiquifyRealTimeUpdater {
console.log("✅ 恢复正常渲染模式"); console.log("✅ 恢复正常渲染模式");
} }
/**
* 获取当前目标对象
*/
getTargetObject() {
return this.targetObject;
}
/** /**
* 设置图像质量 * 设置图像质量
* @param {Number} quality 质量值 (0.1-1.0) * @param {Number} quality 质量值 (0.1-1.0)
@@ -416,6 +382,24 @@ export class LiquifyRealTimeUpdater {
} }
} }
// /**
// * 清理资源
// */
// dispose() {
// this.targetObject = null;
// this.cachedDataURL = null;
// this.pendingImageData = null;
// this.updateQueue.length = 0;
// // 清理临时canvas
// if (this.tempCanvas) {
// this.tempCanvas.width = 0;
// this.tempCanvas.height = 0;
// this.tempCanvas = null;
// this.tempCtx = null;
// }
// }
/** /**
* 清理资源 * 清理资源
*/ */

View File

@@ -30,10 +30,7 @@ export class LiquifyStateManager {
// 设备性能检测 // 设备性能检测
this.devicePerformance = this._detectDevicePerformance(); this.devicePerformance = this._detectDevicePerformance();
console.log( console.log("🎯 液化状态管理器已初始化,设备性能等级:", this.devicePerformance);
"🎯 液化状态管理器已初始化,设备性能等级:",
this.devicePerformance
);
} }
/** /**
@@ -129,17 +126,10 @@ export class LiquifyStateManager {
this.performanceMetrics.totalOperations++; this.performanceMetrics.totalOperations++;
this.performanceMetrics.totalTime += operationTime; this.performanceMetrics.totalTime += operationTime;
this.performanceMetrics.averageTime = this.performanceMetrics.averageTime =
this.performanceMetrics.totalTime / this.performanceMetrics.totalTime / this.performanceMetrics.totalOperations;
this.performanceMetrics.totalOperations;
this.performanceMetrics.maxTime = Math.max( this.performanceMetrics.maxTime = Math.max(this.performanceMetrics.maxTime, operationTime);
this.performanceMetrics.maxTime, this.performanceMetrics.minTime = Math.min(this.performanceMetrics.minTime, operationTime);
operationTime
);
this.performanceMetrics.minTime = Math.min(
this.performanceMetrics.minTime,
operationTime
);
this.performanceMetrics.lastOperationTime = operationTime; this.performanceMetrics.lastOperationTime = operationTime;
} }
@@ -148,15 +138,8 @@ export class LiquifyStateManager {
* @param {Object} metrics 性能指标对象 * @param {Object} metrics 性能指标对象
*/ */
recordOperationMetrics(metrics) { recordOperationMetrics(metrics) {
const { const { operationTime, operationType, mode, coordinates, imageSize, renderMode, isRealTime } =
operationTime, metrics;
operationType,
mode,
coordinates,
imageSize,
renderMode,
isRealTime,
} = metrics;
// 记录基础性能数据 // 记录基础性能数据
this.recordDeformation(operationTime); this.recordDeformation(operationTime);
@@ -195,9 +178,7 @@ export class LiquifyStateManager {
// 降低图像质量 // 降低图像质量
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0; const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
if (currentQuality > 0.7) { if (currentQuality > 0.7) {
this.realtimeUpdater.setImageQuality( this.realtimeUpdater.setImageQuality(Math.max(0.7, currentQuality - 0.1));
Math.max(0.7, currentQuality - 0.1)
);
console.log("⚡ 自动降低图像质量以提升性能"); console.log("⚡ 自动降低图像质量以提升性能");
} }
@@ -215,9 +196,7 @@ export class LiquifyStateManager {
if (operationTime < 20 && this.devicePerformance === "high") { if (operationTime < 20 && this.devicePerformance === "high") {
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0; const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
if (currentQuality < 1.0) { if (currentQuality < 1.0) {
this.realtimeUpdater.setImageQuality( this.realtimeUpdater.setImageQuality(Math.min(1.0, currentQuality + 0.05));
Math.min(1.0, currentQuality + 0.05)
);
} }
} }
} }
@@ -375,8 +354,7 @@ export class LiquifyStateManager {
// 恢复原始设置 // 恢复原始设置
this.canvas.renderOnAddRemove = this._originalSettings.renderOnAddRemove; this.canvas.renderOnAddRemove = this._originalSettings.renderOnAddRemove;
this.canvas.skipOffscreen = this._originalSettings.skipOffscreen; this.canvas.skipOffscreen = this._originalSettings.skipOffscreen;
this.canvas.enableRetinaScaling = this.canvas.enableRetinaScaling = this._originalSettings.enableRetinaScaling;
this._originalSettings.enableRetinaScaling;
this._originalSettings = null; this._originalSettings = null;
} }
@@ -389,14 +367,8 @@ export class LiquifyStateManager {
this.performanceMetrics.averageTime = this.performanceMetrics.averageTime =
this.performanceMetrics.totalTime / (this.operationCount + 1); this.performanceMetrics.totalTime / (this.operationCount + 1);
this.performanceMetrics.maxTime = Math.max( this.performanceMetrics.maxTime = Math.max(this.performanceMetrics.maxTime, operationTime);
this.performanceMetrics.maxTime, this.performanceMetrics.minTime = Math.min(this.performanceMetrics.minTime, operationTime);
operationTime
);
this.performanceMetrics.minTime = Math.min(
this.performanceMetrics.minTime,
operationTime
);
} }
/** /**

View File

@@ -61,8 +61,7 @@ export class LiquifyWebGLManager {
static isSupported() { static isSupported() {
try { try {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const gl = const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
return !!gl; return !!gl;
} catch (e) { } catch (e) {
return false; return false;
@@ -76,9 +75,7 @@ export class LiquifyWebGLManager {
try { try {
// 创建WebGL画布 // 创建WebGL画布
this.canvas = document.createElement("canvas"); this.canvas = document.createElement("canvas");
this.gl = this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");
this.canvas.getContext("webgl") ||
this.canvas.getContext("experimental-webgl");
if (!this.gl) { if (!this.gl) {
throw new Error("WebGL不可用"); throw new Error("WebGL不可用");
@@ -200,10 +197,7 @@ export class LiquifyWebGLManager {
} }
`; `;
this.program = this._createProgram( this.program = this._createProgram(vertexShaderSource, fragmentShaderSource);
vertexShaderSource,
fragmentShaderSource
);
} }
/** /**
@@ -228,10 +222,7 @@ export class LiquifyWebGLManager {
_createProgram(vertexSource, fragmentSource) { _createProgram(vertexSource, fragmentSource) {
const gl = this.gl; const gl = this.gl;
const vertexShader = this._createShader(gl.VERTEX_SHADER, vertexSource); const vertexShader = this._createShader(gl.VERTEX_SHADER, vertexSource);
const fragmentShader = this._createShader( const fragmentShader = this._createShader(gl.FRAGMENT_SHADER, fragmentSource);
gl.FRAGMENT_SHADER,
fragmentSource
);
const program = gl.createProgram(); const program = gl.createProgram();
gl.attachShader(program, vertexShader); gl.attachShader(program, vertexShader);
@@ -267,9 +258,7 @@ export class LiquifyWebGLManager {
*/ */
_createMeshBuffer() { _createMeshBuffer() {
const gl = this.gl; const gl = this.gl;
const vertices = new Float32Array([ const vertices = new Float32Array([-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1]);
-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1,
]);
this.meshBuffer = gl.createBuffer(); this.meshBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer);
@@ -427,12 +416,8 @@ export class LiquifyWebGLManager {
} }
// 计算拖拽向量(用于推拉模式) // 计算拖拽向量(用于推拉模式)
const dragX = this.isDragging const dragX = this.isDragging ? (x - this.initialMouseX) / this.canvas.width : 0;
? (x - this.initialMouseX) / this.canvas.width const dragY = this.isDragging ? -(y - this.initialMouseY) / this.canvas.height : 0;
: 0;
const dragY = this.isDragging
? -(y - this.initialMouseY) / this.canvas.height
: 0;
// 获取模式索引 // 获取模式索引
const modeIndex = Object.values(this.modes).indexOf(this.currentMode); const modeIndex = Object.values(this.modes).indexOf(this.currentMode);
@@ -466,22 +451,13 @@ export class LiquifyWebGLManager {
// 读取结果并转换为ImageData // 读取结果并转换为ImageData
const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4); const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4);
gl.readPixels( gl.readPixels(0, 0, this.canvas.width, this.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
0,
0,
this.canvas.width,
this.canvas.height,
gl.RGBA,
gl.UNSIGNED_BYTE,
pixels
);
// 翻转Y轴以匹配ImageData格式 // 翻转Y轴以匹配ImageData格式
const flippedPixels = new Uint8ClampedArray(pixels.length); const flippedPixels = new Uint8ClampedArray(pixels.length);
for (let y = 0; y < this.canvas.height; y++) { for (let y = 0; y < this.canvas.height; y++) {
for (let x = 0; x < this.canvas.width; x++) { for (let x = 0; x < this.canvas.width; x++) {
const srcIndex = const srcIndex = ((this.canvas.height - 1 - y) * this.canvas.width + x) * 4;
((this.canvas.height - 1 - y) * this.canvas.width + x) * 4;
const dstIndex = (y * this.canvas.width + x) * 4; const dstIndex = (y * this.canvas.width + x) * 4;
flippedPixels[dstIndex] = pixels[srcIndex]; flippedPixels[dstIndex] = pixels[srcIndex];
flippedPixels[dstIndex + 1] = pixels[srcIndex + 1]; flippedPixels[dstIndex + 1] = pixels[srcIndex + 1];
@@ -503,16 +479,7 @@ export class LiquifyWebGLManager {
// 将原始纹理复制到当前纹理 // 将原始纹理复制到当前纹理
gl.bindTexture(gl.TEXTURE_2D, this.currentTexture); gl.bindTexture(gl.TEXTURE_2D, this.currentTexture);
gl.copyTexImage2D( gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.canvas.width, this.canvas.height, 0);
gl.TEXTURE_2D,
0,
gl.RGBA,
0,
0,
this.canvas.width,
this.canvas.height,
0
);
// 读取原始纹理数据 // 读取原始纹理数据
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
@@ -525,21 +492,9 @@ export class LiquifyWebGLManager {
); );
const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4); const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4);
gl.readPixels( gl.readPixels(0, 0, this.canvas.width, this.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
0,
0,
this.canvas.width,
this.canvas.height,
gl.RGBA,
gl.UNSIGNED_BYTE,
pixels
);
return new ImageData( return new ImageData(new Uint8ClampedArray(pixels), this.canvas.width, this.canvas.height);
new Uint8ClampedArray(pixels),
this.canvas.width,
this.canvas.height
);
} }
/** /**

View File

@@ -144,10 +144,7 @@ export class MinimapManager {
this.eventHandlers.mouseup = this.handleMinimapMouseUp; this.eventHandlers.mouseup = this.handleMinimapMouseUp;
// 移除mouseout事件处理允许拖动操作持续到鼠标释放 // 移除mouseout事件处理允许拖动操作持续到鼠标释放
this.minimapCanvas.addEventListener( this.minimapCanvas.addEventListener("mousedown", this.eventHandlers.mousedown);
"mousedown",
this.eventHandlers.mousedown
);
document.addEventListener("mousemove", this.eventHandlers.mousemove); document.addEventListener("mousemove", this.eventHandlers.mousemove);
document.addEventListener("mouseup", this.eventHandlers.mouseup); document.addEventListener("mouseup", this.eventHandlers.mouseup);
// 移除mouseout事件监听 // 移除mouseout事件监听
@@ -177,10 +174,7 @@ export class MinimapManager {
this.eventHandlers.touchend = this.handleMinimapMouseUp; this.eventHandlers.touchend = this.handleMinimapMouseUp;
this.minimapCanvas.addEventListener( this.minimapCanvas.addEventListener("touchstart", this.eventHandlers.touchstart);
"touchstart",
this.eventHandlers.touchstart
);
document.addEventListener("touchmove", this.eventHandlers.touchmove, { document.addEventListener("touchmove", this.eventHandlers.touchmove, {
passive: false, passive: false,
}); });
@@ -204,18 +198,12 @@ export class MinimapManager {
this.mainCanvas.off("object:rotating", this.handleMainCanvasChange); this.mainCanvas.off("object:rotating", this.handleMainCanvasChange);
// 移除鼠标事件监听 // 移除鼠标事件监听
this.minimapCanvas.removeEventListener( this.minimapCanvas.removeEventListener("mousedown", this.eventHandlers.mousedown);
"mousedown",
this.eventHandlers.mousedown
);
document.removeEventListener("mousemove", this.eventHandlers.mousemove); document.removeEventListener("mousemove", this.eventHandlers.mousemove);
document.removeEventListener("mouseup", this.eventHandlers.mouseup); document.removeEventListener("mouseup", this.eventHandlers.mouseup);
// 移除触摸事件监听 // 移除触摸事件监听
this.minimapCanvas.removeEventListener( this.minimapCanvas.removeEventListener("touchstart", this.eventHandlers.touchstart);
"touchstart",
this.eventHandlers.touchstart
);
document.removeEventListener("touchmove", this.eventHandlers.touchmove); document.removeEventListener("touchmove", this.eventHandlers.touchmove);
document.removeEventListener("touchend", this.eventHandlers.touchend); document.removeEventListener("touchend", this.eventHandlers.touchend);
} }
@@ -281,8 +269,7 @@ export class MinimapManager {
} }
// 添加边距 // 添加边距
const padding = const padding = Math.max(this.mainCanvas.getWidth(), this.mainCanvas.getHeight()) * 0.1;
Math.max(this.mainCanvas.getWidth(), this.mainCanvas.getHeight()) * 0.1;
this.contentBounds = { this.contentBounds = {
minX: minX - padding, minX: minX - padding,
minY: minY - padding, minY: minY - padding,
@@ -347,11 +334,7 @@ export class MinimapManager {
this.dragStart = { x, y }; this.dragStart = { x, y };
// 移动画布视口 // 移动画布视口
this.moveViewport( this.moveViewport(this.dragStartViewport.x + deltaX, this.dragStartViewport.y + deltaY, false);
this.dragStartViewport.x + deltaX,
this.dragStartViewport.y + deltaY,
false
);
// 更新拖拽起始视口位置 // 更新拖拽起始视口位置
this.dragStartViewport = this.calculateViewportRect(); this.dragStartViewport = this.calculateViewportRect();
@@ -391,9 +374,7 @@ export class MinimapManager {
viewportHeight = this.lastViewportSize.height; viewportHeight = this.lastViewportSize.height;
} else { } else {
viewportWidth = Math.round((this.mainCanvas.getWidth() / zoom) * scaleX); viewportWidth = Math.round((this.mainCanvas.getWidth() / zoom) * scaleX);
viewportHeight = Math.round( viewportHeight = Math.round((this.mainCanvas.getHeight() / zoom) * scaleY);
(this.mainCanvas.getHeight() / zoom) * scaleY
);
} }
// 添加边界限制,确保视口不会超出小地图 // 添加边界限制,确保视口不会超出小地图
@@ -476,21 +457,11 @@ export class MinimapManager {
try { try {
// 清空小地图 // 清空小地图
this.minimapCtx.clearRect( this.minimapCtx.clearRect(0, 0, this.minimapSize.width, this.minimapSize.height);
0,
0,
this.minimapSize.width,
this.minimapSize.height
);
// 绘制小地图背景 // 绘制小地图背景
this.minimapCtx.fillStyle = this.mainCanvas.backgroundColor || "#f0f0f0"; this.minimapCtx.fillStyle = this.mainCanvas.backgroundColor || "#f0f0f0";
this.minimapCtx.fillRect( this.minimapCtx.fillRect(0, 0, this.minimapSize.width, this.minimapSize.height);
0,
0,
this.minimapSize.width,
this.minimapSize.height
);
// 计算内容边界尺寸 // 计算内容边界尺寸
const contentWidth = this.contentBounds.maxX - this.contentBounds.minX; const contentWidth = this.contentBounds.maxX - this.contentBounds.minX;
@@ -654,11 +625,7 @@ export class MinimapManager {
this.removeEventListeners(); this.removeEventListeners();
// 从DOM中移除canvas // 从DOM中移除canvas
if ( if (this.container && this.minimapCanvas && this.minimapCanvas.parentNode === this.container) {
this.container &&
this.minimapCanvas &&
this.minimapCanvas.parentNode === this.container
) {
this.container.removeChild(this.minimapCanvas); this.container.removeChild(this.minimapCanvas);
} }
@@ -709,12 +676,7 @@ export class MinimapManager {
} }
const offCtx = this._offscreenCanvas.getContext("2d"); const offCtx = this._offscreenCanvas.getContext("2d");
offCtx.clearRect( offCtx.clearRect(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height);
0,
0,
this._offscreenCanvas.width,
this._offscreenCanvas.height
);
// 绘制图层内容到离屏画布 // 绘制图层内容到离屏画布
this._renderLayersToMinimap(offCtx, ratio); this._renderLayersToMinimap(offCtx, ratio);
@@ -755,8 +717,8 @@ export class MinimapManager {
typeof this.canvas.layers.value !== "undefined" typeof this.canvas.layers.value !== "undefined"
? this.canvas.layers.value ? this.canvas.layers.value
: Array.isArray(this.canvas.layers) : Array.isArray(this.canvas.layers)
? this.canvas.layers ? this.canvas.layers
: []; : [];
// 过滤出可见图层 // 过滤出可见图层
layersArray.forEach((layer) => { layersArray.forEach((layer) => {
@@ -800,10 +762,8 @@ export class MinimapManager {
const left = fabricObj.left * ratio; const left = fabricObj.left * ratio;
const top = fabricObj.top * ratio; const top = fabricObj.top * ratio;
const width = const width = (fabricObj.width || 20) * (fabricObj.scaleX || 1) * ratio;
(fabricObj.width || 20) * (fabricObj.scaleX || 1) * ratio; const height = (fabricObj.height || 20) * (fabricObj.scaleY || 1) * ratio;
const height =
(fabricObj.height || 20) * (fabricObj.scaleY || 1) * ratio;
ctx.fillRect(left, top, width, height); ctx.fillRect(left, top, width, height);
} }

View File

@@ -319,10 +319,7 @@ export class SelectionManager {
} }
// 触发选区变化回调 // 触发选区变化回调
if ( if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
this.onSelectionChanged &&
typeof this.onSelectionChanged === "function"
) {
this.onSelectionChanged(); this.onSelectionChanged();
} }
@@ -397,10 +394,7 @@ export class SelectionManager {
this.featherAmount = 0; this.featherAmount = 0;
// 触发选区变化回调 // 触发选区变化回调
if ( if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
this.onSelectionChanged &&
typeof this.onSelectionChanged === "function"
) {
this.onSelectionChanged(); this.onSelectionChanged();
} }
@@ -600,8 +594,7 @@ export class SelectionManager {
// 添加新的点,但避免添加过于密集的点 // 添加新的点,但避免添加过于密集的点
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; const lastPoint = this.drawingPoints[this.drawingPoints.length - 1];
const distance = Math.sqrt( const distance = Math.sqrt(
Math.pow(pointer.x - lastPoint.x, 2) + Math.pow(pointer.x - lastPoint.x, 2) + Math.pow(pointer.y - lastPoint.y, 2)
Math.pow(pointer.y - lastPoint.y, 2)
); );
// 只有当距离大于2像素时才添加新点避免路径过于复杂 // 只有当距离大于2像素时才添加新点避免路径过于复杂
@@ -653,8 +646,7 @@ export class SelectionManager {
const firstPoint = this.drawingPoints[0]; const firstPoint = this.drawingPoints[0];
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; const lastPoint = this.drawingPoints[this.drawingPoints.length - 1];
const closingDistance = Math.sqrt( const closingDistance = Math.sqrt(
Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.y - lastPoint.y, 2)
Math.pow(firstPoint.y - lastPoint.y, 2)
); );
// 如果首尾距离较大,自动添加闭合线段 // 如果首尾距离较大,自动添加闭合线段

View File

@@ -261,10 +261,7 @@ const actions = {
} }
// 如果有当前笔刷实例且有更新方法,则调用 // 如果有当前笔刷实例且有更新方法,则调用
if ( if (state.currentBrushInstance && state.currentBrushInstance.updateProperty) {
state.currentBrushInstance &&
state.currentBrushInstance.updateProperty
) {
state.currentBrushInstance.updateProperty(propId, value); state.currentBrushInstance.updateProperty(propId, value);
} }
}, },
@@ -277,7 +274,7 @@ const actions = {
*/ */
getPropertyValue(propId, defaultValue) { getPropertyValue(propId, defaultValue) {
// 检查属性值是否存在 // 检查属性值是否存在
if (state.propertyValues.hasOwnProperty(propId)) { if (Object.prototype.hasOwnProperty.call(state.propertyValues, propId)) {
return state.propertyValues[propId]; return state.propertyValues[propId];
} }
@@ -551,10 +548,7 @@ const actions = {
// 导入材质预设 // 导入材质预设
if (config.texturePresets && Array.isArray(config.texturePresets)) { if (config.texturePresets && Array.isArray(config.texturePresets)) {
state.texturePresets = [ state.texturePresets = [...state.texturePresets, ...config.texturePresets];
...state.texturePresets,
...config.texturePresets,
];
} }
// 导入自定义材质 // 导入自定义材质
@@ -580,9 +574,7 @@ const actions = {
state.texturePresets.forEach((preset, index) => { state.texturePresets.forEach((preset, index) => {
const texture = texturePresetManager.getTextureById(preset.textureId); const texture = texturePresetManager.getTextureById(preset.textureId);
if (!texture) { if (!texture) {
console.warn( console.warn(`材质预设 "${preset.name}" 引用的材质 ${preset.textureId} 不存在`);
`材质预设 "${preset.name}" 引用的材质 ${preset.textureId} 不存在`
);
// 可以选择使用默认材质替换或删除该预设 // 可以选择使用默认材质替换或删除该预设
if (texturePresetManager.getAllTextures().length > 0) { if (texturePresetManager.getAllTextures().length > 0) {
preset.textureId = texturePresetManager.getAllTextures()[0].id; preset.textureId = texturePresetManager.getAllTextures()[0].id;
@@ -654,9 +646,7 @@ export const BrushStore = {
* @returns {Object} {r, g, b, a} 颜色值对象 * @returns {Object} {r, g, b, a} 颜色值对象
*/ */
parseRGBAColor(rgbaColor) { parseRGBAColor(rgbaColor) {
const rgbaMatch = rgbaColor.match( const rgbaMatch = rgbaColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
);
if (rgbaMatch) { if (rgbaMatch) {
return { return {
r: parseInt(rgbaMatch[1]), r: parseInt(rgbaMatch[1]),

View File

@@ -113,23 +113,12 @@ async function testCPUManagerBasics() {
// 应用变形 // 应用变形
const result = manager.applyDeformation(50, 50); const result = manager.applyDeformation(50, 50);
if ( if (!result || result.width !== testImageData.width || result.height !== testImageData.height) {
!result ||
result.width !== testImageData.width ||
result.height !== testImageData.height
) {
throw new Error("CPU管理器变形结果无效"); throw new Error("CPU管理器变形结果无效");
} }
// 测试不同模式 // 测试不同模式
const modes = [ const modes = ["push", "clockwise", "counterclockwise", "pinch", "expand", "reconstruct"];
"push",
"clockwise",
"counterclockwise",
"pinch",
"expand",
"reconstruct",
];
for (const mode of modes) { for (const mode of modes) {
manager.setMode(mode); manager.setMode(mode);
const modeResult = manager.applyDeformation(25, 25); const modeResult = manager.applyDeformation(25, 25);
@@ -170,11 +159,7 @@ async function testWebGLManagerBasics() {
// 应用变形 // 应用变形
const result = manager.applyDeformation(50, 50); const result = manager.applyDeformation(50, 50);
if ( if (!result || result.width !== testImageData.width || result.height !== testImageData.height) {
!result ||
result.width !== testImageData.width ||
result.height !== testImageData.height
) {
throw new Error("WebGL管理器变形结果无效"); throw new Error("WebGL管理器变形结果无效");
} }
} catch (error) { } catch (error) {
@@ -300,11 +285,9 @@ async function testCoordinateConversion() {
// 转换到图像坐标 // 转换到图像坐标
let imageX = let imageX =
(localX + mockFabricObject.width / 2) * (localX + mockFabricObject.width / 2) * (testImageData.width / mockFabricObject.width);
(testImageData.width / mockFabricObject.width);
let imageY = let imageY =
(localY + mockFabricObject.height / 2) * (localY + mockFabricObject.height / 2) * (testImageData.height / mockFabricObject.height);
(testImageData.height / mockFabricObject.height);
// 处理翻转 // 处理翻转
if (mockFabricObject.flipX) { if (mockFabricObject.flipX) {
@@ -364,9 +347,7 @@ async function testPerformance() {
// 验证性能阈值每次操作不应超过50ms // 验证性能阈值每次操作不应超过50ms
if (avgTime > 50) { if (avgTime > 50) {
throw new Error( throw new Error(`性能不达标:平均操作时间 ${avgTime.toFixed(2)}ms 超过阈值 50ms`);
`性能不达标:平均操作时间 ${avgTime.toFixed(2)}ms 超过阈值 50ms`
);
} }
} }
@@ -374,9 +355,7 @@ async function testPerformance() {
* 测试内存管理 * 测试内存管理
*/ */
async function testMemoryManagement() { async function testMemoryManagement() {
const initialMemory = performance.memory const initialMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
? performance.memory.usedJSHeapSize
: 0;
// 创建多个管理器实例 // 创建多个管理器实例
const managers = []; const managers = [];
@@ -399,9 +378,7 @@ async function testMemoryManagement() {
window.gc(); window.gc();
} }
const finalMemory = performance.memory const finalMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
? performance.memory.usedJSHeapSize
: 0;
const memoryIncrease = finalMemory - initialMemory; const memoryIncrease = finalMemory - initialMemory;
console.log( console.log(
@@ -414,9 +391,7 @@ async function testMemoryManagement() {
// 验证内存增长不超过10MB基本的内存泄漏检查 // 验证内存增长不超过10MB基本的内存泄漏检查
if (memoryIncrease > 10 * 1024 * 1024) { if (memoryIncrease > 10 * 1024 * 1024) {
console.warn( console.warn(`潜在内存泄漏: 内存增长 ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`);
`潜在内存泄漏: 内存增长 ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`
);
} }
} }
@@ -454,12 +429,7 @@ export async function runLiquifyIntegrationTests() {
console.log(`总测试数: ${testResults.totalTests}`); console.log(`总测试数: ${testResults.totalTests}`);
console.log(`通过: ${testResults.passedTests}`); console.log(`通过: ${testResults.passedTests}`);
console.log(`失败: ${testResults.failedTests}`); console.log(`失败: ${testResults.failedTests}`);
console.log( console.log(`成功率: ${((testResults.passedTests / testResults.totalTests) * 100).toFixed(1)}%`);
`成功率: ${(
(testResults.passedTests / testResults.totalTests) *
100
).toFixed(1)}%`
);
if (testResults.errors.length > 0) { if (testResults.errors.length > 0) {
console.log("\n❌ 失败的测试:"); console.log("\n❌ 失败的测试:");

View File

@@ -45,19 +45,13 @@ export function testCoordinateConversion() {
// 模拟转换结果 // 模拟转换结果
const expectedImageX = const expectedImageX =
((coord.x - mockFabricObject.left) / mockFabricObject.scaleX + ((coord.x - mockFabricObject.left) / mockFabricObject.scaleX + mockFabricObject.width / 2) *
mockFabricObject.width / 2) *
(mockImageData.width / mockFabricObject.width); (mockImageData.width / mockFabricObject.width);
const expectedImageY = const expectedImageY =
((coord.y - mockFabricObject.top) / mockFabricObject.scaleY + ((coord.y - mockFabricObject.top) / mockFabricObject.scaleY + mockFabricObject.height / 2) *
mockFabricObject.height / 2) *
(mockImageData.height / mockFabricObject.height); (mockImageData.height / mockFabricObject.height);
console.log( console.log(` 预期图像坐标: (${expectedImageX.toFixed(2)}, ${expectedImageY.toFixed(2)})`);
` 预期图像坐标: (${expectedImageX.toFixed(2)}, ${expectedImageY.toFixed(
2
)})`
);
}); });
console.log("坐标转换测试完成"); console.log("坐标转换测试完成");
@@ -83,10 +77,7 @@ export function testPushAlgorithmPerformance() {
// 模拟液化计算 // 模拟液化计算
const movementLength = const movementLength =
i > 0 i > 0
? Math.sqrt( ? Math.sqrt(Math.pow(x - movements[i - 1].x, 2) + Math.pow(y - movements[i - 1].y, 2))
Math.pow(x - movements[i - 1].x, 2) +
Math.pow(y - movements[i - 1].y, 2)
)
: 0; : 0;
if (movementLength > 0.5) { if (movementLength > 0.5) {
@@ -151,12 +142,8 @@ export function testScaleConsistency() {
const localX = (canvasCoord.x - fabricObject.left) / fabricObject.scaleX; const localX = (canvasCoord.x - fabricObject.left) / fabricObject.scaleX;
const localY = (canvasCoord.y - fabricObject.top) / fabricObject.scaleY; const localY = (canvasCoord.y - fabricObject.top) / fabricObject.scaleY;
const imageX = const imageX = (localX + fabricObject.width / 2) * (imageData.width / fabricObject.width);
(localX + fabricObject.width / 2) * const imageY = (localY + fabricObject.height / 2) * (imageData.height / fabricObject.height);
(imageData.width / fabricObject.width);
const imageY =
(localY + fabricObject.height / 2) *
(imageData.height / fabricObject.height);
console.log(` 画布坐标: (${canvasCoord.x}, ${canvasCoord.y})`); console.log(` 画布坐标: (${canvasCoord.x}, ${canvasCoord.y})`);
console.log(` 本地坐标: (${localX.toFixed(2)}, ${localY.toFixed(2)})`); console.log(` 本地坐标: (${localX.toFixed(2)}, ${localY.toFixed(2)})`);
@@ -164,10 +151,7 @@ export function testScaleConsistency() {
// 验证图像坐标是否在合理范围内 // 验证图像坐标是否在合理范围内
const isValid = const isValid =
imageX >= 0 && imageX >= 0 && imageX < imageData.width && imageY >= 0 && imageY < imageData.height;
imageX < imageData.width &&
imageY >= 0 &&
imageY < imageData.height;
console.log(` 坐标有效性: ${isValid ? "✓" : "✗"}`); console.log(` 坐标有效性: ${isValid ? "✓" : "✗"}`);
}); });
@@ -190,10 +174,7 @@ export function runAllTests() {
console.log("=== 液化功能测试完成 ==="); console.log("=== 液化功能测试完成 ===");
console.log( console.log(
`推荐配置: 节流时间 ${Math.max( `推荐配置: 节流时间 ${Math.max(16, perfResult.avgTimePerOperation * 2).toFixed(0)}ms`
16,
perfResult.avgTimePerOperation * 2
).toFixed(0)}ms`
); );
return { return {

View File

@@ -81,11 +81,7 @@ export class LayerSort {
zIndexMap.set(layer.fabricObject.id, currentZIndex++); zIndexMap.set(layer.fabricObject.id, currentZIndex++);
} else if (!layer.isBackground && !layer.isFixed) { } else if (!layer.isBackground && !layer.isFixed) {
// 普通图层 // 普通图层
currentZIndex = this.processLayerObjects( currentZIndex = this.processLayerObjects(layer, currentZIndex, zIndexMap);
layer,
currentZIndex,
zIndexMap
);
} }
} }
@@ -142,8 +138,7 @@ export class LayerSort {
getChildLayersInOrder(parentLayerId) { getChildLayersInOrder(parentLayerId) {
// 获取所有子图层 // 获取所有子图层
const childLayers = const childLayers =
this.layers.value.filter((layer) => layer.id === parentLayerId) this.layers.value.filter((layer) => layer.id === parentLayerId)?.children || [];
?.children || [];
return childLayers; return childLayers;
} }
@@ -194,9 +189,7 @@ export class LayerSort {
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
const item = sortedObjects[i]; const item = sortedObjects[i];
const currentIndex = this.canvas const currentIndex = this.canvas.getObjects().indexOf(item.object);
.getObjects()
.indexOf(item.object);
if (currentIndex !== i && currentIndex !== -1) { if (currentIndex !== i && currentIndex !== -1) {
this.canvas.moveTo(item.object, i); this.canvas.moveTo(item.object, i);
} }
@@ -288,23 +281,17 @@ export class LayerSort {
return this.layers.value.length; // 背景图层插入到最后 return this.layers.value.length; // 背景图层插入到最后
} else if (newLayer.isFixed) { } else if (newLayer.isFixed) {
// 固定图层插入到背景图层之前 // 固定图层插入到背景图层之前
const bgIndex = this.layers.value.findIndex( const bgIndex = this.layers.value.findIndex((layer) => layer.isBackground);
(layer) => layer.isBackground
);
return bgIndex !== -1 ? bgIndex : this.layers.value.length; return bgIndex !== -1 ? bgIndex : this.layers.value.length;
} else { } else {
// 普通图层插入到固定图层之前 // 普通图层插入到固定图层之前
const fixedIndex = this.layers.value.findIndex( const fixedIndex = this.layers.value.findIndex((layer) => layer.isFixed);
(layer) => layer.isFixed
);
return fixedIndex !== -1 ? fixedIndex : this.layers.value.length; return fixedIndex !== -1 ? fixedIndex : this.layers.value.length;
} }
} }
// 如果指定了目标图层,插入到目标图层之前 // 如果指定了目标图层,插入到目标图层之前
const targetIndex = this.layers.value.findIndex( const targetIndex = this.layers.value.findIndex((layer) => layer.id === targetLayerId);
(layer) => layer.id === targetLayerId
);
return targetIndex !== -1 ? targetIndex : this.layers.value.length; return targetIndex !== -1 ? targetIndex : this.layers.value.length;
} }
@@ -532,9 +519,7 @@ export class LayerSort {
async smartSort(targetLayerIds = null) { async smartSort(targetLayerIds = null) {
const layersToSort = targetLayerIds const layersToSort = targetLayerIds
? this.layers.value.filter((layer) => targetLayerIds.includes(layer.id)) ? this.layers.value.filter((layer) => targetLayerIds.includes(layer.id))
: this.layers.value.filter( : this.layers.value.filter((layer) => !layer.isBackground && !layer.isFixed);
(layer) => !layer.isBackground && !layer.isFixed
);
if (layersToSort.length <= 1) return true; if (layersToSort.length <= 1) return true;
@@ -556,9 +541,7 @@ export class LayerSort {
// 更新图层顺序 // 更新图层顺序
const sortedLayerIds = layersToSort.map((layer) => layer.id); const sortedLayerIds = layersToSort.map((layer) => layer.id);
const otherLayers = this.layers.value.filter( const otherLayers = this.layers.value.filter((layer) => !sortedLayerIds.includes(layer.id));
(layer) => !sortedLayerIds.includes(layer.id)
);
// 重新组织图层数组:保持背景层和固定层的位置 // 重新组织图层数组:保持背景层和固定层的位置
const newLayers = []; const newLayers = [];
@@ -733,12 +716,9 @@ export const LayerSortUtils = {
* @returns {number} 排序权重 * @returns {number} 排序权重
*/ */
getLayerSortWeight(layer) { getLayerSortWeight(layer) {
if (layer.isBackground) if (layer.isBackground) return LayerSortConstants.LAYER_PRIORITY[LayerType.BACKGROUND];
return LayerSortConstants.LAYER_PRIORITY[LayerType.BACKGROUND]; if (layer.isFixed) return LayerSortConstants.LAYER_PRIORITY[LayerType.FIXED];
if (layer.isFixed) if (layer.children?.length > 0) return LayerSortConstants.LAYER_PRIORITY[LayerType.GROUP];
return LayerSortConstants.LAYER_PRIORITY[LayerType.FIXED];
if (layer.children?.length > 0)
return LayerSortConstants.LAYER_PRIORITY[LayerType.GROUP];
return LayerSortConstants.LAYER_PRIORITY[LayerType.NORMAL]; return LayerSortConstants.LAYER_PRIORITY[LayerType.NORMAL];
}, },

View File

@@ -9,7 +9,6 @@ export const createCanvas = (elementId, options = {}) => {
const canvas = new fabric.Canvas(elementId, { const canvas = new fabric.Canvas(elementId, {
enableRetinaScaling: canvasConfig.enableRetinaScaling, enableRetinaScaling: canvasConfig.enableRetinaScaling,
renderOnAddRemove: false, renderOnAddRemove: false,
enableRetinaScaling: true,
preserveObjectStacking: true, // 保持对象堆叠顺序 preserveObjectStacking: true, // 保持对象堆叠顺序
// skipOffscreen: true, // 跳过离屏渲染 // skipOffscreen: true, // 跳过离屏渲染
imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿 imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿

View File

@@ -6,12 +6,7 @@ export function deepCompare(obj1, obj2) {
return null; return null;
} }
if ( if (obj1 === null || obj2 === null || typeof obj1 !== "object" || typeof obj2 !== "object") {
obj1 === null ||
obj2 === null ||
typeof obj1 !== "object" ||
typeof obj2 !== "object"
) {
return { _value: obj2, _oldValue: obj1 }; return { _value: obj2, _oldValue: obj1 };
} }
@@ -78,7 +73,7 @@ export function deepClone(obj) {
if (typeof obj === "object") { if (typeof obj === "object") {
const cloned = {}; const cloned = {};
for (const key in obj) { for (const key in obj) {
if (obj.hasOwnProperty(key)) { if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key]); cloned[key] = deepClone(obj[key]);
} }
} }
@@ -228,11 +223,7 @@ export function formatDuration(milliseconds) {
* @returns {boolean} 是否有效 * @returns {boolean} 是否有效
*/ */
export function isValidCommand(command) { export function isValidCommand(command) {
return ( return command && typeof command === "object" && typeof command.execute === "function";
command &&
typeof command === "object" &&
typeof command.execute === "function"
);
} }
/** /**
@@ -286,7 +277,7 @@ export function getObjectDepth(obj) {
let maxDepth = 0; let maxDepth = 0;
for (const key in obj) { for (const key in obj) {
if (obj.hasOwnProperty(key)) { if (Object.prototype.hasOwnProperty.call(obj, key)) {
const depth = getObjectDepth(obj[key]); const depth = getObjectDepth(obj[key]);
maxDepth = Math.max(maxDepth, depth); maxDepth = Math.max(maxDepth, depth);
} }
@@ -313,8 +304,7 @@ export function checkBrowserSupport() {
return { return {
WeakRef: typeof WeakRef !== "undefined", WeakRef: typeof WeakRef !== "undefined",
FinalizationRegistry: typeof FinalizationRegistry !== "undefined", FinalizationRegistry: typeof FinalizationRegistry !== "undefined",
PerformanceMemory: PerformanceMemory: typeof performance !== "undefined" && !!performance.memory,
typeof performance !== "undefined" && !!performance.memory,
RequestIdleCallback: typeof requestIdleCallback !== "undefined", RequestIdleCallback: typeof requestIdleCallback !== "undefined",
IntersectionObserver: typeof IntersectionObserver !== "undefined", IntersectionObserver: typeof IntersectionObserver !== "undefined",
ResizeObserver: typeof ResizeObserver !== "undefined", ResizeObserver: typeof ResizeObserver !== "undefined",
@@ -337,12 +327,7 @@ export function delay(ms) {
* @returns {Promise} 执行结果 * @returns {Promise} 执行结果
*/ */
export async function retry(fn, options = {}) { export async function retry(fn, options = {}) {
const { const { retries = 3, delay: delayMs = 1000, backoff = 1.5, shouldRetry = () => true } = options;
retries = 3,
delay: delayMs = 1000,
backoff = 1.5,
shouldRetry = () => true,
} = options;
let attempt = 0; let attempt = 0;
let currentDelay = delayMs; let currentDelay = delayMs;
@@ -377,9 +362,7 @@ export async function batchProcess(items, processor, options = {}) {
for (let i = 0; i < items.length; i += batchSize) { for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize); const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all( const batchResults = await Promise.all(batch.map((item) => processor(item)));
batch.map((item) => processor(item))
);
results.push(...batchResults); results.push(...batchResults);

View File

@@ -35,14 +35,8 @@ function initAligningGuidelines(canvas) {
ctx.lineWidth = aligningLineWidth; ctx.lineWidth = aligningLineWidth;
ctx.strokeStyle = aligningLineColor; ctx.strokeStyle = aligningLineColor;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo( ctx.moveTo(x1 * zoom + viewportTransform[4], y1 * zoom + viewportTransform[5]);
x1 * zoom + viewportTransform[4], ctx.lineTo(x2 * zoom + viewportTransform[4], y2 * zoom + viewportTransform[5]);
y1 * zoom + viewportTransform[5]
);
ctx.lineTo(
x2 * zoom + viewportTransform[4],
y2 * zoom + viewportTransform[5]
);
ctx.stroke(); ctx.stroke();
ctx.restore(); ctx.restore();
} }
@@ -50,11 +44,7 @@ function initAligningGuidelines(canvas) {
function isInRange(value1, value2) { function isInRange(value1, value2) {
value1 = Math.round(value1); value1 = Math.round(value1);
value2 = Math.round(value2); value2 = Math.round(value2);
for ( for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin;
i <= len;
i++
) {
if (i === value2) { if (i === value2) {
return true; return true;
} }
@@ -77,8 +67,7 @@ function initAligningGuidelines(canvas) {
activeObjectLeft = activeObjectCenter.x, activeObjectLeft = activeObjectCenter.x,
activeObjectTop = activeObjectCenter.y, activeObjectTop = activeObjectCenter.y,
activeObjectBoundingRect = activeObject.getBoundingRect(), activeObjectBoundingRect = activeObject.getBoundingRect(),
activeObjectHeight = activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
activeObjectBoundingRect.height / viewportTransform[3],
activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0], activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
horizontalInTheRange = false, horizontalInTheRange = false,
verticalInTheRange = false, verticalInTheRange = false,
@@ -100,12 +89,7 @@ function initAligningGuidelines(canvas) {
objectWidth = objectBoundingRect.width / viewportTransform[0]; objectWidth = objectBoundingRect.width / viewportTransform[0];
// snaps if the right side of the active object touches the left side of the object // snaps if the right side of the active object touches the left side of the object
if ( if (isInRange(activeObjectLeft + activeObjectWidth / 2, objectLeft - objectWidth / 2)) {
isInRange(
activeObjectLeft + activeObjectWidth / 2,
objectLeft - objectWidth / 2
)
) {
verticalInTheRange = true; verticalInTheRange = true;
verticalLines.push({ verticalLines.push({
x: objectLeft - objectWidth / 2, x: objectLeft - objectWidth / 2,
@@ -120,22 +104,14 @@ function initAligningGuidelines(canvas) {
}); });
activeObject.setPositionByOrigin( activeObject.setPositionByOrigin(
new fabric.Point( new fabric.Point(objectLeft - objectWidth / 2 - activeObjectWidth / 2, activeObjectTop),
objectLeft - objectWidth / 2 - activeObjectWidth / 2,
activeObjectTop
),
"center", "center",
"center" "center"
); );
} }
// snaps if the left side of the active object touches the right side of the object // snaps if the left side of the active object touches the right side of the object
if ( if (isInRange(activeObjectLeft - activeObjectWidth / 2, objectLeft + objectWidth / 2)) {
isInRange(
activeObjectLeft - activeObjectWidth / 2,
objectLeft + objectWidth / 2
)
) {
verticalInTheRange = true; verticalInTheRange = true;
verticalLines.push({ verticalLines.push({
x: objectLeft + objectWidth / 2, x: objectLeft + objectWidth / 2,
@@ -150,22 +126,14 @@ function initAligningGuidelines(canvas) {
}); });
activeObject.setPositionByOrigin( activeObject.setPositionByOrigin(
new fabric.Point( new fabric.Point(objectLeft + objectWidth / 2 + activeObjectWidth / 2, activeObjectTop),
objectLeft + objectWidth / 2 + activeObjectWidth / 2,
activeObjectTop
),
"center", "center",
"center" "center"
); );
} }
// snaps if the bottom of the object touches the top of the active object // snaps if the bottom of the object touches the top of the active object
if ( if (isInRange(objectTop + objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
isInRange(
objectTop + objectHeight / 2,
activeObjectTop - activeObjectHeight / 2
)
) {
horizontalInTheRange = true; horizontalInTheRange = true;
horizontalLines.push({ horizontalLines.push({
y: objectTop + objectHeight / 2, y: objectTop + objectHeight / 2,
@@ -180,22 +148,14 @@ function initAligningGuidelines(canvas) {
}); });
activeObject.setPositionByOrigin( activeObject.setPositionByOrigin(
new fabric.Point( new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 + activeObjectHeight / 2),
activeObjectLeft,
objectTop + objectHeight / 2 + activeObjectHeight / 2
),
"center", "center",
"center" "center"
); );
} }
// snaps if the top of the object touches the bottom of the active object // snaps if the top of the object touches the bottom of the active object
if ( if (isInRange(objectTop - objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
isInRange(
objectTop - objectHeight / 2,
activeObjectTop + activeObjectHeight / 2
)
) {
horizontalInTheRange = true; horizontalInTheRange = true;
horizontalLines.push({ horizontalLines.push({
y: objectTop - objectHeight / 2, y: objectTop - objectHeight / 2,
@@ -210,10 +170,7 @@ function initAligningGuidelines(canvas) {
}); });
activeObject.setPositionByOrigin( activeObject.setPositionByOrigin(
new fabric.Point( new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 - activeObjectHeight / 2),
activeObjectLeft,
objectTop - objectHeight / 2 - activeObjectHeight / 2
),
"center", "center",
"center" "center"
); );
@@ -241,12 +198,7 @@ function initAligningGuidelines(canvas) {
} }
// snap by the left edge // snap by the left edge
if ( if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
isInRange(
objectLeft - objectWidth / 2,
activeObjectLeft - activeObjectWidth / 2
)
) {
verticalInTheRange = true; verticalInTheRange = true;
verticalLines.push({ verticalLines.push({
x: objectLeft - objectWidth / 2, x: objectLeft - objectWidth / 2,
@@ -260,22 +212,14 @@ function initAligningGuidelines(canvas) {
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset, : activeObjectTop - activeObjectHeight / 2 - aligningLineOffset,
}); });
activeObject.setPositionByOrigin( activeObject.setPositionByOrigin(
new fabric.Point( new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop),
objectLeft - objectWidth / 2 + activeObjectWidth / 2,
activeObjectTop
),
"center", "center",
"center" "center"
); );
} }
// snap by the right edge // snap by the right edge
if ( if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
isInRange(
objectLeft + objectWidth / 2,
activeObjectLeft + activeObjectWidth / 2
)
) {
verticalInTheRange = true; verticalInTheRange = true;
verticalLines.push({ verticalLines.push({
x: objectLeft + objectWidth / 2, x: objectLeft + objectWidth / 2,
@@ -289,10 +233,7 @@ function initAligningGuidelines(canvas) {
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset, : activeObjectTop - activeObjectHeight / 2 - aligningLineOffset,
}); });
activeObject.setPositionByOrigin( activeObject.setPositionByOrigin(
new fabric.Point( new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop),
objectLeft + objectWidth / 2 - activeObjectWidth / 2,
activeObjectTop
),
"center", "center",
"center" "center"
); );
@@ -320,12 +261,7 @@ function initAligningGuidelines(canvas) {
} }
// snap by the top edge // snap by the top edge
if ( if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
isInRange(
objectTop - objectHeight / 2,
activeObjectTop - activeObjectHeight / 2
)
) {
horizontalInTheRange = true; horizontalInTheRange = true;
horizontalLines.push({ horizontalLines.push({
y: objectTop - objectHeight / 2, y: objectTop - objectHeight / 2,
@@ -339,22 +275,14 @@ function initAligningGuidelines(canvas) {
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset, : activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset,
}); });
activeObject.setPositionByOrigin( activeObject.setPositionByOrigin(
new fabric.Point( new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2),
activeObjectLeft,
objectTop - objectHeight / 2 + activeObjectHeight / 2
),
"center", "center",
"center" "center"
); );
} }
// snap by the bottom edge // snap by the bottom edge
if ( if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
isInRange(
objectTop + objectHeight / 2,
activeObjectTop + activeObjectHeight / 2
)
) {
horizontalInTheRange = true; horizontalInTheRange = true;
horizontalLines.push({ horizontalLines.push({
y: objectTop + objectHeight / 2, y: objectTop + objectHeight / 2,
@@ -368,10 +296,7 @@ function initAligningGuidelines(canvas) {
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset, : activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset,
}); });
activeObject.setPositionByOrigin( activeObject.setPositionByOrigin(
new fabric.Point( new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2),
activeObjectLeft,
objectTop + objectHeight / 2 - activeObjectHeight / 2
),
"center", "center",
"center" "center"
); );
@@ -397,7 +322,7 @@ function initAligningGuidelines(canvas) {
for (var i = verticalLines.length; i--; ) { for (var i = verticalLines.length; i--; ) {
drawVerticalLine(verticalLines[i]); drawVerticalLine(verticalLines[i]);
} }
for (var i = horizontalLines.length; i--; ) { for (let i = horizontalLines.length; i--; ) {
drawHorizontalLine(horizontalLines[i]); drawHorizontalLine(horizontalLines[i]);
} }
@@ -433,16 +358,14 @@ export function initCenteringGuidelines(canvas) {
viewportTransform; viewportTransform;
for ( for (
var i = canvasWidthCenter - centerLineMargin, var i = canvasWidthCenter - centerLineMargin, len = canvasWidthCenter + centerLineMargin;
len = canvasWidthCenter + centerLineMargin;
i <= len; i <= len;
i++ i++
) { ) {
canvasWidthCenterMap[Math.round(i)] = true; canvasWidthCenterMap[Math.round(i)] = true;
} }
for ( for (
var i = canvasHeightCenter - centerLineMargin, let i = canvasHeightCenter - centerLineMargin, len = canvasHeightCenter + centerLineMargin;
len = canvasHeightCenter + centerLineMargin;
i <= len; i <= len;
i++ i++
) { ) {
@@ -450,21 +373,11 @@ export function initCenteringGuidelines(canvas) {
} }
function showVerticalCenterLine() { function showVerticalCenterLine() {
showCenterLine( showCenterLine(canvasWidthCenter + 0.5, 0, canvasWidthCenter + 0.5, canvasHeight);
canvasWidthCenter + 0.5,
0,
canvasWidthCenter + 0.5,
canvasHeight
);
} }
function showHorizontalCenterLine() { function showHorizontalCenterLine() {
showCenterLine( showCenterLine(0, canvasHeightCenter + 0.5, canvasWidth, canvasHeightCenter + 0.5);
0,
canvasHeightCenter + 0.5,
canvasWidth,
canvasHeightCenter + 0.5
);
} }
function showCenterLine(x1, y1, x2, y2) { function showCenterLine(x1, y1, x2, y2) {
@@ -493,9 +406,8 @@ export function initCenteringGuidelines(canvas) {
if (!transform) return; if (!transform) return;
(isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap), ((isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap),
(isInHorizontalCenter = (isInHorizontalCenter = Math.round(objectCenter.y) in canvasHeightCenterMap));
Math.round(objectCenter.y) in canvasHeightCenterMap);
if (isInHorizontalCenter || isInVerticalCenter) { if (isInHorizontalCenter || isInVerticalCenter) {
object.setPositionByOrigin( object.setPositionByOrigin(

View File

@@ -1,13 +1,12 @@
/* eslint-disable no-async-promise-executor */
import { fabric } from "fabric-with-all"; import { fabric } from "fabric-with-all";
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper"; import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
// 导入新的复合命令 // 导入新的复合命令
import { CreateImageLayerCommand } from "../commands/LayerCommands"; import { CreateImageLayerCommand } from "../commands/LayerCommands";
// 导入新的命令 // 导入新的命令
import { import { ChangeFixedImageCommand, AddImageToLayerCommand } from "../commands/LayerCommands";
ChangeFixedImageCommand,
AddImageToLayerCommand,
} from "../commands/LayerCommands";
import { generateId } from "./helper"; import { generateId } from "./helper";
import { isBoolean } from "lodash-es";
/** /**
* 加载并处理图片 * 加载并处理图片
@@ -101,9 +100,7 @@ export async function createImageLayer({
if (isBoolean(undoable)) createImageLayerCmd.undoable = undoable; // 是否撤销 if (isBoolean(undoable)) createImageLayerCmd.undoable = undoable; // 是否撤销
// 执行复合命令 // 执行复合命令
const newLayerId = await layerManager.commandManager.execute( const newLayerId = await layerManager.commandManager.execute(createImageLayerCmd);
createImageLayerCmd
);
return newLayerId; return newLayerId;
} catch (error) { } catch (error) {
@@ -120,11 +117,7 @@ export async function createImageLayer({
* @param {Object} options.fabricImage - 新的图像对象 * @param {Object} options.fabricImage - 新的图像对象
* @returns {Promise<boolean>} 是否成功更改 * @returns {Promise<boolean>} 是否成功更改
*/ */
export async function changeFixedImage({ export async function changeFixedImage({ layerManager, fixedLayerId, fabricImage } = {}) {
layerManager,
fixedLayerId,
fabricImage,
} = {}) {
if (!layerManager || !fixedLayerId || !fabricImage) { if (!layerManager || !fixedLayerId || !fabricImage) {
console.error("更改固定图层图像:参数无效"); console.error("更改固定图层图像:参数无效");
return false; return false;
@@ -141,9 +134,7 @@ export async function changeFixedImage({
}); });
// 通过命令管理器执行 // 通过命令管理器执行
const result = await layerManager.commandManager.execute( const result = await layerManager.commandManager.execute(changeFixedImageCmd);
changeFixedImageCmd
);
if (result) { if (result) {
console.log(`✅ 成功更改固定图层 "${fixedLayerId}" 的图像`); console.log(`✅ 成功更改固定图层 "${fixedLayerId}" 的图像`);
@@ -192,9 +183,7 @@ export async function addImageToLayer({
}); });
// 通过命令管理器执行 // 通过命令管理器执行
const resultLayerId = await layerManager.commandManager.execute( const resultLayerId = await layerManager.commandManager.execute(addImageToLayerCmd);
addImageToLayerCmd
);
if (resultLayerId) { if (resultLayerId) {
if (targetLayerId) { if (targetLayerId) {
@@ -219,10 +208,7 @@ export async function addImageToLayer({
* @param {Object} options - 配置选项 * @param {Object} options - 配置选项
* @returns {Promise<string>} 新图层ID的Promise * @returns {Promise<string>} 新图层ID的Promise
*/ */
export function loadImageUrlToLayer( export function loadImageUrlToLayer({ imageUrl, layerManager, canvas, toolManager }, options = {}) {
{ imageUrl, layerManager, canvas, toolManager },
options = {}
) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (!imageUrl || !layerManager || !canvas) { if (!imageUrl || !layerManager || !canvas) {
reject(new Error("参数无效")); reject(new Error("参数无效"));
@@ -231,9 +217,7 @@ export function loadImageUrlToLayer(
try { try {
// 查找背景图层以获取尺寸 // 查找背景图层以获取尺寸
const bgLayer = layerManager.layers.value.find( const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
(layer) => layer.isBackground
);
// 设置最大宽高为背景图层的尺寸 // 设置最大宽高为背景图层的尺寸
const maxWidth = bgLayer?.canvasWidth || canvas.width; const maxWidth = bgLayer?.canvasWidth || canvas.width;
@@ -304,9 +288,7 @@ export function uploadImageAndCreateLayer(
reader.onload = async (e) => { reader.onload = async (e) => {
try { try {
// 查找背景图层以获取尺寸 // 查找背景图层以获取尺寸
const bgLayer = layerManager.layers.value.find( const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
(layer) => layer.isBackground
);
// 设置最大宽高为背景图层的尺寸 // 设置最大宽高为背景图层的尺寸
const maxWidth = bgLayer?.canvasWidth || canvas.width; const maxWidth = bgLayer?.canvasWidth || canvas.width;
@@ -361,9 +343,7 @@ export function safeLoadImage(imageSource, options = {}) {
.then(resolve) .then(resolve)
.catch((error) => { .catch((error) => {
if (attempt < retries) { if (attempt < retries) {
console.warn( console.warn(`图片加载失败,正在重试 (${attempt + 1}/${retries})...`);
`图片加载失败,正在重试 (${attempt + 1}/${retries})...`
);
setTimeout(() => attemptLoad(attempt + 1), 500); setTimeout(() => attemptLoad(attempt + 1), 500);
} else { } else {
reject(error); reject(error);
@@ -426,12 +406,7 @@ export function loadImageAndChangeFixedLayer({
* @param {Object} options.imageOptions - 图片加载选项 * @param {Object} options.imageOptions - 图片加载选项
* @returns {Promise<string>} 新图像对象ID的Promise * @returns {Promise<string>} 新图像对象ID的Promise
*/ */
export function uploadImageAndChangeFixedLayer({ export function uploadImageAndChangeFixedLayer({ file, layerManager, layerId, imageOptions = {} }) {
file,
layerManager,
layerId,
imageOptions = {},
}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!file || !layerManager || !layerId) { if (!file || !layerManager || !layerId) {
reject(new Error("参数无效需要文件、图层管理器和图层ID")); reject(new Error("参数无效需要文件、图层管理器和图层ID"));
@@ -449,9 +424,7 @@ export function uploadImageAndChangeFixedLayer({
reader.onload = async (e) => { reader.onload = async (e) => {
try { try {
// 查找目标固定图层以获取尺寸信息 // 查找目标固定图层以获取尺寸信息
const targetLayer = layerManager.layers.value.find( const targetLayer = layerManager.layers.value.find((layer) => layer.id === layerId);
(layer) => layer.id === layerId
);
if (!targetLayer) { if (!targetLayer) {
throw new Error(`找不到图层 ID: ${layerId}`); throw new Error(`找不到图层 ID: ${layerId}`);
@@ -463,9 +436,7 @@ export function uploadImageAndChangeFixedLayer({
} }
// 查找背景图层以获取画布尺寸 // 查找背景图层以获取画布尺寸
const bgLayer = layerManager.layers.value.find( const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
(layer) => layer.isBackground
);
const maxWidth = bgLayer?.canvasWidth || layerManager.canvas.width; const maxWidth = bgLayer?.canvasWidth || layerManager.canvas.width;
const maxHeight = bgLayer?.canvasHeight || layerManager.canvas.height; const maxHeight = bgLayer?.canvasHeight || layerManager.canvas.height;
@@ -490,14 +461,10 @@ export function uploadImageAndChangeFixedLayer({
}); });
// 通过命令管理器执行 // 通过命令管理器执行
const newImageId = await layerManager.commandManager.execute( const newImageId = await layerManager.commandManager.execute(changeFixedImageCmd);
changeFixedImageCmd
);
if (newImageId) { if (newImageId) {
console.log( console.log(`✅ 成功更改固定图层 "${targetLayer.name}" 的图像新图像ID: ${newImageId}`);
`✅ 成功更改固定图层 "${targetLayer.name}" 的图像新图像ID: ${newImageId}`
);
resolve(newImageId); resolve(newImageId);
} else { } else {
throw new Error("更改固定图层图像失败"); throw new Error("更改固定图层图像失败");
@@ -826,9 +793,7 @@ export class AdvancedImageManager {
total: imageUrls.length, total: imageUrls.length,
successful: results.filter((r) => r.success).length, successful: results.filter((r) => r.success).length,
failed: results.filter((r) => !r.success).length, failed: results.filter((r) => !r.success).length,
cacheHitRate: cacheHitRate: this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads,
this.performanceMetrics.cacheHits /
this.performanceMetrics.imageLoads,
averageLoadTime: this.performanceMetrics.averageLoadTime, averageLoadTime: this.performanceMetrics.averageLoadTime,
}, },
}; };
@@ -884,13 +849,10 @@ export class AdvancedImageManager {
// 立即执行模式 // 立即执行模式
for (const operation of operations) { for (const operation of operations) {
try { try {
const result = await this.canvasManager.changeFixedImage( const result = await this.canvasManager.changeFixedImage(operation.imageUrl, {
operation.imageUrl, targetLayerType: operation.layerType,
{ ...operation.options,
targetLayerType: operation.layerType, });
...operation.options,
}
);
operationResults.push({ success: true, ...result, operation }); operationResults.push({ success: true, ...result, operation });
} catch (error) { } catch (error) {
operationResults.push({ operationResults.push({
@@ -1038,10 +1000,7 @@ export class AdvancedImageManager {
} }
// 替换模板中的变量 // 替换模板中的变量
const operations = this.replaceTemplateVariables( const operations = this.replaceTemplateVariables(template.operations, variables);
template.operations,
variables
);
// 执行操作 // 执行操作
const result = await this.batchAddImagesToLayers(operations); const result = await this.batchAddImagesToLayers(operations);
@@ -1141,8 +1100,7 @@ export class AdvancedImageManager {
this.performanceMetrics.imageLoads++; this.performanceMetrics.imageLoads++;
this.performanceMetrics.totalLoadTime += loadTime; this.performanceMetrics.totalLoadTime += loadTime;
this.performanceMetrics.averageLoadTime = this.performanceMetrics.averageLoadTime =
this.performanceMetrics.totalLoadTime / this.performanceMetrics.totalLoadTime / this.performanceMetrics.imageLoads;
this.performanceMetrics.imageLoads;
} }
getSummary(results) { getSummary(results) {
@@ -1170,12 +1128,9 @@ export class AdvancedImageManager {
// 替换字符串中的变量 {{variable}} // 替换字符串中的变量 {{variable}}
Object.keys(newOp).forEach((key) => { Object.keys(newOp).forEach((key) => {
if (typeof newOp[key] === "string") { if (typeof newOp[key] === "string") {
newOp[key] = newOp[key].replace( newOp[key] = newOp[key].replace(/\{\{(\w+)\}\}/g, (match, varName) => {
/\{\{(\w+)\}\}/g, return variables[varName] || match;
(match, varName) => { });
return variables[varName] || match;
}
);
} }
}); });
@@ -1223,10 +1178,7 @@ export class AdvancedImageManager {
generatePerformanceRecommendations() { generatePerformanceRecommendations() {
const recommendations = []; const recommendations = [];
if ( if (this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads < 0.3) {
this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads <
0.3
) {
recommendations.push("考虑增加缓存大小以提高缓存命中率"); recommendations.push("考虑增加缓存大小以提高缓存命中率");
} }
@@ -1298,12 +1250,7 @@ export const ImageUtils = {
* @param {string} targetLayerId - 目标图层ID (可选) * @param {string} targetLayerId - 目标图层ID (可选)
* @returns {Promise<Object>} 执行结果 * @returns {Promise<Object>} 执行结果
*/ */
quickAddImageToLayer: ( quickAddImageToLayer: (file, layerManager, toolManager, targetLayerId = null) => {
file,
layerManager,
toolManager,
targetLayerId = null
) => {
return uploadImageAndAddToLayerSimple({ return uploadImageAndAddToLayerSimple({
file, file,
layerManager, layerManager,
@@ -1376,12 +1323,7 @@ export function rasterizeCanvasObjects(options = {}) {
function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) { function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const { const { trimWhitespace = true, trimPadding = 10, quality = 1, format = "png" } = options;
trimWhitespace = true,
trimPadding = 10,
quality = 1,
format = "png",
} = options;
// 保存原始状态 // 保存原始状态
const originalObjects = canvas.getObjects(); const originalObjects = canvas.getObjects();
@@ -1389,9 +1331,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
const originalZoom = canvas.getZoom(); const originalZoom = canvas.getZoom();
// 临时隐藏其他对象,只显示要栅格化的对象 // 临时隐藏其他对象,只显示要栅格化的对象
const objectsToHide = originalObjects.filter( const objectsToHide = originalObjects.filter((obj) => !objects.includes(obj));
(obj) => !objects.includes(obj)
);
// 隐藏不需要的对象 // 隐藏不需要的对象
objectsToHide.forEach((obj) => { objectsToHide.forEach((obj) => {
@@ -1415,9 +1355,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
const pixelRatio = canvas.getRetinaScaling(); const pixelRatio = canvas.getRetinaScaling();
// 复制画布元素(这会保持所有变换状态) // 复制画布元素(这会保持所有变换状态)
const copiedCanvas = fabric.util.copyCanvasElement( const copiedCanvas = fabric.util.copyCanvasElement(canvas.lowerCanvasEl);
canvas.lowerCanvasEl
);
let finalCanvas = copiedCanvas; let finalCanvas = copiedCanvas;
let trimOffset = { x: 0, y: 0 }; let trimOffset = { x: 0, y: 0 };
@@ -1496,7 +1434,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
*/ */
function _restoreObjectVisibility(objects) { function _restoreObjectVisibility(objects) {
objects.forEach((obj) => { objects.forEach((obj) => {
if (obj.hasOwnProperty("_originalVisible")) { if (Object.prototype.hasOwnProperty.call(obj, "_originalVisible")) {
obj.set("visible", obj._originalVisible); obj.set("visible", obj._originalVisible);
delete obj._originalVisible; delete obj._originalVisible;
} }
@@ -1521,9 +1459,7 @@ function _rasterizeUsingDataURL(canvas, objects, options = {}) {
const originalObjects = canvas.getObjects(); const originalObjects = canvas.getObjects();
// 临时移除其他对象 // 临时移除其他对象
const objectsToRemove = originalObjects.filter( const objectsToRemove = originalObjects.filter((obj) => !objects.includes(obj));
(obj) => !objects.includes(obj)
);
objectsToRemove.forEach((obj) => { objectsToRemove.forEach((obj) => {
canvas.remove(obj); canvas.remove(obj);
}); });
@@ -1770,12 +1706,7 @@ function _trimCanvas(canvas, padding = 0) {
trimmedCanvas.height = trimHeight; trimmedCanvas.height = trimHeight;
// 复制裁剪区域包含padding // 复制裁剪区域包含padding
const trimmedImageData = ctx.getImageData( const trimmedImageData = ctx.getImageData(paddedMinX, paddedMinY, trimWidth, trimHeight);
paddedMinX,
paddedMinY,
trimWidth,
trimHeight
);
trimmedCtx.putImageData(trimmedImageData, 0, 0); trimmedCtx.putImageData(trimmedImageData, 0, 0);
return { return {
@@ -1955,8 +1886,7 @@ export function smartRasterize(options = {}) {
function _analyzeRasterizationContext(canvas, objects) { function _analyzeRasterizationContext(canvas, objects) {
const zoom = canvas.getZoom(); const zoom = canvas.getZoom();
const viewportTransform = canvas.viewportTransform; const viewportTransform = canvas.viewportTransform;
const hasTransform = const hasTransform = zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
// 分析对象类型分布 // 分析对象类型分布
const objectTypes = objects.reduce((acc, obj) => { const objectTypes = objects.reduce((acc, obj) => {
@@ -1971,9 +1901,7 @@ function _analyzeRasterizationContext(canvas, objects) {
// 计算画布利用率 // 计算画布利用率
const canvasArea = canvas.width * canvas.height; const canvasArea = canvas.width * canvas.height;
const objectsBounds = _calculateObjectsBounds(objects); const objectsBounds = _calculateObjectsBounds(objects);
const objectsArea = objectsBounds const objectsArea = objectsBounds ? objectsBounds.width * objectsBounds.height : 0;
? objectsBounds.width * objectsBounds.height
: 0;
const utilization = objectsArea / canvasArea; const utilization = objectsArea / canvasArea;
return { return {
@@ -1985,9 +1913,7 @@ function _analyzeRasterizationContext(canvas, objects) {
utilization, utilization,
canvasSize: { width: canvas.width, height: canvas.height }, canvasSize: { width: canvas.width, height: canvas.height },
objectsBounds, objectsBounds,
supportsCanvasCopy: !!( supportsCanvasCopy: !!(fabric.util.copyCanvasElement && canvas.lowerCanvasEl),
fabric.util.copyCanvasElement && canvas.lowerCanvasEl
),
}; };
} }
@@ -2223,12 +2149,7 @@ function _getAlternativeMethods(analysis) {
* @param {number} params.canvasWidth - 画布宽度 * @param {number} params.canvasWidth - 画布宽度
* @param {number} params.canvasHeight - 画布高度 * @param {number} params.canvasHeight - 画布高度
*/ */
export const imageModeHandler = ({ export const imageModeHandler = ({ imageMode, newImage, canvasWidth, canvasHeight }) => {
imageMode,
newImage,
canvasWidth,
canvasHeight,
}) => {
switch (imageMode) { switch (imageMode) {
case "stretch": case "stretch":
// 拉伸模式 - 填充整个画布 // 拉伸模式 - 填充整个画布
@@ -2253,10 +2174,8 @@ export const imageModeHandler = ({
// 这里可以添加裁剪逻辑,如果需要的话 // 这里可以添加裁剪逻辑,如果需要的话
// 例如使用fabric.Image.clipPath来裁剪图像 // 例如使用fabric.Image.clipPath来裁剪图像
break; break;
case "contains": case "contains": {
// 包含模式 - 保证图像在画布内完整显示 // 图片缩放后要保证最长边能完全显示在画布内 // 既要考虑画布的宽高比,也要考虑图像的宽高比 // 包含模式 - 保证图像在画布内完整显示
// 既要考虑画布的宽高比,也要考虑图像的宽高比
// 图片缩放后要保证最长边能完全显示在画布内
const canvasAspect = canvasWidth / canvasHeight; const canvasAspect = canvasWidth / canvasHeight;
const imageAspect = newImage.width / newImage.height; const imageAspect = newImage.width / newImage.height;
// 保证图像在画布内完整显示 - 既要考虑画布的宽高比,也要考虑图像的宽高比 // 保证图像在画布内完整显示 - 既要考虑画布的宽高比,也要考虑图像的宽高比
@@ -2269,5 +2188,6 @@ export const imageModeHandler = ({
newImage.scaleToHeight(canvasHeight); newImage.scaleToHeight(canvasHeight);
} }
break; break;
}
} }
}; };

View File

@@ -95,8 +95,7 @@ export const BlendMode = {
export function isGroupLayer(layer) { export function isGroupLayer(layer) {
if (!layer) return false; if (!layer) return false;
return ( return (
layer.type === LayerType.GROUP || layer.type === LayerType.GROUP || (Array.isArray(layer.children) && layer.children.length > 0)
(Array.isArray(layer.children) && layer.children.length > 0)
); );
} }
@@ -107,11 +106,7 @@ export function isGroupLayer(layer) {
* @param {Object} options 其他选项 * @param {Object} options 其他选项
* @returns {Object} 创建的图层对象 * @returns {Object} 创建的图层对象
*/ */
export function createLayerFromFabricObject( export function createLayerFromFabricObject(fabricObject, layerType = "bitmap", options = {}) {
fabricObject,
layerType = "bitmap",
options = {}
) {
if (!fabricObject) return null; if (!fabricObject) return null;
// 确定图层类型 // 确定图层类型
@@ -137,9 +132,7 @@ export function createLayerFromFabricObject(
type: type, type: type,
name: name:
options.name || options.name ||
`${ `${fabricObject.type.charAt(0).toUpperCase() + fabricObject.type.slice(1)} 图层`,
fabricObject.type.charAt(0).toUpperCase() + fabricObject.type.slice(1)
} 图层`,
parentId: options.parentId || null, parentId: options.parentId || null,
}); });
@@ -166,9 +159,7 @@ export function createLayerFromFabricObject(
*/ */
export function createLayer(options = {}) { export function createLayer(options = {}) {
const id = const id =
options.id || options.id || generateId("layer_") || `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
generateId("layer_") ||
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
return { return {
id: id, id: id,
// 图层基本属性 // 图层基本属性
@@ -208,8 +199,7 @@ export function createLayer(options = {}) {
* @returns {Object} 背景图层对象 * @returns {Object} 背景图层对象
*/ */
export function createBackgroundLayer(options = {}) { export function createBackgroundLayer(options = {}) {
const id = const id = options.id || `bg_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
options.id || `bg_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
return { return {
id: id, id: id,
// 图层基本属性 // 图层基本属性
@@ -400,9 +390,7 @@ export function createSmartObjectLayer(options = {}) {
* @returns {Object} 固定图层对象 * @returns {Object} 固定图层对象
*/ */
export function createFixedLayer(options = {}) { export function createFixedLayer(options = {}) {
const id = const id = options.id || `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
options.id ||
`fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
return { return {
id: id, id: id,
// 图层基本属性 // 图层基本属性
@@ -445,9 +433,7 @@ export function cloneLayer(layer) {
// 复制 fabric 对象 (如果存在) // 复制 fabric 对象 (如果存在)
if (Array.isArray(layer.fabricObjects)) { if (Array.isArray(layer.fabricObjects)) {
clonedLayer.fabricObjects = layer.fabricObjects.map((obj) => { clonedLayer.fabricObjects = layer.fabricObjects.map((obj) => {
return obj && typeof obj.clone === "function" return obj && typeof obj.clone === "function" ? obj.clone() : JSON.parse(JSON.stringify(obj));
? obj.clone()
: JSON.parse(JSON.stringify(obj));
}); });
} }

View File

@@ -15,18 +15,13 @@ export function buildLayerAssociations(layer, canvasObjects) {
// 处理单个fabricObject关联 // 处理单个fabricObject关联
if (layer.fabricObject) { if (layer.fabricObject) {
// 如果图层已经有关联的fabricObject确保它的layerId和layerName正确 // 如果图层已经有关联的fabricObject确保它的layerId和layerName正确
layer.fabricObject = layer.fabricObject = canvasObjects.find((obj) => obj.id === layer.fabricObject.id) || null;
canvasObjects.find((obj) => obj.id === layer.fabricObject.id) || null;
} }
if (layer.clippingMask) { if (layer.clippingMask) {
// clippingMask 可能是一个fabricObject或组 也可能是一个简单对象 // clippingMask 可能是一个fabricObject或组 也可能是一个简单对象
const clippingMaskObj = canvasObjects.find( const clippingMaskObj = canvasObjects.find((obj) => obj.id === layer.clippingMask.id);
(obj) => obj.id === layer.clippingMask.id layer.clippingMask = clippingMaskObj ? clippingMaskObj?.toObject?.(["id"]) : layer.clippingMask;
);
layer.clippingMask = clippingMaskObj
? clippingMaskObj?.toObject?.(["id"])
: layer.clippingMask;
} }
// 处理多个fabricObjects关联 // 处理多个fabricObjects关联
@@ -172,10 +167,7 @@ export function simplifyLayers(layers) {
opacity: layer.opacity, opacity: layer.opacity,
isBackground: layer.isBackground || false, isBackground: layer.isBackground || false,
isFixed: layer.isFixed || false, isFixed: layer.isFixed || false,
clippingMask: clippingMask: layer.clippingMask?.toObject?.(["id", "layerId"]) || layer.clippingMask || null, // 可能是一个fabricObject或组 也可能是一个简单对象
layer.clippingMask?.toObject?.(["id", "layerId"]) ||
layer.clippingMask ||
null, // 可能是一个fabricObject或组 也可能是一个简单对象
// ? { // ? {
// id: layer.clippingMask.id, // id: layer.clippingMask.id,
// type: layer.clippingMask.type, // type: layer.clippingMask.type,
@@ -200,10 +192,7 @@ export function simplifyLayers(layers) {
) )
.filter((obj) => obj !== null) .filter((obj) => obj !== null)
: [], : [],
children: children: layer.children && isArray(layer.children) ? simplifyLayers(layer.children) : [],
layer.children && isArray(layer.children)
? simplifyLayers(layer.children)
: [],
}; };
return simplifiedLayer; return simplifiedLayer;
@@ -240,9 +229,7 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
if (layer.clippingMask) { if (layer.clippingMask) {
// clippingMask 可能是一个fabricObject或组 也可能是一个简单对象 // clippingMask 可能是一个fabricObject或组 也可能是一个简单对象
const clippingMaskObj = canvasObjects.find( const clippingMaskObj = canvasObjects.find((obj) => obj.id === layer.clippingMask.id);
(obj) => obj.id === layer.clippingMask.id
);
restoredLayer.clippingMask = clippingMaskObj restoredLayer.clippingMask = clippingMaskObj
? clippingMaskObj?.toObject?.(["id"]) ? clippingMaskObj?.toObject?.(["id"])
: layer.clippingMask; : layer.clippingMask;
@@ -250,17 +237,11 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
// 恢复单个fabricObject关联 // 恢复单个fabricObject关联
if (layer.fabricObject?.id) { if (layer.fabricObject?.id) {
const fabricObj = canvasObjects.find( const fabricObj = canvasObjects.find((obj) => obj.id === layer.fabricObject.id);
(obj) => obj.id === layer.fabricObject.id
);
if (fabricObj) { if (fabricObj) {
fabricObj.layerId = layer.id; fabricObj.layerId = layer.id;
fabricObj.layerName = layer.name; fabricObj.layerName = layer.name;
restoredLayer.fabricObject = fabricObj.toObject([ restoredLayer.fabricObject = fabricObj.toObject(["id", "layerId", "type"]);
"id",
"layerId",
"type",
]);
} else { } else {
restoredLayer.fabricObject = null; restoredLayer.fabricObject = null;
} }
@@ -270,9 +251,7 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
if (layer.fabricObjects && isArray(layer.fabricObjects)) { if (layer.fabricObjects && isArray(layer.fabricObjects)) {
restoredLayer.fabricObjects = layer.fabricObjects restoredLayer.fabricObjects = layer.fabricObjects
.map((fabricRef) => { .map((fabricRef) => {
const fabricObj = canvasObjects.find( const fabricObj = canvasObjects.find((obj) => obj.id === fabricRef.id);
(obj) => obj.id === fabricRef.id
);
if (fabricObj) { if (fabricObj) {
fabricObj.layerId = layer.id; fabricObj.layerId = layer.id;
fabricObj.layerName = layer.name; fabricObj.layerName = layer.name;

View File

@@ -18,10 +18,8 @@ export async function restoreFabricObject(serializedObject, canvas) {
// 恢复自定义属性 // 恢复自定义属性
if (serializedObject.id) fabricObject.id = serializedObject.id; if (serializedObject.id) fabricObject.id = serializedObject.id;
if (serializedObject.layerId) if (serializedObject.layerId) fabricObject.layerId = serializedObject.layerId;
fabricObject.layerId = serializedObject.layerId; if (serializedObject.layerName) fabricObject.layerName = serializedObject.layerName;
if (serializedObject.layerName)
fabricObject.layerName = serializedObject.layerName;
// 更新坐标 // 更新坐标
fabricObject.setCoords(); fabricObject.setCoords();

View File

@@ -22,11 +22,7 @@ export const createRasterizedImage = async ({
isGroupWithMask = false, // 是否为带遮罩的组图层 isGroupWithMask = false, // 是否为带遮罩的组图层
} = {}) => { } = {}) => {
try { try {
console.log( console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象${maskObject ? "(带遮罩)" : ""}`);
`📊 开始栅格化 ${fabricObjects.length} 个对象${
maskObject ? "(带遮罩)" : ""
}`
);
// 确保有对象需要栅格化 // 确保有对象需要栅格化
if (fabricObjects.length === 0) { if (fabricObjects.length === 0) {
@@ -36,10 +32,7 @@ export const createRasterizedImage = async ({
// 高清倍数 // 高清倍数
const currentZoom = canvas.getZoom?.() || 1; const currentZoom = canvas.getZoom?.() || 1;
scaleFactor = Math.max( scaleFactor = Math.max(scaleFactor || canvas?.getRetinaScaling?.(), currentZoom);
scaleFactor || canvas?.getRetinaScaling?.(),
currentZoom
);
if (isThumbnail) scaleFactor = 0.2; // 缩略图使用较小的高清倍数 if (isThumbnail) scaleFactor = 0.2; // 缩略图使用较小的高清倍数
@@ -191,9 +184,7 @@ const createRasterizedImageWithGroup = async ({
hasControls: false, hasControls: false,
}); });
console.log( console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
);
// 调整组的位置,让它位于画布的左上角 // 调整组的位置,让它位于画布的左上角
group.set({ group.set({
@@ -317,9 +308,7 @@ const createRasterizedImageWithMask = async ({
height: canvasHeight, height: canvasHeight,
}); });
console.log( console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
);
// 调整对象位置,相对于遮罩边界重新定位 // 调整对象位置,相对于遮罩边界重新定位
clonedObjects.forEach((obj) => { clonedObjects.forEach((obj) => {

View File

@@ -85,18 +85,12 @@ const createClippedObjects = async ({
console.log("🎯 使用新的图像遮罩裁剪方法创建对象"); console.log("🎯 使用新的图像遮罩裁剪方法创建对象");
// 使用优化后的边界计算,确保包含描边区域 // 使用优化后的边界计算,确保包含描边区域
const optimizedBounds = calculateOptimizedBounds( const optimizedBounds = calculateOptimizedBounds(clippingObject, fabricObjects);
clippingObject,
fabricObjects
);
console.log("📐 优化后的选区边界框:", optimizedBounds); console.log("📐 优化后的选区边界框:", optimizedBounds);
// 获取羽化值 // 获取羽化值
let featherAmount = 0; let featherAmount = 0;
if ( if (selectionManager && typeof selectionManager.getFeatherAmount === "function") {
selectionManager &&
typeof selectionManager.getFeatherAmount === "function"
) {
featherAmount = selectionManager.getFeatherAmount(); featherAmount = selectionManager.getFeatherAmount();
console.log(`🌟 应用羽化效果: ${featherAmount}px`); console.log(`🌟 应用羽化效果: ${featherAmount}px`);
} }
@@ -177,10 +171,7 @@ const createClippedDataURLByCanvas = async ({
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL"); console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
// 使用优化后的边界计算,确保包含描边区域 // 使用优化后的边界计算,确保包含描边区域
const optimizedBounds = calculateOptimizedBounds( const optimizedBounds = calculateOptimizedBounds(clippingObject, fabricObjects);
clippingObject,
fabricObjects
);
// 使用高分辨率以保证质量 // 使用高分辨率以保证质量
const pixelRatio = window.devicePixelRatio || 1; const pixelRatio = window.devicePixelRatio || 1;
@@ -239,13 +230,7 @@ const createClippedDataURLByCanvas = async ({
* 创建简单克隆对象 * 创建简单克隆对象
* 当不需要裁剪时,直接克隆原对象 * 当不需要裁剪时,直接克隆原对象
*/ */
const createSimpleClone = async ({ const createSimpleClone = async ({ canvas, fabricObjects, isReturenDataURL, quality, format }) => {
canvas,
fabricObjects,
isReturenDataURL,
quality,
format,
}) => {
try { try {
console.log("📋 创建简单克隆对象"); console.log("📋 创建简单克隆对象");
@@ -459,10 +444,7 @@ const createLegacyRasterization = async ({
// 这里保留原有的离屏渲染逻辑作为备选方案 // 这里保留原有的离屏渲染逻辑作为备选方案
const currentZoom = canvas.getZoom?.() || 1; const currentZoom = canvas.getZoom?.() || 1;
scaleFactor = Math.max( scaleFactor = Math.max(scaleFactor || canvas?.getRetinaScaling?.(), currentZoom);
scaleFactor || canvas?.getRetinaScaling?.(),
currentZoom
);
scaleFactor = Math.min(scaleFactor, 3); scaleFactor = Math.min(scaleFactor, 3);
const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects); const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects);
@@ -592,9 +574,7 @@ const createOffscreenRasterization = async ({
height: canvasHeight, height: canvasHeight,
}); });
console.log( console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
);
// 克隆对象到离屏画布 // 克隆对象到离屏画布
const clonedObjects = []; const clonedObjects = [];
@@ -749,11 +729,7 @@ export const getObjectsBounds = (fabricObjects) => {
* @param {Number} qualityMultiplier 质量倍数 * @param {Number} qualityMultiplier 质量倍数
* @returns {Promise<String>} 遮罩图像的DataURL * @returns {Promise<String>} 遮罩图像的DataURL
*/ */
const createMaskImageFromPath = async ({ const createMaskImageFromPath = async ({ clippingObject, selectionBounds, qualityMultiplier }) => {
clippingObject,
selectionBounds,
qualityMultiplier,
}) => {
try { try {
console.log("🎭 创建路径遮罩图像"); console.log("🎭 创建路径遮罩图像");
@@ -769,11 +745,7 @@ const createMaskImageFromPath = async ({
}); });
// 克隆路径对象并处理描边转填充 // 克隆路径对象并处理描边转填充
const maskPath = await createSolidMaskPath( const maskPath = await createSolidMaskPath(clippingObject, selectionBounds, qualityMultiplier);
clippingObject,
selectionBounds,
qualityMultiplier
);
// 添加路径到遮罩画布 // 添加路径到遮罩画布
maskCanvas.add(maskPath); maskCanvas.add(maskPath);
@@ -804,11 +776,7 @@ const createMaskImageFromPath = async ({
* @param {Number} qualityMultiplier 质量倍数 * @param {Number} qualityMultiplier 质量倍数
* @returns {Promise<String>} 内容图像的DataURL * @returns {Promise<String>} 内容图像的DataURL
*/ */
const renderContentToImage = async ({ const renderContentToImage = async ({ fabricObjects, selectionBounds, qualityMultiplier }) => {
fabricObjects,
selectionBounds,
qualityMultiplier,
}) => {
try { try {
console.log("🖼️ 渲染内容图像"); console.log("🖼️ 渲染内容图像");
@@ -964,11 +932,7 @@ const createAdvancedMaskImage = async ({
}); });
// 克隆路径对象并处理描边转填充 // 克隆路径对象并处理描边转填充
const maskPath = await createSolidMaskPath( const maskPath = await createSolidMaskPath(clippingObject, selectionBounds, qualityMultiplier);
clippingObject,
selectionBounds,
qualityMultiplier
);
// 如果有羽化值,添加模糊效果 // 如果有羽化值,添加模糊效果
if (featherAmount > 0) { if (featherAmount > 0) {
@@ -987,10 +951,7 @@ const createAdvancedMaskImage = async ({
// 如果有羽化,需要进行后处理 // 如果有羽化,需要进行后处理
if (featherAmount > 0) { if (featherAmount > 0) {
return await applyCanvasBlur( return await applyCanvasBlur(maskCanvas, featherAmount * qualityMultiplier);
maskCanvas,
featherAmount * qualityMultiplier
);
} }
// 生成遮罩图像 // 生成遮罩图像
@@ -1066,11 +1027,7 @@ const applyCanvasBlur = async (canvas, blurAmount) => {
* @param {Number} qualityMultiplier 质量倍数 * @param {Number} qualityMultiplier 质量倍数
* @returns {Promise<fabric.Object>} 处理后的遮罩路径对象 * @returns {Promise<fabric.Object>} 处理后的遮罩路径对象
*/ */
const createSolidMaskPath = async ( const createSolidMaskPath = async (clippingObject, selectionBounds, qualityMultiplier) => {
clippingObject,
selectionBounds,
qualityMultiplier
) => {
try { try {
console.log("🔧 创建实体遮罩路径,处理描边转填充"); console.log("🔧 创建实体遮罩路径,处理描边转填充");
@@ -1081,29 +1038,19 @@ const createSolidMaskPath = async (
const hasStroke = maskPath.stroke && maskPath.strokeWidth > 0; const hasStroke = maskPath.stroke && maskPath.strokeWidth > 0;
if (hasStroke) { if (hasStroke) {
console.log( console.log(`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`);
`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`
);
// 对于有描边的路径,我们需要更精确的处理 // 对于有描边的路径,我们需要更精确的处理
const strokeWidth = maskPath.strokeWidth; const strokeWidth = maskPath.strokeWidth;
// 方法1: 如果是简单的几何形状(矩形、圆形等),可以通过调整尺寸来补偿描边 // 方法1: 如果是简单的几何形状(矩形、圆形等),可以通过调整尺寸来补偿描边
if ( if (maskPath.type === "rect" || maskPath.type === "circle" || maskPath.type === "ellipse") {
maskPath.type === "rect" ||
maskPath.type === "circle" ||
maskPath.type === "ellipse"
) {
// 对于矩形和椭圆,增加宽高来包含描边 // 对于矩形和椭圆,增加宽高来包含描边
const strokeOffset = strokeWidth; const strokeOffset = strokeWidth;
maskPath.set({ maskPath.set({
left: left: (maskPath.left - selectionBounds.left - strokeOffset / 2) * qualityMultiplier,
(maskPath.left - selectionBounds.left - strokeOffset / 2) * top: (maskPath.top - selectionBounds.top - strokeOffset / 2) * qualityMultiplier,
qualityMultiplier,
top:
(maskPath.top - selectionBounds.top - strokeOffset / 2) *
qualityMultiplier,
scaleX: (maskPath.scaleX || 1) * qualityMultiplier, scaleX: (maskPath.scaleX || 1) * qualityMultiplier,
scaleY: (maskPath.scaleY || 1) * qualityMultiplier, scaleY: (maskPath.scaleY || 1) * qualityMultiplier,
width: (maskPath.width || 0) + strokeOffset, width: (maskPath.width || 0) + strokeOffset,
@@ -1122,12 +1069,8 @@ const createSolidMaskPath = async (
const strokeOffset = strokeWidth / 2; const strokeOffset = strokeWidth / 2;
maskPath.set({ maskPath.set({
left: left: (maskPath.left - selectionBounds.left - strokeOffset) * qualityMultiplier,
(maskPath.left - selectionBounds.left - strokeOffset) * top: (maskPath.top - selectionBounds.top - strokeOffset) * qualityMultiplier,
qualityMultiplier,
top:
(maskPath.top - selectionBounds.top - strokeOffset) *
qualityMultiplier,
scaleX: (maskPath.scaleX || 1) * qualityMultiplier * expandRatio, scaleX: (maskPath.scaleX || 1) * qualityMultiplier * expandRatio,
scaleY: (maskPath.scaleY || 1) * qualityMultiplier * expandRatio, scaleY: (maskPath.scaleY || 1) * qualityMultiplier * expandRatio,
fill: "#ffffff", fill: "#ffffff",